@loicngr/kobo 1.11.2 → 1.11.4

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 (79) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +5 -5
  3. package/dist/server/db/migrations.js +20 -0
  4. package/dist/server/db/schema.js +1 -0
  5. package/dist/server/routes/workspaces.js +71 -0
  6. package/dist/server/services/agent/engines/claude-code/engine.js +1 -0
  7. package/dist/server/services/agent/engines/claude-code/options-builder.js +2 -0
  8. package/dist/server/services/agent/engines/codex/capabilities.js +2 -1
  9. package/dist/server/services/agent/engines/codex/engine.js +1 -1
  10. package/dist/server/services/agent/engines/codex/options-builder.js +4 -2
  11. package/dist/server/services/agent/orchestrator.js +45 -8
  12. package/dist/server/services/engine-handoff-service.js +41 -0
  13. package/dist/server/services/forge/bitbucket/provider.js +291 -0
  14. package/dist/server/services/forge/registry.js +3 -1
  15. package/dist/server/services/forge/resolve.js +4 -1
  16. package/dist/server/services/settings-defaults.js +1 -1
  17. package/dist/server/services/settings-service.js +15 -1
  18. package/dist/server/services/workspace-service.js +50 -2
  19. package/package.json +1 -1
  20. package/src/client/dist/pwa/assets/{ActivityFeed-BNhy8oDx.js → ActivityFeed-CoE7FTe_.js} +1 -1
  21. package/src/client/dist/pwa/assets/{ChangelogPage-eRWLuQLR.js → ChangelogPage-Cr_15zCy.js} +1 -1
  22. package/src/client/dist/pwa/assets/{ClosePopup-Cc2YwliF.js → ClosePopup-DQgUw5Fk.js} +1 -1
  23. package/src/client/dist/pwa/assets/{CreatePage-B9Mxi7zD.js → CreatePage-BgDusUKR.js} +1 -1
  24. package/src/client/dist/pwa/assets/{DiffViewer-DglPAzaZ.js → DiffViewer-CfOhZMXv.js} +3 -3
  25. package/src/client/dist/pwa/assets/{HealthPage-sS-N-_9T.js → HealthPage-3GHu-I59.js} +1 -1
  26. package/src/client/dist/pwa/assets/{MainLayout-BoZsUoGd.css → MainLayout-BY-xOjDK.css} +1 -1
  27. package/src/client/dist/pwa/assets/{MainLayout-BelFb6SW.js → MainLayout-DPLjufY9.js} +2 -2
  28. package/src/client/dist/pwa/assets/{QBtnToggle-eC2_l1xJ.js → QBtnToggle-CC3TCOK8.js} +1 -1
  29. package/src/client/dist/pwa/assets/{QChip-BT8qyt_p.js → QChip-CLKcMuqJ.js} +1 -1
  30. package/src/client/dist/pwa/assets/{QSelect-Bj-wqU82.js → QSelect-DUCHcz_p.js} +1 -1
  31. package/src/client/dist/pwa/assets/{QTooltip-CCNfQ3sa.js → QTooltip-CUM1JiWx.js} +1 -1
  32. package/src/client/dist/pwa/assets/{SearchPage-6CSIDDcM.js → SearchPage-f9Bjw2s3.js} +1 -1
  33. package/src/client/dist/pwa/assets/SettingsPage-BmTlGyff.js +16 -0
  34. package/src/client/dist/pwa/assets/{SettingsPage-CENUP7ua.css → SettingsPage-Os-6b-6H.css} +1 -1
  35. package/src/client/dist/pwa/assets/{ThinkingItem-kP-lU251.js → ThinkingItem-D9OZZN0H.js} +1 -1
  36. package/src/client/dist/pwa/assets/{TouchPan-DVggdTB9.js → TouchPan-DvWjWFLZ.js} +1 -1
  37. package/src/client/dist/pwa/assets/{WorkspacePage-D-1HoeQg.css → WorkspacePage-BbJM8PyI.css} +1 -1
  38. package/src/client/dist/pwa/assets/WorkspacePage-CtiULAL9.js +4 -0
  39. package/src/client/dist/pwa/assets/{build-path-tree-FNTYv-td.js → build-path-tree-nLp-H-pQ.js} +1 -1
  40. package/src/client/dist/pwa/assets/{cssMode-DHVn7kI3.js → cssMode-7vYc6lG_.js} +1 -1
  41. package/src/client/dist/pwa/assets/{documents-DoEDCa8X.js → documents-CSi0kXxo.js} +1 -1
  42. package/src/client/dist/pwa/assets/{editor.api2-DO15WOFV.js → editor.api2-BzgKTMyb.js} +1 -1
  43. package/src/client/dist/pwa/assets/{editor.main-Ce_TPxw1.js → editor.main-DOAM9U2f.js} +2 -2
  44. package/src/client/dist/pwa/assets/{expand-template-DnNNlpvQ.js → expand-template-CT5wSSEr.js} +1 -1
  45. package/src/client/dist/pwa/assets/{formatters-BBT0Ye5P.js → formatters-66ZLwD6M.js} +1 -1
  46. package/src/client/dist/pwa/assets/{freemarker2-B5mFQ9MW.js → freemarker2-CgDldzFl.js} +1 -1
  47. package/src/client/dist/pwa/assets/{handlebars-TGXWWwrU.js → handlebars-Dt0cB0x6.js} +1 -1
  48. package/src/client/dist/pwa/assets/{html-BcoyXTgh.js → html-Blz0fA0T.js} +1 -1
  49. package/src/client/dist/pwa/assets/{htmlMode-Dhuwig6w.js → htmlMode-CgzmQ96i.js} +1 -1
  50. package/src/client/dist/pwa/assets/i18n-BjRW3rME.js +1 -0
  51. package/src/client/dist/pwa/assets/index-C0ylRsyw.js +84 -0
  52. package/src/client/dist/pwa/assets/{javascript-v_uXBoLN.js → javascript-DVKtVeCf.js} +1 -1
  53. package/src/client/dist/pwa/assets/{jsonMode-CErvRpqG.js → jsonMode-Cfa435aJ.js} +1 -1
  54. package/src/client/dist/pwa/assets/{kobo-commands-DXJ85QAa.js → kobo-commands-C-rMOa-X.js} +1 -1
  55. package/src/client/dist/pwa/assets/{layout-Di93C8KP.js → layout-BUYxQPjS.js} +1 -1
  56. package/src/client/dist/pwa/assets/{liquid-ZappZiY7.js → liquid-DU4LoIen.js} +1 -1
  57. package/src/client/dist/pwa/assets/{lspLanguageFeatures-ur5VVJZY.js → lspLanguageFeatures-DDmy9kyx.js} +1 -1
  58. package/src/client/dist/pwa/assets/{mdx-BKL2jNxT.js → mdx-CVPu9G6b.js} +1 -1
  59. package/src/client/dist/pwa/assets/{monaco.contribution-WfNiFCn_.js → monaco.contribution-BCKXe1LM.js} +2 -2
  60. package/src/client/dist/pwa/assets/{network-auth-O2hz04zH.js → network-auth-BP0-h4VQ.js} +1 -1
  61. package/src/client/dist/pwa/assets/{pending-diff-open-CysqNMDL.js → pending-diff-open-BAyLIcQ6.js} +1 -1
  62. package/src/client/dist/pwa/assets/{permissionModes-Crg3_WdF.js → permissionModes-CNIg8DwA.js} +1 -1
  63. package/src/client/dist/pwa/assets/{project-color-DVafgHFe.js → project-color-B38mkxVB.js} +1 -1
  64. package/src/client/dist/pwa/assets/{python-DXpnDgWC.js → python-WDhDBOmq.js} +1 -1
  65. package/src/client/dist/pwa/assets/{razor-2pTtVGVn.js → razor-Dv_KM7xr.js} +1 -1
  66. package/src/client/dist/pwa/assets/{render-chat-markdown-BbmsqP4r.js → render-chat-markdown-DahMoI2g.js} +1 -1
  67. package/src/client/dist/pwa/assets/{tsMode-t5L5YTTB.js → tsMode-B3UNfmkB.js} +1 -1
  68. package/src/client/dist/pwa/assets/{typescript-C2FUrxc2.js → typescript-CNkrDBQS.js} +1 -1
  69. package/src/client/dist/pwa/assets/{use-onboarding-CxQRTetV.js → use-onboarding-Bb-Hawzx.js} +1 -1
  70. package/src/client/dist/pwa/assets/{use-quasar-CUBX9V4F.js → use-quasar-DCRxdLu-.js} +1 -1
  71. package/src/client/dist/pwa/assets/{workers-ri7So4GA.js → workers-UUdizQoG.js} +1 -1
  72. package/src/client/dist/pwa/assets/{xml-CvWFj3be.js → xml-zRWWyHRk.js} +1 -1
  73. package/src/client/dist/pwa/assets/{yaml-DsSH3Hc4.js → yaml-Uma2sS3z.js} +1 -1
  74. package/src/client/dist/pwa/index.html +1 -1
  75. package/src/client/dist/pwa/sw.js +1 -1
  76. package/src/client/dist/pwa/assets/SettingsPage-jGIZfB7N.js +0 -16
  77. package/src/client/dist/pwa/assets/WorkspacePage-BWRpD_9V.js +0 -4
  78. package/src/client/dist/pwa/assets/i18n-BHXcyfLT.js +0 -1
  79. package/src/client/dist/pwa/assets/index-CGnQPjwo.js +0 -84
