@kw-yeh/vibeflow 4.6.2 → 4.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/vibeflow.mjs CHANGED
@@ -522,9 +522,9 @@ function truncateUtf8(value, maxBytes) {
522
522
  return result;
523
523
  }
524
524
  function safeAttachmentName(name) {
525
- const basename2 = name.replace(/\\/g, "/").split("/").pop() ?? "";
525
+ const basename3 = name.replace(/\\/g, "/").split("/").pop() ?? "";
526
526
  const sanitized = truncateUtf8(
527
- basename2.replace(/[\u0000-\u001f\u007f]/g, "").trim(),
527
+ basename3.replace(/[\u0000-\u001f\u007f]/g, "").trim(),
528
528
  MAX_FILENAME_BYTES
529
529
  );
530
530
  return sanitized && sanitized !== "." && sanitized !== ".." ? sanitized : "attachment";
@@ -1333,8 +1333,8 @@ function quitHelp(backend, isHost) {
1333
1333
  if (isHost) return "q \u96E2\u958B\uFF08\u9019\u500B TUI \u5C31\u662F host\uFF0C\u57F7\u884C\u4E2D\u7684 agent \u6703\u4E00\u8D77\u7D50\u675F\uFF09";
1334
1334
  return "q \u96E2\u958B\uFF08agent \u7531\u57F7\u884C\u4E2D\u7684 host \u6301\u6709\uFF0C\u6703\u7E7C\u7E8C\u57F7\u884C\uFF09";
1335
1335
  }
1336
- function diffEnv(base, hasDelta) {
1337
- const env = { ...base };
1336
+ function diffEnv(base2, hasDelta) {
1337
+ const env = { ...base2 };
1338
1338
  if (hasDelta) env.GIT_PAGER = "delta --side-by-side --paging=always";
1339
1339
  else if (env.LESS === void 0) env.LESS = "R";
1340
1340
  return env;
@@ -1607,9 +1607,9 @@ function hasCommand(cmd, args) {
1607
1607
  return spawnSync2(cmd, args, { stdio: "ignore" }).status === 0;
1608
1608
  }
1609
1609
  function showDiff(task) {
1610
- const base = task.baseBranch ?? "HEAD";
1610
+ const base2 = task.baseBranch ?? "HEAD";
1611
1611
  const env = diffEnv(process.env, hasCommand("delta", ["--version"]));
1612
- spawnSync2("git", ["-C", task.worktreePath, "-c", "color.ui=always", "diff", base], {
1612
+ spawnSync2("git", ["-C", task.worktreePath, "-c", "color.ui=always", "diff", base2], {
1613
1613
  stdio: "inherit",
1614
1614
  env
1615
1615
  });
@@ -2223,6 +2223,10 @@ function createBridge(t) {
2223
2223
  startGithubAuthLogin: () => t.invoke("settings:startGithubAuthLogin"),
2224
2224
  cancelGithubAuthLogin: () => t.invoke("settings:cancelGithubAuthLogin"),
2225
2225
  logoutGithubAuth: () => t.invoke("settings:logoutGithubAuth"),
2226
+ /** Open Issues/PRs of the board's GitHub repos that involve me; cached unless `force`. */
2227
+ getGithubInbox: (opts) => t.invoke("github:inbox", opts ?? {}),
2228
+ /** The Issue/PR each card belongs to, keyed by task id. */
2229
+ getGithubTaskLinks: (opts) => t.invoke("github:taskLinks", opts ?? {}),
2226
2230
  onGithubAuthEvent: (callback) => {
2227
2231
  return t.on("github-auth:event", (payload) => callback(payload));
2228
2232
  },
@@ -2533,8 +2537,8 @@ async function generateBranchName(title, description) {
2533
2537
  }
2534
2538
 
2535
2539
  // packages/core/src/git.ts
2536
- import { execFile as execFile4 } from "child_process";
2537
- import { promisify } from "util";
2540
+ import { execFile as execFile5 } from "child_process";
2541
+ import { promisify as promisify2 } from "util";
2538
2542
  import path12 from "path";
2539
2543
  import fs8 from "fs/promises";
2540
2544
 
@@ -2881,6 +2885,300 @@ function buildAgentCommand(task, systemPrompt, opts, workspacePath) {
2881
2885
 
2882
2886
  // packages/core/src/git.ts
2883
2887
  init_env();
2888
+
2889
+ // packages/core/src/github.ts
2890
+ init_env();
2891
+ import { execFile as execFile4 } from "child_process";
2892
+ import { promisify } from "util";
2893
+ var pexec = promisify(execFile4);
2894
+ var defaultGhRunner = async (args) => {
2895
+ const { stdout } = await pexec("gh", args, {
2896
+ env: execEnv(),
2897
+ maxBuffer: 16 * 1024 * 1024,
2898
+ timeout: 3e4,
2899
+ windowsHide: true
2900
+ });
2901
+ return stdout.toString();
2902
+ };
2903
+ function parseGithubRepo(remoteUrl) {
2904
+ if (!remoteUrl) return null;
2905
+ const url = remoteUrl.trim();
2906
+ const match = url.match(/^git@github\.com:([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/) ?? url.match(/^ssh:\/\/git@github\.com(?::\d+)?\/([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/) ?? url.match(/^https?:\/\/(?:[^@/\s]+@)?github\.com\/([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/);
2907
+ return match ? `${match[1]}/${match[2]}` : null;
2908
+ }
2909
+ var REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
2910
+ function isGithubRef(value) {
2911
+ if (!value || typeof value !== "object") return false;
2912
+ const v = value;
2913
+ 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}/`);
2914
+ }
2915
+ var ITEM_FIELDS = "number,title,url,body,author,assignees,labels,milestone,createdAt";
2916
+ var ISSUE_FIELDS = `${ITEM_FIELDS},issueType`;
2917
+ var PR_FIELDS = `${ITEM_FIELDS},state,isDraft,reviewDecision,headRefName,baseRefName`;
2918
+ var PR_INDEX_FIELDS = "number,url,state,isDraft,headRefName,closingIssuesReferences";
2919
+ var LIST_LIMIT = "50";
2920
+ var PR_INDEX_LIMIT = "100";
2921
+ async function runJson(run3, args) {
2922
+ const out = await run3(args);
2923
+ const parsed = JSON.parse(out || "[]");
2924
+ return Array.isArray(parsed) ? parsed : [];
2925
+ }
2926
+ function user(raw) {
2927
+ const u = raw;
2928
+ if (!u || typeof u.login !== "string") return null;
2929
+ return typeof u.name === "string" && u.name ? { login: u.login, name: u.name } : { login: u.login };
2930
+ }
2931
+ function base(raw, repo) {
2932
+ const milestone = raw.milestone;
2933
+ return {
2934
+ repo,
2935
+ number: Number(raw.number),
2936
+ title: String(raw.title ?? ""),
2937
+ url: String(raw.url ?? ""),
2938
+ body: typeof raw.body === "string" ? raw.body : "",
2939
+ author: user(raw.author),
2940
+ assignees: Array.isArray(raw.assignees) ? raw.assignees.map(user).filter(Boolean) : [],
2941
+ labels: Array.isArray(raw.labels) ? raw.labels.map((l) => ({ name: String(l.name ?? ""), color: String(l.color ?? "") })) : [],
2942
+ milestone: milestone && typeof milestone.title === "string" ? milestone.title : null,
2943
+ createdAt: String(raw.createdAt ?? "")
2944
+ };
2945
+ }
2946
+ function prStateOf(raw) {
2947
+ if (raw.state === "MERGED") return "merged";
2948
+ if (raw.state === "CLOSED") return "closed";
2949
+ return raw.isDraft ? "draft" : "open";
2950
+ }
2951
+ function toIssue(raw, repo) {
2952
+ const type = raw.issueType;
2953
+ return {
2954
+ ...base(raw, repo),
2955
+ kind: "issue",
2956
+ issueType: type && typeof type.name === "string" ? { name: type.name, ...typeof type.color === "string" ? { color: type.color } : {} } : null
2957
+ };
2958
+ }
2959
+ function toPr(raw, repo) {
2960
+ return {
2961
+ ...base(raw, repo),
2962
+ kind: "pr",
2963
+ state: prStateOf(raw),
2964
+ reviewDecision: typeof raw.reviewDecision === "string" && raw.reviewDecision ? raw.reviewDecision : null,
2965
+ headRefName: String(raw.headRefName ?? ""),
2966
+ baseRefName: String(raw.baseRefName ?? "")
2967
+ };
2968
+ }
2969
+ function mergeItems(lists) {
2970
+ const byNumber = /* @__PURE__ */ new Map();
2971
+ for (const list of lists) for (const item of list) if (!byNumber.has(item.number)) byNumber.set(item.number, item);
2972
+ return Array.from(byNumber.values()).sort(
2973
+ (a, b) => b.createdAt.localeCompare(a.createdAt) || b.number - a.number
2974
+ );
2975
+ }
2976
+ async function listIssues(run3, repo, filter) {
2977
+ const args = ["issue", "list", "-R", repo, "--state", "open", "--limit", LIST_LIMIT, ...filter];
2978
+ let rows;
2979
+ try {
2980
+ rows = await runJson(run3, [...args, "--json", ISSUE_FIELDS]);
2981
+ } catch (err) {
2982
+ if (!/issueType/.test(errorText(err))) throw err;
2983
+ rows = await runJson(run3, [...args, "--json", ITEM_FIELDS]);
2984
+ }
2985
+ return rows.map((r) => toIssue(r, repo));
2986
+ }
2987
+ async function listPrs(run3, repo, filter) {
2988
+ const rows = await runJson(run3, [
2989
+ "pr",
2990
+ "list",
2991
+ "-R",
2992
+ repo,
2993
+ "--state",
2994
+ "open",
2995
+ "--limit",
2996
+ LIST_LIMIT,
2997
+ ...filter,
2998
+ "--json",
2999
+ PR_FIELDS
3000
+ ]);
3001
+ return rows.map((r) => toPr(r, repo));
3002
+ }
3003
+ async function fetchRepoItems(run3, repo) {
3004
+ const [assignedIssues, authoredIssues, assignedPrs, authoredPrs, reviewPrs] = await Promise.all([
3005
+ listIssues(run3, repo, ["--assignee", "@me"]),
3006
+ listIssues(run3, repo, ["--author", "@me"]),
3007
+ listPrs(run3, repo, ["--assignee", "@me"]),
3008
+ listPrs(run3, repo, ["--author", "@me"]),
3009
+ listPrs(run3, repo, ["--search", "review-requested:@me"])
3010
+ ]);
3011
+ return {
3012
+ issues: mergeItems([assignedIssues, authoredIssues]),
3013
+ prs: mergeItems([assignedPrs, authoredPrs, reviewPrs])
3014
+ };
3015
+ }
3016
+ async function fetchPrIndex(run3, repo) {
3017
+ const rows = await runJson(run3, [
3018
+ "pr",
3019
+ "list",
3020
+ "-R",
3021
+ repo,
3022
+ "--state",
3023
+ "all",
3024
+ "--limit",
3025
+ PR_INDEX_LIMIT,
3026
+ "--json",
3027
+ PR_INDEX_FIELDS
3028
+ ]);
3029
+ const [owner, name] = repo.split("/");
3030
+ return rows.map((r) => ({
3031
+ number: Number(r.number),
3032
+ url: String(r.url ?? ""),
3033
+ state: prStateOf(r),
3034
+ headRefName: String(r.headRefName ?? ""),
3035
+ closingIssues: (Array.isArray(r.closingIssuesReferences) ? r.closingIssuesReferences : []).filter((ref) => {
3036
+ const repository = ref.repository;
3037
+ const refOwner = repository?.owner?.login;
3038
+ return !repository || repository.name === name && refOwner === owner;
3039
+ }).map((ref) => ({
3040
+ number: Number(ref.number),
3041
+ url: typeof ref.url === "string" ? ref.url : `https://github.com/${repo}/issues/${ref.number}`
3042
+ }))
3043
+ }));
3044
+ }
3045
+ function linkFromRef(ref, state) {
3046
+ return { kind: ref.kind, number: ref.number, url: ref.url, state };
3047
+ }
3048
+ function hasRealBranch(task) {
3049
+ return Boolean(task.provisionedAt || task.worktreePath || task.outcome);
3050
+ }
3051
+ function resolveTaskLinks(tasks, repoOfProject, prIndex) {
3052
+ const links = {};
3053
+ for (const task of tasks) {
3054
+ const source = task.github;
3055
+ const repo = source?.repo ?? (task.projectPath ? repoOfProject.get(task.projectPath) : void 0);
3056
+ const index = repo ? prIndex.get(repo) ?? [] : [];
3057
+ let pr;
3058
+ let prEntry;
3059
+ if (source?.kind === "pr") {
3060
+ prEntry = index.find((e) => e.number === source.number);
3061
+ pr = linkFromRef(source, prEntry?.state ?? "open");
3062
+ } else {
3063
+ if (hasRealBranch(task)) {
3064
+ prEntry = index.filter((e) => e.headRefName === task.branch).sort((a, b) => b.number - a.number)[0];
3065
+ }
3066
+ if (prEntry) {
3067
+ pr = { kind: "pr", number: prEntry.number, url: prEntry.url, state: prEntry.state };
3068
+ } else if (task.outcome?.pr) {
3069
+ const o = task.outcome.pr;
3070
+ pr = { kind: "pr", number: o.number, url: o.url, state: prStateOf({ state: o.state }) };
3071
+ }
3072
+ }
3073
+ const issueState = pr?.state === "merged" ? "closed" : "open";
3074
+ let issue;
3075
+ if (source?.kind === "issue") {
3076
+ issue = linkFromRef(source, issueState);
3077
+ } else if (prEntry?.closingIssues[0]) {
3078
+ const ref = prEntry.closingIssues[0];
3079
+ issue = { kind: "issue", number: ref.number, url: ref.url, state: issueState };
3080
+ }
3081
+ if (pr || issue) links[task.id] = { ...issue ? { issue } : {}, ...pr ? { pr } : {} };
3082
+ }
3083
+ return links;
3084
+ }
3085
+ function errorText(err) {
3086
+ const e = err;
3087
+ const stderr = e?.stderr ? e.stderr.toString().trim() : "";
3088
+ return stderr || e?.message || String(err);
3089
+ }
3090
+ var GITHUB_CACHE_TTL_MS = 5 * 60 * 1e3;
3091
+ function boardTasks(board) {
3092
+ return [...board.in_progress, ...board.backlog, ...board.done];
3093
+ }
3094
+ function boardProjects(board) {
3095
+ const latest = /* @__PURE__ */ new Map();
3096
+ for (const t of boardTasks(board)) {
3097
+ if (!t.projectPath) continue;
3098
+ latest.set(t.projectPath, Math.max(latest.get(t.projectPath) ?? 0, t.createdAt ?? 0));
3099
+ }
3100
+ return Array.from(latest.entries()).sort((a, b) => b[1] - a[1]).map(([p]) => p);
3101
+ }
3102
+ function basename(p) {
3103
+ return p.split(/[/\\]/).filter(Boolean).pop() ?? p;
3104
+ }
3105
+ function createGithubService(options) {
3106
+ const run3 = options.run ?? defaultGhRunner;
3107
+ const authStatus = options.authStatus;
3108
+ const now = options.now ?? Date.now;
3109
+ const items = /* @__PURE__ */ new Map();
3110
+ const indexes = /* @__PURE__ */ new Map();
3111
+ const repos = /* @__PURE__ */ new Map();
3112
+ function cached2(map, key, force, load) {
3113
+ const hit = map.get(key);
3114
+ if (hit && !force && now() - hit.at < GITHUB_CACHE_TTL_MS) return hit;
3115
+ const entry = { at: now(), value: load() };
3116
+ entry.value.catch(() => {
3117
+ });
3118
+ map.set(key, entry);
3119
+ return entry;
3120
+ }
3121
+ async function projectRepos(board, force) {
3122
+ const out = /* @__PURE__ */ new Map();
3123
+ const projects = boardProjects(board);
3124
+ const resolved = await Promise.all(
3125
+ projects.map((p) => cached2(repos, p, force, () => options.repoOf(p)).value.catch(() => null))
3126
+ );
3127
+ projects.forEach((p, i) => {
3128
+ const repo = resolved[i];
3129
+ if (repo) out.set(p, repo);
3130
+ });
3131
+ return out;
3132
+ }
3133
+ return {
3134
+ async inbox(board, opts = {}) {
3135
+ const force = opts.force === true;
3136
+ const auth = await authStatus();
3137
+ if (!auth.installed) return { status: "gh-missing", fetchedAt: 0, repos: [] };
3138
+ if (!auth.authenticated) return { status: "unauthenticated", fetchedAt: 0, repos: [] };
3139
+ const byProject = await projectRepos(board, force);
3140
+ const repoProject = /* @__PURE__ */ new Map();
3141
+ for (const [project, repo] of byProject) if (!repoProject.has(repo)) repoProject.set(repo, project);
3142
+ const entries = Array.from(repoProject.entries());
3143
+ const results = await Promise.all(
3144
+ entries.map(async ([repo, projectPath]) => {
3145
+ const entry = cached2(items, repo, force, () => fetchRepoItems(run3, repo));
3146
+ const head = { repo, projectPath, projectName: basename(projectPath) };
3147
+ try {
3148
+ return { at: entry.at, inbox: { ...head, ...await entry.value } };
3149
+ } catch (err) {
3150
+ return { at: entry.at, inbox: { ...head, issues: [], prs: [], error: errorText(err) } };
3151
+ }
3152
+ })
3153
+ );
3154
+ return {
3155
+ status: "ok",
3156
+ ...auth.login ? { login: auth.login } : {},
3157
+ fetchedAt: results.length ? Math.min(...results.map((r) => r.at)) : 0,
3158
+ repos: results.map((r) => r.inbox).sort((a, b) => a.repo.localeCompare(b.repo))
3159
+ };
3160
+ },
3161
+ async taskLinks(board, opts = {}) {
3162
+ const force = opts.force === true;
3163
+ const tasks = boardTasks(board);
3164
+ const byProject = await projectRepos(board, force);
3165
+ const wanted = new Set(byProject.values());
3166
+ for (const t of tasks) if (t.github) wanted.add(t.github.repo);
3167
+ const prIndex = /* @__PURE__ */ new Map();
3168
+ await Promise.all(
3169
+ Array.from(wanted).map(async (repo) => {
3170
+ try {
3171
+ prIndex.set(repo, await cached2(indexes, repo, force, () => fetchPrIndex(run3, repo)).value);
3172
+ } catch {
3173
+ }
3174
+ })
3175
+ );
3176
+ return resolveTaskLinks(tasks, byProject, prIndex);
3177
+ }
3178
+ };
3179
+ }
3180
+
3181
+ // packages/core/src/git.ts
2884
3182
  init_attachments();
2885
3183
 
2886
3184
  // packages/core/src/artifacts.ts
@@ -3038,9 +3336,9 @@ function deleteArtifacts(baseDir, worktreePath) {
3038
3336
  }
3039
3337
 
3040
3338
  // packages/core/src/git.ts
3041
- var pexec = promisify(execFile4);
3339
+ var pexec2 = promisify2(execFile5);
3042
3340
  async function git(cwd, args) {
3043
- const { stdout } = await pexec("git", args, {
3341
+ const { stdout } = await pexec2("git", args, {
3044
3342
  cwd,
3045
3343
  maxBuffer: 32 * 1024 * 1024,
3046
3344
  env: execEnv()
@@ -3409,7 +3707,7 @@ function copyIgnoredDependenciesInBackground(projectPath, worktreePath, entries)
3409
3707
  }
3410
3708
  })();
3411
3709
  }
3412
- async function adoptRemoteBranch(projectPath, worktreePath, branch, base) {
3710
+ async function adoptRemoteBranch(projectPath, worktreePath, branch, base2) {
3413
3711
  try {
3414
3712
  await git(projectPath, ["fetch", "origin", branch]);
3415
3713
  } catch {
@@ -3431,32 +3729,32 @@ async function adoptRemoteBranch(projectPath, worktreePath, branch, base) {
3431
3729
  }
3432
3730
  const ignoredEntries = await copySmallIgnoredFiles(projectPath, worktreePath);
3433
3731
  copyIgnoredDependenciesInBackground(projectPath, worktreePath, ignoredEntries);
3434
- return { branch, worktreePath, pushed: true, baseBranch: base };
3732
+ return { branch, worktreePath, pushed: true, baseBranch: base2 };
3435
3733
  }
3436
3734
  async function provisionWorktree(projectPath, workspacePath, taskId, baseBranch, preferredBranch, opts = {}) {
3437
3735
  await ensureLocalExclude(projectPath);
3438
3736
  const branch = opts.explicitBranch && preferredBranch ? preferredBranch : await resolveBranchName(projectPath, workspacePath, taskId, preferredBranch);
3439
3737
  const worktreePath = path12.join(workspacePath, worktreeDirName(branch));
3440
3738
  const info = await getGitInfo(projectPath);
3441
- const base = baseBranch || info.defaultBase || info.currentBranch || "HEAD";
3739
+ const base2 = baseBranch || info.defaultBase || info.currentBranch || "HEAD";
3442
3740
  if (opts.explicitBranch && info.hasRemote && await remoteBranchExists(projectPath, branch)) {
3443
- return adoptRemoteBranch(projectPath, worktreePath, branch, base);
3741
+ return adoptRemoteBranch(projectPath, worktreePath, branch, base2);
3444
3742
  }
3445
- let startPoint = base;
3743
+ let startPoint = base2;
3446
3744
  if (info.hasRemote) {
3447
3745
  try {
3448
- await git(projectPath, ["fetch", "origin", `${base}:${base}`]);
3746
+ await git(projectPath, ["fetch", "origin", `${base2}:${base2}`]);
3449
3747
  } catch {
3450
3748
  try {
3451
- await git(projectPath, ["fetch", "origin", base]);
3749
+ await git(projectPath, ["fetch", "origin", base2]);
3452
3750
  } catch {
3453
3751
  }
3454
3752
  }
3455
3753
  try {
3456
- await git(projectPath, ["rev-parse", "--verify", `origin/${base}`]);
3457
- startPoint = `origin/${base}`;
3754
+ await git(projectPath, ["rev-parse", "--verify", `origin/${base2}`]);
3755
+ startPoint = `origin/${base2}`;
3458
3756
  } catch {
3459
- startPoint = base;
3757
+ startPoint = base2;
3460
3758
  }
3461
3759
  }
3462
3760
  try {
@@ -3486,7 +3784,7 @@ async function provisionWorktree(projectPath, workspacePath, taskId, baseBranch,
3486
3784
  copySmallIgnoredFiles(projectPath, worktreePath)
3487
3785
  ]);
3488
3786
  copyIgnoredDependenciesInBackground(projectPath, worktreePath, ignoredEntries);
3489
- return { branch, worktreePath, pushed, baseBranch: base };
3787
+ return { branch, worktreePath, pushed, baseBranch: base2 };
3490
3788
  }
3491
3789
  async function removeWorktree(projectPath, worktreePath) {
3492
3790
  try {
@@ -3698,7 +3996,7 @@ var MAX_OUTCOME_COMMITS = 50;
3698
3996
  var OUTCOME_LOG_FORMAT = "%h%x00%s";
3699
3997
  async function getOutcomePr(worktreePath) {
3700
3998
  try {
3701
- const { stdout } = await pexec(
3999
+ const { stdout } = await pexec2(
3702
4000
  "gh",
3703
4001
  ["pr", "view", "--json", "url,number,state,title,body"],
3704
4002
  { cwd: worktreePath, env: execEnv() }
@@ -3759,7 +4057,7 @@ async function captureTaskOutcome(worktreePath, baseBranch) {
3759
4057
  }
3760
4058
  async function getPrStatus(worktreePath) {
3761
4059
  try {
3762
- const { stdout } = await pexec(
4060
+ const { stdout } = await pexec2(
3763
4061
  "gh",
3764
4062
  ["pr", "view", "--json", "url,number,state"],
3765
4063
  { cwd: worktreePath, env: execEnv() }
@@ -3775,17 +4073,9 @@ async function getGithubCompareUrl(worktreePath, baseBranch) {
3775
4073
  try {
3776
4074
  const remoteUrl = await git(worktreePath, ["remote", "get-url", "origin"]);
3777
4075
  const branch = await git(worktreePath, ["rev-parse", "--abbrev-ref", "HEAD"]);
3778
- let webUrl;
3779
- const sshMatch = remoteUrl.match(/git@github\.com:(.+?)(?:\.git)?$/);
3780
- const httpsMatch = remoteUrl.match(/https?:\/\/github\.com\/(.+?)(?:\.git)?$/);
3781
- if (sshMatch) {
3782
- webUrl = `https://github.com/${sshMatch[1]}`;
3783
- } else if (httpsMatch) {
3784
- webUrl = `https://github.com/${httpsMatch[1]}`;
3785
- } else {
3786
- return null;
3787
- }
3788
- return `${webUrl}/compare/${baseBranch}...${branch}?expand=1`;
4076
+ const repo = parseGithubRepo(remoteUrl);
4077
+ if (!repo) return null;
4078
+ return `https://github.com/${repo}/compare/${baseBranch}...${branch}?expand=1`;
3789
4079
  } catch {
3790
4080
  return null;
3791
4081
  }
@@ -3852,9 +4142,9 @@ function taskAttachmentStagingPath(workspacePath, taskId) {
3852
4142
 
3853
4143
  // packages/core/src/agents.ts
3854
4144
  init_env();
3855
- import { execFile as execFile5 } from "child_process";
3856
- import { promisify as promisify2 } from "util";
3857
- var pexec2 = promisify2(execFile5);
4145
+ import { execFile as execFile6 } from "child_process";
4146
+ import { promisify as promisify3 } from "util";
4147
+ var pexec3 = promisify3(execFile6);
3858
4148
  var AGENT_EFFORTS = ["low", "medium", "high", "xhigh"];
3859
4149
  var DEFAULT_TASK_EFFORT = "medium";
3860
4150
  var AGENT_CLIS = [
@@ -3882,7 +4172,7 @@ var AGENT_CLIS = [
3882
4172
  async function commandExists(bin) {
3883
4173
  try {
3884
4174
  const command = process.platform === "win32" ? "where.exe" : "which";
3885
- await pexec2(command, [bin], {
4175
+ await pexec3(command, [bin], {
3886
4176
  env: execEnv(),
3887
4177
  timeout: 2500,
3888
4178
  windowsHide: true
@@ -3957,7 +4247,8 @@ async function createTaskFromInput(input) {
3957
4247
  agentCli: input.agentCli ?? "claude",
3958
4248
  model: input.model || void 0,
3959
4249
  effort: input.effort ?? DEFAULT_TASK_EFFORT,
3960
- autoMode: input.autoMode
4250
+ autoMode: input.autoMode,
4251
+ ...input.github ? { github: input.github } : {}
3961
4252
  };
3962
4253
  try {
3963
4254
  const board = store.get("board");
@@ -4311,9 +4602,9 @@ function truncateUtf82(value, maxBytes) {
4311
4602
  return result;
4312
4603
  }
4313
4604
  function safeLibraryName(name) {
4314
- const basename2 = name.replace(/\\/g, "/").split("/").pop() ?? "";
4605
+ const basename3 = name.replace(/\\/g, "/").split("/").pop() ?? "";
4315
4606
  const cleaned = truncateUtf82(
4316
- basename2.replace(/[\u0000-\u001f\u007f]/g, "").trim(),
4607
+ basename3.replace(/[\u0000-\u001f\u007f]/g, "").trim(),
4317
4608
  MAX_NAME_BYTES
4318
4609
  );
4319
4610
  return cleaned && cleaned !== "." && cleaned !== ".." ? cleaned : "untitled";
@@ -4789,13 +5080,13 @@ import path21 from "path";
4789
5080
  import { randomUUID as randomUUID4 } from "crypto";
4790
5081
 
4791
5082
  // packages/core/src/agent-models.ts
4792
- import { execFile as execFile6 } from "child_process";
5083
+ import { execFile as execFile7 } from "child_process";
4793
5084
  import fs12 from "fs";
4794
5085
  import os4 from "os";
4795
5086
  import path16 from "path";
4796
- import { promisify as promisify3 } from "util";
5087
+ import { promisify as promisify4 } from "util";
4797
5088
  init_env();
4798
- var pexec3 = promisify3(execFile6);
5089
+ var pexec4 = promisify4(execFile7);
4799
5090
  var CODEX_MODELS_TIMEOUT_MS = 2e4;
4800
5091
  function builtinModels(id) {
4801
5092
  return (AGENT_CLIS.find((a) => a.id === id) ?? AGENT_CLIS[0]).models;
@@ -4805,7 +5096,7 @@ function defaultCodexHome() {
4805
5096
  return configured ? configured : path16.join(os4.homedir(), ".codex");
4806
5097
  }
4807
5098
  async function defaultRunCodexModels() {
4808
- const { stdout } = await pexec3("codex", ["debug", "models"], {
5099
+ const { stdout } = await pexec4("codex", ["debug", "models"], {
4809
5100
  env: execEnv(),
4810
5101
  timeout: CODEX_MODELS_TIMEOUT_MS,
4811
5102
  maxBuffer: 16 * 1024 * 1024,
@@ -4862,10 +5153,10 @@ async function listAgentModels(id, opts = {}) {
4862
5153
 
4863
5154
  // packages/core/src/github-auth.ts
4864
5155
  init_env();
4865
- import { execFile as execFile7 } from "child_process";
4866
- import { promisify as promisify4 } from "util";
5156
+ import { execFile as execFile8 } from "child_process";
5157
+ import { promisify as promisify5 } from "util";
4867
5158
  import * as nodePty3 from "node-pty";
4868
- var pexec4 = promisify4(execFile7);
5159
+ var pexec5 = promisify5(execFile8);
4869
5160
  var GITHUB_HOST = "github.com";
4870
5161
  var DEVICE_URL = "https://github.com/login/device";
4871
5162
  var activeLogin = null;
@@ -4900,7 +5191,7 @@ function errorMessage2(err) {
4900
5191
  }
4901
5192
  async function getGitHubCliAuthStatus() {
4902
5193
  try {
4903
- const { stdout } = await pexec4(
5194
+ const { stdout } = await pexec5(
4904
5195
  "gh",
4905
5196
  ["auth", "status", "--hostname", GITHUB_HOST, "--json", "hosts"],
4906
5197
  {
@@ -4932,7 +5223,7 @@ async function logoutGitHubCli() {
4932
5223
  const status = await getGitHubCliAuthStatus();
4933
5224
  if (!status.installed) throw new Error("\u627E\u4E0D\u5230 GitHub CLI\uFF08gh\uFF09\u3002");
4934
5225
  if (!status.login) return status;
4935
- await pexec4(
5226
+ await pexec5(
4936
5227
  "gh",
4937
5228
  ["auth", "logout", "--hostname", GITHUB_HOST, "--user", status.login],
4938
5229
  {
@@ -5184,7 +5475,7 @@ function clearMessages(taskId, newSessionId) {
5184
5475
 
5185
5476
  // packages/core/src/chat-session.ts
5186
5477
  init_attachments();
5187
- function basename(p) {
5478
+ function basename2(p) {
5188
5479
  return p.split("/").pop() ?? p;
5189
5480
  }
5190
5481
  function truncate(s, n) {
@@ -5200,14 +5491,14 @@ function summarizeToolUse(name, input) {
5200
5491
  break;
5201
5492
  }
5202
5493
  case "Read":
5203
- summary = `Reading \`${basename(String(input.file_path ?? ""))}\``;
5494
+ summary = `Reading \`${basename2(String(input.file_path ?? ""))}\``;
5204
5495
  break;
5205
5496
  case "Write":
5206
- summary = `Writing \`${basename(String(input.file_path ?? ""))}\``;
5497
+ summary = `Writing \`${basename2(String(input.file_path ?? ""))}\``;
5207
5498
  break;
5208
5499
  case "Edit":
5209
5500
  case "MultiEdit":
5210
- summary = `Editing \`${basename(String(input.file_path ?? ""))}\``;
5501
+ summary = `Editing \`${basename2(String(input.file_path ?? ""))}\``;
5211
5502
  break;
5212
5503
  case "Glob":
5213
5504
  case "Grep":
@@ -5765,7 +6056,7 @@ function validBoard(value) {
5765
6056
  }
5766
6057
  return next;
5767
6058
  }
5768
- function createCore({ sessions, bus, version, homeDir }) {
6059
+ function createCore({ sessions, bus, version, homeDir, ghRunner, githubAuthStatus }) {
5769
6060
  const sink = bus.sink();
5770
6061
  let libraryCodexHome = null;
5771
6062
  void libraryRoot().then((root) => {
@@ -5907,6 +6198,11 @@ function createCore({ sessions, bus, version, homeDir }) {
5907
6198
  bus.emit("state:changed", getState());
5908
6199
  }
5909
6200
  let folderDialog = null;
6201
+ const github = createGithubService({
6202
+ run: ghRunner,
6203
+ authStatus: githubAuthStatus ?? getGitHubCliAuthStatus,
6204
+ repoOf: async (projectPath) => parseGithubRepo((await getGitInfo(projectPath)).remoteUrl)
6205
+ });
5910
6206
  const handlers = {
5911
6207
  "vibeflow:getState": () => getState(),
5912
6208
  // A card's progress right now (todo list, usage, activity), for a frontend
@@ -5937,6 +6233,16 @@ function createCore({ sessions, bus, version, homeDir }) {
5937
6233
  cancelGitHubCliLogin();
5938
6234
  },
5939
6235
  "settings:logoutGithubAuth": () => logoutGitHubCli(),
6236
+ // Open Issues/PRs of the board's GitHub repos that involve the signed-in
6237
+ // `gh` user; cached per repo, `force` refetches.
6238
+ "github:inbox": async (payload) => {
6239
+ const p = payload === void 0 ? {} : obj(payload, "payload");
6240
+ return github.inbox(getState().board, { force: p.force === true });
6241
+ },
6242
+ "github:taskLinks": async (payload) => {
6243
+ const p = payload === void 0 ? {} : obj(payload, "payload");
6244
+ return github.taskLinks(getState().board, { force: p.force === true });
6245
+ },
5940
6246
  "env:detectAgents": () => detectAgents(),
5941
6247
  "agents:listModels": (payload) => {
5942
6248
  const { agentId: rawId, refresh } = obj(payload, "payload");
@@ -5966,6 +6272,7 @@ function createCore({ sessions, bus, version, homeDir }) {
5966
6272
  str(p.title ?? "", "title");
5967
6273
  if (p.effort !== void 0 && !AGENT_EFFORTS.includes(p.effort)) invalid("unknown effort");
5968
6274
  if (p.agentCli !== void 0 && !AGENT_IDS2.includes(p.agentCli)) invalid("unknown agent");
6275
+ if (p.github !== void 0 && !isGithubRef(p.github)) invalid("github must be an issue or pr ref on github.com");
5969
6276
  const { task } = await createTaskFromInput({
5970
6277
  projectPath: p.projectPath,
5971
6278
  title: p.title,
@@ -5977,7 +6284,8 @@ function createCore({ sessions, bus, version, homeDir }) {
5977
6284
  model: p.model,
5978
6285
  effort: p.effort,
5979
6286
  autoMode: p.autoMode,
5980
- attachments: p.attachments
6287
+ attachments: p.attachments,
6288
+ github: p.github
5981
6289
  });
5982
6290
  return { state: getState(), task };
5983
6291
  },
@@ -6193,12 +6501,12 @@ function createCore({ sessions, bus, version, homeDir }) {
6193
6501
  },
6194
6502
  "task:getSpecs": async (taskId) => {
6195
6503
  const task = requireTask(taskId);
6196
- const base = task.baseBranch ?? "main";
6197
- if (task.worktreePath) return readWorktreeSpecs(task.worktreePath, base);
6504
+ const base2 = task.baseBranch ?? "main";
6505
+ if (task.worktreePath) return readWorktreeSpecs(task.worktreePath, base2);
6198
6506
  const since = task.provisionedAt ?? task.launchedAt ?? (task.pushed !== void 0 ? task.createdAt : void 0);
6199
6507
  if (!task.projectPath || !since) return [];
6200
6508
  const changed = task.outcome?.files.filter((f) => f.status !== "D").map((f) => f.path);
6201
- return readBranchSpecs(task.projectPath, task.branch, base, changed, since);
6509
+ return readBranchSpecs(task.projectPath, task.branch, base2, changed, since);
6202
6510
  },
6203
6511
  "board:getCliLaunchInfo": () => boardCliLaunchInfo(),
6204
6512
  "library:list": async () => listLibrary(await libraryRoot(), builtinSkillsDir()),
@@ -6466,11 +6774,11 @@ function resolveStatic(root, urlPath) {
6466
6774
  }
6467
6775
  if (decoded.includes("\0")) return null;
6468
6776
  const rel = path22.posix.normalize(decoded).replace(/^\/+/, "");
6469
- const base = path22.resolve(root);
6777
+ const base2 = path22.resolve(root);
6470
6778
  const candidates = [rel, path22.posix.join(rel, "index.html"), `${rel}.html`];
6471
6779
  for (const c of candidates) {
6472
- const full = path22.resolve(base, c);
6473
- if (full !== base && !full.startsWith(base + path22.sep)) return null;
6780
+ const full = path22.resolve(base2, c);
6781
+ if (full !== base2 && !full.startsWith(base2 + path22.sep)) return null;
6474
6782
  try {
6475
6783
  if (fs18.statSync(full).isFile()) return full;
6476
6784
  } catch {
@@ -6845,12 +7153,12 @@ function connectToHost(lock) {
6845
7153
  // packages/cli/src/doctor.ts
6846
7154
  import fs20 from "fs";
6847
7155
  import path24 from "path";
6848
- import { execFile as execFile8 } from "child_process";
6849
- import { promisify as promisify5 } from "util";
7156
+ import { execFile as execFile9 } from "child_process";
7157
+ import { promisify as promisify6 } from "util";
6850
7158
  init_env();
6851
7159
  init_git_bash();
6852
7160
  init_tmux_backend();
6853
- var pexec5 = promisify5(execFile8);
7161
+ var pexec6 = promisify6(execFile9);
6854
7162
  function parseVersion(text) {
6855
7163
  const m = /(\d+)\.(\d+)(?:\.(\d+))?/.exec(text);
6856
7164
  return m ? [Number(m[1]), Number(m[2]), Number(m[3] ?? 0)] : [];
@@ -6864,7 +7172,7 @@ function versionAtLeast(have, want) {
6864
7172
  }
6865
7173
  async function run2(cmd, args) {
6866
7174
  try {
6867
- const { stdout } = await pexec5(cmd, args, { env: execEnv(), timeout: 1e4, windowsHide: true });
7175
+ const { stdout } = await pexec6(cmd, args, { env: execEnv(), timeout: 1e4, windowsHide: true });
6868
7176
  return String(stdout).trim();
6869
7177
  } catch {
6870
7178
  return null;