@loicngr/kobo 1.11.2 → 1.11.3
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 +4 -0
- package/README.md +2 -2
- package/dist/server/services/agent/engines/claude-code/engine.js +1 -0
- package/dist/server/services/agent/engines/claude-code/options-builder.js +2 -0
- package/dist/server/services/agent/engines/codex/engine.js +1 -1
- package/dist/server/services/agent/orchestrator.js +35 -1
- package/dist/server/services/forge/bitbucket/provider.js +291 -0
- package/dist/server/services/forge/registry.js +3 -1
- package/dist/server/services/forge/resolve.js +4 -1
- package/dist/server/services/settings-defaults.js +1 -1
- package/dist/server/services/settings-service.js +15 -1
- package/package.json +1 -1
- package/src/client/dist/pwa/assets/{ActivityFeed-BNhy8oDx.js → ActivityFeed-DB_EucGW.js} +1 -1
- package/src/client/dist/pwa/assets/{ChangelogPage-eRWLuQLR.js → ChangelogPage-CNn9l8Ht.js} +1 -1
- package/src/client/dist/pwa/assets/{ClosePopup-Cc2YwliF.js → ClosePopup-BycCSMYN.js} +1 -1
- package/src/client/dist/pwa/assets/{CreatePage-B9Mxi7zD.js → CreatePage-BnEu_-oB.js} +1 -1
- package/src/client/dist/pwa/assets/{DiffViewer-DglPAzaZ.js → DiffViewer-DiV6QEdI.js} +3 -3
- package/src/client/dist/pwa/assets/{HealthPage-sS-N-_9T.js → HealthPage-ByBfOzgZ.js} +1 -1
- package/src/client/dist/pwa/assets/{MainLayout-BoZsUoGd.css → MainLayout-BY-xOjDK.css} +1 -1
- package/src/client/dist/pwa/assets/{MainLayout-BelFb6SW.js → MainLayout-DOuqjNgy.js} +2 -2
- package/src/client/dist/pwa/assets/{QBtnToggle-eC2_l1xJ.js → QBtnToggle-BClaEu9N.js} +1 -1
- package/src/client/dist/pwa/assets/{QChip-BT8qyt_p.js → QChip-4nbXNOAl.js} +1 -1
- package/src/client/dist/pwa/assets/{QSelect-Bj-wqU82.js → QSelect-BCz_9zhf.js} +1 -1
- package/src/client/dist/pwa/assets/{QTooltip-CCNfQ3sa.js → QTooltip-DSxTMMkV.js} +1 -1
- package/src/client/dist/pwa/assets/{SearchPage-6CSIDDcM.js → SearchPage-BRv1M8JZ.js} +1 -1
- package/src/client/dist/pwa/assets/SettingsPage-BqsWfOOK.js +16 -0
- package/src/client/dist/pwa/assets/{SettingsPage-CENUP7ua.css → SettingsPage-Os-6b-6H.css} +1 -1
- package/src/client/dist/pwa/assets/{ThinkingItem-kP-lU251.js → ThinkingItem-Bx_u0wMq.js} +1 -1
- package/src/client/dist/pwa/assets/{TouchPan-DVggdTB9.js → TouchPan-a3VbuxQ6.js} +1 -1
- package/src/client/dist/pwa/assets/{WorkspacePage-BWRpD_9V.js → WorkspacePage-Dsj0f4XV.js} +3 -3
- package/src/client/dist/pwa/assets/{build-path-tree-FNTYv-td.js → build-path-tree-BKKu-ryI.js} +1 -1
- package/src/client/dist/pwa/assets/{cssMode-DHVn7kI3.js → cssMode-BKAdI7ff.js} +1 -1
- package/src/client/dist/pwa/assets/{documents-DoEDCa8X.js → documents-Cc-OXiwm.js} +1 -1
- package/src/client/dist/pwa/assets/{editor.api2-DO15WOFV.js → editor.api2-BaCyAVGk.js} +1 -1
- package/src/client/dist/pwa/assets/{editor.main-Ce_TPxw1.js → editor.main-BbVQG_bB.js} +2 -2
- package/src/client/dist/pwa/assets/{expand-template-DnNNlpvQ.js → expand-template-CZOjiOqB.js} +1 -1
- package/src/client/dist/pwa/assets/{formatters-BBT0Ye5P.js → formatters-CCEkVVIc.js} +1 -1
- package/src/client/dist/pwa/assets/{freemarker2-B5mFQ9MW.js → freemarker2-Bh-TbpI2.js} +1 -1
- package/src/client/dist/pwa/assets/{handlebars-TGXWWwrU.js → handlebars-B8nDlL6P.js} +1 -1
- package/src/client/dist/pwa/assets/{html-BcoyXTgh.js → html-7xHGi5EV.js} +1 -1
- package/src/client/dist/pwa/assets/{htmlMode-Dhuwig6w.js → htmlMode-Bj3pQot4.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-DXWy90AZ.js +1 -0
- package/src/client/dist/pwa/assets/{index-CGnQPjwo.js → index-CH7ykfX_.js} +7 -7
- package/src/client/dist/pwa/assets/{javascript-v_uXBoLN.js → javascript-BtBc5IVR.js} +1 -1
- package/src/client/dist/pwa/assets/{jsonMode-CErvRpqG.js → jsonMode-0IOmldeM.js} +1 -1
- package/src/client/dist/pwa/assets/{kobo-commands-DXJ85QAa.js → kobo-commands-2L1djzbe.js} +1 -1
- package/src/client/dist/pwa/assets/{layout-Di93C8KP.js → layout-DgfOrzwf.js} +1 -1
- package/src/client/dist/pwa/assets/{liquid-ZappZiY7.js → liquid-DF94cTT4.js} +1 -1
- package/src/client/dist/pwa/assets/{lspLanguageFeatures-ur5VVJZY.js → lspLanguageFeatures-iHqiZBzm.js} +1 -1
- package/src/client/dist/pwa/assets/{mdx-BKL2jNxT.js → mdx-C-x1eJF5.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco.contribution-WfNiFCn_.js → monaco.contribution-DSfodOAp.js} +2 -2
- package/src/client/dist/pwa/assets/{network-auth-O2hz04zH.js → network-auth-DA6dNTa5.js} +1 -1
- package/src/client/dist/pwa/assets/{pending-diff-open-CysqNMDL.js → pending-diff-open-bDBsLjPx.js} +1 -1
- package/src/client/dist/pwa/assets/{permissionModes-Crg3_WdF.js → permissionModes-DQd7fbSy.js} +1 -1
- package/src/client/dist/pwa/assets/{project-color-DVafgHFe.js → project-color-CQtsHSqi.js} +1 -1
- package/src/client/dist/pwa/assets/{python-DXpnDgWC.js → python-fAiLpu-5.js} +1 -1
- package/src/client/dist/pwa/assets/{razor-2pTtVGVn.js → razor-Clyexxhi.js} +1 -1
- package/src/client/dist/pwa/assets/{render-chat-markdown-BbmsqP4r.js → render-chat-markdown-MzPqUxrD.js} +1 -1
- package/src/client/dist/pwa/assets/{tsMode-t5L5YTTB.js → tsMode-aOX259X0.js} +1 -1
- package/src/client/dist/pwa/assets/{typescript-C2FUrxc2.js → typescript-BiPmygzZ.js} +1 -1
- package/src/client/dist/pwa/assets/{use-onboarding-CxQRTetV.js → use-onboarding-NfnxjTzg.js} +1 -1
- package/src/client/dist/pwa/assets/{use-quasar-CUBX9V4F.js → use-quasar-CyeldpIw.js} +1 -1
- package/src/client/dist/pwa/assets/{workers-ri7So4GA.js → workers-BcOflh4r.js} +1 -1
- package/src/client/dist/pwa/assets/{xml-CvWFj3be.js → xml-6qooibMC.js} +1 -1
- package/src/client/dist/pwa/assets/{yaml-DsSH3Hc4.js → yaml-DzDdyl50.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/SettingsPage-jGIZfB7N.js +0 -16
- package/src/client/dist/pwa/assets/i18n-BHXcyfLT.js +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,10 @@ All notable changes to Kōbō are documented here. The format is based on
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/). Each release is an `## <version>`
|
|
5
5
|
section — the in-app "What's new" dialog reads this file.
|
|
6
6
|
|
|
7
|
+
## 1.11.3
|
|
8
|
+
|
|
9
|
+
- feat(forge): add bitbucket community support
|
|
10
|
+
|
|
7
11
|
## 1.11.2
|
|
8
12
|
|
|
9
13
|
- feat(integrations): let Notion and Sentry be enabled independently, keep
|
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ Kōbō runs multiple coding agents in parallel, each isolated in its own git wor
|
|
|
21
21
|
- **Full MCP toolset (`kobo-tasks`)**: a per-workspace MCP server the agent uses for far more than tasks — task/acceptance-criteria CRUD, starting/stopping the dev server and reading its logs, a unified `get_ticket` (Notion or Sentry), searching past conversations across every workspace, per-session token/cost usage, and a `.ai/thoughts` decision log. Native Claude Code Task tools complement it for lightweight sub-agent coordination. See [`AGENTS.md`](./AGENTS.md) for the full tool list.
|
|
22
22
|
|
|
23
23
|

|
|
24
|
-
- **Git panel**: a Monaco-based diff viewer with **inline file editing** (edit the right-hand panel directly, save with `Ctrl/Cmd+S`, conflict-guarded via sha precondition), inline conflict resolution, and `Sync` / `Push` / `Commit` / `Open PR` / `Merge ready PR` / `Change PR base` / `Change source branch`. You can write the commit message yourself or ask the active engine to prepare the commit; PR/MR merging always requires a confirmation, then Kōbō can offer to delete the merged remote branch. Edit/Write cards in chat open their exact changed file in the diff viewer. Multi-forge: GitHub (`gh`), GitLab (`glab`), or no forge, auto-detected from the remote and overridable per project.
|
|
24
|
+
- **Git panel**: a Monaco-based diff viewer with **inline file editing** (edit the right-hand panel directly, save with `Ctrl/Cmd+S`, conflict-guarded via sha precondition), inline conflict resolution, and `Sync` / `Push` / `Commit` / `Open PR` / `Merge ready PR` / `Change PR base` / `Change source branch`. You can write the commit message yourself or ask the active engine to prepare the commit; PR/MR merging always requires a confirmation, then Kōbō can offer to delete the merged remote branch. Edit/Write cards in chat open their exact changed file in the diff viewer. Multi-forge: GitHub (`gh`), GitLab (`glab`), Bitbucket Community (`bkt`), or no forge, auto-detected from the remote and overridable per project.
|
|
25
25
|
|
|
26
26
|

|
|
27
27
|
- **Dev server panel**: start, stop, and tail logs for a workspace's dev server (Docker or npm) straight from the Tools panel — no need to leave the UI. When a project has no configured server, the panel and MCP tool report that explicitly instead of implying one is running.
|
|
@@ -84,7 +84,7 @@ The most common knobs:
|
|
|
84
84
|
| `ANTHROPIC_API_KEY` | none | Claude Code engine credential (alternative to `claude /login`) |
|
|
85
85
|
| `OPENAI_API_KEY` | none | Codex engine credential (alternative to `codex login`) |
|
|
86
86
|
|
|
87
|
-
Global and per-project settings (worktree path, dev server commands, E2E framework, prompt templates, git conventions, branch prefixes, lifecycle scripts, task prompt) are edited in **Settings** at runtime. Notion and Sentry are enabled independently in their respective tabs; disabling one keeps its configuration and existing workspace links, but removes its import and automation UI. In **Settings → General → Activity feed**, you can hide agent reasoning panels. Per-project values inherit from the global ones when left empty.
|
|
87
|
+
Global and per-project settings (worktree path, dev server commands, E2E framework, prompt templates, git conventions, branch prefixes, lifecycle scripts, task prompt) are edited in **Settings** at runtime. Notion and Sentry are enabled independently in their respective tabs; disabling one keeps its configuration and existing workspace links, but removes its import and automation UI. **Settings → Forge** holds local Bitbucket Community credentials for `bkt`; Kōbō uses them for PR actions and provides them only to agents working in Bitbucket projects. In **Settings → General → Activity feed**, you can hide agent reasoning panels. Per-project values inherit from the global ones when left empty.
|
|
88
88
|
|
|
89
89
|
The full reference (every env var, every setting key, MCP server registration, Notion / Sentry / Voice setup) is in [`CONFIGURATION.md`](./CONFIGURATION.md).
|
|
90
90
|
|
|
@@ -199,6 +199,7 @@ export function createClaudeCodeEngine() {
|
|
|
199
199
|
console.warn(`[claude-engine stderr] ${data}`);
|
|
200
200
|
}
|
|
201
201
|
},
|
|
202
|
+
env: options.env,
|
|
202
203
|
});
|
|
203
204
|
sdkOptions.abortController = abortController;
|
|
204
205
|
// Override the SDK's libc-blind binary resolution on Linux glibc — see
|
|
@@ -60,7 +60,7 @@ export function createCodexEngine() {
|
|
|
60
60
|
};
|
|
61
61
|
const streamingBatcher = createStreamingBatcher(emitDirect);
|
|
62
62
|
const safeEmit = (ev) => streamingBatcher.push(ev);
|
|
63
|
-
const child = spawnAppServer({ cwd: options.workingDir, signal: abortController.signal });
|
|
63
|
+
const child = spawnAppServer({ cwd: options.workingDir, env: options.env, signal: abortController.signal });
|
|
64
64
|
if (child.stderr) {
|
|
65
65
|
child.stderr.setEncoding('utf8');
|
|
66
66
|
child.stderr.on('data', (chunk) => {
|
|
@@ -6,6 +6,7 @@ import { unregisterProcess } from '../../utils/process-tracker.js';
|
|
|
6
6
|
import * as autoLoopService from '../auto-loop-service.js';
|
|
7
7
|
import * as cleanupScriptService from '../cleanup-script-service.js';
|
|
8
8
|
import * as cronService from '../cron-service.js';
|
|
9
|
+
import { resolveForge } from '../forge/resolve.js';
|
|
9
10
|
import * as quotaBackoffService from '../quota-backoff-service.js';
|
|
10
11
|
import { getEffectiveSettings, getGlobalSettings } from '../settings-service.js';
|
|
11
12
|
import { refreshNow } from '../usage/poller.js';
|
|
@@ -342,6 +343,38 @@ function buildMcpServers(workspaceId) {
|
|
|
342
343
|
},
|
|
343
344
|
];
|
|
344
345
|
}
|
|
346
|
+
function isBitbucketProject(projectPath) {
|
|
347
|
+
try {
|
|
348
|
+
return resolveForge(projectPath) === 'bitbucket-community';
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
function buildAgentEnv(projectPath) {
|
|
355
|
+
if (!isBitbucketProject(projectPath))
|
|
356
|
+
return undefined;
|
|
357
|
+
const global = getGlobalSettings();
|
|
358
|
+
if (!global.bitbucketToken)
|
|
359
|
+
return undefined;
|
|
360
|
+
return {
|
|
361
|
+
...process.env,
|
|
362
|
+
BKT_HOST: 'https://bitbucket.org',
|
|
363
|
+
BKT_TOKEN: global.bitbucketToken,
|
|
364
|
+
BKT_USERNAME: global.bitbucketUsername,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
function buildAgentPrompt(prompt, projectPath) {
|
|
368
|
+
if (!projectPath || !isBitbucketProject(projectPath))
|
|
369
|
+
return prompt;
|
|
370
|
+
return [
|
|
371
|
+
'[Kōbō Bitbucket] Bitbucket credentials are available through the `bkt` CLI in this session.',
|
|
372
|
+
'Use `bkt pr edit <number> --body <description>` and `bkt pr comment <number> --body <comment>` for PR updates.',
|
|
373
|
+
'Do not call the Bitbucket REST API with curl or reuse BKT_TOKEN as a Bearer token: Bitbucket Cloud API tokens require the bkt CLI authentication flow.',
|
|
374
|
+
'',
|
|
375
|
+
prompt,
|
|
376
|
+
].join('\n');
|
|
377
|
+
}
|
|
345
378
|
function resolveSessionForResume(workspaceId, existingSessionId, model) {
|
|
346
379
|
const db = getDb();
|
|
347
380
|
let lastSession;
|
|
@@ -761,7 +794,7 @@ export function startAgent(workspaceId, workingDir, prompt, model, resume = fals
|
|
|
761
794
|
const options = {
|
|
762
795
|
workspaceId,
|
|
763
796
|
workingDir,
|
|
764
|
-
prompt,
|
|
797
|
+
prompt: buildAgentPrompt(prompt, ws?.projectPath),
|
|
765
798
|
model,
|
|
766
799
|
effort: reasoningEffort,
|
|
767
800
|
// Cascade: explicit caller override → workspace setting → 'bypass'.
|
|
@@ -778,6 +811,7 @@ export function startAgent(workspaceId, workingDir, prompt, model, resume = fals
|
|
|
778
811
|
})(),
|
|
779
812
|
settings,
|
|
780
813
|
mcpServers: buildMcpServers(workspaceId),
|
|
814
|
+
env: ws ? buildAgentEnv(ws.projectPath) : undefined,
|
|
781
815
|
};
|
|
782
816
|
const controller = new SessionController(workspaceId, agentSessionId, engine, (ev) => handleEvent(workspaceId, agentSessionId, ev));
|
|
783
817
|
controllers.set(workspaceId, controller);
|
|
@@ -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',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loicngr/kobo",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.3",
|
|
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-
|
|
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-CH7ykfX_.js";import{t as M}from"./QSpinnerDots-DIV68-cV.js";import{t as N}from"./QTooltip-DSxTMMkV.js";import{a as te}from"./permissionModes-DQd7fbSy.js";import{r as P,t as ne}from"./pending-diff-open-bDBsLjPx.js";import{t as F}from"./documents-Cc-OXiwm.js";import{i as I,n as re,t as L}from"./render-chat-markdown-MzPqUxrD.js";import{t as R}from"./_plugin-vue_export-helper-BDNMzG2s.js";import{t as z}from"./ThinkingItem-Bx_u0wMq.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-
|
|
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-CH7ykfX_.js";import{t as v}from"./render-chat-markdown-MzPqUxrD.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-4nbXNOAl.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-
|
|
1
|
+
import{P as e,b as t}from"./QIcon-C6E-oEjI.js";import{ct as n,lt as r}from"./index-CH7ykfX_.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};
|