@kw-yeh/vibeflow 4.8.0 → 4.10.0

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/vibeflow.mjs CHANGED
@@ -621,22 +621,25 @@ function assembleCommand(agent, systemPrompt, prompt, model, effort, opts, workt
621
621
  const combined = [systemPrompt, prompt].filter(Boolean).join("\n\n");
622
622
  const codexEffortFlag = effort ? `-c ${shellQuote(`model_reasoning_effort="${effort}"`)} ` : "";
623
623
  const codexHome = opts?.library ? `CODEX_HOME=${shellQuote(toShellPath(opts.library.codexHome))} ` : "";
624
- const promptArg = combined ? ` ${shellQuote(combined)}` : "";
625
- cmd = `${codexHome}codex ${codexAutoFlag(opts?.autoMode)}${codexEffortFlag}--model ${model}${promptArg}\r`;
624
+ const args = [
625
+ codexAutoFlag(opts?.autoMode).trim(),
626
+ codexEffortFlag.trim(),
627
+ model ? `--model ${model}` : "",
628
+ combined ? shellQuote(combined) : ""
629
+ ].filter(Boolean);
630
+ cmd = `${codexHome}${["codex", ...args].join(" ")}\r`;
626
631
  }
627
632
  if (cmd.length > 2e5) console.warn(`[VibeFlow] launch command is ${cmd.length} bytes \u2014 approaching ARG_MAX`);
628
633
  return cmd;
629
634
  }
630
- function normalizeModel(agent, model) {
631
- return LEGACY_MODEL_FALLBACKS[agent]?.[model] ?? model;
632
- }
633
635
  function taskAgent(task) {
634
636
  const agent = task.agentCli;
635
637
  return agent && agent in AGENT_NAMES ? agent : "claude";
636
638
  }
637
639
  function taskModel(task) {
638
- const agent = taskAgent(task);
639
- return normalizeModel(agent, task.model || DEFAULT_MODELS[agent]);
640
+ const model = task.model?.trim();
641
+ if (!model || RETIRED_MODELS[taskAgent(task)]?.includes(model)) return void 0;
642
+ return model;
640
643
  }
641
644
  function buildAgentCommand(task, systemPrompt, opts, workspacePath) {
642
645
  const agent = taskAgent(task);
@@ -661,7 +664,7 @@ function buildAgentCommand(task, systemPrompt, opts, workspacePath) {
661
664
  workspacePath
662
665
  );
663
666
  }