@@ -0,0 +1,291 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ import { getGlobalSettings } from '../../settings-service.js';
4
+ import { deriveReadyToMerge } from '../types.js';
5
+ const execFileAsync = promisify(execFile);
6
+ function record(value) {
7
+ return value !== null && typeof value === 'object' ? value : {};
8
+ }
9
+ function string(value) {
10
+ return typeof value === 'string' ? value : '';
11
+ }
12
+ function number(value) {
13
+ return typeof value === 'number' && Number.isFinite(value) ? value : 0;
14
+ }
15
+ function branch(raw, cloudKey, dcKey) {
16
+ const cloud = record(record(raw[cloudKey]).branch);
17
+ return string(cloud.name) || string(record(raw[dcKey]).displayId);
18
+ }
19
+ function login(raw) {
20
+ const user = record(raw);
21
+ return string(user.nickname) || string(user.username) || string(user.name) || string(user.display_name);
22
+ }
23
+ function url(raw) {
24
+ const links = record(raw.links);
25
+ const html = record(links.html);
26
+ if (string(html.href))
27
+ return string(html.href);
28
+ const self = links.self;
29
+ if (Array.isArray(self))
30
+ return string(record(self[0]).href);
31
+ return '';
32
+ }
33
+ function mapState(value) {
34
+ const state = value.toUpperCase();
35
+ if (state === 'MERGED')
36
+ return 'MERGED';
37
+ if (state === 'DECLINED' || state === 'CLOSED' || state === 'SUPERSEDED')
38
+ return 'CLOSED';
39
+ return 'OPEN';
40
+ }
41
+ function mapReviewers(raw) {
42
+ const result = new Map();
43
+ const add = (candidate, fallback) => {
44
+ const entry = record(candidate);
45
+ const reviewerLogin = login(entry.user) || login(entry);
46
+ if (!reviewerLogin)
47
+ return;
48
+ const rawState = string(entry.status || entry.state).toUpperCase();
49
+ const state = entry.approved === true || rawState === 'APPROVED'
50
+ ? 'APPROVED'
51
+ : rawState === 'NEEDS_WORK' || rawState === 'CHANGES_REQUESTED'
52
+ ? 'CHANGES_REQUESTED'
53
+ : fallback;
54
+ result.set(reviewerLogin, state);
55
+ };
56
+ for (const reviewer of Array.isArray(raw.reviewers) ? raw.reviewers : [])
57
+ add(reviewer, 'PENDING');
58
+ for (const participant of Array.isArray(raw.participants) ? raw.participants : [])
59
+ add(participant, 'COMMENTED');
60
+ return [...result].map(([reviewerLogin, state]) => ({ login: reviewerLogin, state }));
61
+ }
62
+ function mapCheck(raw) {
63
+ const check = record(raw);
64
+ const state = string(check.state || check.status).toUpperCase();
65
+ const completed = ['SUCCESSFUL', 'SUCCESS', 'FAILED', 'FAILURE', 'CANCELLED', 'CANCELED'].includes(state);
66
+ const conclusion = ['SUCCESSFUL', 'SUCCESS'].includes(state)
67
+ ? 'SUCCESS'
68
+ : ['FAILED', 'FAILURE'].includes(state)
69
+ ? 'FAILURE'
70
+ : ['CANCELLED', 'CANCELED'].includes(state)
71
+ ? 'CANCELLED'
72
+ : null;
73
+ return {
74
+ name: string(check.name) || string(check.key),
75
+ status: completed ? 'COMPLETED' : 'IN_PROGRESS',
76
+ conclusion,
77
+ detailsUrl: string(check.url) || null,
78
+ };
79
+ }
80
+ function mapBktPr(raw, ci = { rollup: null, checks: [] }) {
81
+ const state = mapState(string(raw.state));
82
+ const reviewers = mapReviewers(raw);
83
+ const reviewDecision = reviewers.some((reviewer) => reviewer.state === 'CHANGES_REQUESTED')
84
+ ? 'CHANGES_REQUESTED'
85
+ : null;
86
+ const mergeable = raw.open === false ? 'CONFLICTING' : null;
87
+ return {
88
+ number: number(raw.id),
89
+ title: string(raw.title),
90
+ url: url(raw),
91
+ state,
92
+ base: branch(raw, 'destination', 'toRef'),
93
+ reviewDecision,
94
+ author: { login: login(raw.author) },
95
+ assignees: [],
96
+ reviewers,
97
+ labels: [],
98
+ ci,
99
+ updatedAt: string(raw.updated_on) || string(raw.updatedAt) || String(raw.updatedDate ?? ''),
100
+ unresolvedReviewThreadsCount: 0,
101
+ mergeable,
102
+ readyToMerge: deriveReadyToMerge({ state, ci, reviewDecision, reviewers, mergeable }),
103
+ };
104
+ }
105
+ function availabilityFromError(err) {
106
+ const code = err.code;
107
+ if (code === 'ENOENT')
108
+ return { available: false, reason: 'cli_missing' };
109
+ const message = err.message?.toLowerCase() ?? '';
110
+ if (message.includes('no active context') ||
111
+ message.includes('not authenticated') ||
112
+ message.includes('auth login')) {
113
+ return { available: false, reason: 'not_authenticated' };
114
+ }
115
+ return { available: false };
116
+ }
117
+ function parseBitbucketRemote(remote) {
118
+ const match = remote.trim().match(/bitbucket(?:\.org)?[/:]([^/\s:]+)\/([^/\s]+?)(?:\.git)?\/?$/i);
119
+ if (!match)
120
+ return null;
121
+ return { workspace: match[1], repo: match[2] };
122
+ }
123
+ function contextName(workspace, repo) {
124
+ return `kobo-${workspace}-${repo}`.toLowerCase().replace(/[^a-z0-9._-]/g, '-');
125
+ }
126
+ function bktArgs(target, args) {
127
+ return target.context ? ['--context', target.context, ...args] : args;
128
+ }
129
+ function bktEnv() {
130
+ try {
131
+ const settings = getGlobalSettings();
132
+ if (settings.bitbucketToken) {
133
+ return {
134
+ ...process.env,
135
+ BKT_HOST: 'https://bitbucket.org',
136
+ BKT_TOKEN: settings.bitbucketToken,
137
+ BKT_USERNAME: settings.bitbucketUsername || process.env.BKT_USERNAME,
138
+ };
139
+ }
140
+ }
141
+ catch {
142
+ // Settings are deliberately uninitialized in isolated forge tests.
143
+ }
144
+ return process.env;
145
+ }
146
+ function bktOptions(repoPath) {
147
+ return { cwd: repoPath, encoding: 'utf-8', env: bktEnv() };
148
+ }
149
+ async function resolveTarget(repoPath) {
150
+ const { stdout } = await execFileAsync('git', ['remote', 'get-url', 'origin'], {
151
+ cwd: repoPath,
152
+ encoding: 'utf-8',
153
+ });
154
+ const target = parseBitbucketRemote(stdout);
155
+ if (!target)
156
+ throw new Error('Could not determine Bitbucket workspace and repository from the origin remote');
157
+ // BKT_TOKEN is the CLI's documented headless mode. It deliberately avoids
158
+ // keyring and context access, which cannot prompt from the Kobo server.
159
+ if (bktEnv().BKT_TOKEN)
160
+ return { ...target, context: null };
161
+ const { stdout: contextOutput } = await execFileAsync('bkt', ['context', 'list', '--json'], {
162
+ cwd: repoPath,
163
+ encoding: 'utf-8',
164
+ });
165
+ const contexts = record(JSON.parse(contextOutput)).contexts;
166
+ const existing = (Array.isArray(contexts) ? contexts : [])
167
+ .map(record)
168
+ .find((context) => string(context.workspace) === target.workspace && string(context.default_repo) === target.repo);
169
+ if (existing && string(existing.name))
170
+ return { ...target, context: string(existing.name) };
171
+ const { stdout: authOutput } = await execFileAsync('bkt', ['auth', 'status', '--json'], {
172
+ cwd: repoPath,
173
+ encoding: 'utf-8',
174
+ });
175
+ const hosts = record(JSON.parse(authOutput)).hosts;
176
+ const host = (Array.isArray(hosts) ? hosts : []).map(record).find((item) => string(item.kind) === 'cloud');
177
+ const hostKey = string(host?.key);
178
+ if (!hostKey)
179
+ throw new Error('No authenticated Bitbucket Cloud host found; run bkt auth login first');
180
+ const context = contextName(target.workspace, target.repo);
181
+ await execFileAsync('bkt', ['context', 'create', context, '--host', hostKey, '--workspace', target.workspace, '--repo', target.repo], { cwd: repoPath, encoding: 'utf-8' });
182
+ return { ...target, context };
183
+ }
184
+ function targetArgs(target) {
185
+ return ['--workspace', target.workspace, '--repo', target.repo];
186
+ }
187
+ async function fetchCi(repoPath, number, target) {
188
+ try {
189
+ const { stdout } = await execFileAsync('bkt', bktArgs(target, ['pr', 'checks', String(number), ...targetArgs(target), '--json']), bktOptions(repoPath));
190
+ const statuses = record(JSON.parse(stdout)).statuses;
191
+ const checks = (Array.isArray(statuses) ? statuses : []).map(mapCheck);
192
+ const rollup = checks.length === 0
193
+ ? null
194
+ : checks.some((check) => check.conclusion === 'FAILURE')
195
+ ? 'FAILURE'
196
+ : checks.some((check) => check.conclusion === 'CANCELLED')
197
+ ? 'CANCELLED'
198
+ : checks.some((check) => check.status !== 'COMPLETED')
199
+ ? 'PENDING'
200
+ : 'SUCCESS';
201
+ return { rollup, checks };
202
+ }
203
+ catch {
204
+ return { rollup: null, checks: [] };
205
+ }
206
+ }
207
+ export const bitbucketProvider = {
208
+ id: 'bitbucket-community',
209
+ capabilities: {
210
+ canCreatePr: true,
211
+ // bkt 0.30 does not expose a target-branch edit command.
212
+ canChangePrBase: false,
213
+ canMergeRequest: true,
214
+ canDeleteRemoteBranch: true,
215
+ requestTermShort: 'PR',
216
+ },
217
+ async isAvailable(repoPath) {
218
+ try {
219
+ const { stdout } = await execFileAsync('bkt', ['auth', 'status', '--json'], bktOptions(repoPath));
220
+ const status = record(JSON.parse(stdout));
221
+ const hosts = status.hosts;
222
+ if ((Array.isArray(hosts) && hosts.length > 0) || bktEnv().BKT_TOKEN)
223
+ return { available: true };
224
+ return { available: false, reason: 'not_authenticated' };
225
+ }
226
+ catch (err) {
227
+ return availabilityFromError(err);
228
+ }
229
+ },
230
+ async getPrStatus(repoPath, branchName) {
231
+ try {
232
+ const target = await resolveTarget(repoPath);
233
+ let match;
234
+ // The post-merge workflow asks for the final state before offering remote
235
+ // branch deletion, so do not stop at OPEN requests.
236
+ for (const state of ['OPEN', 'MERGED', 'DECLINED']) {
237
+ const { stdout: listOutput } = await execFileAsync('bkt', bktArgs(target, ['pr', 'list', '--state', state, '--limit', '50', ...targetArgs(target), '--json']), bktOptions(repoPath));
238
+ const pullRequests = record(JSON.parse(listOutput)).pull_requests;
239
+ match = (Array.isArray(pullRequests) ? pullRequests : [])
240
+ .map(record)
241
+ .find((pullRequest) => branch(pullRequest, 'source', 'fromRef') === branchName);
242
+ if (match)
243
+ break;
244
+ }
245
+ if (!match)
246
+ return null;
247
+ const { stdout: viewOutput } = await execFileAsync('bkt', bktArgs(target, ['pr', 'view', String(number(match.id)), ...targetArgs(target), '--json']), bktOptions(repoPath));
248
+ const raw = record(record(JSON.parse(viewOutput)).pull_request);
249
+ if (!number(raw.id))
250
+ return null;
251
+ const ci = await fetchCi(repoPath, number(raw.id), target);
252
+ return mapBktPr(raw, ci);
253
+ }
254
+ catch {
255
+ return null;
256
+ }
257
+ },
258
+ async createPr(repoPath, opts) {
259
+ const target = await resolveTarget(repoPath);
260
+ const { stdout } = await execFileAsync('bkt', bktArgs(target, [
261
+ 'pr',
262
+ 'create',
263
+ '--source',
264
+ opts.head,
265
+ '--target',
266
+ opts.base,
267
+ '--title',
268
+ opts.title,
269
+ '--description',
270
+ opts.body,
271
+ ...targetArgs(target),
272
+ '--json',
273
+ ]), bktOptions(repoPath));
274
+ const raw = record(JSON.parse(stdout));
275
+ const prNumber = number(raw.id);
276
+ const prUrl = string(raw.url);
277
+ if (!prNumber || !prUrl)
278
+ throw new Error('Could not parse PR URL from bkt output');
279
+ return { url: prUrl, number: prNumber };
280
+ },
281
+ async changePrBase() {
282
+ throw new Error('Changing a Bitbucket PR base branch is not supported by the installed bkt CLI');
283
+ },
284
+ async mergeRequest(repoPath, number) {
285
+ const target = await resolveTarget(repoPath);
286
+ await execFileAsync('bkt', bktArgs(target, ['pr', 'merge', String(number), '--close-source=false', ...targetArgs(target)]), bktOptions(repoPath));
287
+ },
288
+ async deleteRemoteBranch(repoPath, branchName) {
289
+ await execFileAsync('git', ['push', 'origin', '--delete', branchName], { cwd: repoPath, encoding: 'utf-8' });
290
+ },
291
+ };
@@ -1,10 +1,12 @@
1
1
  // src/server/services/forge/registry.ts
