@loicngr/kobo 1.11.21 → 1.11.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/server/db/migrations.js +13 -0
  3. package/dist/server/db/schema.js +1 -0
  4. package/dist/server/index.js +2 -0
  5. package/dist/server/routes/pull-requests.js +112 -0
  6. package/dist/server/routes/workspaces.js +98 -0
  7. package/dist/server/services/forge/bitbucket/provider.js +86 -0
  8. package/dist/server/services/forge/github/provider.js +76 -0
  9. package/dist/server/services/forge/gitlab/provider.js +44 -0
  10. package/dist/server/services/forge/none.js +4 -0
  11. package/dist/server/services/pr-checkout-service.js +380 -0
  12. package/dist/server/services/workspace-service.js +4 -3
  13. package/dist/server/services/worktree-service.js +5 -5
  14. package/package.json +1 -1
  15. package/src/client/dist/pwa/assets/{ActivityFeed-DtsapcUb.js → ActivityFeed-BNyFzRkx.js} +1 -1
  16. package/src/client/dist/pwa/assets/{ChangelogPage-D5FtafDI.js → ChangelogPage-7pgilQJn.js} +1 -1
  17. package/src/client/dist/pwa/assets/ClosePopup-BK6mNWsy.js +1 -0
  18. package/src/client/dist/pwa/assets/CreatePage-BCuDTB_0.css +1 -0
  19. package/src/client/dist/pwa/assets/CreatePage-P4N9bro4.js +2 -0
  20. package/src/client/dist/pwa/assets/{DiffViewer-B7CQC4kD.js → DiffViewer-Cl2w9liE.js} +4 -4
  21. package/src/client/dist/pwa/assets/{DocumentsPanel-BgIrc-i0.js → DocumentsPanel-CARiAfZs.js} +1 -1
  22. package/src/client/dist/pwa/assets/GitPanel-txKWaxSr.js +3 -0
  23. package/src/client/dist/pwa/assets/HealthPage-B0_v81_H.js +1 -0
  24. package/src/client/dist/pwa/assets/MainLayout-Cl26t1Yw.js +2 -0
  25. package/src/client/dist/pwa/assets/{QBtnDropdown-COnv-9Kx.js → QBtnDropdown-9dpk4IiI.js} +1 -1
  26. package/src/client/dist/pwa/assets/QItemSection-5GBV9MR2.js +1 -0
  27. package/src/client/dist/pwa/assets/{SchedulePanel-CS__azUn.js → SchedulePanel-CSJbQpWB.js} +1 -1
  28. package/src/client/dist/pwa/assets/{SearchPage-CBQqJmmb.js → SearchPage-B5Wndh73.js} +1 -1
  29. package/src/client/dist/pwa/assets/SettingsPage-BmSaJrE5.js +16 -0
  30. package/src/client/dist/pwa/assets/{TerminalPanel-lsPJEdE-.js → TerminalPanel-CuKWlENm.js} +1 -1
  31. package/src/client/dist/pwa/assets/TouchPan-CflYDvzb.js +1 -0
  32. package/src/client/dist/pwa/assets/WorkspacePage-C8SAYVkt.js +4 -0
  33. package/src/client/dist/pwa/assets/{build-path-tree-CmNRcZvN.js → build-path-tree-nS5a0hfa.js} +1 -1
  34. package/src/client/dist/pwa/assets/{cssMode-DuFZt6Ie.js → cssMode-BWoHpG_3.js} +1 -1
  35. package/src/client/dist/pwa/assets/{editor.api-Dt5YHQkt.js → editor.api-CHHKMKIl.js} +1 -1
  36. package/src/client/dist/pwa/assets/efforts-C0gGtu_O.js +9 -0
  37. package/src/client/dist/pwa/assets/engineFeatures-Cm3wk7lo.js +1 -0
  38. package/src/client/dist/pwa/assets/expand-template-CRv7R1En.js +1 -0
  39. package/src/client/dist/pwa/assets/{freemarker2-DQWkQIhe.js → freemarker2-ChmoNkJH.js} +1 -1
  40. package/src/client/dist/pwa/assets/{handlebars-C8DkGF05.js → handlebars-DjdwH2Vr.js} +1 -1
  41. package/src/client/dist/pwa/assets/{html-DlhhK7Jm.js → html-DJszTgCl.js} +1 -1
  42. package/src/client/dist/pwa/assets/{htmlMode-BVmEqAhh.js → htmlMode-D0QTtBWO.js} +1 -1
  43. package/src/client/dist/pwa/assets/i18n-BQFPc-Bc.js +1 -0
  44. package/src/client/dist/pwa/assets/{index-B29y2aJO.js → index-okqx5CVc.js} +7 -7
  45. package/src/client/dist/pwa/assets/{javascript-C80MQqD5.js → javascript-ByhJGDgS.js} +1 -1
  46. package/src/client/dist/pwa/assets/{jsonMode-r9kfqNya.js → jsonMode-Db1oPA-y.js} +1 -1
  47. package/src/client/dist/pwa/assets/{liquid-DT_yR5El.js → liquid-CGMily4q.js} +1 -1
  48. package/src/client/dist/pwa/assets/{lspLanguageFeatures-CY9UVeSD.js → lspLanguageFeatures-B85CeJTe.js} +1 -1
  49. package/src/client/dist/pwa/assets/{mdx-C_PbX1dC.js → mdx-B5_UPmNo.js} +1 -1
  50. package/src/client/dist/pwa/assets/{network-auth-BP2CrR3z.js → network-auth-D9vq0xDI.js} +1 -1
  51. package/src/client/dist/pwa/assets/{notify-theme-DIVwJeyL.js → notify-theme-kRr6YTnZ.js} +1 -1
  52. package/src/client/dist/pwa/assets/{pending-diff-open-B9fNhQic.js → pending-diff-open-nLdNq-y-.js} +1 -1
  53. package/src/client/dist/pwa/assets/{permissionModes-Be3ZDmns.js → permissionModes-Du6lXebH.js} +3 -3
  54. package/src/client/dist/pwa/assets/{project-color-Bc8ih-HF.js → project-color-tT7gsmB5.js} +1 -1
  55. package/src/client/dist/pwa/assets/{python-8G6qNwxa.js → python-FzBHx7LG.js} +1 -1
  56. package/src/client/dist/pwa/assets/{razor-BazUxiHR.js → razor-BU_JvIXc.js} +1 -1
  57. package/src/client/dist/pwa/assets/{render-chat-markdown-CI48Q5rA.js → render-chat-markdown-DrGdVCku.js} +1 -1
  58. package/src/client/dist/pwa/assets/{tsMode-BUJh8zAj.js → tsMode-BDW6Cb6M.js} +1 -1
  59. package/src/client/dist/pwa/assets/{typescript-C5pPIpSU.js → typescript-21K3hWN-.js} +1 -1
  60. package/src/client/dist/pwa/assets/{use-onboarding-BQCOq7nO.js → use-onboarding-BCM8AGRg.js} +1 -1
  61. package/src/client/dist/pwa/assets/{vs-QND9VumP.js → vs-Ch3CDiTS.js} +3 -3
  62. package/src/client/dist/pwa/assets/{workers-CXEAdpVt.js → workers-CbIvH33O.js} +1 -1
  63. package/src/client/dist/pwa/assets/{xml-DNCFiQ9N.js → xml-d-kArTuN.js} +1 -1
  64. package/src/client/dist/pwa/assets/{yaml-Ck1iNNOE.js → yaml-tzAPZ-1Q.js} +1 -1
  65. package/src/client/dist/pwa/index.html +1 -1
  66. package/src/client/dist/pwa/sw.js +1 -1
  67. package/src/client/dist/pwa/assets/ClosePopup-DEy0yFXE.js +0 -1
  68. package/src/client/dist/pwa/assets/CreatePage-BhcHSOqz.js +0 -2
  69. package/src/client/dist/pwa/assets/CreatePage-RMBX-Zjn.css +0 -1
  70. package/src/client/dist/pwa/assets/GitPanel-DHvRFdbB.js +0 -3
  71. package/src/client/dist/pwa/assets/HealthPage-YqOn5Ig0.js +0 -1
  72. package/src/client/dist/pwa/assets/MainLayout-aDNwgrrT.js +0 -2
  73. package/src/client/dist/pwa/assets/QItemSection-CDBweQnA.js +0 -1
  74. package/src/client/dist/pwa/assets/QList-Ccqhimcb.js +0 -1
  75. package/src/client/dist/pwa/assets/SettingsPage-kx7aNmeH.js +0 -16
  76. package/src/client/dist/pwa/assets/TouchPan-DnEDaTFL.js +0 -1
  77. package/src/client/dist/pwa/assets/WorkspacePage-Cyoc2Mvs.js +0 -4
  78. package/src/client/dist/pwa/assets/efforts-C3gjASPy.js +0 -9
  79. package/src/client/dist/pwa/assets/engineFeatures-DkgYepOC.js +0 -1
  80. package/src/client/dist/pwa/assets/expand-template-CLqglUAa.js +0 -1
  81. package/src/client/dist/pwa/assets/i18n-C-chrpJN.js +0 -1