664
- var ARTIFACTS_DIR_SUFFIX, SCRATCH_DIR_NAME, ARTIFACTS_FALLBACK_DIR, AUTO_PERMISSION_MODE, SUBAGENTS_DIR2, PROGRESS_EVENTS_DIR, TODO_TOOLS, AGENT_NAMES, DEFAULT_MODELS, LEGACY_MODEL_FALLBACKS;
667
+ var ARTIFACTS_DIR_SUFFIX, SCRATCH_DIR_NAME, ARTIFACTS_FALLBACK_DIR, AUTO_PERMISSION_MODE, SUBAGENTS_DIR2, PROGRESS_EVENTS_DIR, TODO_TOOLS, AGENT_NAMES, RETIRED_MODELS;
665
668
  var init_launch = __esm({
666
669
  "packages/core/src/launch.ts"() {
667
670
  "use strict";
@@ -676,15 +679,8 @@ var init_launch = __esm({
676
679
  claude: "Claude Code",
677
680
  codex: "Codex CLI"
678
681
  };
679
- DEFAULT_MODELS = {
680
- claude: "sonnet",
681
- codex: "gpt-5.5"
682
- };
683
- LEGACY_MODEL_FALLBACKS = {
684
- codex: {
685
- "gpt-5-codex": "gpt-5.5",
686
- "gpt-5": "gpt-5.5"
687
- }
682
+ RETIRED_MODELS = {
683
+ codex: ["gpt-5-codex", "gpt-5"]
688
684
  };
689
685
  }
690
686
  });
@@ -790,6 +786,9 @@ var init_attachments = __esm({
790
786
  });
791
787
 
792
788
  // packages/core/src/effort.ts
789
+ function modelFor(models, modelId) {
790
+ return modelId ? models?.find((m) => m.id === modelId) : models?.find((m) => m.isDefault);
791
+ }
793
792
  function isAgentEffort(value) {
794
793
  return typeof value === "string" && AGENT_EFFORTS.includes(value);
795
794
  }
@@ -1104,7 +1103,7 @@ import fs22 from "fs";
1104
1103
  import os9 from "os";
1105
1104
  import path26 from "path";
1106
1105
  import React, { useEffect, useRef, useState } from "react";
1107
- import { Box, Text, render, useApp, useInput } from "ink";
1106
+ import { Box, Text, render as render2, useApp, useInput } from "ink";
1108
1107
  function newFormState(defaultAutoMode, agents) {
1109
1108
  const agentCli = agents && agents.length > 0 && !agents.some((a) => a.id === "claude") ? agents[0].id : "claude";
1110
1109
  return {
@@ -1174,8 +1173,7 @@ function modelOptions(ctx, agentCli, current) {
1174
1173
  return options;
1175
1174
  }
1176
1175
  function effortOptions(ctx, agentCli, model) {
1177
- const id = taskModel({ agentCli, model });
1178
- return selectableEfforts(ctx.models[agentCli]?.find((m) => m.id === id));
1176
+ return selectableEfforts(modelFor(ctx.models[agentCli], taskModel({ agentCli, model })));
1179
1177
  }
1180
1178
  function fitEffort(ctx, s) {
1181
1179
  const effort = clampEffort(s.effort, effortOptions(ctx, s.agentCli, s.model)) ?? s.effort;
@@ -1417,7 +1415,8 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
1417
1415
  case "model": {
1418
1416
  if (modelOptions(ctx, s.agentCli, s.model).length <= 1) return "\u7121\u6CD5\u53D6\u5F97 model list\uFF0C\u5C07\u4F7F\u7528\u9810\u8A2D model";
1419
1417
  const listed = ctx.models[s.agentCli]?.find((m) => m.id === s.model);
1420
- return `\u2039 ${s.model ? listed?.label ?? s.model : "\u4F7F\u7528\u9810\u8A2D model"} \u203A`;
1418
+ const fallback = ctx.models[s.agentCli]?.find((m) => m.isDefault)?.label;
1419
+ return `\u2039 ${s.model ? listed?.label ?? s.model : fallback ? `\u4F7F\u7528\u9810\u8A2D model\uFF08${fallback}\uFF09` : "\u4F7F\u7528\u9810\u8A2D model"} \u203A`;
1421
1420
  }
1422
1421
  case "submit":
1423
1422
  return busy ?? (s.mode === "new" ? "\uFF3B \u5EFA\u7ACB\u4EFB\u52D9 \uFF3D" : "\uFF3B \u5132\u5B58\u8B8A\u66F4 \uFF3D");
@@ -1478,7 +1477,7 @@ function FormView({ api, ctx, initial, initialFocus, initialMessage, pristine, o
1478
1477
  function showForm(api, ctx, initial, pristine, initialFocus = 0, initialMessage) {
1479
1478
  return new Promise((resolve) => {
1480
1479
  let result = { type: "cancel" };
1481
- const instance = render(
1480
+ const instance = render2(
1482
1481
  h(FormView, { api, ctx, initial, initialFocus, initialMessage, pristine, onResult: (r) => result = r }),
1483
1482
  { exitOnCtrlC: false }
1484
1483
  );
@@ -1561,7 +1560,7 @@ __export(src_exports, {
1561
1560
  import { spawnSync as spawnSync2 } from "child_process";
1562
1561
  import path27 from "path";
1563
1562
  import React2, { useEffect as useEffect2, useState as useState2 } from "react";
1564
- import { Box as Box2, Text as Text2, render as render2, useApp as useApp2, useInput as useInput2, useStdout } from "ink";
1563
+ import { Box as Box2, Text as Text2, render as render3, useApp as useApp2, useInput as useInput2, useStdout } from "ink";
1565
1564
  function findDetachKey(text) {
1566
1565
  const plain = text.indexOf(DETACH_KEY);
1567
1566
  const m = /\x1b\[\d+;\d+;29;1;\d+;\d+_/.exec(text);
@@ -1829,7 +1828,7 @@ function BoardView({ opts, initial, initialMessage, onAction }) {
1829
1828
  function showBoard(opts, initial, initialMessage) {
1830
1829
  return new Promise((resolve) => {
1831
1830
  let result = { action: { type: "quit" }, sel: initial };
1832
- const instance = render2(
1831
+ const instance = render3(
1833
1832
  h2(BoardView, {
1834
1833
  opts,
1835
1834
  initial,
@@ -2465,6 +2464,13 @@ function createBridge(t) {
2465
2464
  logoutGithubAuth: () => t.invoke("settings:logoutGithubAuth"),
2466
2465
  /** Open Issues/PRs of the board's GitHub repos that involve me; cached unless `force`. */
2467
2466
  getGithubInbox: (opts) => t.invoke("github:inbox", opts ?? {}),
2467
+ getJiraAuthStatus: () => t.invoke("jira:authStatus"),
2468
+ startJiraAuthLogin: () => t.invoke("settings:startJiraAuthLogin"),
2469
+ cancelJiraAuthLogin: () => t.invoke("settings:cancelJiraAuthLogin"),
2470
+ inputJiraAuthLogin: (input) => t.invoke("settings:inputJiraAuthLogin", input),
2471
+ logoutJiraAuth: () => t.invoke("settings:logoutJiraAuth"),
2472
+ onJiraAuthEvent: (callback) => t.on("jira-auth:event", (event) => callback(event)),
2473
+ getJiraInbox: (opts) => t.invoke("jira:inbox", opts ?? {}),
2468
2474
  /** The Issue/PR each card belongs to, keyed by task id. */
2469
2475
  getGithubTaskLinks: (opts) => t.invoke("github:taskLinks", opts ?? {}),
2470
2476
  onGithubAuthEvent: (callback) => {
@@ -2964,10 +2970,11 @@ function parseGithubRepo(remoteUrl) {
2964
2970
  return match ? `${match[1]}/${match[2]}` : null;
2965
2971
  }
2966
2972
  var REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
2973
+ var isRepoName = (repo) => REPO_RE.test(repo) && repo.split("/").every((part) => part !== "." && part !== "..");
2967
2974
  function isGithubRef(value) {
2968
2975
  if (!value || typeof value !== "object") return false;
2969
2976
  const v = value;
2970
- return (v.kind === "issue" || v.kind === "pr") && typeof v.repo === "string" && REPO_RE.test(v.repo) && typeof v.number === "number" && Number.isInteger(v.number) && v.number > 0 && typeof v.url === "string" && v.url.startsWith(`https://github.com/${v.repo}/`);
2977
+ return (v.kind === "issue" || v.kind === "pr") && typeof v.repo === "string" && isRepoName(v.repo) && typeof v.number === "number" && Number.isInteger(v.number) && v.number > 0 && typeof v.url === "string" && v.url.startsWith(`https://github.com/${v.repo}/`);
2971
2978
  }
2972
2979
  var ITEM_FIELDS = "number,title,url,body,author,assignees,labels,milestone,createdAt";
2973
2980
  var ISSUE_FIELDS = `${ITEM_FIELDS},issueType`;
@@ -2975,6 +2982,7 @@ var PR_FIELDS = `${ITEM_FIELDS},state,isDraft,reviewDecision,headRefName,baseRef
2975
2982
  var PR_INDEX_FIELDS = "number,url,state,isDraft,headRefName,closingIssuesReferences";
2976
2983
  var LIST_LIMIT = "50";
2977
2984
  var PR_INDEX_LIMIT = "100";
2985
+ var SEARCH_LIMIT = "1000";
2978
2986
  async function runJson(run3, args) {
2979
2987
  const out = await run3(args);
2980
2988
  const parsed = JSON.parse(out || "[]");
@@ -3058,18 +3066,47 @@ async function listPrs(run3, repo, filter) {
3058
3066
  return rows.map((r) => toPr(r, repo));
3059
3067
  }
3060
3068
  async function fetchRepoItems(run3, repo) {
3061
- const [assignedIssues, authoredIssues, assignedPrs, authoredPrs, reviewPrs] = await Promise.all([
3069
+ const [assignedIssues, authoredIssues, assignedPrs, authoredPrs, reviewPrs, reviewedPrs] = await Promise.all([
3062
3070
  listIssues(run3, repo, ["--assignee", "@me"]),
3063
3071
  listIssues(run3, repo, ["--author", "@me"]),
3064
3072
  listPrs(run3, repo, ["--assignee", "@me"]),
3065
3073
  listPrs(run3, repo, ["--author", "@me"]),
3066
- listPrs(run3, repo, ["--search", "review-requested:@me"])
3074
+ listPrs(run3, repo, ["--search", "review-requested:@me"]),
3075
+ listPrs(run3, repo, ["--search", "reviewed-by:@me"])
3067
3076
  ]);
3068
3077
  return {
3069
3078
  issues: mergeItems([assignedIssues, authoredIssues]),
3070
- prs: mergeItems([assignedPrs, authoredPrs, reviewPrs])
3079
+ prs: mergeItems([assignedPrs, authoredPrs, reviewPrs, reviewedPrs])
3071
3080
  };
3072
3081
  }
3082
+ async function findRelatedRepos(run3) {
3083
+ const searches = [
3084
+ ["issues", ["--assignee", "@me"]],
3085
+ ["issues", ["--author", "@me"]],
3086
+ ["prs", ["--assignee", "@me"]],
3087
+ ["prs", ["--author", "@me"]],
3088
+ ["prs", ["--review-requested", "@me"]],
3089
+ ["prs", ["--reviewed-by", "@me"]]
3090
+ ];
3091
+ const results = await Promise.all(searches.map(([kind, filter]) => runJson(run3, [
3092
+ "search",
3093
+ kind,
3094
+ "--state",
3095
+ "open",
3096
+ "--limit",
3097
+ SEARCH_LIMIT,
3098
+ ...filter,
3099
+ "--json",
3100
+ "repository"
3101
+ ])));
3102
+ const found2 = /* @__PURE__ */ new Set();
3103
+ for (const rows of results) for (const row of rows) {
3104
+ const repository = row.repository;
3105
+ const name = repository?.nameWithOwner;
3106
+ if (typeof name === "string" && isRepoName(name)) found2.add(name);
3107
+ }
3108
+ return Array.from(found2).sort();
3109
+ }
3073
3110
  async function fetchPrIndex(run3, repo) {
3074
3111
  const rows = await runJson(run3, [
3075
3112
  "pr",
@@ -3166,6 +3203,7 @@ function createGithubService(options) {
3166
3203
  const items = /* @__PURE__ */ new Map();
3167
3204
  const indexes = /* @__PURE__ */ new Map();
3168
3205
  const repos = /* @__PURE__ */ new Map();
3206
+ const discovery = /* @__PURE__ */ new Map();
3169
3207
  function cached2(map, key, force, load) {
3170
3208
  const hit = map.get(key);
3171
3209
  if (hit && !force && now() - hit.at < GITHUB_CACHE_TTL_MS) return hit;
@@ -3196,11 +3234,12 @@ function createGithubService(options) {
3196
3234
  const byProject = await projectRepos(board, force);
3197
3235
  const repoProject = /* @__PURE__ */ new Map();
3198
3236
  for (const [project, repo] of byProject) if (!repoProject.has(repo)) repoProject.set(repo, project);
3199
- const entries = Array.from(repoProject.entries());
3237
+ const discovered = cached2(discovery, "related", force, () => findRelatedRepos(run3));
3238
+ const entries = (await discovered.value).map((repo) => [repo, repoProject.get(repo) ?? null]);
3200
3239
  const results = await Promise.all(
3201
3240
  entries.map(async ([repo, projectPath]) => {
3202
3241
  const entry = cached2(items, repo, force, () => fetchRepoItems(run3, repo));
3203
- const head = { repo, projectPath, projectName: basename(projectPath) };
3242
+ const head = { repo, projectPath, projectName: projectPath ? basename(projectPath) : repo };
3204
3243
  try {
3205
3244
  return { at: entry.at, inbox: { ...head, ...await entry.value } };
3206
3245
  } catch (err) {
@@ -3211,7 +3250,7 @@ function createGithubService(options) {
3211
3250
  return {
3212
3251
  status: "ok",
3213
3252
  ...auth.login ? { login: auth.login } : {},
3214
- fetchedAt: results.length ? Math.min(...results.map((r) => r.at)) : 0,
3253
+ fetchedAt: Math.min(discovered.at, ...results.map((r) => r.at)),
3215
3254
  repos: results.map((r) => r.inbox).sort((a, b) => a.repo.localeCompare(b.repo))
3216
3255
  };
3217
3256
  },
@@ -3654,10 +3693,10 @@ async function ignoredEntryFitsCopyBudget(src) {
3654
3693
  const stat = await fs8.lstat(current);
3655
3694
  if (stat.isSymbolicLink()) continue;
3656
3695
  if (stat.isDirectory()) {
3657
- const children = await fs8.readdir(current, { withFileTypes: true });
3658
- files += children.length;
3696
+ const children2 = await fs8.readdir(current, { withFileTypes: true });
3697
+ files += children2.length;
3659
3698
  if (files > MAX_IGNORED_COPY_FILES) return false;
3660
- for (const child of children) stack.push(path12.join(current, child.name));
3699
+ for (const child of children2) stack.push(path12.join(current, child.name));
3661
3700
  continue;
3662
3701
  }
3663
3702
  files += 1;
@@ -4210,7 +4249,7 @@ var AGENT_CLIS = [
4210
4249
  bin: "claude",
4211
4250
  name: "Claude Code",
4212
4251
  models: [
4213
- { id: "sonnet", label: "Sonnet\uFF08\u5E73\u8861\u30FB\u9810\u8A2D\uFF09" },
4252
+ { id: "sonnet", label: "Sonnet\uFF08\u5E73\u8861\uFF09" },
4214
4253
  { id: "haiku", label: "Haiku\uFF08\u8F15\u91CF\uFF09" },
4215
4254
  { id: "opus", label: "Opus\uFF08\u6700\u5F37\uFF09" }
4216
4255
  ]
@@ -4220,7 +4259,7 @@ var AGENT_CLIS = [
4220
4259
  bin: "codex",
4221
4260
  name: "Codex CLI",
4222
4261
  models: [
4223
- { id: "gpt-5.5", label: "GPT-5.5\uFF08\u9810\u8A2D\uFF09" },
4262
+ { id: "gpt-5.5", label: "GPT-5.5" },
4224
4263
  { id: "gpt-5.4", label: "GPT-5.4" },
4225
4264
  { id: "gpt-5.4-mini", label: "GPT-5.4 Mini" }
4226
4265
  ]
@@ -4305,7 +4344,8 @@ async function createTaskFromInput(input) {
4305
4344
  model: input.model || void 0,
4306
4345
  effort: input.effort ?? DEFAULT_TASK_EFFORT,
4307
4346
  autoMode: input.autoMode,
4308
- ...input.github ? { github: input.github } : {}
4347
+ ...input.github ? { github: input.github } : {},
4348
+ ...input.jira ? { jira: input.jira } : {}
4309
4349
  };
4310
4350
  try {
4311
4351
  const board = store.get("board");
@@ -4789,17 +4829,17 @@ function listLibrary(root, builtinDir) {
4789
4829
  const full = path14.join(dir, name);
4790
4830
  if (wantsDirectory && !fs10.existsSync(path14.join(full, "SKILL.md"))) continue;
4791
4831
  const key = entryKey(kind, name);
4792
- const record = index.entries[key] ?? {};
4832
+ const record2 = index.entries[key] ?? {};
4793
4833
  entries.push({
4794
4834
  key,
4795
4835
  kind,
4796
4836
  name,
4797
4837
  path: full,
4798
- description: wantsDirectory ? readSkillDescription(path14.join(full, "SKILL.md")) : record.description,
4799
- sourcePath: record.sourcePath,
4800
- importedAt: record.importedAt,
4801
- enabled: record.enabled !== false,
4802
- builtin: wantsDirectory && record.builtinHash && builtinDir !== void 0 ? builtinStatus(full, name, record.builtinHash, builtinDir) : void 0
4838
+ description: wantsDirectory ? readSkillDescription(path14.join(full, "SKILL.md")) : record2.description,
4839
+ sourcePath: record2.sourcePath,
4840
+ importedAt: record2.importedAt,
4841
+ enabled: record2.enabled !== false,
4842
+ builtin: wantsDirectory && record2.builtinHash && builtinDir !== void 0 ? builtinStatus(full, name, record2.builtinHash, builtinDir) : void 0
4803
4843
  });
4804
4844
  }
4805
4845
  }
@@ -4874,8 +4914,8 @@ function markBuiltinDeleted(root, key) {
4874
4914
  function deleteEntry(root, kind, name) {
4875
4915
  fs10.rmSync(entryPath(root, kind, name), { recursive: true, force: true });
4876
4916
  const key = entryKey(kind, name);
4877
- const record = readIndex(root).entries[key];
4878
- if (record?.builtinHash || record?.builtinDeleted) {
4917
+ const record2 = readIndex(root).entries[key];
4918
+ if (record2?.builtinHash || record2?.builtinDeleted) {
4879
4919
  markBuiltinDeleted(root, key);
4880
4920
  return;
4881
4921
  }
@@ -5070,10 +5110,10 @@ function syncBuiltinSkills(root, builtinDir) {
5070
5110
  }
5071
5111
  function syncOne(root, builtinDir, name, result) {
5072
5112
  const key = entryKey("skill", name);
5073
- const record = readIndex(root).entries[key] ?? {};
5074
- if (record.builtinDeleted) return;
5113
+ const record2 = readIndex(root).entries[key] ?? {};
5114
+ if (record2.builtinDeleted) return;
5075
5115
  const target = entryPath(root, "skill", name);
5076
- if (!record.builtinHash) {
5116
+ if (!record2.builtinHash) {
5077
5117
  if (fs11.existsSync(target)) return;
5078
5118
  annotate(root, key, {
5079
5119
  builtinHash: installBuiltin(root, builtinDir, name),
@@ -5087,8 +5127,8 @@ function syncOne(root, builtinDir, name, result) {
5087
5127
  markBuiltinDeleted(root, key);
5088
5128
  return;
5089
5129
  }
5090
- if (hashSkillDir(path15.join(builtinDir, name)) === record.builtinHash) return;
5091
- if (hashSkillDir(target) !== record.builtinHash) return;
5130
+ if (hashSkillDir(path15.join(builtinDir, name)) === record2.builtinHash) return;
5131
+ if (hashSkillDir(target) !== record2.builtinHash) return;
5092
5132
  annotate(root, key, { builtinHash: installBuiltin(root, builtinDir, name) });
5093
5133
  result.updated.push(name);
5094
5134
  }
@@ -5103,10 +5143,10 @@ function restoreBuiltinSkill(root, builtinDir, name) {
5103
5143
  ensureLibrary(root);
5104
5144
  const builtinHash = installBuiltin(root, builtinDir, name);
5105
5145
  const index = readIndex(root);
5106
- const record = { ...index.entries[key], builtinHash, enabled: true, importedAt: Date.now() };
5107
- delete record.builtinDeleted;
5108
- delete record.sourcePath;
5109
- index.entries[key] = record;
5146
+ const record2 = { ...index.entries[key], builtinHash, enabled: true, importedAt: Date.now() };
5147
+ delete record2.builtinDeleted;
5148
+ delete record2.sourcePath;
5149
+ index.entries[key] = record2;
5110
5150
  writeIndex(root, index);
5111
5151
  const entry = listLibrary(root, builtinDir).find((e) => e.key === key);
5112
5152
  if (!entry) throw new Error(`\u9084\u539F\u5931\u6557\uFF1A${name}`);
@@ -5203,12 +5243,12 @@ function spawnJsonLines(bin, args, opts) {
5203
5243
  }
5204
5244
  });
5205
5245
  proc.on("exit", (code2, signal) => {
5206
- finished();
5207
5246
  clearTimeout(timer);
5208
5247
  clearTimeout(graceTimer);
5209
5248
  const reason = signal ? `was killed (${signal})` : `exited with code ${code2}`;
5210
5249
  fail2(new Error(`${bin} ${reason}${stderr.trim() ? `: ${stderr.trim()}` : ""}`));
5211
5250
  });
5251
+ proc.on("close", finished);
5212
5252
  proc.stdin.on("error", () => {
5213
5253
  });
5214
5254
  const kill = () => {
@@ -5267,6 +5307,9 @@ function parseClaudeInitialize(message) {
5267
5307
  if (outer?.subtype === "error") throw new Error(`claude refused the handshake: ${String(outer.error ?? "unknown error")}`);
5268
5308
  const body = outer && isRecord(outer.response) ? outer.response : null;
5269
5309
  if (!body || !Array.isArray(body.models)) throw new Error("claude handshake has no models array");
5310
+ const defaultEntry = body.models.find((e) => isRecord(e) && e.value === "default");
5311
+ const defaultResolved = isRecord(defaultEntry) && typeof defaultEntry.resolvedModel === "string" ? defaultEntry.resolvedModel : void 0;
5312
+ let defaultMarked = false;
5270
5313
  const seen = /* @__PURE__ */ new Set();
5271
5314
  const models = [];
5272
5315
  for (const entry of body.models) {
@@ -5277,7 +5320,9 @@ function parseClaudeInitialize(message) {
5277
5320
  const label = typeof entry.displayName === "string" && entry.displayName.trim() ? entry.displayName.trim() : id;
5278
5321
  const description = typeof entry.description === "string" && entry.description.trim() ? entry.description.trim() : void 0;
5279
5322
  const levels = entry.supportsEffort === true && Array.isArray(entry.supportedEffortLevels) ? entry.supportedEffortLevels.filter(isAgentEffort) : [];
5280
- models.push({ id, label, ...description ? { description } : {}, efforts: levels });
5323
+ const isDefault = !defaultMarked && defaultResolved !== void 0 && entry.resolvedModel === defaultResolved;
5324
+ if (isDefault) defaultMarked = true;
5325
+ models.push({ id, label, ...description ? { description } : {}, efforts: levels, ...isDefault ? { isDefault } : {} });
5281
5326
  }
5282
5327
  if (models.length === 0) throw new Error("claude handshake lists no selectable models");
5283
5328
  return models;
@@ -5338,7 +5383,13 @@ function parseCodexModelPage(result) {
5338
5383
  const label = typeof entry.displayName === "string" && entry.displayName.trim() ? entry.displayName.trim() : id;
5339
5384
  const description = typeof entry.description === "string" && entry.description.trim() ? entry.description.trim() : void 0;
5340
5385
  const efforts = Array.isArray(entry.supportedReasoningEfforts) ? entry.supportedReasoningEfforts.map((e) => isRecord2(e) ? e.reasoningEffort : void 0).filter(isAgentEffort) : void 0;
5341
- models.push({ id, label, ...description ? { description } : {}, ...efforts ? { efforts } : {} });
5386
+ models.push({
5387
+ id,
5388
+ label,
5389
+ ...description ? { description } : {},
5390
+ ...efforts ? { efforts } : {},
5391
+ ...entry.isDefault === true ? { isDefault: true } : {}
5392
+ });
5342
5393
  }
5343
5394
  const nextCursor = typeof result.nextCursor === "string" && result.nextCursor ? result.nextCursor : null;
5344
5395
  return { models, nextCursor };
@@ -5385,6 +5436,7 @@ async function fetchCodexModels(clientVersion, open = openCodex) {
5385
5436
  }
5386
5437
 
5387
5438
  // packages/core/src/agent-models.ts
5439
+ init_effort();
5388
5440
  function builtinModels(id) {
5389
5441
  return (AGENT_CLIS.find((a) => a.id === id) ?? AGENT_CLIS[0]).models;
5390
5442
  }
@@ -5470,7 +5522,7 @@ function createModelCatalog(opts) {
5470
5522
  for (const agent of AGENT_CLIS) void refresh(agent.id);
5471
5523
  },
5472
5524
  findModel(id, modelId) {
5473
- return settled.get(id)?.models.find((model) => model.id === modelId);
5525
+ return modelFor(settled.get(id)?.models, modelId);
5474
5526
  }
5475
5527
  };
5476
5528
  }
@@ -5632,6 +5684,334 @@ function startGitHubCliLogin(onEvent) {
5632
5684
  });
5633
5685
  }
5634
5686
 
5687
+ // packages/core/src/jira-adf.ts
5688
+ function record(value) {
5689
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
5690
+ }
5691
+ function children(node) {
5692
+ return Array.isArray(node.content) ? node.content : [];
5693
+ }
5694
+ function render(node, depth = 0) {
5695
+ if (typeof node === "string") return node;
5696
+ const item = record(node);
5697
+ if (!item) return "";
5698
+ const type = item.type;
5699
+ const body = () => children(item).map((child) => render(child, depth)).join("");
5700
+ const attrs = record(item.attrs);
5701
+ if (type === "text") {
5702
+ let value = typeof item.text === "string" ? item.text : "";
5703
+ for (const mark of Array.isArray(item.marks) ? item.marks : []) {
5704
+ const m = record(mark);
5705
+ if (m?.type === "strong") value = `**${value}**`;
5706
+ if (m?.type === "em") value = `*${value}*`;
5707
+ if (m?.type === "strike") value = `~~${value}~~`;
5708
+ if (m?.type === "code") value = `\`${value}\``;
5709
+ if (m?.type === "link" && typeof record(m.attrs)?.href === "string") value = `[${value}](${record(m.attrs)?.href})`;
5710
+ }
5711
+ return value;
5712
+ }
5713
+ if (type === "hardBreak") return " \n";
5714
+ if (type === "rule") return "\n---\n\n";
5715
+ if (type === "mention") return `@${String(attrs?.text ?? attrs?.id ?? "")}`;
5716
+ if (type === "emoji") return String(attrs?.shortName ?? "");
5717
+ if (type === "inlineCard" || type === "blockCard") return `[${String(attrs?.url ?? "\u9023\u7D50")}](${String(attrs?.url ?? "")})`;
5718
+ if (type === "media" || type === "mediaSingle") return "[\u9644\u4EF6]";
5719
+ if (type === "paragraph") return `${body()}
5720
+
5721
+ `;
5722
+ if (type === "heading") return `${"#".repeat(Math.min(6, Math.max(1, Number(attrs?.level) || 1)))} ${body()}
5723
+
5724
+ `;
5725
+ if (type === "blockquote") return body().trim().split("\n").map((line) => `> ${line}`).join("\n") + "\n\n";
5726
+ if (type === "codeBlock") return `
5727
+ \`\`\`${String(attrs?.language ?? "")}
5728
+ ${children(item).map((child) => render(child)).join("").trimEnd()}
5729
+ \`\`\`
5730
+
5731
+ `;
5732
+ if (type === "bulletList" || type === "orderedList") {
5733
+ return children(item).map((child, index) => {
5734
+ const content = render(child, depth + 1).trim().replace(/\n\n/g, "\n");
5735
+ const prefix = type === "orderedList" ? `${index + 1}. ` : "- ";
5736
+ return `${" ".repeat(depth)}${prefix}${content}
5737
+ `;
5738
+ }).join("") + "\n";
5739
+ }
5740
+ if (type === "listItem") return body();
5741
+ if (type === "table") {
5742
+ const rows = children(item).map((row) => {
5743
+ const cells = children(record(row) ?? {}).map((cell) => render(cell).trim().replace(/\|/g, "\\|").replace(/\n+/g, " "));
5744
+ return `| ${cells.join(" | ")} |`;
5745
+ });
5746
+ if (rows.length === 0) return "";
5747
+ const width = children(record(children(item)[0]) ?? {}).length;
5748
+ rows.splice(1, 0, `| ${Array(width).fill("---").join(" | ")} |`);
5749
+ return rows.join("\n") + "\n\n";
5750
+ }
5751
+ return body();
5752
+ }
5753
+ function adfToMarkdown(value) {
5754
+ return render(value).trim();
5755
+ }
5756
+
5757
+ // packages/core/src/jira-auth.ts
5758
+ init_env();
5759
+ import { execFile as execFile8 } from "child_process";
5760
+ import { promisify as promisify5 } from "util";
5761
+ import * as nodePty4 from "node-pty";
5762
+ var pexec5 = promisify5(execFile8);
5763
+ var activeLogin2 = null;
5764
+ var activeCancel = null;
5765
+ function stripAnsi2(value) {
5766
+ return value.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "").replace(/\x07/g, "");
5767
+ }
5768
+ function hasSingleSitePrompt(output) {
5769
+ const clean = stripAnsi2(output);
5770
+ if (!/select the site to login/i.test(clean) || !/enter submit/i.test(clean)) return false;
5771
+ const sites = new Set([...clean.matchAll(/https:\/\/([a-z0-9.-]+\.atlassian\.net)\b/gi)].map((match) => match[1].toLowerCase()));
5772
+ return sites.size === 1;
5773
+ }
5774
+ function parseAuthStatus(output) {
5775
+ const clean = stripAnsi2(output);
5776
+ const site = clean.match(/(?:site|host)\s*:\s*(?:https?:\/\/)?([a-z0-9.-]+\.atlassian\.net)/i)?.[1];
5777
+ const email = clean.match(/(?:email|user)\s*:\s*([^\s]+@[^\s]+)/i)?.[1];
5778
+ return {
5779
+ installed: true,
5780
+ authenticated: Boolean(site) && !/not (?:logged|authenticated)|unauthenticated/i.test(clean),
5781
+ ...site ? { site } : {},
5782
+ ...email ? { email } : {}
5783
+ };
5784
+ }
5785
+ var defaultAcliRunner = async (args) => {
5786
+ const { stdout } = await pexec5("acli", args, {
5787
+ env: execEnv(),
5788
+ timeout: 6e4,
5789
+ maxBuffer: 32 * 1024 * 1024,
5790
+ windowsHide: true
5791
+ });
5792
+ return stdout.toString();
5793
+ };
5794
+ async function getJiraAuthStatus(run3 = defaultAcliRunner) {
5795
+ try {
5796
+ return parseAuthStatus(await run3(["jira", "auth", "status"]));
5797
+ } catch (err) {
5798
+ const error = err;
5799
+ if (error.code === "ENOENT") return { installed: false, authenticated: false };
5800
+ return { installed: true, authenticated: false, error: stripAnsi2(String(error.stderr || error.message || err)) };
5801
+ }
5802
+ }
5803
+ function cancelJiraLogin() {
5804
+ activeCancel?.();
5805
+ activeCancel = null;
5806
+ if (!activeLogin2) return;
5807
+ const process2 = activeLogin2;
5808
+ activeLogin2 = null;
5809
+ try {
5810
+ process2.kill();
5811
+ } catch {
5812
+ }
5813
+ }
5814
+ function inputJiraLogin(data) {
5815
+ activeLogin2?.write(data);
5816
+ }
5817
+ function startJiraLogin(onEvent, run3 = defaultAcliRunner) {
5818
+ cancelJiraLogin();
5819
+ onEvent({ type: "starting" });
5820
+ let process2;
5821
+ try {
5822
+ process2 = nodePty4.spawn("acli", ["jira", "auth", "login", "--web"], {
5823
+ name: "xterm-256color",
5824
+ cwd: processCwd(),
5825
+ env: { ...execEnv(), TERM: "xterm-256color" },
5826
+ cols: 100,
5827
+ rows: 24
5828
+ });
5829
+ } catch (err) {
5830
+ onEvent({ type: "error", error: String(err.message ?? err) });
5831
+ return;
5832
+ }
5833
+ activeLogin2 = process2;
5834
+ let output = "";
5835
+ let sentUrl = false;
5836
+ let autoSelectedSite = false;
5837
+ let cancelled = false;
5838
+ activeCancel = () => {
5839
+ cancelled = true;
5840
+ onEvent({ type: "cancelled" });
5841
+ };
5842
+ process2.onData((chunk) => {
5843
+ onEvent({ type: "output", data: chunk });
5844
+ output += chunk;
5845
+ if (output.length > 64e3) output = output.slice(-64e3);
5846
+ const clean = stripAnsi2(output);
5847
+ const url = clean.match(/https:\/\/[^\s<>"']+/)?.[0];
5848
+ if (url && !sentUrl) {
5849
+ sentUrl = true;
5850
+ onEvent({ type: "url", url });
5851
+ }
5852
+ if (!autoSelectedSite && hasSingleSitePrompt(clean)) {
5853
+ autoSelectedSite = true;
5854
+ process2.write("\r");
5855
+ }
5856
+ });
5857
+ process2.onExit(async ({ exitCode }) => {
5858
+ if (activeLogin2 !== process2) return;
5859
+ activeLogin2 = null;
5860
+ activeCancel = null;
5861
+ if (cancelled) return;
5862
+ if (exitCode === 0) {
5863
+ const status = await getJiraAuthStatus(run3);
5864
+ onEvent(status.authenticated ? { type: "success", status } : { type: "error", error: status.error ?? "\u767B\u5165\u5B8C\u6210\uFF0C\u4F46\u7121\u6CD5\u78BA\u8A8D Atlassian \u5E33\u865F\u3002" });
5865
+ } else {
5866
+ const clean = stripAnsi2(output);
5867
+ const error = clean.match(/(?:✗\s*)?Error:[^\r\n]*/g)?.at(-1) ?? clean.split(/[\r\n]+/).map((line) => line.trim()).filter(Boolean).at(-1);
5868
+ onEvent({ type: "error", error: error || `Atlassian \u767B\u5165\u5931\u6557\uFF08exit ${exitCode}\uFF09\u3002` });
5869
+ }
5870
+ });
5871
+ }
5872
+ function processCwd() {
5873
+ return process.cwd();
5874
+ }
5875
+ async function logoutJira(run3 = defaultAcliRunner) {
5876
+ await run3(["jira", "auth", "logout"]);
5877
+ return getJiraAuthStatus(run3);
5878
+ }
5879
+
5880
+ // packages/core/src/jira.ts
5881
+ var asRaw = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : {};
5882
+ var nameOf = (v) => typeof v === "string" ? v : String(asRaw(v).name ?? "");
5883
+ var JIRA_INBOX_JQL = "assignee = currentUser() AND (statusCategory != Done OR sprint in openSprints()) ORDER BY updated DESC";
5884
+ var DEFAULT_POINT_FIELDS = ["customfield_10033", "customfield_10016"];
5885
+ var JIRA_CACHE_TTL_MS = 5 * 60 * 1e3;
5886
+ function buildSearchArgs() {
5887
+ return [
5888
+ "jira",
5889
+ "workitem",
5890
+ "search",
5891
+ "--jql",
5892
+ JIRA_INBOX_JQL,
5893
+ "--fields",
5894
+ "key,summary,status,issuetype,priority,description",
5895
+ "--json",
5896
+ "--paginate",
5897
+ "--limit",
5898
+ "200"
5899
+ ];
5900
+ }
5901
+ function buildViewArgs(key, pointFields = DEFAULT_POINT_FIELDS) {
5902
+ return [
5903
+ "jira",
5904
+ "workitem",
5905
+ "view",
5906
+ key,
5907
+ "--fields",
5908
+ [.../* @__PURE__ */ new Set(["duedate", "parent", "created", "updated", "project", "customfield_10020", ...pointFields])].join(","),
5909
+ "--json"
5910
+ ];
5911
+ }
5912
+ function isJiraRef(value) {
5913
+ const v = asRaw(value);
5914
+ return typeof v.key === "string" && /^[A-Z][A-Z0-9_]+-\d+$/.test(v.key) && typeof v.site === "string" && /^[a-z0-9-]+(?:\.[a-z0-9-]+)*\.atlassian\.net$/.test(v.site) && v.url === `https://${v.site}/browse/${v.key}`;
5915
+ }
5916
+ function toTicket(raw, site, pointFields = DEFAULT_POINT_FIELDS) {
5917
+ const row = asRaw(raw);
5918
+ const fields = asRaw(row.fields);
5919
+ const source = Object.keys(fields).length ? fields : row;
5920
+ const key = row.key;
5921
+ if (typeof key !== "string" || !/^[A-Z][A-Z0-9_]+-\d+$/.test(key)) return null;
5922
+ const status = asRaw(source.status);
5923
+ const category = asRaw(status.statusCategory);
5924
+ const categoryKey = String(category.key ?? category.name ?? "").toLowerCase();
5925
+ const points = pointFields.map((field) => source[field]).find((v) => typeof v === "number" && Number.isFinite(v));
5926
+ const sprintValue = source.customfield_10020;
5927
+ const sprintList = Array.isArray(sprintValue) ? sprintValue : sprintValue ? [sprintValue] : [];
5928
+ const activeSprint = sprintList.map(asRaw).find((s) => String(s.state ?? "").toLowerCase() === "active");
5929
+ const parent = asRaw(source.parent);
5930
+ return {
5931
+ key,
5932
+ url: `https://${site}/browse/${key}`,
5933
+ projectKey: String(asRaw(source.project).key ?? key.split("-")[0]),
5934
+ summary: String(source.summary ?? ""),
5935
+ status: { name: nameOf(source.status), category: categoryKey === "done" ? "done" : categoryKey === "indeterminate" || categoryKey === "in progress" ? "indeterminate" : "new" },
5936
+ issueType: nameOf(source.issuetype),
5937
+ priority: nameOf(source.priority) || null,
5938
+ storyPoints: typeof points === "number" ? points : null,
5939
+ dueDate: typeof source.duedate === "string" ? source.duedate : null,
5940
+ sprint: typeof activeSprint?.name === "string" ? activeSprint.name : null,
5941
+ parentKey: typeof parent.key === "string" ? parent.key : null,
5942
+ description: adfToMarkdown(source.description),
5943
+ createdAt: String(source.created ?? ""),
5944
+ updatedAt: String(source.updated ?? "")
5945
+ };
5946
+ }
5947
+ function searchRows(output) {
5948
+ const parsed = JSON.parse(output);
5949
+ if (Array.isArray(parsed)) return parsed;
5950
+ const body = asRaw(parsed);
5951
+ if (Array.isArray(body.issues)) return body.issues;
5952
+ if (Array.isArray(body.values)) return body.values;
5953
+ throw new Error("\u7121\u6CD5\u8FA8\u8B58 acli Jira search \u7684 JSON \u683C\u5F0F");
5954
+ }
5955
+ async function enrichRows(rows, run3, pointFields) {
5956
+ const enriched = Array.from(rows);
5957
+ let next = 0;
5958
+ let failed = 0;
5959
+ async function worker() {
5960
+ while (next < rows.length) {
5961
+ const index = next++;
5962
+ const row = asRaw(rows[index]);
5963
+ if (typeof row.key !== "string") continue;
5964
+ try {
5965
+ const detail = asRaw(JSON.parse(await run3(buildViewArgs(row.key, pointFields))));
5966
+ enriched[index] = { ...row, fields: { ...asRaw(row.fields), ...asRaw(detail.fields) } };
5967
+ } catch {
5968
+ failed++;
5969
+ }
5970
+ }
5971
+ }
5972
+ await Promise.all(Array.from({ length: Math.min(6, rows.length) }, () => worker()));
5973
+ return { rows: enriched, failed };
5974
+ }
5975
+ function createJiraService(options) {
5976
+ const run3 = options.run ?? defaultAcliRunner;
5977
+ const authStatus = options.authStatus ?? (() => getJiraAuthStatus(run3));
5978
+ const now = options.now ?? Date.now;
5979
+ let cache = null;
5980
+ return {
5981
+ clear() {
5982
+ cache = null;
5983
+ },
5984
+ async inbox(opts = {}) {
5985
+ const auth = await authStatus();
5986
+ if (!auth.installed) return { status: "acli-missing", fetchedAt: 0, tickets: [] };
5987
+ if (!auth.authenticated || !auth.site) return { status: "unauthenticated", fetchedAt: 0, tickets: [] };
5988
+ if (cache && cache.site === auth.site && !opts.force && now() - cache.at < JIRA_CACHE_TTL_MS) return cache.value;
5989
+ const at = now();
5990
+ const pointFields = options.pointFields?.() ?? DEFAULT_POINT_FIELDS;
5991
+ const value = run3(buildSearchArgs()).then(async (output) => {
5992
+ const detail = await enrichRows(searchRows(output), run3, pointFields);
5993
+ return {
5994
+ status: "ok",
5995
+ site: auth.site,
5996
+ email: auth.email,
5997
+ fetchedAt: at,
5998
+ tickets: detail.rows.map((row) => toTicket(row, auth.site, pointFields)).filter((ticket) => ticket !== null),
5999
+ ...detail.failed ? { error: `${detail.failed} \u5F35 Jira ticket \u7684\u8A73\u7D30\u6B04\u4F4D\u8B80\u53D6\u5931\u6557\uFF1B\u6E05\u55AE\u4ECD\u53EF\u67E5\u770B\u3002` } : {}
6000
+ };
6001
+ }).catch((err) => ({
6002
+ status: "ok",
6003
+ site: auth.site,
6004
+ email: auth.email,
6005
+ fetchedAt: at,
6006
+ tickets: [],
6007
+ error: String(err.stderr || err.message || err)
6008
+ }));
6009
+ cache = { at, site: auth.site, value };
6010
+ return value;
6011
+ }
6012
+ };
6013
+ }
6014
+
5635
6015
  // packages/core/src/board-cli.ts
5636
6016
  import fs14 from "fs";
5637
6017
  import path18 from "path";
@@ -5754,7 +6134,7 @@ async function readSpecsAt(cwd, ref, paths) {
5754
6134
  // packages/core/src/chat-session.ts
5755
6135
  init_env();
5756
6136
  init_git_bash();
5757
- import { spawn as spawn6 } from "child_process";
6137
+ import { spawn as spawn7 } from "child_process";
5758
6138
  import { randomUUID as randomUUID3 } from "crypto";
5759
6139
 
5760
6140
  // packages/core/src/chat-store.ts
@@ -5910,7 +6290,7 @@ ${attachmentLines.join("\n")}` : text;
5910
6290
  `--append-system-prompt ${quote(systemPrompt)}`,
5911
6291
  quote(fullText)
5912
6292
  ].filter(Boolean).join(" ") : buildRawAgentCommand(agentCli, model, fullText, systemPrompt, worktreePath, quote, workspacePath);
5913
- const proc = spawn6(shell.command, shell.args(cmd), {
6293
+ const proc = spawn7(shell.command, shell.args(cmd), {
5914
6294
  cwd: worktreePath,
5915
6295
  env: execEnv()
5916
6296
  });
@@ -6385,7 +6765,7 @@ function validBoard(value) {
6385
6765
  }
6386
6766
  return next;
6387
6767
  }
6388
- function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatus, modelCatalog }) {
6768
+ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatus, acliRunner, jiraAuthStatus, modelCatalog }) {
6389
6769
  const sink = bus.sink();
6390
6770
  const models = modelCatalog ?? createModelCatalog({ version });
6391
6771
  let libraryCodexHome = null;
@@ -6537,6 +6917,11 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
6537
6917
  authStatus: githubAuthStatus ?? getGitHubCliAuthStatus,
6538
6918
  repoOf: async (projectPath) => parseGithubRepo((await getGitInfo(projectPath)).remoteUrl)
6539
6919
  });
6920
+ const jira = createJiraService({
6921
+ run: acliRunner,
6922
+ authStatus: jiraAuthStatus ?? (() => getJiraAuthStatus(acliRunner)),
6923
+ pointFields: () => getSettings().jira?.storyPointsFields?.length ? getSettings().jira.storyPointsFields : DEFAULT_POINT_FIELDS
6924
+ });
6540
6925
  const handlers = {
6541
6926
  "vibeflow:getState": () => getState(),
6542
6927
  // A card's progress right now (todo list, usage, activity), for a frontend
@@ -6556,7 +6941,13 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
6556
6941
  return getState();
6557
6942
  },
6558
6943
  "vibeflow:setSettings": (patch) => {
6559
- setSettings(obj(patch, "patch"));
6944
+ const value = obj(patch, "patch");
6945
+ if (value.jira !== void 0) {
6946
+ const fields = obj(value.jira, "jira")?.storyPointsFields;
6947
+ if (fields !== void 0 && (!Array.isArray(fields) || !fields.every((field) => typeof field === "string" && /^customfield_\d+$/.test(field)))) invalid("jira.storyPointsFields must contain customfield IDs");
6948
+ jira.clear();
6949
+ }
6950
+ setSettings(value);
6560
6951
  return getState();
6561
6952
  },
6562
6953
  "settings:githubAuthStatus": () => getGitHubCliAuthStatus(),
@@ -6577,6 +6968,25 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
6577
6968
  const p = payload === void 0 ? {} : obj(payload, "payload");
6578
6969
  return github.taskLinks(getState().board, { force: p.force === true });
6579
6970
  },
6971
+ "jira:authStatus": () => (jiraAuthStatus ?? (() => getJiraAuthStatus(acliRunner)))(),
6972
+ "settings:startJiraAuthLogin": () => {
6973
+ startJiraLogin((event) => {
6974
+ if (event.type === "success") jira.clear();
6975
+ bus.emit("jira-auth:event", event);
6976
+ }, acliRunner);
6977
+ },
6978
+ "settings:cancelJiraAuthLogin": () => cancelJiraLogin(),
6979
+ "settings:inputJiraAuthLogin": (payload) => inputJiraLogin(str(payload, "input").slice(0, 1e3)),
6980
+ "settings:logoutJiraAuth": async () => {
6981
+ const status = await logoutJira(acliRunner);
6982
+ jira.clear();
6983
+ bus.emit("jira-auth:event", { type: "signed-out", status });
6984
+ return status;
6985
+ },
6986
+ "jira:inbox": (payload) => {
6987
+ const p = payload === void 0 ? {} : obj(payload, "payload");
6988
+ return jira.inbox({ force: p.force === true });
6989
+ },
6580
6990
  "env:detectAgents": () => detectAgents(),
6581
6991
  "agents:listModels": (payload) => {
6582
6992
  const { agentId: rawId, refresh } = obj(payload, "payload");
@@ -6608,6 +7018,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
6608
7018
  if (p.effort !== void 0 && !AGENT_EFFORTS.includes(p.effort)) invalid("unknown effort");
6609
7019
  if (p.agentCli !== void 0 && !AGENT_IDS2.includes(p.agentCli)) invalid("unknown agent");
6610
7020
  if (p.github !== void 0 && !isGithubRef(p.github)) invalid("github must be an issue or pr ref on github.com");
7021
+ if (p.jira !== void 0 && !isJiraRef(p.jira)) invalid("jira must be a Jira ticket ref");
6611
7022
  const { task } = await createTaskFromInput({
6612
7023
  projectPath: p.projectPath,
6613
7024
  title: p.title,
@@ -6620,7 +7031,8 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
6620
7031
  effort: p.effort,
6621
7032
  autoMode: p.autoMode,
6622
7033
  attachments: p.attachments,
6623
- github: p.github
7034
+ github: p.github,
7035
+ jira: p.jira
6624
7036
  });
6625
7037
  return { state: getState(), task };
6626
7038
  },
@@ -7037,6 +7449,7 @@ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatu
7037
7449
  models.warm();
7038
7450
  },
7039
7451
  shutdown() {
7452
+ cancelJiraLogin();
7040
7453
  sessions.shutdown();
7041
7454
  cancelAllChatSends();
7042
7455
  unwatchAllSubAgents();
@@ -7493,12 +7906,12 @@ function connectToHost(lock) {
7493
7906
  // packages/cli/src/doctor.ts
7494
7907
  import fs21 from "fs";
7495
7908
  import path25 from "path";
7496
- import { execFile as execFile8 } from "child_process";
7497
- import { promisify as promisify5 } from "util";
7909
+ import { execFile as execFile9 } from "child_process";
7910
+ import { promisify as promisify6 } from "util";
7498
7911
  init_env();
7499
7912
  init_git_bash();
7500
7913
  init_tmux_backend();
7501
- var pexec5 = promisify5(execFile8);
7914
+ var pexec6 = promisify6(execFile9);
7502
7915
  function parseVersion(text) {
7503
7916
  const m = /(\d+)\.(\d+)(?:\.(\d+))?/.exec(text);
7504
7917
  return m ? [Number(m[1]), Number(m[2]), Number(m[3] ?? 0)] : [];
@@ -7512,7 +7925,7 @@ function versionAtLeast(have, want) {
7512
7925
  }
7513
7926
  async function run2(cmd, args) {
7514
7927
  try {
7515
- const { stdout } = await pexec5(cmd, args, { env: execEnv(), timeout: 1e4, windowsHide: true });
7928
+ const { stdout } = await pexec6(cmd, args, { env: execEnv(), timeout: 1e4, windowsHide: true });
7516
7929
  return String(stdout).trim();
7517
7930
  } catch {
7518
7931
  return null;