2
+ import { bitbucketProvider } from './bitbucket/provider.js';
2
3
  import { githubProvider } from './github/provider.js';
3
4
  import { gitlabProvider } from './gitlab/provider.js';
4
5
  import { noneProvider } from './none.js';
5
6
  const PROVIDERS = {
6
7
  github: githubProvider,
7
8
  gitlab: gitlabProvider,
9
+ 'bitbucket-community': bitbucketProvider,
8
10
  none: noneProvider,
9
11
  };
10
12
  /** Resolve a provider by id. Unknown ids fall back to the none provider. */
@@ -13,5 +15,5 @@ export function getForgeProvider(id) {
13
15
  }
14
16
  /** The selectable forge ids, in display order. */
15
17
  export function listForges() {
16
- return ['github', 'gitlab', 'none'];
18
+ return ['github', 'gitlab', 'bitbucket-community', 'none'];
17
19
  }
@@ -8,6 +8,8 @@ export function forgeFromRemoteUrl(url) {
8
8
  return 'github';
9
9
  if (lower.includes('gitlab'))
10
10
  return 'gitlab';
11
+ if (lower.includes('bitbucket'))
12
+ return 'bitbucket-community';
11
13
  return 'none';
12
14
  }
13
15
  /** Read the `origin` remote URL, or '' when there is no remote. */
