@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.
- package/CHANGELOG.md +9 -0
- package/dist/server/db/migrations.js +13 -0
- package/dist/server/db/schema.js +1 -0
- package/dist/server/index.js +2 -0
- package/dist/server/routes/pull-requests.js +112 -0
- package/dist/server/routes/workspaces.js +98 -0
- package/dist/server/services/forge/bitbucket/provider.js +86 -0
- package/dist/server/services/forge/github/provider.js +76 -0
- package/dist/server/services/forge/gitlab/provider.js +44 -0
- package/dist/server/services/forge/none.js +4 -0
- package/dist/server/services/pr-checkout-service.js +380 -0
- package/dist/server/services/workspace-service.js +4 -3
- package/dist/server/services/worktree-service.js +5 -5
- package/package.json +1 -1
- package/src/client/dist/pwa/assets/{ActivityFeed-DtsapcUb.js → ActivityFeed-BNyFzRkx.js} +1 -1
- package/src/client/dist/pwa/assets/{ChangelogPage-D5FtafDI.js → ChangelogPage-7pgilQJn.js} +1 -1
- package/src/client/dist/pwa/assets/ClosePopup-BK6mNWsy.js +1 -0
- package/src/client/dist/pwa/assets/CreatePage-BCuDTB_0.css +1 -0
- package/src/client/dist/pwa/assets/CreatePage-P4N9bro4.js +2 -0
- package/src/client/dist/pwa/assets/{DiffViewer-B7CQC4kD.js → DiffViewer-Cl2w9liE.js} +4 -4
- package/src/client/dist/pwa/assets/{DocumentsPanel-BgIrc-i0.js → DocumentsPanel-CARiAfZs.js} +1 -1
- package/src/client/dist/pwa/assets/GitPanel-txKWaxSr.js +3 -0
- package/src/client/dist/pwa/assets/HealthPage-B0_v81_H.js +1 -0
- package/src/client/dist/pwa/assets/MainLayout-Cl26t1Yw.js +2 -0
- package/src/client/dist/pwa/assets/{QBtnDropdown-COnv-9Kx.js → QBtnDropdown-9dpk4IiI.js} +1 -1
- package/src/client/dist/pwa/assets/QItemSection-5GBV9MR2.js +1 -0
- package/src/client/dist/pwa/assets/{SchedulePanel-CS__azUn.js → SchedulePanel-CSJbQpWB.js} +1 -1
- package/src/client/dist/pwa/assets/{SearchPage-CBQqJmmb.js → SearchPage-B5Wndh73.js} +1 -1
- package/src/client/dist/pwa/assets/SettingsPage-BmSaJrE5.js +16 -0
- package/src/client/dist/pwa/assets/{TerminalPanel-lsPJEdE-.js → TerminalPanel-CuKWlENm.js} +1 -1
- package/src/client/dist/pwa/assets/TouchPan-CflYDvzb.js +1 -0
- package/src/client/dist/pwa/assets/WorkspacePage-C8SAYVkt.js +4 -0
- package/src/client/dist/pwa/assets/{build-path-tree-CmNRcZvN.js → build-path-tree-nS5a0hfa.js} +1 -1
- package/src/client/dist/pwa/assets/{cssMode-DuFZt6Ie.js → cssMode-BWoHpG_3.js} +1 -1
- package/src/client/dist/pwa/assets/{editor.api-Dt5YHQkt.js → editor.api-CHHKMKIl.js} +1 -1
- package/src/client/dist/pwa/assets/efforts-C0gGtu_O.js +9 -0
- package/src/client/dist/pwa/assets/engineFeatures-Cm3wk7lo.js +1 -0
- package/src/client/dist/pwa/assets/expand-template-CRv7R1En.js +1 -0
- package/src/client/dist/pwa/assets/{freemarker2-DQWkQIhe.js → freemarker2-ChmoNkJH.js} +1 -1
- package/src/client/dist/pwa/assets/{handlebars-C8DkGF05.js → handlebars-DjdwH2Vr.js} +1 -1
- package/src/client/dist/pwa/assets/{html-DlhhK7Jm.js → html-DJszTgCl.js} +1 -1
- package/src/client/dist/pwa/assets/{htmlMode-BVmEqAhh.js → htmlMode-D0QTtBWO.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-BQFPc-Bc.js +1 -0
- package/src/client/dist/pwa/assets/{index-B29y2aJO.js → index-okqx5CVc.js} +7 -7
- package/src/client/dist/pwa/assets/{javascript-C80MQqD5.js → javascript-ByhJGDgS.js} +1 -1
- package/src/client/dist/pwa/assets/{jsonMode-r9kfqNya.js → jsonMode-Db1oPA-y.js} +1 -1
- package/src/client/dist/pwa/assets/{liquid-DT_yR5El.js → liquid-CGMily4q.js} +1 -1
- package/src/client/dist/pwa/assets/{lspLanguageFeatures-CY9UVeSD.js → lspLanguageFeatures-B85CeJTe.js} +1 -1
- package/src/client/dist/pwa/assets/{mdx-C_PbX1dC.js → mdx-B5_UPmNo.js} +1 -1
- package/src/client/dist/pwa/assets/{network-auth-BP2CrR3z.js → network-auth-D9vq0xDI.js} +1 -1
- package/src/client/dist/pwa/assets/{notify-theme-DIVwJeyL.js → notify-theme-kRr6YTnZ.js} +1 -1
- package/src/client/dist/pwa/assets/{pending-diff-open-B9fNhQic.js → pending-diff-open-nLdNq-y-.js} +1 -1
- package/src/client/dist/pwa/assets/{permissionModes-Be3ZDmns.js → permissionModes-Du6lXebH.js} +3 -3
- package/src/client/dist/pwa/assets/{project-color-Bc8ih-HF.js → project-color-tT7gsmB5.js} +1 -1
- package/src/client/dist/pwa/assets/{python-8G6qNwxa.js → python-FzBHx7LG.js} +1 -1
- package/src/client/dist/pwa/assets/{razor-BazUxiHR.js → razor-BU_JvIXc.js} +1 -1
- package/src/client/dist/pwa/assets/{render-chat-markdown-CI48Q5rA.js → render-chat-markdown-DrGdVCku.js} +1 -1
- package/src/client/dist/pwa/assets/{tsMode-BUJh8zAj.js → tsMode-BDW6Cb6M.js} +1 -1
- package/src/client/dist/pwa/assets/{typescript-C5pPIpSU.js → typescript-21K3hWN-.js} +1 -1
- package/src/client/dist/pwa/assets/{use-onboarding-BQCOq7nO.js → use-onboarding-BCM8AGRg.js} +1 -1
- package/src/client/dist/pwa/assets/{vs-QND9VumP.js → vs-Ch3CDiTS.js} +3 -3
- package/src/client/dist/pwa/assets/{workers-CXEAdpVt.js → workers-CbIvH33O.js} +1 -1
- package/src/client/dist/pwa/assets/{xml-DNCFiQ9N.js → xml-d-kArTuN.js} +1 -1
- package/src/client/dist/pwa/assets/{yaml-Ck1iNNOE.js → yaml-tzAPZ-1Q.js} +1 -1
- package/src/client/dist/pwa/index.html +1 -1
- package/src/client/dist/pwa/sw.js +1 -1
- package/src/client/dist/pwa/assets/ClosePopup-DEy0yFXE.js +0 -1
- package/src/client/dist/pwa/assets/CreatePage-BhcHSOqz.js +0 -2
- package/src/client/dist/pwa/assets/CreatePage-RMBX-Zjn.css +0 -1
- package/src/client/dist/pwa/assets/GitPanel-DHvRFdbB.js +0 -3
- package/src/client/dist/pwa/assets/HealthPage-YqOn5Ig0.js +0 -1
- package/src/client/dist/pwa/assets/MainLayout-aDNwgrrT.js +0 -2
- package/src/client/dist/pwa/assets/QItemSection-CDBweQnA.js +0 -1
- package/src/client/dist/pwa/assets/QList-Ccqhimcb.js +0 -1
- package/src/client/dist/pwa/assets/SettingsPage-kx7aNmeH.js +0 -16
- package/src/client/dist/pwa/assets/TouchPan-DnEDaTFL.js +0 -1
- package/src/client/dist/pwa/assets/WorkspacePage-Cyoc2Mvs.js +0 -4
- package/src/client/dist/pwa/assets/efforts-C3gjASPy.js +0 -9
- package/src/client/dist/pwa/assets/engineFeatures-DkgYepOC.js +0 -1
- package/src/client/dist/pwa/assets/expand-template-CLqglUAa.js +0 -1
- 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, () =>
|
|
150
|
+
return withGitRepoLock(projectPath, () => removeWorktreeUnlocked(projectPath, worktreePath));
|
|
151
151
|
}
|
|
152
|
-
/**
|
|
153
|
-
function
|
|
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.
|
|
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-
|
|
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-
|
|
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}}
|