@@ -0,0 +1,380 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import * as gitOps from '../utils/git-ops.js';
6
+ import { GitConflictError } from '../utils/git-ops.js';
7
+ import { withGitRepoLock } from '../utils/git-repo-lock.js';
8
+ import { resolveWorkspaceWorktreePath } from '../utils/worktree-paths.js';
9
+ import { createWorktree, listWorktrees, removeWorktreeUnlocked } from './worktree-service.js';
10
+ /**
11
+ * A `pathCollision.worktreePath` decision is untrusted input reaching
12
+ * `git worktree add` directly — including from a network-access client that
13
+ * only holds the shared token, not full local trust. Reject anything that
14
+ * isn't a clean, absolute, non-existent path so `createWorktree`'s explicit
15
+ * path override can never be used to write outside a location the user
16
+ * genuinely typed (no `..`/`.` traversal tricks, no relative paths resolved
17
+ * against an unexpected cwd, no silently overwriting something already there).
18
+ */
19
+ function assertSafeExplicitWorktreePath(candidate) {
20
+ if (!path.isAbsolute(candidate)) {
21
+ throw new Error(`The worktree path must be absolute: '${candidate}'`);
22
+ }
23
+ const trimmed = candidate.replace(/[/\\]+$/, '') || path.sep;
24
+ if (path.resolve(trimmed) !== trimmed) {
25
+ throw new Error(`The worktree path is not a clean absolute path (no '.' or '..' segments): '${candidate}'`);
26
+ }
27
+ if (fs.existsSync(trimmed)) {
28
+ throw new Error(`'${trimmed}' already exists; choose a location that does not exist yet`);
29
+ }
30
+ }
31
+ const EMPTY_CHANGES = { present: false, modified: 0, staged: 0, untracked: 0 };
32
+ /** Run a git command in a repo and return trimmed stdout. Forces LC_ALL=C so
33
+ * error-message matching elsewhere in this service stays locale-independent. */
34
+ // Every call here runs inside `withGitRepoLock` (see `resolvePrCheckout`), so
35
+ // a hang blocks every other lock-guarded operation in the app (worktree
36
+ // purge, change-source-branch) with no recovery. `GIT_TERMINAL_PROMPT: '0'`
37
+ // makes a missing credential fail immediately instead of prompting
38
+ // interactively over a pipe nobody can answer; the timeout is a backstop for
39
+ // anything else that could still stall (a slow filesystem, a stuck lock).
40
+ const GIT_TIMEOUT_MS = 60_000;
41
+ function git(repoPath, args) {
42
+ return execFileSync('git', args, {
43
+ cwd: repoPath,
44
+ encoding: 'utf-8',
45
+ stdio: ['pipe', 'pipe', 'pipe'],
46
+ timeout: GIT_TIMEOUT_MS,
47
+ env: { ...process.env, LC_ALL: 'C', LANG: 'C', GIT_TERMINAL_PROMPT: '0' },
48
+ }).trim();
49
+ }
50
+ /** Compare the local branch against `origin/<branch>`. */
51
+ function diagnoseBranch(projectPath, branch) {
52
+ if (!gitOps.localBranchExists(projectPath, branch))
53
+ return { state: 'absent' };
54
+ if (!gitOps.listRemoteBranches(projectPath).includes(`origin/${branch}`))
55
+ return { state: 'in-sync' };
56
+ const ahead = gitOps.getCommitCount(projectPath, branch, branch);
57
+ const behind = gitOps.getCommitsBehind(projectPath, branch, branch);
58
+ if (ahead > 0 && behind > 0)
59
+ return { state: 'diverged', ahead, behind };
60
+ // Note: `hasCommonAncestor` below is what produces the `no-common-ancestor`
61
+ // blocker; a same-named branch with unrelated history reads as "diverged"
62
+ // otherwise, and every strategy offered for it would be wrong.
63
+ if (ahead > 0)
64
+ return { state: 'ahead', ahead };
65
+ if (behind > 0)
66
+ return { state: 'behind', behind };
67
+ return { state: 'in-sync' };
68
+ }
69
+ /** False when the local branch and its remote share no history at all — a name
70
+ * collision, not a divergence. Every realignment strategy is wrong for it. */
71
+ function hasCommonAncestor(repoPath, branch) {
72
+ try {
73
+ git(repoPath, ['merge-base', branch, `origin/${branch}`]);
74
+ return true;
75
+ }
76
+ catch {
77
+ return false;
78
+ }
79
+ }
80
+ /** Locate any worktree already sitting on `branch`, or at the target path. */
81
+ function diagnoseWorktree(projectPath, branch, targetPath, attachedPaths) {
82
+ const blockers = [];
83
+ const worktrees = listWorktrees(projectPath);
84
+ const onBranch = worktrees.find((wt) => wt.branch === branch);
85
+ if (onBranch) {
86
+ if (!fs.existsSync(onBranch.path)) {
87
+ return { worktree: { state: 'stale-metadata', path: onBranch.path }, blockers };
88
+ }
89
+ const workspaceId = attachedPaths.get(onBranch.path);
90
+ return {
91
+ worktree: workspaceId
92
+ ? { state: 'attached', path: onBranch.path, workspaceId }
93
+ : { state: 'orphan', path: onBranch.path },
94
+ blockers,
95
+ };
96
+ }
97
+ const atTarget = worktrees.find((wt) => wt.path === targetPath);
98
+ if (atTarget) {
99
+ blockers.push({ kind: 'worktree-other-branch', path: targetPath, branch: atTarget.branch });
100
+ }
101
+ else if (fs.existsSync(targetPath)) {
102
+ blockers.push({ kind: 'path-occupied', path: targetPath });
103
+ }
104
+ return { worktree: { state: 'none' }, blockers };
105
+ }
106
+ /** Count working-tree changes by category. */
107
+ function diagnoseChanges(worktreePath) {
108
+ const status = gitOps.getWorkingTreeStatus(worktreePath);
109
+ const modified = status.modified ?? 0;
110
+ const staged = status.staged ?? 0;
111
+ const untracked = status.untracked ?? 0;
112
+ return { present: modified + staged + untracked > 0, modified, staged, untracked };
113
+ }
114
+ /** Classify the workspace, if any, that already tracks this branch.
115
+ * A purged worktree outranks the archived flag: purging always archives, and
116
+ * "recreate the worktree" is the useful offer, not "unarchive". */
117
+ export function resolveWorkspaceState(workspaces, branch) {
118
+ const match = workspaces.find((w) => w.workingBranch === branch);
119
+ if (!match)
120
+ return { state: 'none' };
121
+ if (match.worktreePurgedAt)
122
+ return { state: 'purged', id: match.id, name: match.name };
123
+ if (match.archivedAt)
124
+ return { state: 'archived', id: match.id, name: match.name };
125
+ return { state: 'active', id: match.id, name: match.name };
126
+ }
127
+ /** `git rev-parse <ref>`, or null when the ref does not resolve. */
128
+ function safeRevParse(repoPath, ref) {
129
+ try {
130
+ return git(repoPath, ['rev-parse', ref]);
131
+ }
132
+ catch {
133
+ return null;
134
+ }
135
+ }
136
+ /** Hash the observed state so `/resolve` can refuse a plan built on stale facts. */
137
+ export function computeFingerprint(report) {
138
+ const localHead = safeRevParse(report.projectPath, report.headBranch);
139
+ const remoteHead = safeRevParse(report.projectPath, `origin/${report.headBranch}`);
140
+ const payload = JSON.stringify({
141
+ localHead,
142
+ remoteHead,
143
+ worktree: report.worktree,
144
+ branch: report.branch,
145
+ workspace: report.workspace,
146
+ changes: report.localChanges,
147
+ ongoing: report.ongoingOperation,
148
+ blockers: report.blockers.map((b) => b.kind).sort(),
149
+ });
150
+ // 128 bits is far more than enough entropy for a staleness check, not a security boundary.
151
+ return createHash('sha256').update(payload).digest('hex').slice(0, 32);
152
+ }
153
+ /**
154
+ * Diagnose everything Kōbō can see locally for a PR head branch.
155
+ * `attachedPaths` maps worktree path -> workspace id; the route supplies it so
156
+ * this function stays free of database access and easy to test.
157
+ */
158
+ export function diagnoseLocalState(projectPath, headBranch, worktreesPath, attachedPaths = new Map(), workspaces = []) {
159
+ const targetWorktreePath = resolveWorkspaceWorktreePath(projectPath, headBranch, worktreesPath);
160
+ const blockers = [];
161
+ const lockPath = gitOps.getIndexLockPath(projectPath);
162
+ if (lockPath)
163
+ blockers.push({ kind: 'index-lock', lockPath });
164
+ if (gitOps.localBranchExists(projectPath, headBranch) &&
165
+ gitOps.listRemoteBranches(projectPath).includes(`origin/${headBranch}`) &&
166
+ !hasCommonAncestor(projectPath, headBranch)) {
167
+ blockers.push({ kind: 'no-common-ancestor', branch: headBranch });
168
+ }
169
+ const { worktree, blockers: worktreeBlockers } = diagnoseWorktree(projectPath, headBranch, targetWorktreePath, attachedPaths);
170
+ blockers.push(...worktreeBlockers);
171
+ const inspectPath = worktree.state === 'orphan' || worktree.state === 'attached' ? worktree.path : null;
172
+ return {
173
+ projectPath,
174
+ headBranch,
175
+ targetWorktreePath,
176
+ blockers,
177
+ workspace: resolveWorkspaceState(workspaces, headBranch),
178
+ worktree,
179
+ localChanges: inspectPath ? diagnoseChanges(inspectPath) : EMPTY_CHANGES,
180
+ ongoingOperation: inspectPath ? gitOps.getOngoingGitOperation(inspectPath) : null,
181
+ branch: diagnoseBranch(projectPath, headBranch),
182
+ };
183
+ }
184
+ /**
185
+ * Bring the local branch in line with `origin/<branch>` according to `strategy`.
186
+ * The destructive strategy always leaves a `kobo-backup/<branch>-<unix-ts>` behind
187
+ * first — the same safety net `change-source-branch-service` uses, so nothing the
188
+ * user had locally becomes unrecoverable.
189
+ *
190
+ * Operates on the repository's branch ref, so the caller must hold the repo lock
191
+ * AND the branch must not be checked out anywhere (see the ordering note below).
192
+ */
193
+ export function applyBranchStrategy(repoPath, branch, strategy) {
194
+ const remoteRef = `origin/${branch}`;
195
+ if (strategy === 'keep')
196
+ return { strategy, backupBranch: null };
197
+ if (strategy === 'fast-forward') {
198
+ git(repoPath, ['branch', '--force', branch, remoteRef]);
199
+ return { strategy, backupBranch: null };
200
+ }
201
+ if (strategy === 'reset-hard') {
202
+ const backupBranch = `kobo-backup/${branch}-${Date.now()}`;
203
+ git(repoPath, ['branch', backupBranch, branch]);
204
+ git(repoPath, ['branch', '--force', branch, remoteRef]);
205
+ return { strategy, backupBranch };
206
+ }
207
+ // strategy === 'rebase': `git rebase <upstream> <branch>` is shorthand for
208
+ // `git checkout <branch> && git rebase <upstream>` — it checks out `branch`
209
+ // as a side effect, unlike the other strategies above which only move the
210
+ // ref via `git branch --force`. Record what's currently checked out so we
211
+ // can restore it once the rebase completes cleanly. If HEAD is detached,
212
+ // `--abbrev-ref` returns the literal string "HEAD", which isn't a valid ref
213
+ // to check back out — fall back to the SHA in that case.
214
+ const currentRef = git(repoPath, ['rev-parse', '--abbrev-ref', 'HEAD']);
215
+ const originalCheckout = currentRef === 'HEAD' ? git(repoPath, ['rev-parse', 'HEAD']) : currentRef;
216
+ try {
217
+ git(repoPath, ['rebase', remoteRef, branch]);
218
+ }
219
+ catch (err) {
220
+ const conflicted = gitOps.getConflictedFiles(repoPath);
221
+ if (conflicted.length > 0 || gitOps.getOngoingGitOperation(repoPath) === 'rebase') {
222
+ // Leave the rebase in progress on `branch` so the caller can abort or
223
+ // request agent-assisted resolution — do NOT restore the original checkout.
224
+ throw new GitConflictError('rebase', conflicted);
225
+ }
226
+ const message = err instanceof Error ? err.message : String(err);
227
+ throw new Error(`Rebase of '${branch}' onto '${remoteRef}' failed: ${message}`);
228
+ }
229
+ // Clean rebase: restore the working copy to exactly what was checked out before.
230
+ git(repoPath, ['checkout', originalCheckout]);
231
+ return { strategy, backupBranch: null };
232
+ }
233
+ /** The repository moved between diagnosis and resolution. */
234
+ export class StaleDiagnosisError extends Error {
235
+ report;
236
+ code = 'stale_diagnosis';
237
+ constructor(report) {
238
+ super('The repository changed since the diagnosis was taken');
239
+ this.report = report;
240
+ this.name = 'StaleDiagnosisError';
241
+ }
242
+ }
243
+ /**
244
+ * Apply a resolution plan and leave a worktree ready on the PR head branch.
245
+ * Never creates a workspace — that stays `POST /api/workspaces`'s job.
246
+ *
247
+ * Holds the repository lock throughout, like `removeWorktree` and
248
+ * `change-source-branch-service`, so it cannot interleave with a worktree purge
249
+ * or a source-branch change.
250
+ */
251
+ export function resolvePrCheckout(input) {
252
+ return withGitRepoLock(input.projectPath, async () => {
253
+ const applied = [];
254
+ // 1. Refresh, then refuse a plan built on facts that no longer hold.
255
+ gitOps.fetchSourceBranch(input.projectPath, input.headBranch);
256
+ applied.push({ kind: 'fetch', detail: `origin/${input.headBranch}` });
257
+ const fresh = diagnoseLocalState(input.projectPath, input.headBranch, input.worktreesPath, input.attachedPaths ?? new Map(), input.workspaces ?? []);
258
+ if (computeFingerprint(fresh) !== input.fingerprint)
259
+ throw new StaleDiagnosisError(fresh);
260
+ // 2. Clear stale worktree metadata before anything needs the path.
261
+ if (fresh.worktree.state === 'stale-metadata') {
262
+ git(input.projectPath, ['worktree', 'prune']);
263
+ applied.push({ kind: 'prune-stale-worktree', detail: fresh.worktree.path });
264
+ }
265
+ const reuse = (fresh.worktree.state === 'orphan' || fresh.worktree.state === 'attached') &&
266
+ input.decisions.orphanWorktree !== 'create-elsewhere';
267
+ const existingPath = reuse && 'path' in fresh.worktree ? fresh.worktree.path : null;
268
+ // 3. In a reused worktree, settle the working tree before touching history.
269
+ if (existingPath) {
270
+ if (fresh.ongoingOperation && input.decisions.ongoingOperation === 'abort') {
271
+ gitOps.abortOngoingGitOperation(existingPath);
272
+ applied.push({ kind: 'abort-operation', detail: fresh.ongoingOperation });
273
+ }
274
+ if (fresh.ongoingOperation && input.decisions.ongoingOperation !== 'abort') {
275
+ throw new Error(`A ${fresh.ongoingOperation} is in progress in '${existingPath}' and the resolution plan did not choose to abort it`);
276
+ }
277
+ if (fresh.localChanges.present) {
278
+ const choice = input.decisions.localChanges ?? 'keep';
279
+ if (choice === 'stash') {
280
+ gitOps.stashPush(existingPath, `kobo-pr-checkout-${input.headBranch}`);
281
+ applied.push({ kind: 'stash-changes' });
282
+ }
283
+ else if (choice === 'commit') {
284
+ gitOps.commitAllChanges(existingPath, `chore: save work before checking out ${input.headBranch}`);
285
+ applied.push({ kind: 'commit-changes' });
286
+ }
287
+ else if (choice === 'discard') {
288
+ // Deliberately a labelled stash, not `checkout -- .`: recoverable.
289
+ gitOps.stashPush(existingPath, `kobo-pr-checkout-discard-${input.headBranch}`);
290
+ applied.push({ kind: 'discard-changes', detail: 'kept as a labelled stash' });
291
+ }
292
+ }
293
+ }
294
+ // 4. Align the branch. Ref surgery only works when the branch is NOT checked
295
+ // out, so an existing worktree gets the in-worktree equivalent instead.
296
+ const strategy = input.decisions.divergence ?? 'keep';
297
+ if (strategy !== 'keep') {
298
+ if (existingPath) {
299
+ alignInsideWorktree(input.projectPath, existingPath, input.headBranch, strategy, applied);
300
+ }
301
+ else {
302
+ const outcome = applyBranchStrategy(input.projectPath, input.headBranch, strategy);
303
+ applied.push({ kind: 'align-branch', detail: outcome.backupBranch ?? strategy });
304
+ }
305
+ }
306
+ // 5. Reuse or create the worktree.
307
+ let worktreePath;
308
+ let created = false;
309
+ if (existingPath) {
310
+ worktreePath = existingPath;
311
+ applied.push({ kind: 'attach-worktree', detail: existingPath });
312
+ }
313
+ else {
314
+ const explicitPath = input.decisions.pathCollision?.worktreePath ?? null;
315
+ if (explicitPath)
316
+ assertSafeExplicitWorktreePath(explicitPath);
317
+ const baseRef = gitOps.localBranchExists(input.projectPath, input.headBranch)
318
+ ? input.headBranch
319
+ : `origin/${input.headBranch}`;
320
+ const target = createWorktree(input.projectPath, input.headBranch, baseRef, input.worktreesPath, undefined, explicitPath);
321
+ worktreePath = target.worktreePath;
322
+ created = true;
323
+ applied.push({ kind: 'create-worktree', detail: worktreePath });
324
+ }
325
+ // 6. Roll back only the worktree WE created if a later step throws. Branch
326
+ // realignment from step 4 (if any) is NOT undone here — a `reset-hard`
327
+ // strategy already left its own `kobo-backup/<branch>-<ts>` safety net,
328
+ // and fast-forward/rebase are non-destructive by construction.
329
+ try {
330
+ input.afterWorktreeHook?.();
331
+ }
332
+ catch (err) {
333
+ if (created) {
334
+ try {
335
+ removeWorktreeUnlocked(input.projectPath, worktreePath);
336
+ }
337
+ catch (cleanupErr) {
338
+ console.error('[pr-checkout] rollback failed to remove the worktree:', cleanupErr);
339
+ const original = err instanceof Error ? err.message : String(err);
340
+ const cleanup = cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr);
341
+ throw new Error(`${original} (additionally, rollback failed to remove the worktree at '${worktreePath}': ${cleanup})`);
342
+ }
343
+ }
344
+ throw err;
345
+ }
346
+ return { worktreePath, workingBranch: input.headBranch, sourceBranch: input.baseBranch, applied };
347
+ });
348
+ }
349
+ /** Realign a branch from inside its own worktree, where ref surgery is refused. */
350
+ function alignInsideWorktree(repoPath, worktreePath, branch, strategy, applied) {
351
+ const remoteRef = `origin/${branch}`;
352
+ if (strategy === 'fast-forward') {
353
+ git(worktreePath, ['merge', '--ff-only', remoteRef]);
354
+ applied.push({ kind: 'align-branch', detail: 'fast-forward' });
355
+ return;
356
+ }
357
+ if (strategy === 'rebase') {
358
+ try {
359
+ git(worktreePath, ['rebase', remoteRef]);
360
+ }
361
+ catch (err) {
362
+ const conflicted = gitOps.getConflictedFiles(worktreePath);
363
+ if (conflicted.length > 0 || gitOps.getOngoingGitOperation(worktreePath) === 'rebase') {
364
+ // Leave the rebase in progress so the caller can abort or request
365
+ // agent-assisted resolution — mirrors applyBranchStrategy's own rebase
366
+ // conflict handling and git-ops.ts's rebaseBranch.
367
+ throw new GitConflictError('rebase', conflicted);
368
+ }
369
+ const message = err instanceof Error ? err.message : String(err);
370
+ throw new Error(`Rebase of '${branch}' onto '${remoteRef}' failed: ${message}`);
371
+ }
372
+ applied.push({ kind: 'align-branch', detail: 'rebase' });
373
+ return;
374
+ }
375
+ // reset-hard
376
+ const backupBranch = `kobo-backup/${branch}-${Date.now()}`;
377
+ git(repoPath, ['branch', backupBranch, branch]);
378
+ git(worktreePath, ['reset', '--hard', remoteRef]);
379
+ applied.push({ kind: 'align-branch', detail: backupBranch });
380
+ }
@@ -51,6 +51,7 @@ function mapWorkspace(row) {
51
51
  notionUrl: row.notion_url,
52
52
  notionPageId: row.notion_page_id,
53
53
  sentryUrl: row.sentry_url,
54
+ prUrl: row.pr_url,
54
55
  model: row.model,
55
56
  brainstormModel: row.brainstorm_model,
56
57
  reasoningEffort: row.reasoning_effort ?? 'auto',
@@ -119,10 +120,10 @@ export function createWorkspace(data) {
119
120
  db.prepare(`
120
121
  INSERT INTO workspaces (
121
122
  id, name, project_path, source_branch, working_branch, status,
122
- notion_url, notion_page_id, sentry_url, worktree_path, worktree_owned,
123
+ notion_url, notion_page_id, sentry_url, pr_url, worktree_path, worktree_owned,
123
124
  model, brainstorm_model, reasoning_effort, permission_mode, permission_profile, agent_permission_mode, engine, created_at, updated_at
124
- ) VALUES (?, ?, ?, ?, ?, 'created', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
125
- `).run(id, data.name, data.projectPath, data.sourceBranch, data.workingBranch, data.notionUrl ?? null, data.notionPageId ?? null, data.sentryUrl ?? null, computedWorktreePath, owned ? 1 : 0, data.model ?? 'claude-opus-4-8', data.brainstormModel ?? null, data.reasoningEffort ?? 'auto', legacyMode, legacyProfile, unifiedMode, data.engine ?? 'claude-code', now, now);
125
+ ) VALUES (?, ?, ?, ?, ?, 'created', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
126
+ `).run(id, data.name, data.projectPath, data.sourceBranch, data.workingBranch, data.notionUrl ?? null, data.notionPageId ?? null, data.sentryUrl ?? null, data.prUrl ?? null, computedWorktreePath, owned ? 1 : 0, data.model ?? 'claude-opus-4-8', data.brainstormModel ?? null, data.reasoningEffort ?? 'auto', legacyMode, legacyProfile, unifiedMode, data.engine ?? 'claude-code', now, now);
126
127
  return getWorkspace(id);
127
128
  }
128
129
  /** Fetch a single workspace by ID, or null if not found. */
@@ -87,12 +87,12 @@ function removeFromExclude(projectPath, worktreePath) {
87
87
  * `baseRef` is passed explicitly by the caller: `origin/<sourceBranch>` in the
88
88
  * nominal path, or the local `<sourceBranch>` when origin is unreachable.
89
89
  * Returns the worktree path and which base was used. */
90
- export function createWorktree(projectPath, branchName, baseRef, worktreesPath, projectSlug) {
90
+ export function createWorktree(projectPath, branchName, baseRef, worktreesPath, projectSlug, explicitPath) {
91
91
  const worktreesDir = resolveWorktreesRoot(projectPath, worktreesPath);
92
92
  if (!fs.existsSync(worktreesDir)) {
93
93
  fs.mkdirSync(worktreesDir, { recursive: true });
94
94
  }
95
- const worktreePath = resolveWorkspaceWorktreePath(projectPath, branchName, worktreesPath, projectSlug);
95
+ const worktreePath = explicitPath || resolveWorkspaceWorktreePath(projectPath, branchName, worktreesPath, projectSlug);
96
96
  const base = baseRef.startsWith('origin/') ? 'origin' : 'local';
97
97
  // Tracks which of the two git commands below actually ran. Callers use it to
98
98
  // decide whether a rollback may delete the branch: deleting one we merely
@@ -147,10 +147,10 @@ export function removeWorktree(projectPath, worktreePath) {
147
147
  // the same lock. The whole removal sequence is held, Docker chown included:
148
148
  // it sits between the `remove` and the `prune` that repairs its metadata, so
149
149
  // releasing in the middle would expose a half-removed repository.
150
- return withGitRepoLock(projectPath, () => removeWorktreeLocked(projectPath, worktreePath));
150
+ return withGitRepoLock(projectPath, () => removeWorktreeUnlocked(projectPath, worktreePath));
151
151
  }
152
- /** The actual removal. Always called with the repository lock held. */
153
- function removeWorktreeLocked(projectPath, worktreePath) {
152
+ /** Remove a worktree. Call ONLY while already holding the repository lock. */
153
+ export function removeWorktreeUnlocked(projectPath, worktreePath) {
154
154
  try {
155
155
  git(projectPath, ['worktree', 'remove', worktreePath, '--force']);
156
156
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loicngr/kobo",
3
- "version": "1.11.21",
3
+ "version": "1.11.23",
4
4
  "description": "Kōbō — multi-workspace agent manager for Claude Code. Orchestrates isolated git worktrees with dev servers, Notion integration, and MCP tools.",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0-or-later",
@@ -1,4 +1,4 @@
1
- import{n as e}from"./vue-i18n-C3W5i7h6.js";import{B as t,C as n,St as r,wt as i,yt as a}from"./reactivity.esm-bundler-DtSZKRQB.js";import{$ as o,A as s,At as c,B as l,Dt as u,Et as d,H as f,It as p,Mt as m,Ot as h,Rt as g,Tt as _,U as v,V as y,W as b,Wt as x,X as S,Z as C,_n as w,fn as T,st as E,t as D,ut as O,w as k}from"./runtime-dom.esm-bundler-DgevF6KV.js";import{S as A,W as j,Z as M,b as N,l as P,t as F,w as ee}from"./QBtn-B7WFdfmy.js";import{O as te,c as I,d as L,k as ne}from"./use-id-DlouqdB8.js";import{H as re,T as ie,h as ae,lt as R,m as oe,w as se}from"./index-B29y2aJO.js";import{t as ce}from"./QSpinnerDots-DWOFEezR.js";import{t as z}from"./QList-Ccqhimcb.js";import{h as le,v as B,y as V}from"./permissionModes-Be3ZDmns.js";import{r as ue,t as H}from"./pending-diff-open-B9fNhQic.js";import{t as U}from"./documents-B6eOZeFK.js";import{a as W,i as G}from"./layout-nWIyHeJf.js";import{i as de,n as fe,o as pe,r as me,t as K}from"./render-chat-markdown-CI48Q5rA.js";import{a as he,i as ge,n as _e,o as ve,r as ye,s as be}from"./WorkspacePage-Cyoc2Mvs.js";var q=[`horizontal`,`vertical`,`cell`,`none`],J=M({name:`QMarkupTable`,props:{...ne,dense:Boolean,flat:Boolean,bordered:Boolean,square:Boolean,wrapCells:Boolean,separator:{type:String,default:`horizontal`,validator:e=>q.includes(e)}},setup(e,{slots:t}){let n=E(),r=te(e,n.proxy.$q),i=l(()=>`q-markup-table q-table__container q-table__card q-table--${e.separator}-separator`+(r.value?` q-table--dark q-table__card--dark q-dark`:``)+(e.dense?` q-table--dense`:``)+(e.flat?` q-table--flat`:``)+(e.bordered?` q-table--bordered`:``)+(e.square?` q-table--square`:``)+(e.wrapCells?``:` q-table--no-wrap`));return()=>O(`div`,{class:i.value},[O(`table`,{class:`q-table`},A(t.default))])}});function xe(e,t){return O(`div`,e,[O(`table`,{class:`q-table`},t)])}var Se={list:z,table:J},Ce=[`list`,`table`,`__qtable`],we=M({name:`QVirtualScroll`,props:{...V,type:{type:String,default:`list`,validator:e=>Ce.includes(e)},items:{type:Array,default:()=>[]},itemsFn:Function,itemsSize:Number,scrollTarget:L},setup(e,{slots:t,attrs:r}){let i,a=n(null),o=l(()=>e.itemsSize>=0&&e.itemsFn!==void 0?Number.parseInt(e.itemsSize,10):Array.isArray(e.items)?e.items.length:0),{virtualScrollSliceRange:s,localResetVirtualScroll:f,padVirtualScroll:p,onVirtualScrollEvt:g}=B({virtualScrollLength:o,getVirtualScrollTarget:x,getVirtualScrollEl:b}),_=l(()=>{if(o.value===0)return[];let t=(e,t)=>({index:s.value.from+t,item:e});return e.itemsFn===void 0?e.items.slice(s.value.from,s.value.to).map(t):e.itemsFn(s.value.from,s.value.to-s.value.from).map(t)}),v=l(()=>`q-virtual-scroll q-virtual-scroll`+(e.virtualScrollHorizontal?`--horizontal`:`--vertical`)+(e.scrollTarget===void 0?` scroll`:``)),y=l(()=>e.scrollTarget===void 0?{tabindex:0}:{});T(o,()=>{f()}),T(()=>e.scrollTarget,()=>{C(),S()});function b(){let e=a.value;return e===null?null:e.$el||e}function x(){return i}function S(){let t=b();t!==null&&(i=I(t,e.scrollTarget),i.addEventListener(`scroll`,g,j.passive))}function C(){i!==void 0&&(i.removeEventListener(`scroll`,g,j.passive),i=void 0)}function w(){let n=p(e.type===`list`?`div`:`tbody`,_.value.map(t.default));return t.before!==void 0&&(n=t.before().concat(n)),N(t.after,n)}return u(()=>{f()}),m(()=>{S()}),d(()=>{S()}),c(()=>{C()}),h(()=>{C()}),()=>{if(t.default===void 0){console.error(`QVirtualScroll: default scoped slot is required for rendering`);return}return e.type===`__qtable`?xe({ref:a,class:`q-table__middle `+v.value},w()):O(Se[e.type],{...r,ref:a,class:[r.class,v.value],...y.value},w)}}}),Te=new Set([`setup`,`cleanup`,`archive`]);function Ee(e){switch(e.type){case`user`:return e.sender===`system-prompt`?`system-prompt`:Te.has(e.sender)?`script`:`user`;case`session`:return`session`;default:return`agent`}}function De(e){let t=[],n=null,r=null;for(let i of e){let e=Ee(i),a=e===`session`||e===`system-prompt`,o=e===`script`&&i.type===`user`?`script:${i.sender}`:e;!n||r!==o||a?(n={speaker:e,ts:i.ts,items:[i]},r=o,t.push(n),a&&(n=null)):n.items.push(i)}return t}function Y(e){switch(e.type){case`text`:return`text:${e.messageId}`;case`thinking`:return`thinking:${e.messageId}:${e.eventIds?.[0]??e.ts??``}`;case`tool`:return`tool:${e.toolCallId}`;case`session`:return`session:${e.kind}:${e.eventIds?.[0]??e.ts??``}`;case`user`:return`user:${e.eventIds?.[0]??`${e.sender}:${e.ts??``}`}`;case`error`:return`error:${e.eventIds?.[0]??`${e.category}:${e.ts??``}`}`}}function Oe(e){let t=e.items[0];return`${e.speaker}:${t?Y(t):`empty`}`}function ke(e,t){let n=Math.min(t,e.length)-1;for(let t=n;t>=0;t--)if(e[t]?.speaker===`user`)return t;return-1}function Ae(e,t,n){return new Promise(r=>{let i=!1,a=null,o=null,s=e=>{i||(i=!0,a!==null&&clearTimeout(a),a=null,o?.(),r(e))};if(o=T(e,e=>{t(e)&&s(!0)},{immediate:!0}),i){o();return}a=setTimeout(()=>s(!1),n)})}var je={class:`agent-error-item`},X={class:`agent-error-item__body`},Me={class:`agent-error-item__title`},Z={class:`agent-error-item__message`},Ne=G(o({__name:`AgentErrorItem`,props:{item:{}},setup(t){let n=t,{te:r}=e(),a=l(()=>{let e=`agent.error.${n.item.category}`;return r(e)?e:`agent.error.other`});return(e,n)=>(g(),b(`div`,je,[C(P,{name:`error_outline`,size:`16px`,class:`agent-error-item__icon`}),y(`div`,X,[y(`div`,Me,i(e.$t(a.value)),1),y(`div`,Z,i(t.item.message),1)])]))}}),[[`__scopeId`,`data-v-e98c3c02`]]),Q={class:`text-caption text-kobo-3`},$=o({__name:`SessionEventItem`,props:{item:{}},setup(e){let t=e,n=l(()=>{switch(t.item.kind){case`started`:return`session.started`;case`ended`:return be(t.item.detail);case`compacted`:return`session.compacted`;default:return`session.started`}});return(e,t)=>(g(),b(`span`,Q,i(e.$t(n.value)),1))}});function Pe(e,t){if(t.length===0||e.length===0)return e;let n=[...t].sort((e,t)=>t.length-e.length),r=new DOMParser().parseFromString(`<div>${e}</div>`,`text/html`),i=r.body.firstChild;if(!i)return e;function a(e){if(e.nodeType===Node.TEXT_NODE){Fe(e,n,r);return}if(e.nodeName===`A`)return;let t=Array.from(e.childNodes);for(let e of t)a(e)}return a(i),i.innerHTML}function Fe(e,t,n){let r=e.textContent??``;if(!t.some(e=>r.includes(e)))return;let i=n.createDocumentFragment(),a=0;for(;a<r.length;){let e=Ie(r,a,t);if(!e){i.appendChild(n.createTextNode(r.slice(a)));break}e.index>a&&i.appendChild(n.createTextNode(r.slice(a,e.index)));let o=n.createElement(`a`);o.className=`document-link`,o.setAttribute(`data-document-path`,e.path),o.setAttribute(`href`,`#`),o.textContent=e.path,i.appendChild(o),a=e.index+e.path.length}e.parentNode?.replaceChild(i,e)}function Ie(e,t,n){let r=null;for(let i of n){let n=e.indexOf(i,t);n<0||(!r||n<r.index||n===r.index&&i.length>r.path.length)&&(r={index:n,path:i})}return r}var Le=[`innerHTML`],Re=G(o({__name:`TextMessageItem`,props:{item:{}},setup(e){let t=e,n=U(),r=oe(),i=l(()=>{let e=r.selectedWorkspaceId;return e?n.documentsFor(e).map(e=>e.path):[]}),a=l(()=>{let e=t.item;if(e.streaming)return K(e.text);let n=i.value;return me(`${e.messageId}|${n.join(`,`)}`,e.text,e=>{let t=Pe(pe.parse(e,{async:!1,breaks:!0,gfm:!0}),n);return de(t,{addAttr:[`data-document-path`]})})});function o(e){let t=e.target?.closest(`.document-link`);if(!t)return;e.preventDefault();let i=t.getAttribute(`data-document-path`),a=r.selectedWorkspaceId;!i||!a||n.openDocumentByPath(a,i)}return(t,n)=>(g(),b(`div`,{class:`markdown-message`,onClick:o},[y(`div`,{innerHTML:a.value},null,8,Le),e.item.streaming?(g(),f(ee,{key:0,size:`xs`,class:`q-ml-xs`})):v(``,!0)]))}}),[[`__scopeId`,`data-v-539815f6`]]);function ze(e,t){let n=e.split(`
1
+ import{n as e}from"./vue-i18n-C3W5i7h6.js";import{B as t,C as n,St as r,wt as i,yt as a}from"./reactivity.esm-bundler-DtSZKRQB.js";import{$ as o,A as s,At as c,B as l,Dt as u,Et as d,H as f,It as p,Mt as m,Ot as h,Rt as g,Tt as _,U as v,V as y,W as b,Wt as x,X as S,Z as C,_n as w,fn as T,st as E,t as D,ut as O,w as k}from"./runtime-dom.esm-bundler-DgevF6KV.js";import{S as A,W as j,Z as M,b as N,l as P,t as F,w as ee}from"./QBtn-B7WFdfmy.js";import{O as te,c as I,d as L,k as ne}from"./use-id-DlouqdB8.js";import{H as re,T as ie,h as ae,lt as R,m as oe,w as se}from"./index-okqx5CVc.js";import{t as ce}from"./QSpinnerDots-DWOFEezR.js";import{r as z}from"./QItemSection-5GBV9MR2.js";import{_ as le,b as B,x as V}from"./permissionModes-Du6lXebH.js";import{r as ue,t as H}from"./pending-diff-open-nLdNq-y-.js";import{t as U}from"./documents-B6eOZeFK.js";import{a as W,i as G}from"./layout-nWIyHeJf.js";import{i as de,n as fe,o as pe,r as me,t as K}from"./render-chat-markdown-DrGdVCku.js";import{a as he,i as ge,n as _e,o as ve,r as ye,s as be}from"./WorkspacePage-C8SAYVkt.js";var q=[`horizontal`,`vertical`,`cell`,`none`],J=M({name:`QMarkupTable`,props:{...ne,dense:Boolean,flat:Boolean,bordered:Boolean,square:Boolean,wrapCells:Boolean,separator:{type:String,default:`horizontal`,validator:e=>q.includes(e)}},setup(e,{slots:t}){let n=E(),r=te(e,n.proxy.$q),i=l(()=>`q-markup-table q-table__container q-table__card q-table--${e.separator}-separator`+(r.value?` q-table--dark q-table__card--dark q-dark`:``)+(e.dense?` q-table--dense`:``)+(e.flat?` q-table--flat`:``)+(e.bordered?` q-table--bordered`:``)+(e.square?` q-table--square`:``)+(e.wrapCells?``:` q-table--no-wrap`));return()=>O(`div`,{class:i.value},[O(`table`,{class:`q-table`},A(t.default))])}});function xe(e,t){return O(`div`,e,[O(`table`,{class:`q-table`},t)])}var Se={list:z,table:J},Ce=[`list`,`table`,`__qtable`],we=M({name:`QVirtualScroll`,props:{...V,type:{type:String,default:`list`,validator:e=>Ce.includes(e)},items:{type:Array,default:()=>[]},itemsFn:Function,itemsSize:Number,scrollTarget:L},setup(e,{slots:t,attrs:r}){let i,a=n(null),o=l(()=>e.itemsSize>=0&&e.itemsFn!==void 0?Number.parseInt(e.itemsSize,10):Array.isArray(e.items)?e.items.length:0),{virtualScrollSliceRange:s,localResetVirtualScroll:f,padVirtualScroll:p,onVirtualScrollEvt:g}=B({virtualScrollLength:o,getVirtualScrollTarget:x,getVirtualScrollEl:b}),_=l(()=>{if(o.value===0)return[];let t=(e,t)=>({index:s.value.from+t,item:e});return e.itemsFn===void 0?e.items.slice(s.value.from,s.value.to).map(t):e.itemsFn(s.value.from,s.value.to-s.value.from).map(t)}),v=l(()=>`q-virtual-scroll q-virtual-scroll`+(e.virtualScrollHorizontal?`--horizontal`:`--vertical`)+(e.scrollTarget===void 0?` scroll`:``)),y=l(()=>e.scrollTarget===void 0?{tabindex:0}:{});T(o,()=>{f()}),T(()=>e.scrollTarget,()=>{C(),S()});function b(){let e=a.value;return e===null?null:e.$el||e}function x(){return i}function S(){let t=b();t!==null&&(i=I(t,e.scrollTarget),i.addEventListener(`scroll`,g,j.passive))}function C(){i!==void 0&&(i.removeEventListener(`scroll`,g,j.passive),i=void 0)}function w(){let n=p(e.type===`list`?`div`:`tbody`,_.value.map(t.default));return t.before!==void 0&&(n=t.before().concat(n)),N(t.after,n)}return u(()=>{f()}),m(()=>{S()}),d(()=>{S()}),c(()=>{C()}),h(()=>{C()}),()=>{if(t.default===void 0){console.error(`QVirtualScroll: default scoped slot is required for rendering`);return}return e.type===`__qtable`?xe({ref:a,class:`q-table__middle `+v.value},w()):O(Se[e.type],{...r,ref:a,class:[r.class,v.value],...y.value},w)}}}),Te=new Set([`setup`,`cleanup`,`archive`]);function Ee(e){switch(e.type){case`user`:return e.sender===`system-prompt`?`system-prompt`:Te.has(e.sender)?`script`:`user`;case`session`:return`session`;default:return`agent`}}function De(e){let t=[],n=null,r=null;for(let i of e){let e=Ee(i),a=e===`session`||e===`system-prompt`,o=e===`script`&&i.type===`user`?`script:${i.sender}`:e;!n||r!==o||a?(n={speaker:e,ts:i.ts,items:[i]},r=o,t.push(n),a&&(n=null)):n.items.push(i)}return t}function Y(e){switch(e.type){case`text`:return`text:${e.messageId}`;case`thinking`:return`thinking:${e.messageId}:${e.eventIds?.[0]??e.ts??``}`;case`tool`:return`tool:${e.toolCallId}`;case`session`:return`session:${e.kind}:${e.eventIds?.[0]??e.ts??``}`;case`user`:return`user:${e.eventIds?.[0]??`${e.sender}:${e.ts??``}`}`;case`error`:return`error:${e.eventIds?.[0]??`${e.category}:${e.ts??``}`}`}}function Oe(e){let t=e.items[0];return`${e.speaker}:${t?Y(t):`empty`}`}function ke(e,t){let n=Math.min(t,e.length)-1;for(let t=n;t>=0;t--)if(e[t]?.speaker===`user`)return t;return-1}function Ae(e,t,n){return new Promise(r=>{let i=!1,a=null,o=null,s=e=>{i||(i=!0,a!==null&&clearTimeout(a),a=null,o?.(),r(e))};if(o=T(e,e=>{t(e)&&s(!0)},{immediate:!0}),i){o();return}a=setTimeout(()=>s(!1),n)})}var je={class:`agent-error-item`},X={class:`agent-error-item__body`},Me={class:`agent-error-item__title`},Z={class:`agent-error-item__message`},Ne=G(o({__name:`AgentErrorItem`,props:{item:{}},setup(t){let n=t,{te:r}=e(),a=l(()=>{let e=`agent.error.${n.item.category}`;return r(e)?e:`agent.error.other`});return(e,n)=>(g(),b(`div`,je,[C(P,{name:`error_outline`,size:`16px`,class:`agent-error-item__icon`}),y(`div`,X,[y(`div`,Me,i(e.$t(a.value)),1),y(`div`,Z,i(t.item.message),1)])]))}}),[[`__scopeId`,`data-v-e98c3c02`]]),Q={class:`text-caption text-kobo-3`},$=o({__name:`SessionEventItem`,props:{item:{}},setup(e){let t=e,n=l(()=>{switch(t.item.kind){case`started`:return`session.started`;case`ended`:return be(t.item.detail);case`compacted`:return`session.compacted`;default:return`session.started`}});return(e,t)=>(g(),b(`span`,Q,i(e.$t(n.value)),1))}});function Pe(e,t){if(t.length===0||e.length===0)return e;let n=[...t].sort((e,t)=>t.length-e.length),r=new DOMParser().parseFromString(`<div>${e}</div>`,`text/html`),i=r.body.firstChild;if(!i)return e;function a(e){if(e.nodeType===Node.TEXT_NODE){Fe(e,n,r);return}if(e.nodeName===`A`)return;let t=Array.from(e.childNodes);for(let e of t)a(e)}return a(i),i.innerHTML}function Fe(e,t,n){let r=e.textContent??``;if(!t.some(e=>r.includes(e)))return;let i=n.createDocumentFragment(),a=0;for(;a<r.length;){let e=Ie(r,a,t);if(!e){i.appendChild(n.createTextNode(r.slice(a)));break}e.index>a&&i.appendChild(n.createTextNode(r.slice(a,e.index)));let o=n.createElement(`a`);o.className=`document-link`,o.setAttribute(`data-document-path`,e.path),o.setAttribute(`href`,`#`),o.textContent=e.path,i.appendChild(o),a=e.index+e.path.length}e.parentNode?.replaceChild(i,e)}function Ie(e,t,n){let r=null;for(let i of n){let n=e.indexOf(i,t);n<0||(!r||n<r.index||n===r.index&&i.length>r.path.length)&&(r={index:n,path:i})}return r}var Le=[`innerHTML`],Re=G(o({__name:`TextMessageItem`,props:{item:{}},setup(e){let t=e,n=U(),r=oe(),i=l(()=>{let e=r.selectedWorkspaceId;return e?n.documentsFor(e).map(e=>e.path):[]}),a=l(()=>{let e=t.item;if(e.streaming)return K(e.text);let n=i.value;return me(`${e.messageId}|${n.join(`,`)}`,e.text,e=>{let t=Pe(pe.parse(e,{async:!1,breaks:!0,gfm:!0}),n);return de(t,{addAttr:[`data-document-path`]})})});function o(e){let t=e.target?.closest(`.document-link`);if(!t)return;e.preventDefault();let i=t.getAttribute(`data-document-path`),a=r.selectedWorkspaceId;!i||!a||n.openDocumentByPath(a,i)}return(t,n)=>(g(),b(`div`,{class:`markdown-message`,onClick:o},[y(`div`,{innerHTML:a.value},null,8,Le),e.item.streaming?(g(),f(ee,{key:0,size:`xs`,class:`q-ml-xs`})):v(``,!0)]))}}),[[`__scopeId`,`data-v-539815f6`]]);function ze(e,t){let n=e.split(`
2
2
  `),r=t.split(`
3
3
  `),i=0;for(;i<n.length&&i<r.length&&n[i]===r[i];)i++;let a=n.length,o=r.length;for(;a>i&&o>i&&n[a-1]===r[o-1];)a--,o--;let s=n.slice(0,i).map(e=>({type:`context`,content:e})),c=n.slice(a).map(e=>({type:`context`,content:e})),l=n.slice(i,a),u=r.slice(i,o);return l.length>800||u.length>800?[...s,...l.map(e=>({type:`del`,content:e})),...u.map(e=>({type:`add`,content:e})),...c]:[...s,...Be(l,u),...c]}function Be(e,t){let n=e.length,r=t.length;if(n===0&&r===0)return[];if(n===0)return t.map(e=>({type:`add`,content:e}));if(r===0)return e.map(e=>({type:`del`,content:e}));let i=r+1,a=new Int32Array((n+1)*i);for(let o=n-1;o>=0;o--)for(let n=r-1;n>=0;n--)a[o*i+n]=e[o]===t[n]?a[(o+1)*i+(n+1)]+1:Math.max(a[(o+1)*i+n],a[o*i+(n+1)]);let o=[],s=0,c=0;for(;s<n&&c<r;)e[s]===t[c]?(o.push({type:`context`,content:e[s]}),s++,c++):a[(s+1)*i+c]>=a[s*i+(c+1)]?(o.push({type:`del`,content:e[s]}),s++):(o.push({type:`add`,content:t[c]}),c++);for(;s<n;)o.push({type:`del`,content:e[s++]});for(;c<r;)o.push({type:`add`,content:t[c++]});return o}function Ve(e){let t=e.split(`
4
4
  `),n=[],r=!1;for(let e of t){if(e.startsWith(`@@`)){r=!0;continue}!r&&(e.startsWith(`+++`)||e.startsWith(`---`))||(e.startsWith(`+`)?n.push({type:`add`,content:e.slice(1)}):e.startsWith(`-`)?n.push({type:`del`,content:e.slice(1)}):e.startsWith(` `)?n.push({type:`context`,content:e.slice(1)}):e.length>0&&n.push({type:`context`,content:e}))}return n}function He(e,t){if(!t||typeof t!=`object`)return null;let n=t;if(e===`Edit`){let e=n.file_path;if(!e)return null;let t=n.old_string??``,r=n.new_string??``,i=typeof n.diff==`string`?n.diff:``;if(!t&&!r&&i.length>0){let t=Ve(i);return{toolName:`Edit`,filePath:e,additions:t.filter(e=>e.type===`add`).length,deletions:t.filter(e=>e.type===`del`).length,diffLines:t}}return{toolName:`Edit`,filePath:e,oldString:t,newString:r,replaceAll:n.replace_all??!1,additions:r?r.split(`
@@ -1 +1 @@
1
- import{B as e,C as t,wt as n}from"./reactivity.esm-bundler-DtSZKRQB.js";import{$ as r,A as i,H as a,Mt as o,Rt as s,U as c,V as l,W as u,Wt as d,Z as f,_n as p}from"./runtime-dom.esm-bundler-DgevF6KV.js";import{t as m}from"./QBtn-B7WFdfmy.js";import{_t as h,ht as g,r as _}from"./index-B29y2aJO.js";import{i as v}from"./layout-nWIyHeJf.js";import{n as y}from"./render-chat-markdown-CI48Q5rA.js";import{t as b}from"./QSpace-D3PNLwa4.js";import{t as x}from"./QChip-BzB_GF4Z.js";import{t as S}from"./QPage-RpDxx5ah.js";import{t as C}from"./DrawerToggleButton-CBdOJzVZ.js";var w={class:`row items-center q-mb-md`},T={class:`text-h6 q-ml-sm`},E={key:0,class:`text-kobo-3 text-center q-pa-lg`},D={key:1,class:`text-negative text-center q-pa-lg`},O={key:2,class:`text-kobo-3 text-center q-pa-lg`},k={key:3,class:`column q-gutter-md`},A={key:0,class:`text-caption text-kobo-3`},j={class:`row items-center q-mb-sm`},M={class:`text-subtitle1 text-kobo-2`,style:{"font-family":`var(--kobo-font-mono, monospace)`}},N=[`innerHTML`],P=v(r({__name:`ChangelogPage`,setup(r){let v=_(),P=t([]),F=t(``),I=t(!1),L=t(null);function R(e){return y(e)}async function z(){I.value=!0,L.value=null;try{let e=await fetch(`/api/changelog`);if(!e.ok)throw Error(`HTTP ${e.status}`);let t=await e.json();F.value=t.currentVersion??``,P.value=t.versions??[]}catch(e){L.value=e instanceof Error?e.message:String(e)}finally{I.value=!1}}return o(z),(t,r)=>(s(),a(S,{class:`q-pa-md`,style:{"max-width":`900px`,margin:`0 auto`}},{default:p(()=>[l(`div`,w,[f(C,{class:`q-mr-xs`}),f(m,{flat:``,dense:``,round:``,icon:`arrow_back`,onClick:r[0]||=t=>e(v).back()}),l(`div`,T,n(t.$t(`changelog.title`)),1),f(b),f(m,{flat:``,dense:``,icon:`refresh`,loading:I.value,label:t.$t(`common.refresh`),onClick:z},null,8,[`loading`,`label`])]),I.value&&P.value.length===0?(s(),u(`div`,E,n(t.$t(`common.loading`)),1)):L.value?(s(),u(`div`,D,n(L.value),1)):P.value.length===0?(s(),u(`div`,O,n(t.$t(`changelog.empty`)),1)):(s(),u(`div`,k,[F.value?(s(),u(`div`,A,n(t.$t(`changelog.currentVersion`,{version:F.value})),1)):c(``,!0),(s(!0),u(i,null,d(P.value,e=>(s(),a(g,{key:e.version,dark:``,flat:``,bordered:``},{default:p(()=>[f(h,null,{default:p(()=>[l(`div`,j,[l(`div`,M,` v`+n(e.version),1),e.version===F.value?(s(),a(x,{key:0,dense:``,size:`sm`,color:`primary`,"text-color":`kobo-1`,label:t.$t(`changelog.current`),class:`q-ml-sm`},null,8,[`label`])):c(``,!0)]),l(`div`,{class:`changelog-notes`,innerHTML:R(e.notes)},null,8,N)]),_:2},1024)]),_:2},1024))),128))]))]),_:1}))}}),[[`__scopeId`,`data-v-303054e6`]]);export{P as default};
1
+ import{B as e,C as t,wt as n}from"./reactivity.esm-bundler-DtSZKRQB.js";import{$ as r,A as i,H as a,Mt as o,Rt as s,U as c,V as l,W as u,Wt as d,Z as f,_n as p}from"./runtime-dom.esm-bundler-DgevF6KV.js";import{t as m}from"./QBtn-B7WFdfmy.js";import{_t as h,ht as g,r as _}from"./index-okqx5CVc.js";import{i as v}from"./layout-nWIyHeJf.js";import{n as y}from"./render-chat-markdown-DrGdVCku.js";import{t as b}from"./QSpace-D3PNLwa4.js";import{t as x}from"./QChip-BzB_GF4Z.js";import{t as S}from"./QPage-RpDxx5ah.js";import{t as C}from"./DrawerToggleButton-CBdOJzVZ.js";var w={class:`row items-center q-mb-md`},T={class:`text-h6 q-ml-sm`},E={key:0,class:`text-kobo-3 text-center q-pa-lg`},D={key:1,class:`text-negative text-center q-pa-lg`},O={key:2,class:`text-kobo-3 text-center q-pa-lg`},k={key:3,class:`column q-gutter-md`},A={key:0,class:`text-caption text-kobo-3`},j={class:`row items-center q-mb-sm`},M={class:`text-subtitle1 text-kobo-2`,style:{"font-family":`var(--kobo-font-mono, monospace)`}},N=[`innerHTML`],P=v(r({__name:`ChangelogPage`,setup(r){let v=_(),P=t([]),F=t(``),I=t(!1),L=t(null);function R(e){return y(e)}async function z(){I.value=!0,L.value=null;try{let e=await fetch(`/api/changelog`);if(!e.ok)throw Error(`HTTP ${e.status}`);let t=await e.json();F.value=t.currentVersion??``,P.value=t.versions??[]}catch(e){L.value=e instanceof Error?e.message:String(e)}finally{I.value=!1}}return o(z),(t,r)=>(s(),a(S,{class:`q-pa-md`,style:{"max-width":`900px`,margin:`0 auto`}},{default:p(()=>[l(`div`,w,[f(C,{class:`q-mr-xs`}),f(m,{flat:``,dense:``,round:``,icon:`arrow_back`,onClick:r[0]||=t=>e(v).back()}),l(`div`,T,n(t.$t(`changelog.title`)),1),f(b),f(m,{flat:``,dense:``,icon:`refresh`,loading:I.value,label:t.$t(`common.refresh`),onClick:z},null,8,[`loading`,`label`])]),I.value&&P.value.length===0?(s(),u(`div`,E,n(t.$t(`common.loading`)),1)):L.value?(s(),u(`div`,D,n(L.value),1)):P.value.length===0?(s(),u(`div`,O,n(t.$t(`changelog.empty`)),1)):(s(),u(`div`,k,[F.value?(s(),u(`div`,A,n(t.$t(`changelog.currentVersion`,{version:F.value})),1)):c(``,!0),(s(!0),u(i,null,d(P.value,e=>(s(),a(g,{key:e.version,dark:``,flat:``,bordered:``},{default:p(()=>[f(h,null,{default:p(()=>[l(`div`,j,[l(`div`,M,` v`+n(e.version),1),e.version===F.value?(s(),a(x,{key:0,dense:``,size:`sm`,color:`primary`,"text-color":`kobo-1`,label:t.$t(`changelog.current`),class:`q-ml-sm`},null,8,[`label`])):c(``,!0)]),l(`div`,{class:`changelog-notes`,innerHTML:R(e.notes)},null,8,N)]),_:2},1024)]),_:2},1024))),128))]))]),_:1}))}}),[[`__scopeId`,`data-v-303054e6`]]);export{P as default};
@@ -0,0 +1 @@
1
+ import{Q as e,R as t}from"./QBtn-B7WFdfmy.js";import{v as n,y as r}from"./use-id-DlouqdB8.js";function i(e){return e===!1?0:e===!0||e===void 0?1:Number.parseInt(e,10)||0}var a=e({name:`close-popup`,beforeMount(e,{value:a}){let o={depth:i(a),handler(t){o.depth!==0&&setTimeout(()=>{let i=r(e);i!==void 0&&n(i,t,o.depth)},0)},handlerKey(e){t(e,13)&&o.handler(e)}};e.__qclosepopup=o,e.addEventListener(`click`,o.handler),e.addEventListener(`keyup`,o.handlerKey)},updated(e,{value:t,oldValue:n}){t!==n&&(e.__qclosepopup.depth=i(t))},beforeUnmount(e){let t=e.__qclosepopup;e.removeEventListener(`click`,t.handler),e.removeEventListener(`keyup`,t.handlerKey),delete e.__qclosepopup}});export{a as t};
@@ -0,0 +1 @@
1
+ .pr-picker__list-section[data-v-2cab3a30]{max-height:60vh;overflow-y:auto}.pr-picker__item[aria-disabled=true][data-v-2cab3a30]{opacity:.5}.create-page[data-v-669787bd]{background:var(--kobo-bg);min-height:100%;padding:48px 24px 80px;position:relative}.create-page__drawer-toggle[data-v-669787bd]{top:var(--kobo-space-md);left:var(--kobo-space-md);z-index:1;position:absolute}.create-page__progress[data-v-669787bd]{align-items:center;gap:var(--kobo-space-sm);color:var(--kobo-text-2);display:flex}.create-page__progress-counter[data-v-669787bd]{font-family:var(--kobo-font-mono);color:var(--kobo-text-3)}.create-page__overrides[data-v-669787bd]{gap:var(--kobo-space-xs);padding:var(--kobo-space-md);border:1px solid var(--kobo-border-subtle);border-radius:var(--kobo-radius-sm);background:var(--kobo-surface-2);flex-direction:column;display:flex}.create-page__overrides-title[data-v-669787bd]{align-items:center;gap:var(--kobo-space-xs);color:var(--kobo-text-2);display:flex}.create-page__overrides-item[data-v-669787bd]{color:var(--kobo-text-3)}.create-inner[data-v-669787bd]{width:100%;max-width:960px}.create-title[data-v-669787bd]{letter-spacing:-.02em;font-size:30px;line-height:1.2}.create-sections[data-v-669787bd]{gap:24px}.responsive-fields[data-v-669787bd]{row-gap:16px}.responsive-fields--sm[data-v-669787bd]{row-gap:8px}.create-section-card[data-v-669787bd],.create-actions-card[data-v-669787bd]{background:var(--kobo-surface);border-color:#ffffff1f}.card-textarea-wrap[data-v-669787bd]{position:relative}.create-textarea[data-v-669787bd] .q-field__control{min-height:220px}.create-textarea[data-v-669787bd] textarea{resize:vertical;line-height:1.6;min-height:180px!important}.create-slash-popup[data-v-669787bd]{z-index:20;position:absolute;top:calc(100% + 4px);left:12px;right:12px}.source-panel[data-v-669787bd]{background:var(--kobo-surface-2);border:1px solid #ffffff1a}.peek-card[data-v-669787bd]{color:var(--kobo-text-2);background:#ffffff06;border-color:#ffffff1a;transition:background .15s,border-color .15s,transform .1s}.peek-card[data-v-669787bd]:hover{background:#ffffff0f;border-color:#818cf880}.peek-card[data-v-669787bd]:active{transform:scale(.99)}.peek-card--active[data-v-669787bd]{border-color:var(--kobo-accent);background:#6366f124;box-shadow:inset 0 0 0 1px #818cf859}.peek-card--active .peek-card-icon[data-v-669787bd]{color:var(--kobo-text-2)}.peek-card-icon[data-v-669787bd]{color:var(--kobo-text-3);flex-shrink:0}.manual-expansion[data-v-669787bd],.advanced-options[data-v-669787bd]{background:var(--kobo-surface-2);border:1px solid #ffffff1a;overflow:hidden}.manual-expansion[data-v-669787bd] .q-expansion-item__content,.advanced-options[data-v-669787bd] .q-expansion-item__content{background:var(--kobo-bg-deep)}.manual-expansion[data-v-669787bd] .q-chip{max-width:100%;height:auto;min-height:32px}.manual-expansion[data-v-669787bd] .q-chip__content{white-space:normal;overflow-wrap:anywhere}.auto-loop-panel[data-v-669787bd]{background:#f59e0b0f;border-top:1px solid #f59e0b38;gap:16px}.auto-loop-session-toggle[data-v-669787bd] .q-btn{min-height:32px;padding-top:4px;padding-bottom:4px}[data-v-669787bd] .agent-config-header{min-height:56px;padding-top:6px;padding-bottom:8px}.agent-summary[data-v-669787bd]{row-gap:4px}.agent-summary[data-v-669787bd] .q-chip{margin:0}.create-actions-card[data-v-669787bd]{z-index:10;position:sticky;bottom:16px;box-shadow:0 10px 36px #00000061}.voice-btn--recording[data-v-669787bd]{animation:1.1s ease-in-out infinite voice-pulse-669787bd}@keyframes voice-pulse-669787bd{0%,to{opacity:1;transform:scale(1)}50%{opacity:.86;transform:scale(1.06)}}.slide-enter-active[data-v-669787bd],.slide-leave-active[data-v-669787bd]{transition:max-height .2s,opacity .2s;overflow:hidden}.slide-enter-from[data-v-669787bd],.slide-leave-to[data-v-669787bd]{opacity:0;max-height:0}.slide-enter-to[data-v-669787bd],.slide-leave-from[data-v-669787bd]{opacity:1;max-height:900px}@media (max-width:599px){.create-page[data-v-669787bd]{padding:28px 12px 56px}.create-title[data-v-669787bd]{font-size:26px}.create-actions-card[data-v-669787bd]{bottom:8px}.create-actions-card[data-v-669787bd] .q-card__section{align-items:stretch}.create-actions[data-v-669787bd]{width:100%}.create-actions .q-btn[data-v-669787bd]:last-child{flex:1}}