@@ -28,7 +30,8 @@ function readRemoteUrl(projectPath) {
28
30
  */
29
31
  export function resolveForge(projectPath) {
30
32
  const setting = getProjectSettings(projectPath)?.forge ?? 'auto';
31
- if (setting === 'github' || setting === 'gitlab' || setting === 'none')
33
+ if (setting === 'github' || setting === 'gitlab' || setting === 'bitbucket-community' || setting === 'none') {
32
34
  return setting;
35
+ }
33
36
  return forgeFromRemoteUrl(readRemoteUrl(projectPath));
34
37
  }
@@ -18,7 +18,7 @@ export const DEFAULT_CHANGE_SOURCE_BRANCH_SCRIPT = `#!/usr/bin/env bash
18
18
  # KOBO_PROJECT_NAME project directory name (basename of KOBO_PROJECT_PATH)
19
19
  # KOBO_WORKSPACE_ID Kōbō workspace id (stable across renames)
20
20
  # KOBO_WORKSPACE_NAME workspace display name
21
- # KOBO_FORGE resolved forge: github / gitlab / none
21
+ # KOBO_FORGE resolved forge: github / gitlab / bitbucket-community / none
22
22
  # KOBO_PR_NUMBER PR/MR number on the resolved forge (empty if none open)
23
23
  set -euo pipefail
24
24
 
@@ -793,6 +793,16 @@ const settingsMigrations = [
793
793
  }
794
794
  },
795
795
  },
796
+ {
797
+ version: 50,
798
+ name: 'add-bitbucket-credentials',
799
+ migrate: ({ global }) => {
800
+ if (typeof global.bitbucketToken !== 'string')
801
+ global.bitbucketToken = '';
802
+ if (typeof global.bitbucketUsername !== 'string')
803
+ global.bitbucketUsername = '';
804
+ },
805
+ },
796
806
  ];
797
807
  /** Current settings schema version — always equals the highest migration version. */
798
808
  export const SETTINGS_SCHEMA_VERSION = settingsMigrations.length > 0 ? settingsMigrations[settingsMigrations.length - 1].version : 0;
@@ -843,6 +853,8 @@ function defaultSettings() {
843
853
  networkAccessEnabled: false,
844
854
  networkAccessToken: '',
845
855
  networkAccessBehindProxy: false,
856
+ bitbucketToken: '',
857
+ bitbucketUsername: '',
846
858
  browserNotifications: true,
847
859
  audioNotifications: true,
848
860
  audioQuestionNotifications: false,
@@ -1068,7 +1080,7 @@ export function getSettings() {
1068
1080
  return readSettings();
1069
1081
  }
1070
1082
  /** Keys stripped from exports — secrets that should stay on the machine. */
1071
- const SECRET_GLOBAL_KEYS = ['notionMcpKey', 'sentryMcpKey', 'networkAccessToken'];
1083
+ const SECRET_GLOBAL_KEYS = ['notionMcpKey', 'sentryMcpKey', 'networkAccessToken', 'bitbucketToken'];
1072
1084
  /** Build an export bundle with settings + templates. MCP keys are stripped. */
1073
1085
  export function exportConfigBundle(templates) {
1074
1086
  const settings = readSettings();
@@ -1282,6 +1294,8 @@ export function updateGlobalSettings(data) {
1282
1294
  'defaultPermissionModeByEngine',
1283
1295
  'notionMcpKey',
1284
1296
  'sentryMcpKey',
1297
+ 'bitbucketToken',
1298
+ 'bitbucketUsername',
1285
1299
  'notionEnabled',
1286
1300
  'sentryEnabled',
1287
1301
  'showThinkingBlocks',
@@ -209,6 +209,22 @@ export function updateWorkspaceReasoningEffort(id, reasoningEffort) {
209
209
  }
210
210
  return getWorkspace(id);
211
211
  }
212
+ /** Atomically change the engine configuration used by future workspace turns. */
213
+ export function updateWorkspaceEngineConfiguration(id, engine, model, reasoningEffort, agentPermissionMode) {
214
+ const db = getDb();
215
+ const now = new Date().toISOString();
216
+ const legacyMode = agentPermissionMode === 'plan' ? 'plan' : 'auto-accept';
217
+ const legacyProfile = agentPermissionMode === 'plan' ? 'bypass' : agentPermissionMode;
218
+ const result = db
219
+ .prepare(`UPDATE workspaces
220
+ SET engine = ?, model = ?, reasoning_effort = ?, agent_permission_mode = ?,
221
+ permission_mode = ?, permission_profile = ?, updated_at = ?
222
+ WHERE id = ?`)
223
+ .run(engine, model, reasoningEffort, agentPermissionMode, legacyMode, legacyProfile, now, id);
224
+ if (result.changes === 0)
225
+ throw new Error(`Workspace '${id}' not found`);
226
+ return getWorkspace(id);
227
+ }
212
228
  /**
213
229
  * Update the working branch for a workspace. Used both after ticket-ID
214
230
  * injection at creation time and after the rename-branch / resync-branch
@@ -649,6 +665,7 @@ function mapSession(row) {
649
665
  workspaceId: row.workspace_id,
650
666
  pid: row.pid,
651
667
  engineSessionId: row.engine_session_id,
668
+ engine: row.engine,
652
669
  status: row.status,
653
670
  model: row.model,
654
671
  startedAt: row.started_at,
@@ -695,16 +712,20 @@ export function getActiveSession(workspaceId) {
695
712
  /** Create an idle agent session (no Claude process yet) for a workspace. */
696
713
  export function createIdleSession(workspaceId) {
697
714
  const db = getDb();
715
+ const workspace = getWorkspace(workspaceId);
716
+ if (!workspace)
717
+ throw new Error(`Workspace '${workspaceId}' not found`);
698
718
  const id = nanoid();
699
719
  const now = new Date().toISOString();
700
- db.prepare('INSERT INTO agent_sessions (id, workspace_id, pid, status, started_at) VALUES (?, ?, NULL, ?, ?)').run(id, workspaceId, 'idle', now);
720
+ db.prepare('INSERT INTO agent_sessions (id, workspace_id, pid, engine, status, model, started_at) VALUES (?, ?, NULL, ?, ?, ?, ?)').run(id, workspaceId, workspace.engine, 'idle', workspace.model, now);
701
721
  return {
702
722
  id,
703
723
  workspaceId,
704
724
  pid: null,
705
725
  engineSessionId: null,
726
+ engine: workspace.engine,
706
727
  status: 'idle',
707
- model: null,
728
+ model: workspace.model,
708
729
  startedAt: now,
709
730
  endedAt: null,
710
731
  name: null,
@@ -721,3 +742,30 @@ export function renameSession(sessionId, workspaceId, name) {
721
742
  const row = db.prepare('SELECT * FROM agent_sessions WHERE id = ?').get(sessionId);
722
743
  return row ? mapSession(row) : null;
723
744
  }
745
+ /** Delete a stopped session and only the persisted conversation attached to it. */
746
+ export function deleteSession(sessionId, workspaceId) {
747
+ const db = getDb();
748
+ const session = db
749
+ .prepare('SELECT status, engine FROM agent_sessions WHERE id = ? AND workspace_id = ?')
750
+ .get(sessionId, workspaceId);
751
+ if (!session)
752
+ return false;
753
+ if (session.status === 'running') {
754
+ throw new Error(`Cannot delete active session '${sessionId}'`);
755
+ }
756
+ const transaction = db.transaction(() => {
757
+ db.prepare('DELETE FROM ws_events WHERE workspace_id = ? AND session_id = ?').run(workspaceId, sessionId);
758
+ db.prepare('DELETE FROM agent_sessions WHERE id = ? AND workspace_id = ?').run(sessionId, workspaceId);
759
+ const workspace = getWorkspace(workspaceId);
760
+ if (workspace?.engine === session.engine) {
761
+ const fallback = db
762
+ .prepare('SELECT engine, model FROM agent_sessions WHERE workspace_id = ? AND engine IS NOT NULL ORDER BY started_at DESC LIMIT 1')
763
+ .get(workspaceId);
764
+ if (fallback) {
765
+ db.prepare('UPDATE workspaces SET engine = ?, model = COALESCE(?, model), updated_at = ? WHERE id = ?').run(fallback.engine, fallback.model, new Date().toISOString(), workspaceId);
766
+ }
767
+ }
768
+ });
769
+ transaction();
770
+ return true;
771
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loicngr/kobo",
3
- "version": "1.11.2",
3
+ "version": "1.11.4",
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{$ as e,B as t,F as n,I as r,R as i,S as a,U as o,W as s,_ as c,at as l,b as u,c as d,ct as f,g as p,h as m,it as h,k as g,m as _,n as v,p as y,s as b,st as x,y as S}from"./vue.runtime.esm-bundler-JZnIeD9D.js";import{l as C,t as w}from"./QIcon-C6E-oEjI.js";import{G as T,K as E,d as D,k as O,p as k,v as A,y as j,z as ee}from"./index-CGnQPjwo.js";import{t as M}from"./QSpinnerDots-DIV68-cV.js";import{t as N}from"./QTooltip-CCNfQ3sa.js";import{a as te}from"./permissionModes-Crg3_WdF.js";import{r as P,t as ne}from"./pending-diff-open-CysqNMDL.js";import{t as F}from"./documents-DoEDCa8X.js";import{i as I,n as re,t as L}from"./render-chat-markdown-BbmsqP4r.js";import{t as R}from"./_plugin-vue_export-helper-BDNMzG2s.js";import{t as z}from"./ThinkingItem-kP-lU251.js";function ie(e,t,n=!0,r){let i=[],a=new Map,o=new Map;for(let n=0;n<e.length;n++){let s=e[n],c=t?.[n],l=r?.[n]??void 0;switch(s.kind){case`message:text`:{let e=a.get(s.messageId);if(e)e.text+=s.text,e.streaming=s.streaming,l&&e.eventIds?.push(l);else{let e={type:`text`,messageId:s.messageId,text:s.text,streaming:s.streaming,ts:c,eventIds:l?[l]:void 0};a.set(s.messageId,e),i.push(e)}break}case`message:end`:{let e=a.get(s.messageId);e&&(e.streaming=!1);break}case`message:thinking`:i.push({type:`thinking`,messageId:s.messageId,text:s.text,ts:c,eventIds:l?[l]:void 0});break;case`tool:call`:{let e={type:`tool`,toolCallId:s.toolCallId,name:s.name,input:s.input,ts:c,eventIds:l?[l]:void 0};o.set(s.toolCallId,e),i.push(e);break}case`tool:result`:{let e=o.get(s.toolCallId);e&&(e.result={output:s.output,isError:s.isError},l&&e.eventIds?.push(l));break}case`session:started`:i.push({type:`session`,kind:`started`,detail:{engineSessionId:s.engineSessionId,model:s.model},ts:c,eventIds:l?[l]:void 0});break;case`session:ended`:i.push({type:`session`,kind:`ended`,detail:{reason:s.reason,exitCode:s.exitCode},ts:c,eventIds:l?[l]:void 0});break;case`session:compacted`:i.push({type:`session`,kind:`compacted`,ts:c,eventIds:l?[l]:void 0});break;case`session:compacting`:case`session:brainstorm-complete`:case`session:user-input-requested`:case`mcp:status`:case`message:raw`:case`skills:discovered`:case`usage`:case`rate_limit`:case`subagent:progress`:case`error`:break;default:}}let s=null;for(let e of i)e.type===`text`&&e.streaming&&(s&&(s.streaming=!1),s=e);return s&&!n&&(s.streaming=!1),i}function ae(e,t){if(t.length===0)return e;let n=t.map(e=>({type:`user`,content:e.content,sender:e.sender,ts:e.ts,eventIds:e.eventIds})),r=[...e,...n];r.sort((e,t)=>{let n=e.ts??``,r=t.ts??``;return n===r?0:n?r?n<r?-1:1:-1:1});let i;for(let e of r)e.type===`user`&&e.sender!==`system-prompt`&&e.ts&&(!i||e.ts>i)&&(i=e.ts);if(i)for(let e of r)e.type===`text`&&e.streaming&&(!e.ts||e.ts<i)&&(e.streaming=!1);return r}var B=new Set([`setup`,`cleanup`,`archive`]);function V(e){switch(e.type){case`user`:return e.sender===`system-prompt`?`system-prompt`:B.has(e.sender)?`script`:`user`;case`session`:return`session`;default:return`agent`}}function oe(e){let t=[],n=null,r=null;for(let i of e){let e=V(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}var H={class:`text-caption text-grey-6`},se=a({__name:`SessionEventItem`,props:{item:{}},setup(e){let t=e,n=y(()=>{switch(t.item.kind){case`started`:return`session.started`;case`ended`:return`session.ended`;case`compacted`:return`session.compacted`;default:return`session.started`}});return(e,t)=>(i(),c(`span`,H,f(e.$t(n.value)),1))}});function ce(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){le(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 le(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=ue(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 ue(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 U=[`innerHTML`],de=R(a({__name:`TextMessageItem`,props:{item:{}},setup(e){let t=e,n=F(),r=D(),a=y(()=>{let e=r.selectedWorkspaceId;return e?n.documentsFor(e).map(e=>e.path):[]}),o=y(()=>re(ce(I.parse(t.item.text,{async:!1,breaks:!0,gfm:!0}),a.value),{addAttr:[`data-document-path`]}));function s(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)=>(i(),c(`div`,{class:`markdown-message`,onClick:s},[_(`div`,{innerHTML:o.value},null,8,U),e.item.streaming?(i(),m(C,{key:0,size:`xs`,class:`q-ml-xs`})):p(``,!0)]))}}),[[`__scopeId`,`data-v-1b7bd8ca`]]);function fe(e,t){let n=e.split(`
1
+ import{$ as e,B as t,F as n,I as r,R as i,S as a,U as o,W as s,_ as c,at as l,b as u,c as d,ct as f,g as p,h as m,it as h,k as g,m as _,n as v,p as y,s as b,st as x,y as S}from"./vue.runtime.esm-bundler-JZnIeD9D.js";import{l as C,t as w}from"./QIcon-C6E-oEjI.js";import{G as T,K as E,d as D,k as O,p as k,v as A,y as j,z as ee}from"./index-C0ylRsyw.js";import{t as M}from"./QSpinnerDots-DIV68-cV.js";import{t as N}from"./QTooltip-CUM1JiWx.js";import{a as te}from"./permissionModes-CNIg8DwA.js";import{r as P,t as ne}from"./pending-diff-open-BAyLIcQ6.js";import{t as F}from"./documents-CSi0kXxo.js";import{i as I,n as re,t as L}from"./render-chat-markdown-DahMoI2g.js";import{t as R}from"./_plugin-vue_export-helper-BDNMzG2s.js";import{t as z}from"./ThinkingItem-D9OZZN0H.js";function ie(e,t,n=!0,r){let i=[],a=new Map,o=new Map;for(let n=0;n<e.length;n++){let s=e[n],c=t?.[n],l=r?.[n]??void 0;switch(s.kind){case`message:text`:{let e=a.get(s.messageId);if(e)e.text+=s.text,e.streaming=s.streaming,l&&e.eventIds?.push(l);else{let e={type:`text`,messageId:s.messageId,text:s.text,streaming:s.streaming,ts:c,eventIds:l?[l]:void 0};a.set(s.messageId,e),i.push(e)}break}case`message:end`:{let e=a.get(s.messageId);e&&(e.streaming=!1);break}case`message:thinking`:i.push({type:`thinking`,messageId:s.messageId,text:s.text,ts:c,eventIds:l?[l]:void 0});break;case`tool:call`:{let e={type:`tool`,toolCallId:s.toolCallId,name:s.name,input:s.input,ts:c,eventIds:l?[l]:void 0};o.set(s.toolCallId,e),i.push(e);break}case`tool:result`:{let e=o.get(s.toolCallId);e&&(e.result={output:s.output,isError:s.isError},l&&e.eventIds?.push(l));break}case`session:started`:i.push({type:`session`,kind:`started`,detail:{engineSessionId:s.engineSessionId,model:s.model},ts:c,eventIds:l?[l]:void 0});break;case`session:ended`:i.push({type:`session`,kind:`ended`,detail:{reason:s.reason,exitCode:s.exitCode},ts:c,eventIds:l?[l]:void 0});break;case`session:compacted`:i.push({type:`session`,kind:`compacted`,ts:c,eventIds:l?[l]:void 0});break;case`session:compacting`:case`session:brainstorm-complete`:case`session:user-input-requested`:case`mcp:status`:case`message:raw`:case`skills:discovered`:case`usage`:case`rate_limit`:case`subagent:progress`:case`error`:break;default:}}let s=null;for(let e of i)e.type===`text`&&e.streaming&&(s&&(s.streaming=!1),s=e);return s&&!n&&(s.streaming=!1),i}function ae(e,t){if(t.length===0)return e;let n=t.map(e=>({type:`user`,content:e.content,sender:e.sender,ts:e.ts,eventIds:e.eventIds})),r=[...e,...n];r.sort((e,t)=>{let n=e.ts??``,r=t.ts??``;return n===r?0:n?r?n<r?-1:1:-1:1});let i;for(let e of r)e.type===`user`&&e.sender!==`system-prompt`&&e.ts&&(!i||e.ts>i)&&(i=e.ts);if(i)for(let e of r)e.type===`text`&&e.streaming&&(!e.ts||e.ts<i)&&(e.streaming=!1);return r}var B=new Set([`setup`,`cleanup`,`archive`]);function V(e){switch(e.type){case`user`:return e.sender===`system-prompt`?`system-prompt`:B.has(e.sender)?`script`:`user`;case`session`:return`session`;default:return`agent`}}function oe(e){let t=[],n=null,r=null;for(let i of e){let e=V(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}var H={class:`text-caption text-grey-6`},se=a({__name:`SessionEventItem`,props:{item:{}},setup(e){let t=e,n=y(()=>{switch(t.item.kind){case`started`:return`session.started`;case`ended`:return`session.ended`;case`compacted`:return`session.compacted`;default:return`session.started`}});return(e,t)=>(i(),c(`span`,H,f(e.$t(n.value)),1))}});function ce(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){le(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 le(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=ue(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 ue(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 U=[`innerHTML`],de=R(a({__name:`TextMessageItem`,props:{item:{}},setup(e){let t=e,n=F(),r=D(),a=y(()=>{let e=r.selectedWorkspaceId;return e?n.documentsFor(e).map(e=>e.path):[]}),o=y(()=>re(ce(I.parse(t.item.text,{async:!1,breaks:!0,gfm:!0}),a.value),{addAttr:[`data-document-path`]}));function s(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)=>(i(),c(`div`,{class:`markdown-message`,onClick:s},[_(`div`,{innerHTML:o.value},null,8,U),e.item.streaming?(i(),m(C,{key:0,size:`xs`,class:`q-ml-xs`})):p(``,!0)]))}}),[[`__scopeId`,`data-v-1b7bd8ca`]]);function fe(e,t){let n=e.split(`
2
2
  `),r=t.split(`
3
3
  `),i=n.length,a=r.length,o=Array.from({length:i+1},()=>Array(a+1).fill(0));for(let e=i-1;e>=0;e--)for(let t=a-1;t>=0;t--)n[e]===r[t]?o[e][t]=o[e+1][t+1]+1:o[e][t]=Math.max(o[e+1][t],o[e][t+1]);let s=[],c=0,l=0;for(;c<i&&l<a;)n[c]===r[l]?(s.push({type:`context`,content:n[c]}),c++,l++):o[c+1][l]>=o[c][l+1]?(s.push({type:`del`,content:n[c]}),c++):(s.push({type:`add`,content:r[l]}),l++);for(;c<i;)s.push({type:`del`,content:n[c++]});for(;l<a;)s.push({type:`add`,content:r[l++]});return s}function W(e){let t=e.split(`
4
4
  `),n=[];for(let e of t)e.startsWith(`@@`)||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 G(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=W(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{$ as e,B as t,F as n,R as r,S as i,W as a,_ as o,b as s,c,ct as l,g as u,h as d,it as f,m as p}from"./vue.runtime.esm-bundler-JZnIeD9D.js";import{St as m,bt as h,s as g,z as _}from"./index-CGnQPjwo.js";import{t as v}from"./render-chat-markdown-BbmsqP4r.js";import{t as y}from"./_plugin-vue_export-helper-BDNMzG2s.js";import{t as b}from"./QSpace-ezNrZkuj.js";import{t as x}from"./QChip-BT8qyt_p.js";import{t as S}from"./QPage-D3rjwA1l.js";var C={class:`row items-center q-mb-md`},w={class:`text-h6 q-ml-sm`},T={key:0,class:`text-grey-6 text-center q-pa-lg`},E={key:1,class:`text-negative text-center q-pa-lg`},D={key:2,class:`text-grey-6 text-center q-pa-lg`},O={key:3,class:`column q-gutter-md`},k={key:0,class:`text-caption text-grey-6`},A={class:`row items-center q-mb-sm`},j={class:`text-subtitle1 text-indigo-3`,style:{"font-family":`var(--kobo-font-mono, monospace)`}},M=[`innerHTML`],N=y(i({__name:`ChangelogPage`,setup(i){let y=g(),N=e([]),P=e(``),F=e(!1),I=e(null);function L(e){return v(e)}async function R(){F.value=!0,I.value=null;try{let e=await fetch(`/api/changelog`);if(!e.ok)throw Error(`HTTP ${e.status}`);let t=await e.json();P.value=t.currentVersion??``,N.value=t.versions??[]}catch(e){I.value=e instanceof Error?e.message:String(e)}finally{F.value=!1}}return n(R),(e,n)=>(r(),d(S,{class:`q-pa-md`,style:{"max-width":`900px`,margin:`0 auto`}},{default:a(()=>[p(`div`,C,[s(_,{flat:``,dense:``,round:``,icon:`arrow_back`,onClick:n[0]||=e=>f(y).back()}),p(`div`,w,l(e.$t(`changelog.title`)),1),s(b),s(_,{flat:``,dense:``,icon:`refresh`,loading:F.value,label:e.$t(`common.refresh`),onClick:R},null,8,[`loading`,`label`])]),F.value&&N.value.length===0?(r(),o(`div`,T,l(e.$t(`common.loading`)),1)):I.value?(r(),o(`div`,E,l(I.value),1)):N.value.length===0?(r(),o(`div`,D,l(e.$t(`changelog.empty`)),1)):(r(),o(`div`,O,[P.value?(r(),o(`div`,k,l(e.$t(`changelog.currentVersion`,{version:P.value})),1)):u(``,!0),(r(!0),o(c,null,t(N.value,t=>(r(),d(h,{key:t.version,dark:``,flat:``,bordered:``},{default:a(()=>[s(m,null,{default:a(()=>[p(`div`,A,[p(`div`,j,` v`+l(t.version),1),t.version===P.value?(r(),d(x,{key:0,dense:``,size:`sm`,color:`indigo-7`,"text-color":`grey-2`,label:e.$t(`changelog.current`),class:`q-ml-sm`},null,8,[`label`])):u(``,!0)]),p(`div`,{class:`changelog-notes`,innerHTML:L(t.notes)},null,8,M)]),_:2},1024)]),_:2},1024))),128))]))]),_:1}))}}),[[`__scopeId`,`data-v-ed73d661`]]);export{N as default};
1
+ import{$ as e,B as t,F as n,R as r,S as i,W as a,_ as o,b as s,c,ct as l,g as u,h as d,it as f,m as p}from"./vue.runtime.esm-bundler-JZnIeD9D.js";import{St as m,bt as h,s as g,z as _}from"./index-C0ylRsyw.js";import{t as v}from"./render-chat-markdown-DahMoI2g.js";import{t as y}from"./_plugin-vue_export-helper-BDNMzG2s.js";import{t as b}from"./QSpace-ezNrZkuj.js";import{t as x}from"./QChip-CLKcMuqJ.js";import{t as S}from"./QPage-D3rjwA1l.js";var C={class:`row items-center q-mb-md`},w={class:`text-h6 q-ml-sm`},T={key:0,class:`text-grey-6 text-center q-pa-lg`},E={key:1,class:`text-negative text-center q-pa-lg`},D={key:2,class:`text-grey-6 text-center q-pa-lg`},O={key:3,class:`column q-gutter-md`},k={key:0,class:`text-caption text-grey-6`},A={class:`row items-center q-mb-sm`},j={class:`text-subtitle1 text-indigo-3`,style:{"font-family":`var(--kobo-font-mono, monospace)`}},M=[`innerHTML`],N=y(i({__name:`ChangelogPage`,setup(i){let y=g(),N=e([]),P=e(``),F=e(!1),I=e(null);function L(e){return v(e)}async function R(){F.value=!0,I.value=null;try{let e=await fetch(`/api/changelog`);if(!e.ok)throw Error(`HTTP ${e.status}`);let t=await e.json();P.value=t.currentVersion??``,N.value=t.versions??[]}catch(e){I.value=e instanceof Error?e.message:String(e)}finally{F.value=!1}}return n(R),(e,n)=>(r(),d(S,{class:`q-pa-md`,style:{"max-width":`900px`,margin:`0 auto`}},{default:a(()=>[p(`div`,C,[s(_,{flat:``,dense:``,round:``,icon:`arrow_back`,onClick:n[0]||=e=>f(y).back()}),p(`div`,w,l(e.$t(`changelog.title`)),1),s(b),s(_,{flat:``,dense:``,icon:`refresh`,loading:F.value,label:e.$t(`common.refresh`),onClick:R},null,8,[`loading`,`label`])]),F.value&&N.value.length===0?(r(),o(`div`,T,l(e.$t(`common.loading`)),1)):I.value?(r(),o(`div`,E,l(I.value),1)):N.value.length===0?(r(),o(`div`,D,l(e.$t(`changelog.empty`)),1)):(r(),o(`div`,O,[P.value?(r(),o(`div`,k,l(e.$t(`changelog.currentVersion`,{version:P.value})),1)):u(``,!0),(r(!0),o(c,null,t(N.value,t=>(r(),d(h,{key:t.version,dark:``,flat:``,bordered:``},{default:a(()=>[s(m,null,{default:a(()=>[p(`div`,A,[p(`div`,j,` v`+l(t.version),1),t.version===P.value?(r(),d(x,{key:0,dense:``,size:`sm`,color:`indigo-7`,"text-color":`grey-2`,label:e.$t(`changelog.current`),class:`q-ml-sm`},null,8,[`label`])):u(``,!0)]),p(`div`,{class:`changelog-notes`,innerHTML:L(t.notes)},null,8,M)]),_:2},1024)]),_:2},1024))),128))]))]),_:1}))}}),[[`__scopeId`,`data-v-ed73d661`]]);export{N as default};
@@ -1 +1 @@
1
- import{P as e,b as t}from"./QIcon-C6E-oEjI.js";import{ct as n,lt as r}from"./index-CGnQPjwo.js";var i={left:!0,right:!0,up:!0,down:!0,horizontal:!0,vertical:!0},a=Object.keys(i);i.all=!0;function o(e){let t={};for(let n of a)e[n]&&(t[n]=!0);return Object.keys(t).length===0?i:(t.horizontal?t.left=t.right=!0:t.left&&t.right&&(t.horizontal=!0),t.vertical?t.up=t.down=!0:t.up&&t.down&&(t.vertical=!0),t.horizontal&&t.vertical&&(t.all=!0),t)}var s=[`INPUT`,`TEXTAREA`];function c(e,t){return t.event===void 0&&e.target!==void 0&&!e.target.draggable&&typeof t.handler==`function`&&!s.includes(e.target.nodeName.toUpperCase())&&(e.qClonedBy===void 0||!e.qClonedBy.includes(t.uid))}function l(e){return e===!1?0:e===!0||e===void 0?1:Number.parseInt(e,10)||0}var u=e({name:`close-popup`,beforeMount(e,{value:i}){let a={depth:l(i),handler(t){a.depth!==0&&setTimeout(()=>{let i=r(e);i!==void 0&&n(i,t,a.depth)},0)},handlerKey(e){t(e,13)&&a.handler(e)}};e.__qclosepopup=a,e.addEventListener(`click`,a.handler),e.addEventListener(`keyup`,a.handlerKey)},updated(e,{value:t,oldValue:n}){t!==n&&(e.__qclosepopup.depth=l(t))},beforeUnmount(e){let t=e.__qclosepopup;e.removeEventListener(`click`,t.handler),e.removeEventListener(`keyup`,t.handlerKey),delete e.__qclosepopup}});export{o as n,c as r,u as t};
1
+ import{P as e,b as t}from"./QIcon-C6E-oEjI.js";import{ct as n,lt as r}from"./index-C0ylRsyw.js";var i={left:!0,right:!0,up:!0,down:!0,horizontal:!0,vertical:!0},a=Object.keys(i);i.all=!0;function o(e){let t={};for(let n of a)e[n]&&(t[n]=!0);return Object.keys(t).length===0?i:(t.horizontal?t.left=t.right=!0:t.left&&t.right&&(t.horizontal=!0),t.vertical?t.up=t.down=!0:t.up&&t.down&&(t.vertical=!0),t.horizontal&&t.vertical&&(t.all=!0),t)}var s=[`INPUT`,`TEXTAREA`];function c(e,t){return t.event===void 0&&e.target!==void 0&&!e.target.draggable&&typeof t.handler==`function`&&!s.includes(e.target.nodeName.toUpperCase())&&(e.qClonedBy===void 0||!e.qClonedBy.includes(t.uid))}function l(e){return e===!1?0:e===!0||e===void 0?1:Number.parseInt(e,10)||0}var u=e({name:`close-popup`,beforeMount(e,{value:i}){let a={depth:l(i),handler(t){a.depth!==0&&setTimeout(()=>{let i=r(e);i!==void 0&&n(i,t,a.depth)},0)},handlerKey(e){t(e,13)&&a.handler(e)}};e.__qclosepopup=a,e.addEventListener(`click`,a.handler),e.addEventListener(`keyup`,a.handlerKey)},updated(e,{value:t,oldValue:n}){t!==n&&(e.__qclosepopup.depth=l(t))},beforeUnmount(e){let t=e.__qclosepopup;e.removeEventListener(`click`,t.handler),e.removeEventListener(`keyup`,t.handlerKey),delete e.__qclosepopup}});export{o as n,c as r,u as t};