@loicngr/kobo 1.11.33 → 1.11.35
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/AGENTS.md +10 -0
- package/CHANGELOG.md +8 -0
- package/dist/server/db/migrations.js +14 -0
- package/dist/server/db/schema.js +9 -0
- package/dist/server/index.js +32 -15
- package/dist/server/routes/activity.js +17 -0
- package/dist/server/routes/changelog.js +2 -23
- package/dist/server/routes/workspaces.js +37 -0
- package/dist/server/services/activity-service.js +62 -0
- package/dist/server/services/pr-watcher-service.js +92 -34
- package/dist/server/services/websocket-service.js +3 -0
- package/dist/server/services/worktree-purge-service.js +25 -1
- package/dist/server/services/worktree-restore-service.js +79 -0
- package/dist/server/services/worktree-service.js +151 -3
- package/dist/server/utils/changelog.js +23 -0
- package/dist/server/utils/startup-display.js +71 -0
- package/dist/server/utils/workspace-lifecycle-guard.js +22 -0
- package/package.json +1 -1
- package/src/client/dist/pwa/assets/ActivityFeed-YRbIRutK.js +8 -0
- package/src/client/dist/pwa/assets/ChangelogPage-DVLWWvvz.js +1 -0
- package/src/client/dist/pwa/assets/ClosePopup-BqW5dy-O.js +1 -0
- package/src/client/dist/pwa/assets/ComparisonPanel-LZrvk6a6.js +2 -0
- package/src/client/dist/pwa/assets/CreatePage-gFrn4xl9.js +2 -0
- package/src/client/dist/pwa/assets/DashboardPage-0odIhseb.js +1 -0
- package/src/client/dist/pwa/assets/DiffViewer-NWB2uQkz.js +8 -0
- package/src/client/dist/pwa/assets/DocumentsPanel-3yApHgbl.js +1 -0
- package/src/client/dist/pwa/assets/DrawerToggleButton-C_bNtZsE.js +1 -0
- package/src/client/dist/pwa/assets/{GitPanel-De5_E4NA.css → GitPanel-BFCnyhE2.css} +1 -1
- package/src/client/dist/pwa/assets/GitPanel-BMjWwiAX.js +3 -0
- package/src/client/dist/pwa/assets/HealthPage-Vyr1YG2o.js +1 -0
- package/src/client/dist/pwa/assets/MainLayout-DxMVVA6f.css +1 -0
- package/src/client/dist/pwa/assets/MainLayout-s8_9FPE2.js +2 -0
- package/src/client/dist/pwa/assets/{QBadge-CWEaKfKi.js → QBadge-DbkxGtht.js} +1 -1
- package/src/client/dist/pwa/assets/{QBanner-DRiFK0GP.js → QBanner-DhqH95GW.js} +1 -1
- package/src/client/dist/pwa/assets/QBtn-CZaBclNw.js +1 -0
- package/src/client/dist/pwa/assets/QBtnDropdown-BxuXEBp5.js +1 -0
- package/src/client/dist/pwa/assets/{QBtnGroup-BY6XqeTJ.js → QBtnGroup-DELKYo3u.js} +1 -1
- package/src/client/dist/pwa/assets/{QBtnToggle-CCgwqSgg.js → QBtnToggle-BI3k3Ch2.js} +1 -1
- package/src/client/dist/pwa/assets/{QChip-CJ6WmvGE.js → QChip-owciRuZu.js} +1 -1
- package/src/client/dist/pwa/assets/QDrawer-BdlDiU6a.js +1 -0
- package/src/client/dist/pwa/assets/QItemSection-D_5_0UrX.js +1 -0
- package/src/client/dist/pwa/assets/QList-qupzuEqO.js +1 -0
- package/src/client/dist/pwa/assets/{QMarkupTable-MxPL53nz.js → QMarkupTable-B9QuKtK_.js} +1 -1
- package/src/client/dist/pwa/assets/QPage-DaX9sMQC.js +1 -0
- package/src/client/dist/pwa/assets/QSelect-BrSemS3o.js +1 -0
- package/src/client/dist/pwa/assets/{QSpinnerDots-6v3Yl0T6.js → QSpinnerDots-DnCV3VzI.js} +1 -1
- package/src/client/dist/pwa/assets/QTooltip-CLCFHW2D.js +1 -0
- package/src/client/dist/pwa/assets/SchedulePanel-F9bdAq6K.js +1 -0
- package/src/client/dist/pwa/assets/SearchPage-CBCefYqc.js +1 -0
- package/src/client/dist/pwa/assets/SettingsPage-BPgTHmKj.js +9 -0
- package/src/client/dist/pwa/assets/{SettingsPage-CDA54yPx.css → SettingsPage-Do9d0Pov.css} +1 -1
- package/src/client/dist/pwa/assets/SplitWorkspacePage-DDs2nuoT.js +1 -0
- package/src/client/dist/pwa/assets/SplitWorkspacePage-lWQO8xro.css +1 -0
- package/src/client/dist/pwa/assets/{TerminalPanel-jufNVqXc.js → TerminalPanel-CulE0wTU.js} +9 -9
- package/src/client/dist/pwa/assets/TouchPan-DUgqs2cW.js +1 -0
- package/src/client/dist/pwa/assets/TourReplayButton-CdHpGBdr.js +1 -0
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-W5SvqzXO.js +1 -0
- package/src/client/dist/pwa/assets/WorkspacePage-B6O7j57u.js +4 -0
- package/src/client/dist/pwa/assets/{WorkspacePage-D3-AEaPN.css → WorkspacePage-DYQrfeYw.css} +1 -1
- package/src/client/dist/pwa/assets/{browser-DUmYgiEg.js → browser-CnuqDTyc.js} +1 -1
- package/src/client/dist/pwa/assets/build-path-tree-JodIO7-I.js +1 -0
- package/src/client/dist/pwa/assets/de-Dt_m-H83.js +17 -0
- package/src/client/dist/pwa/assets/{documents-39BXaRLp.js → documents-3ZBK1pXE.js} +1 -1
- package/src/client/dist/pwa/assets/{editor.api-Do1AYiz7.js → editor.api-DZ9WO_J7.js} +1 -1
- package/src/client/dist/pwa/assets/{efforts-DjlRTQQN.js → efforts-LrtJPx-j.js} +2 -2
- package/src/client/dist/pwa/assets/engineFeatures-DmnHj_uD.js +1 -0
- package/src/client/dist/pwa/assets/es-DFg25DCS.js +17 -0
- package/src/client/dist/pwa/assets/{expand-template-17KFFCsY.js → expand-template-Dv4JC2zU.js} +1 -1
- package/src/client/dist/pwa/assets/{format-DdlEFKmU.js → format-CHjfjuhr.js} +7 -7
- package/src/client/dist/pwa/assets/{formatters-Bq2A89Eg.js → formatters-BsgH8CoH.js} +1 -1
- package/src/client/dist/pwa/assets/fr-CQ1slpol.js +17 -0
- package/src/client/dist/pwa/assets/{freemarker2-Br0g81VI.js → freemarker2-Dla55SIR.js} +1 -1
- package/src/client/dist/pwa/assets/{handlebars-FbaXsXfL.js → handlebars-DtuuZy7D.js} +1 -1
- package/src/client/dist/pwa/assets/{html-BSoQjC6U.js → html-CKYOCR8W.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-DTfKQUMf.js +1 -0
- package/src/client/dist/pwa/assets/index-CIP08MpU.js +20 -0
- package/src/client/dist/pwa/assets/it-w09rfaP7.js +17 -0
- package/src/client/dist/pwa/assets/{javascript-DyaXAXwe.js → javascript-BxzTnsIQ.js} +1 -1
- package/src/client/dist/pwa/assets/{liquid-CM0jAcRe.js → liquid-C8BfPFUy.js} +1 -1
- package/src/client/dist/pwa/assets/{mdx-C8TCxhMn.js → mdx-DZm_YXkC.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco-contributions-DWI2C6KU.js → monaco-contributions-HDbRHiNR.js} +1 -1
- package/src/client/dist/pwa/assets/monaco.contribution-DRW-851R.js +2 -0
- package/src/client/dist/pwa/assets/network-auth-Ce9hsryo.js +1 -0
- package/src/client/dist/pwa/assets/notify-theme-OBpMEBvP.js +1 -0
- package/src/client/dist/pwa/assets/pending-diff-open-CVQxWyzP.js +1 -0
- package/src/client/dist/pwa/assets/permissionModes-CVFHS2ec.js +36 -0
- package/src/client/dist/pwa/assets/{pinia-jcytgarH.js → pinia-BKqmoVru.js} +1 -1
- package/src/client/dist/pwa/assets/{private.use-form-DVqJLg-9.js → private.use-form-C7umbGO-.js} +1 -1
- package/src/client/dist/pwa/assets/{project-color-DujVR8Fa.js → project-color-mcpQp2o6.js} +1 -1
- package/src/client/dist/pwa/assets/{python-7qqqHkG7.js → python-CO5iQN74.js} +1 -1
- package/src/client/dist/pwa/assets/{razor-o3t0P5Qt.js → razor-BC7D8LMf.js} +1 -1
- package/src/client/dist/pwa/assets/{render-chat-markdown-BHUn1rwa.js → render-chat-markdown-Cb15kvox.js} +1 -1
- package/src/client/dist/pwa/assets/runtime-core.esm-bundler-CDnVUfeZ.js +1 -0
- package/src/client/dist/pwa/assets/{typescript-gEevI6ZM.js → typescript-BCHCSNpD.js} +1 -1
- package/src/client/dist/pwa/assets/{use-checkbox-DDRAnhSJ.js → use-checkbox-FnauAgTp.js} +1 -1
- package/src/client/dist/pwa/assets/use-id-BIuaWKkB.js +1 -0
- package/src/client/dist/pwa/assets/use-is-mobile-CWjqe8gC.js +1 -0
- package/src/client/dist/pwa/assets/use-tours-DhLhglwr.js +2 -0
- package/src/client/dist/pwa/assets/workspace-page-layout-Dw9NhZsL.js +1 -0
- package/src/client/dist/pwa/assets/{workspace-templates-BH4xqYR_.js → workspace-templates-CoWrOEac.js} +1 -1
- package/src/client/dist/pwa/assets/{xml-xV5YWh8z.js → xml-y9M3NAIZ.js} +1 -1
- package/src/client/dist/pwa/assets/{yaml-DNkjt0UK.js → yaml-CthlToPo.js} +1 -1
- package/src/client/dist/pwa/index.html +7 -6
- package/src/client/dist/pwa/sw.js +1 -1
- package/src/client/dist/pwa/assets/ActivityFeed--4irDPfF.js +0 -8
- package/src/client/dist/pwa/assets/ChangelogPage-BT_iOfWn.js +0 -1
- package/src/client/dist/pwa/assets/ClosePopup-_NdLt5i2.js +0 -1
- package/src/client/dist/pwa/assets/ComparisonPanel-Kij_awqg.js +0 -2
- package/src/client/dist/pwa/assets/CreatePage-KdrJL7Me.js +0 -2
- package/src/client/dist/pwa/assets/DashboardPage-CPAd3eCD.js +0 -1
- package/src/client/dist/pwa/assets/DiffViewer-CYpgLe75.js +0 -8
- package/src/client/dist/pwa/assets/DocumentsPanel-0KQrXuXF.js +0 -1
- package/src/client/dist/pwa/assets/DrawerToggleButton-CcH-cA9p.js +0 -1
- package/src/client/dist/pwa/assets/GitPanel-BD05_3k6.js +0 -3
- package/src/client/dist/pwa/assets/HealthPage-Dr-h990u.js +0 -1
- package/src/client/dist/pwa/assets/MainLayout-BUTz2ks4.css +0 -1
- package/src/client/dist/pwa/assets/MainLayout-BmhjVDiz.js +0 -2
- package/src/client/dist/pwa/assets/QBtn-DqZryKOh.js +0 -1
- package/src/client/dist/pwa/assets/QBtnDropdown-Ch7WOARF.js +0 -1
- package/src/client/dist/pwa/assets/QDrawer-C5PVKIEd.js +0 -1
- package/src/client/dist/pwa/assets/QItemSection-CjrpcdN_.js +0 -1
- package/src/client/dist/pwa/assets/SchedulePanel-D1L1upGW.js +0 -1
- package/src/client/dist/pwa/assets/SearchPage-CXGTP0of.js +0 -1
- package/src/client/dist/pwa/assets/SettingsPage-D7V7uN5K.js +0 -9
- package/src/client/dist/pwa/assets/TouchPan-Ci_dyxVk.js +0 -1
- package/src/client/dist/pwa/assets/TourReplayButton-CZqPH9hM.js +0 -1
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-BEC7IA-V.js +0 -1
- package/src/client/dist/pwa/assets/WorkspacePage-D-TanDoR.js +0 -4
- package/src/client/dist/pwa/assets/build-path-tree-DyyHUwzz.js +0 -1
- package/src/client/dist/pwa/assets/de-BUjatW-C.js +0 -17
- package/src/client/dist/pwa/assets/engineFeatures-C78AGiF7.js +0 -1
- package/src/client/dist/pwa/assets/es-DRiVweiR.js +0 -17
- package/src/client/dist/pwa/assets/fr-lXvcEWhJ.js +0 -17
- package/src/client/dist/pwa/assets/i18n-663QQ_uF.js +0 -1
- package/src/client/dist/pwa/assets/index-BDTPwynK.js +0 -20
- package/src/client/dist/pwa/assets/it-DBTMyadC.js +0 -17
- package/src/client/dist/pwa/assets/monaco.contribution-DS4KiLpR.js +0 -2
- package/src/client/dist/pwa/assets/network-auth-WHMGZHjT.js +0 -1
- package/src/client/dist/pwa/assets/notify-theme--s1RcQmT.js +0 -1
- package/src/client/dist/pwa/assets/pending-diff-open-DWEfW98v.js +0 -1
- package/src/client/dist/pwa/assets/permissionModes-B5Kgd8T6.js +0 -36
- package/src/client/dist/pwa/assets/runtime-core.esm-bundler-CS0xkoG_.js +0 -1
- package/src/client/dist/pwa/assets/use-is-mobile-ClPMtqZc.js +0 -1
- package/src/client/dist/pwa/assets/use-tours-Co_sci4c.js +0 -2
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
1
2
|
import fs from 'node:fs';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
2
4
|
import { logError, logWarn } from '../utils/logger.js';
|
|
5
|
+
import { withWorkspaceLifecycleGuard } from '../utils/workspace-lifecycle-guard.js';
|
|
3
6
|
import * as agentManager from './agent/orchestrator.js';
|
|
4
7
|
import * as devServerService from './dev-server-service.js';
|
|
5
8
|
import { getForgeProvider } from './forge/registry.js';
|
|
@@ -8,10 +11,17 @@ import { destroyTerminal } from './terminal-service.js';
|
|
|
8
11
|
import { emitEphemeral } from './websocket-service.js';
|
|
9
12
|
import { archiveWorkspace, getWorkspace, markWorktreePurged, } from './workspace-service.js';
|
|
10
13
|
import { isPermissionError, removeWorktree } from './worktree-service.js';
|
|
11
|
-
|
|
14
|
+
const execFileAsync = promisify(execFile);
|
|
15
|
+
export async function purgeWorktree(workspaceId, expectedArchivedAt) {
|
|
16
|
+
return withWorkspaceLifecycleGuard(workspaceId, () => purgeWorktreeGuarded(workspaceId, expectedArchivedAt));
|
|
17
|
+
}
|
|
18
|
+
async function purgeWorktreeGuarded(workspaceId, expectedArchivedAt) {
|
|
12
19
|
const workspace = getWorkspace(workspaceId);
|
|
13
20
|
if (!workspace)
|
|
14
21
|
return { outcome: 'not-found', warnings: [] };
|
|
22
|
+
if (expectedArchivedAt !== undefined && workspace.archivedAt !== expectedArchivedAt) {
|
|
23
|
+
return { outcome: 'cancelled', warnings: [] };
|
|
24
|
+
}
|
|
15
25
|
if (workspace.worktreePurgedAt)
|
|
16
26
|
return { outcome: 'already-purged', warnings: [] };
|
|
17
27
|
if (!workspace.worktreeOwned)
|
|
@@ -98,6 +108,19 @@ function buildRemovalFailureMessage(worktreePath, projectPath, errMsg) {
|
|
|
98
108
|
}
|
|
99
109
|
async function captureRestoreData(workspace) {
|
|
100
110
|
const forge = resolveForge(workspace.projectPath);
|
|
111
|
+
let headCommitSha = null;
|
|
112
|
+
try {
|
|
113
|
+
const { stdout } = await execFileAsync('git', ['rev-parse', '--verify', 'HEAD'], {
|
|
114
|
+
cwd: workspace.worktreePath,
|
|
115
|
+
encoding: 'utf8',
|
|
116
|
+
timeout: 30_000,
|
|
117
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
118
|
+
});
|
|
119
|
+
headCommitSha = stdout.trim() || null;
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// A missing or broken checkout must not prevent a best-effort purge.
|
|
123
|
+
}
|
|
101
124
|
let prNumber = null;
|
|
102
125
|
let prUrl = null;
|
|
103
126
|
// Direct provider call (not the pr-watcher cache) avoids an import cycle
|
|
@@ -123,6 +146,7 @@ async function captureRestoreData(workspace) {
|
|
|
123
146
|
prUrl,
|
|
124
147
|
forge: forge,
|
|
125
148
|
mergeCommitSha: null,
|
|
149
|
+
headCommitSha,
|
|
126
150
|
originalWorktreePath: workspace.worktreePath,
|
|
127
151
|
originalSourceBranch: workspace.sourceBranch,
|
|
128
152
|
originalWorkingBranch: workspace.workingBranch,
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { withGitRepoLock } from '../utils/git-repo-lock.js';
|
|
2
|
+
import { withWorkspaceLifecycleGuard } from '../utils/workspace-lifecycle-guard.js';
|
|
3
|
+
import { invalidateWorkspacePrCaches } from './pr-watcher-service.js';
|
|
4
|
+
import { emitEphemeral } from './websocket-service.js';
|
|
5
|
+
import { getWorkspace, restoreWorktreeFromDisk } from './workspace-service.js';
|
|
6
|
+
import { isMatchingWorkspaceWorktree, restoreWorktreeCheckoutUnlocked, } from './worktree-service.js';
|
|
7
|
+
export class WorktreeRestoreError extends Error {
|
|
8
|
+
code;
|
|
9
|
+
constructor(code, message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.code = code;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function savedHead(json) {
|
|
15
|
+
try {
|
|
16
|
+
const data = JSON.parse(json ?? 'null');
|
|
17
|
+
if (data && typeof data === 'object' && 'headCommitSha' in data && typeof data.headCommitSha === 'string') {
|
|
18
|
+
return /^[a-f0-9]{40,64}$/i.test(data.headCommitSha) ? data.headCommitSha : null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// Older/hand-edited metadata does not prevent restoring a surviving branch.
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
export async function restorePurgedWorktree(id) {
|
|
27
|
+
try {
|
|
28
|
+
return await withWorkspaceLifecycleGuard(id, async () => {
|
|
29
|
+
const initial = getWorkspace(id);
|
|
30
|
+
if (!initial)
|
|
31
|
+
throw new WorktreeRestoreError('not-found', 'Workspace not found.');
|
|
32
|
+
return withGitRepoLock(initial.projectPath, async () => {
|
|
33
|
+
const workspace = getWorkspace(id);
|
|
34
|
+
if (!workspace)
|
|
35
|
+
throw new WorktreeRestoreError('not-found', 'Workspace not found.');
|
|
36
|
+
if (!workspace.worktreeOwned) {
|
|
37
|
+
throw new WorktreeRestoreError('worktree-not-owned', 'This workspace uses an externally managed worktree.');
|
|
38
|
+
}
|
|
39
|
+
const input = {
|
|
40
|
+
projectPath: workspace.projectPath,
|
|
41
|
+
worktreePath: workspace.worktreePath,
|
|
42
|
+
workingBranch: workspace.workingBranch,
|
|
43
|
+
headCommitSha: savedHead(workspace.worktreePurgeRestoreData),
|
|
44
|
+
};
|
|
45
|
+
if (!workspace.worktreePurgedAt) {
|
|
46
|
+
if (!isMatchingWorkspaceWorktree(input)) {
|
|
47
|
+
throw new WorktreeRestoreError('not-purged', 'Workspace is not marked as purged and its checkout is missing or different.');
|
|
48
|
+
}
|
|
49
|
+
return { workspace, outcome: 'already-restored', source: 'existing-worktree' };
|
|
50
|
+
}
|
|
51
|
+
const checkout = await restoreWorktreeCheckoutUnlocked(input);
|
|
52
|
+
if (!isMatchingWorkspaceWorktree(input)) {
|
|
53
|
+
throw new WorktreeRestoreError('path-conflict', 'The recreated checkout does not match this workspace.');
|
|
54
|
+
}
|
|
55
|
+
// Preserve a valid checkout on DB failure. A retry or manual detection can
|
|
56
|
+
// safely complete the metadata update without deleting any Git history.
|
|
57
|
+
const restored = restoreWorktreeFromDisk(id);
|
|
58
|
+
invalidateWorkspacePrCaches(id);
|
|
59
|
+
emitEphemeral(id, 'workspace:worktree-restored', { workspace: restored });
|
|
60
|
+
return { workspace: restored, outcome: 'restored', source: checkout.source };
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
if (err instanceof WorktreeRestoreError)
|
|
66
|
+
throw err;
|
|
67
|
+
const code = err && typeof err === 'object' && 'code' in err ? err.code : null;
|
|
68
|
+
const known = [
|
|
69
|
+
'workspace-busy',
|
|
70
|
+
'project-unavailable',
|
|
71
|
+
'path-conflict',
|
|
72
|
+
'branch-in-use',
|
|
73
|
+
'recovery-source-unavailable',
|
|
74
|
+
];
|
|
75
|
+
const matched = known.find((candidate) => candidate === code);
|
|
76
|
+
// Don't expose Git stderr: remote URLs can contain credentials.
|
|
77
|
+
throw new WorktreeRestoreError(matched ?? 'git-failed', 'Unable to restore the worktree. Check the project and Git recovery source, then retry.');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execFileSync } from 'node:child_process';
|
|
1
|
+
import { execFile, execFileSync } from 'node:child_process';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { isGitBranchExistsError } from '../utils/git-ops.js';
|
|
@@ -50,11 +50,10 @@ function projectRelativeWorktreePath(projectPath, worktreePath) {
|
|
|
50
50
|
return null;
|
|
51
51
|
return relativePath;
|
|
52
52
|
}
|
|
53
|
-
function addToExclude(projectPath, worktreePath) {
|
|
53
|
+
function addToExclude(projectPath, worktreePath, excludeFile = getExcludeFilePath(projectPath)) {
|
|
54
54
|
const relativePath = projectRelativeWorktreePath(projectPath, worktreePath);
|
|
55
55
|
if (!relativePath)
|
|
56
56
|
return;
|
|
57
|
-
const excludeFile = getExcludeFilePath(projectPath);
|
|
58
57
|
// Ensure the .git/info directory exists
|
|
59
58
|
const infoDir = path.dirname(excludeFile);
|
|
60
59
|
if (!fs.existsSync(infoDir)) {
|
|
@@ -279,3 +278,152 @@ export function listOrphanWorktrees(projectPath, attachedPaths) {
|
|
|
279
278
|
suggestedSourceBranch: detectSourceBranch(projectPath, wt.path, wt.branch),
|
|
280
279
|
}));
|
|
281
280
|
}
|
|
281
|
+
export class WorktreeCheckoutError extends Error {
|
|
282
|
+
code;
|
|
283
|
+
constructor(code, message) {
|
|
284
|
+
super(message);
|
|
285
|
+
this.code = code;
|
|
286
|
+
this.name = 'WorktreeCheckoutError';
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const restoreGitEnv = () => ({ ...process.env, LC_ALL: 'C', LANG: 'C', GIT_TERMINAL_PROMPT: '0' });
|
|
290
|
+
function restoreGit(cwd, args) {
|
|
291
|
+
return execFileSync('git', ['-c', 'core.hooksPath=/dev/null', ...args], {
|
|
292
|
+
cwd,
|
|
293
|
+
encoding: 'utf8',
|
|
294
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
295
|
+
timeout: 30_000,
|
|
296
|
+
env: restoreGitEnv(),
|
|
297
|
+
}).replace(/\n$/, '');
|
|
298
|
+
}
|
|
299
|
+
/** NUL delimiters preserve quoted, whitespace and newline-containing worktree paths. */
|
|
300
|
+
function restoreRegistrations(projectPath) {
|
|
301
|
+
return restoreGit(projectPath, ['worktree', 'list', '--porcelain', '-z'])
|
|
302
|
+
.split('\0\0')
|
|
303
|
+
.filter(Boolean)
|
|
304
|
+
.map((block) => {
|
|
305
|
+
const fields = block.split('\0');
|
|
306
|
+
return {
|
|
307
|
+
path: fields.find((field) => field.startsWith('worktree '))?.slice(9) ?? '',
|
|
308
|
+
branch: fields.find((field) => field.startsWith('branch '))?.slice(7) ?? '',
|
|
309
|
+
};
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
function commonGitDirectory(cwd) {
|
|
313
|
+
return fs.realpathSync(restoreGit(cwd, ['rev-parse', '--path-format=absolute', '--git-common-dir']));
|
|
314
|
+
}
|
|
315
|
+
/** Accept only the exact registered root in the same repository on the expected branch. */
|
|
316
|
+
export function isMatchingWorkspaceWorktree(input) {
|
|
317
|
+
try {
|
|
318
|
+
if (!fs.lstatSync(input.worktreePath).isDirectory())
|
|
319
|
+
return false;
|
|
320
|
+
const target = fs.realpathSync(input.worktreePath);
|
|
321
|
+
if (fs.realpathSync(restoreGit(input.worktreePath, ['rev-parse', '--show-toplevel'])) !== target)
|
|
322
|
+
return false;
|
|
323
|
+
if (commonGitDirectory(input.projectPath) !== commonGitDirectory(input.worktreePath))
|
|
324
|
+
return false;
|
|
325
|
+
const ref = `refs/heads/${input.workingBranch}`;
|
|
326
|
+
if (restoreGit(input.worktreePath, ['symbolic-ref', 'HEAD']) !== ref)
|
|
327
|
+
return false;
|
|
328
|
+
return restoreRegistrations(input.projectPath).some((entry) => canonicalize(entry.path) === target && entry.branch === ref);
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
function restorePathExists(target) {
|
|
335
|
+
try {
|
|
336
|
+
fs.lstatSync(target);
|
|
337
|
+
return true;
|
|
338
|
+
}
|
|
339
|
+
catch (err) {
|
|
340
|
+
if (err.code === 'ENOENT')
|
|
341
|
+
return false;
|
|
342
|
+
throw err;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function resolveRestoreCommit(projectPath, ref) {
|
|
346
|
+
try {
|
|
347
|
+
return restoreGit(projectPath, ['rev-parse', '--verify', '--end-of-options', `${ref}^{commit}`]);
|
|
348
|
+
}
|
|
349
|
+
catch {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
/** Caller must already hold withGitRepoLock(projectPath). Never resets or replaces a checkout. */
|
|
354
|
+
export async function restoreWorktreeCheckoutUnlocked(input) {
|
|
355
|
+
const { projectPath, workingBranch } = input;
|
|
356
|
+
const worktreePath = path.resolve(input.worktreePath);
|
|
357
|
+
try {
|
|
358
|
+
commonGitDirectory(projectPath);
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
throw new WorktreeCheckoutError('project-unavailable', 'The project Git repository is unavailable. Restore its location first.');
|
|
362
|
+
}
|
|
363
|
+
try {
|
|
364
|
+
if (workingBranch.startsWith('-'))
|
|
365
|
+
throw new Error('Branch names cannot start with a dash');
|
|
366
|
+
restoreGit(projectPath, ['check-ref-format', `refs/heads/${workingBranch}`]);
|
|
367
|
+
if (restorePathExists(worktreePath)) {
|
|
368
|
+
if (!isMatchingWorkspaceWorktree(input)) {
|
|
369
|
+
throw new WorktreeCheckoutError('path-conflict', 'The destination is occupied by a different checkout or directory. Move it before retrying.');
|
|
370
|
+
}
|
|
371
|
+
return { source: 'existing-worktree', headCommitSha: restoreGit(worktreePath, ['rev-parse', '--verify', 'HEAD']) };
|
|
372
|
+
}
|
|
373
|
+
const registrations = restoreRegistrations(projectPath);
|
|
374
|
+
// Do not globally prune: another missing checkout may still need its registration.
|
|
375
|
+
if (registrations.some((entry) => path.resolve(entry.path) === worktreePath)) {
|
|
376
|
+
throw new WorktreeCheckoutError('path-conflict', 'Git still registers the missing destination. Repair that worktree registration before retrying.');
|
|
377
|
+
}
|
|
378
|
+
if (registrations.some((entry) => entry.branch === `refs/heads/${workingBranch}`)) {
|
|
379
|
+
throw new WorktreeCheckoutError('branch-in-use', 'The working branch is already checked out elsewhere. Free that checkout before retrying.');
|
|
380
|
+
}
|
|
381
|
+
let source = 'local-branch';
|
|
382
|
+
const localCommit = resolveRestoreCommit(projectPath, `refs/heads/${workingBranch}`);
|
|
383
|
+
let commit = localCommit;
|
|
384
|
+
if (!commit) {
|
|
385
|
+
source = 'saved-commit';
|
|
386
|
+
commit =
|
|
387
|
+
input.headCommitSha && /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/i.test(input.headCommitSha)
|
|
388
|
+
? resolveRestoreCommit(projectPath, input.headCommitSha)
|
|
389
|
+
: null;
|
|
390
|
+
if (!commit) {
|
|
391
|
+
source = 'remote-branch';
|
|
392
|
+
try {
|
|
393
|
+
await new Promise((resolve, reject) => {
|
|
394
|
+
execFile('git', ['-c', 'core.hooksPath=/dev/null', 'fetch', '--no-tags', 'origin', `refs/heads/${workingBranch}`], {
|
|
395
|
+
cwd: projectPath,
|
|
396
|
+
timeout: 60_000,
|
|
397
|
+
env: restoreGitEnv(),
|
|
398
|
+
}, (error) => (error ? reject(error) : resolve()));
|
|
399
|
+
});
|
|
400
|
+
commit = resolveRestoreCommit(projectPath, 'FETCH_HEAD');
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
// Avoid exposing remote credentials embedded in Git error output.
|
|
404
|
+
}
|
|
405
|
+
if (!commit) {
|
|
406
|
+
throw new WorktreeCheckoutError('recovery-source-unavailable', 'No local branch, saved commit or reachable origin branch is available. Check origin access or recover the branch manually.');
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// Recheck after the asynchronous fetch, including empty directories and dangling links.
|
|
411
|
+
if (restorePathExists(worktreePath)) {
|
|
412
|
+
throw new WorktreeCheckoutError('path-conflict', 'The destination appeared during recovery. Move the conflicting directory before retrying.');
|
|
413
|
+
}
|
|
414
|
+
if (localCommit)
|
|
415
|
+
restoreGit(projectPath, ['worktree', 'add', '--', worktreePath, workingBranch]);
|
|
416
|
+
else
|
|
417
|
+
restoreGit(projectPath, ['worktree', 'add', '-b', workingBranch, '--', worktreePath, commit]);
|
|
418
|
+
if (!isMatchingWorkspaceWorktree(input)) {
|
|
419
|
+
throw new WorktreeCheckoutError('git-failed', 'The restored checkout could not be verified. Inspect the checkout before retrying.');
|
|
420
|
+
}
|
|
421
|
+
addToExclude(projectPath, worktreePath, path.join(commonGitDirectory(projectPath), 'info', 'exclude'));
|
|
422
|
+
return { source, headCommitSha: restoreGit(worktreePath, ['rev-parse', '--verify', 'HEAD']) };
|
|
423
|
+
}
|
|
424
|
+
catch (err) {
|
|
425
|
+
if (err instanceof WorktreeCheckoutError)
|
|
426
|
+
throw err;
|
|
427
|
+
throw new WorktreeCheckoutError('git-failed', 'Git could not restore the worktree. Check the branch name, repository permissions and worktree registrations.');
|
|
428
|
+
}
|
|
429
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a Keep-a-Changelog markdown file into ordered version sections. Each
|
|
3
|
+
* `## <version>` heading starts a new entry; everything until the next heading
|
|
4
|
+
* is its notes. A leading `v` on the version is stripped.
|
|
5
|
+
*/
|
|
6
|
+
export function parseChangelog(markdown) {
|
|
7
|
+
const entries = [];
|
|
8
|
+
let current = null;
|
|
9
|
+
for (const line of markdown.split('\n')) {
|
|
10
|
+
const heading = line.match(/^##\s+v?(\d+\.\d+\.\d+[\w./-]*)\s*$/);
|
|
11
|
+
if (heading) {
|
|
12
|
+
if (current)
|
|
13
|
+
entries.push({ version: current.version, notes: current.lines.join('\n').trim() });
|
|
14
|
+
current = { version: heading[1], lines: [] };
|
|
15
|
+
}
|
|
16
|
+
else if (current) {
|
|
17
|
+
current.lines.push(line);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if (current)
|
|
21
|
+
entries.push({ version: current.version, notes: current.lines.join('\n').trim() });
|
|
22
|
+
return entries;
|
|
23
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { stripVTControlCharacters } from 'node:util';
|
|
3
|
+
import { parseChangelog } from './changelog.js';
|
|
4
|
+
/** Optional package asset: a missing changelog must never prevent startup. */
|
|
5
|
+
export function readStartupNotes(file) {
|
|
6
|
+
try {
|
|
7
|
+
return fs.readFileSync(file, 'utf-8');
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return '';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function plainHighlight(line) {
|
|
14
|
+
const text = stripVTControlCharacters(line)
|
|
15
|
+
.replace(/!?\[([^\]]+)\]\([^)]*\)/g, '$1')
|
|
16
|
+
.replace(/[*`~]/g, '')
|
|
17
|
+
.replace(/\s+/g, ' ')
|
|
18
|
+
.trim();
|
|
19
|
+
return text.length > 92 ? `${text.slice(0, 91).trimEnd()}…` : text;
|
|
20
|
+
}
|
|
21
|
+
export function formatStartupBanner(options) {
|
|
22
|
+
const style = (code, text) => (options.color ? `\x1b[${code}m${text}\x1b[0m` : text);
|
|
23
|
+
const url = options.devClientOrigin?.trim().replace(/\/+$/, '') || `http://localhost:${options.port}`;
|
|
24
|
+
const lines = [
|
|
25
|
+
'',
|
|
26
|
+
` ${style(1, '工房 Kōbō')} ${style(2, `v${options.version}`)}`,
|
|
27
|
+
` ${style(2, 'Your development workshop')}`,
|
|
28
|
+
'',
|
|
29
|
+
` ${style(32, '✓')} Server ready`,
|
|
30
|
+
` → ${style(1, url)}`,
|
|
31
|
+
];
|
|
32
|
+
if (options.networkEnabled) {
|
|
33
|
+
for (const lanUrl of options.lanUrls ?? [])
|
|
34
|
+
lines.push(` LAN: ${lanUrl}`);
|
|
35
|
+
if (options.token)
|
|
36
|
+
lines.push(` Token: ${options.token}`);
|
|
37
|
+
}
|
|
38
|
+
const release = parseChangelog(options.changelog ?? '').find((entry) => entry.version === options.version);
|
|
39
|
+
const highlights = (release?.notes ?? '')
|
|
40
|
+
.split('\n')
|
|
41
|
+
.filter((line) => /^[-*+]\s+/.test(line))
|
|
42
|
+
.map((line) => plainHighlight(line.slice(2)))
|
|
43
|
+
.filter(Boolean);
|
|
44
|
+
if (highlights.length > 0) {
|
|
45
|
+
lines.push('', ` ${style(1, `What’s new · ${options.version}`)}`);
|
|
46
|
+
for (const highlight of highlights.slice(0, 5))
|
|
47
|
+
lines.push(` • ${highlight}`);
|
|
48
|
+
lines.push(` Full changelog: ${url}/#/changelog`);
|
|
49
|
+
}
|
|
50
|
+
lines.push('', ` ${style(2, options.networkEnabled ? 'Network access enabled' : 'Local access only')}`, ` ${style(2, 'Ctrl+C to stop')}`, '');
|
|
51
|
+
return lines.join('\n');
|
|
52
|
+
}
|
|
53
|
+
/** Only animate an interactive terminal; redirected logs stay plain text. */
|
|
54
|
+
export function startStartupSpinner() {
|
|
55
|
+
if (!process.stderr.isTTY || process.env.TERM === 'dumb')
|
|
56
|
+
return () => { };
|
|
57
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
58
|
+
let frame = 0;
|
|
59
|
+
const render = () => process.stderr.write(`\r ${frames[frame++ % frames.length]} Starting Kōbō…`);
|
|
60
|
+
render();
|
|
61
|
+
const timer = setInterval(render, 80);
|
|
62
|
+
timer.unref();
|
|
63
|
+
return () => {
|
|
64
|
+
clearInterval(timer);
|
|
65
|
+
process.stderr.write('\r\x1b[2K');
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export function startupDebug(message) {
|
|
69
|
+
if (process.env.KOBO_VERBOSE === '1')
|
|
70
|
+
console.log(message);
|
|
71
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const busyWorkspaces = new Set();
|
|
2
|
+
export class WorkspaceLifecycleBusyError extends Error {
|
|
3
|
+
code = 'workspace-busy';
|
|
4
|
+
constructor() {
|
|
5
|
+
super('Another workspace operation is in progress. Wait for it to finish and retry.');
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export function isWorkspaceLifecycleBusy(id) {
|
|
9
|
+
return busyWorkspaces.has(id);
|
|
10
|
+
}
|
|
11
|
+
/** Exclude competing restore/purge/delete operations, including their awaits. */
|
|
12
|
+
export async function withWorkspaceLifecycleGuard(id, action) {
|
|
13
|
+
if (busyWorkspaces.has(id))
|
|
14
|
+
throw new WorkspaceLifecycleBusyError();
|
|
15
|
+
busyWorkspaces.add(id);
|
|
16
|
+
try {
|
|
17
|
+
return await action();
|
|
18
|
+
}
|
|
19
|
+
finally {
|
|
20
|
+
busyWorkspaces.delete(id);
|
|
21
|
+
}
|
|
22
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loicngr/kobo",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.35",
|
|
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",
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import{S as e,ct as t,j as n,ot as r,ut as i,v as a}from"./reactivity.esm-bundler-DEJj9cFv.js";import{C as o,F as s,J as c,L as l,N as u,O as d,P as f,Y as p,_ as m,d as h,f as g,g as _,j as v,k as y,l as b,p as x,r as S,u as C,y as w,z as T}from"./runtime-core.esm-bundler-CDnVUfeZ.js";import{J as E,b as D,ct as O,l as k,pt as A,t as ee,tt as j,w as M,y as te}from"./QBtn-CZaBclNw.js";import{A as N,At as ne,Ct as P,Nt as F,O as re,k as ie,m as ae,p as I,q as oe,xt as L}from"./index-CIP08MpU.js";import{t as se}from"./QSpinnerDots-DnCV3VzI.js";import{t as ce}from"./QList-qupzuEqO.js";import{t as R}from"./QMarkupTable-B9QuKtK_.js";import{c as z,l as B}from"./QSelect-BrSemS3o.js";import{d as le}from"./permissionModes-CVFHS2ec.js";import{r as ue,t as V}from"./pending-diff-open-CVQxWyzP.js";import{t as H}from"./documents-3ZBK1pXE.js";import{a as U}from"./use-tours-DhLhglwr.js";import{t as W}from"./QTooltip-CLCFHW2D.js";import{i as de,n as G,o as fe,r as K,t as pe}from"./render-chat-markdown-Cb15kvox.js";import{a as me,i as he,n as q,o as ge,r as _e,s as ve}from"./WorkspacePage-B6O7j57u.js";function ye(e,t){return o(`div`,e,[o(`table`,{class:`q-table`},t)])}var J={list:ce,table:R},Y=[`list`,`table`,`__qtable`],be=j({name:`QVirtualScroll`,props:{...B,type:{type:String,default:`list`,validator:e=>Y.includes(e)},items:{type:Array,default:()=>[]},itemsFn:Function,itemsSize:Number,scrollTarget:F},setup(t,{slots:n,attrs:r}){let i,a=e(null),s=b(()=>t.itemsSize>=0&&t.itemsFn!==void 0?Number.parseInt(t.itemsSize,10):Array.isArray(t.items)?t.items.length:0),{virtualScrollSliceRange:l,localResetVirtualScroll:d,padVirtualScroll:p,onVirtualScrollEvt:m}=z({virtualScrollLength:s,getVirtualScrollTarget:S,getVirtualScrollEl:x}),h=b(()=>{if(s.value===0)return[];let e=(e,t)=>({index:l.value.from+t,item:e});return t.itemsFn===void 0?t.items.slice(l.value.from,l.value.to).map(e):t.itemsFn(l.value.from,l.value.to-l.value.from).map(e)}),g=b(()=>`q-virtual-scroll q-virtual-scroll`+(t.virtualScrollHorizontal?`--horizontal`:`--vertical`)+(t.scrollTarget===void 0?` scroll`:``)),_=b(()=>t.scrollTarget===void 0?{tabindex:0}:{});c(s,()=>{d()}),c(()=>t.scrollTarget,()=>{w(),C()});function x(){let e=a.value;return e===null?null:e.$el||e}function S(){return i}function C(){let e=x();e!==null&&(i=ne(e,t.scrollTarget),i.addEventListener(`scroll`,m,E.passive))}function w(){i!==void 0&&(i.removeEventListener(`scroll`,m,E.passive),i=void 0)}function T(){let e=p(t.type===`list`?`div`:`tbody`,h.value.map(n.default));return n.before!==void 0&&(e=n.before().concat(e)),D(n.after,e)}return d(),f(()=>{C()}),y(()=>{C()}),u(()=>{w()}),v(()=>{w()}),()=>{if(n.default===void 0){console.error(`QVirtualScroll: default scoped slot is required for rendering`);return}return t.type===`__qtable`?ye({ref:a,class:`q-table__middle `+g.value,tabindex:0},T()):o(J[t.type],{...r,ref:a,class:[r.class,g.value],..._.value},T)}}}),xe=new Set([`setup`,`cleanup`,`archive`]);function Se(e){switch(e.type){case`user`:return e.sender===`system-prompt`?`system-prompt`:xe.has(e.sender)||re(e.sender)?`script`:`user`;case`session`:return`session`;default:return`agent`}}function Ce(e){let t=[],n=null,r=null;for(let i of e){let e=Se(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 we(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 Te(e){let t=e.items[0];return`${e.speaker}:${t?we(t):`empty`}`}function Ee(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 De(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=c(e,e=>{t(e)&&s(!0)},{immediate:!0}),i){o();return}a=setTimeout(()=>s(!1),n)})}var Oe={class:`agent-error-item`},ke={class:`agent-error-item__body`},Ae={class:`agent-error-item__title`},je={class:`agent-error-item__message`},X=U(w({__name:`AgentErrorItem`,props:{item:{}},setup(e){let t=e,{te:n}=L(),r=b(()=>{let e=`agent.error.${t.item.category}`;return n(e)?e:`agent.error.other`});return(t,n)=>(l(),x(`div`,Oe,[m(k,{name:`error_outline`,size:`16px`,class:`agent-error-item__icon`}),C(`div`,ke,[C(`div`,Ae,i(t.$t(r.value)),1),C(`div`,je,i(e.item.message),1)])]))}}),[[`__scopeId`,`data-v-e98c3c02`]]),Me={class:`text-caption text-kobo-3`},Z=w({__name:`SessionEventItem`,props:{item:{}},setup(e){let t=e,n=b(()=>{switch(t.item.kind){case`started`:return`session.started`;case`ended`:return ve(t.item.detail);case`compacted`:return`session.compacted`;default:return`session.started`}});return(e,t)=>(l(),x(`span`,Me,i(e.$t(n.value)),1))}});function Ne(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){Q(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 Q(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=$(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 $(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 Pe=[`innerHTML`],Fe=U(w({__name:`TextMessageItem`,props:{item:{}},setup(e){let t=e,n=H(),r=I(),i=te(),{t:a}=L(),o=b(()=>{let e=r.selectedWorkspaceId;return e?n.documentsFor(e).map(e=>e.path):[]}),s=b(()=>{let e=t.item;if(e.streaming)return pe(e.text);let n=o.value;return K(`${e.messageId}|${n.join(`,`)}`,e.text,e=>{let t=Ne(fe.parse(e,{async:!1,breaks:!0,gfm:!0}),n);return de(t,{addAttr:[`data-document-path`]})})});function c(e){let t=e.target?.closest(`.document-link`);if(!t)return;e.preventDefault();let o=t.getAttribute(`data-document-path`),s=r.selectedWorkspaceId;o&&s&&n.openDocumentByPath(s,o).catch(e=>{console.error(`[TextMessageItem] openDocumentByPath failed:`,e),i.notify({type:`negative`,message:a(`documents.loadFailed`),position:`top`})})}return(t,n)=>(l(),x(`div`,{class:`markdown-message`,onClick:c},[C(`div`,{innerHTML:s.value},null,8,Pe),e.item.streaming?(l(),h(M,{key:0,size:`xs`,class:`q-ml-xs`})):g(``,!0)]))}}),[[`__scopeId`,`data-v-99371035`]]);function Ie(e,t){let n=e.split(`
|
|
2
|
+
`),r=t.split(`
|
|
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,...Le(l,u),...c]}function Le(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 Re(e){let t=e.split(`
|
|
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 ze(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=Re(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(`
|
|
5
|
+
`).length:0,deletions:t?t.split(`
|
|
6
|
+
`).length:0}}if(e===`Write`){let e=n.file_path;if(!e)return null;let t=n.content??``;return{toolName:`Write`,filePath:e,content:t,additions:t?t.split(`
|
|
7
|
+
`).length:0,deletions:0}}if(e===`Bash`){let e=(n.command??``).match(/^\s*rm\s+(?:-[a-zA-Z]*\s+)*(.+)/);if(e)return{toolName:`Bash:rm`,filePath:e[1].trim().replace(/["']/g,``),additions:0,deletions:1}}return null}function Be(e,t){if(!e||!t?.projectPath)return e;let n=t.worktreePath;if(n){let t=Ve(e,n);if(t!==e)return t}let r=Ve(e,`${t.projectPath}/${N}/${t.workingBranch}`);return r===e?Ve(e,t.projectPath):r}function Ve(e,t){if(!t)return e;let n=He(t);return n?e.replace(RegExp(`${n}[\\\\/]+`,`g`),``).replace(RegExp(`${n}(?=\\s|$|["'\`])`,`g`),`.`):e}function He(e){let t=e.replace(/[\\/]+$/,``);return t?`${/^[\\/]+/.test(t)?`[\\\\/]+`:``}${t.split(/[\\/]+/).filter(Boolean).map(Ue).join(`[\\\\/]+`)}`:``}function Ue(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}var We={class:`tool-name`},Ge=[`title`],Ke={key:0,class:`tool-stat-add`},qe={key:1,class:`tool-stat-del`},Je={class:`diff-sign`},Ye={class:`tool-name`},Xe=[`title`],Ze=U(w({__name:`ToolCallItem`,props:{item:{}},setup(e){let t=e,o=a(!1),s=I(),{t:u}=L(),d=b(()=>ze(t.item.name,t.item.input)),f=b(()=>d.value?Be(d.value.filePath,s.selectedWorkspace):``),v={Bash:`terminal`,Read:`description`,Edit:`edit`,Write:`edit_note`,MultiEdit:`edit`,Glob:`folder_open`,Grep:`manage_search`,LS:`list`,Skill:`auto_awesome`,Task:`hub`,Agent:`hub`,TodoWrite:`checklist`,TodoRead:`checklist`,TaskCreate:`add_task`,TaskUpdate:`checklist`,TaskGet:`checklist`,TaskList:`checklist`,ToolSearch:`search`,WebFetch:`public`,WebSearch:`travel_explore`,NotebookRead:`book`,NotebookEdit:`edit_note`,SendMessage:`send`,ExitPlanMode:`check_circle_outline`,KillShell:`stop_circle`,BashOutput:`terminal`},y=b(()=>v[t.item.name]??`build`),w=b(()=>{if(d.value)return``;let e=t.item.input,n=E(e);return n?Be(n,s.selectedWorkspace):``});function E(e){if(!e||typeof e!=`object`)return typeof e==`string`?e:``;let t=e;for(let e of[`file_path`,`path`,`command`,`pattern`,`query`,`url`,`skill`,`description`,`subject`,`prompt`]){let n=t[e];if(typeof n==`string`&&n.length>0)return n}for(let e of Object.values(t))if(typeof e==`string`&&e.length>0)return e;return``}let D=b(()=>{let e=d.value;return e?e.diffLines?e.diffLines:e.toolName===`Edit`&&e.oldString!==void 0&&e.newString!==void 0?Ie(e.oldString,e.newString):e.toolName===`Write`&&e.content!==void 0?e.content.split(`
|
|
8
|
+
`).map(e=>({type:`add`,content:e})):e.toolName===`Bash:rm`?[{type:`del`,content:`File deleted`}]:null:null}),O=b(()=>{let e=t.item.result;if(!e)return``;if(typeof e.output==`string`)return e.output;try{return JSON.stringify(e.output)}catch{return String(e.output)}}),j=new Set([`Read`]),M=b(()=>!!(t.item.result&&O.value)&&(!j.has(t.item.name)||t.item.result?.isError===!0));function te(){o.value=!o.value}function N(){let e=d.value?.filePath,t=s.selectedWorkspace,n=t?.id,r=e?Be(e,t):``;r&&!/^(?:[A-Za-z]:)?[\\/]/.test(r)&&n&&(V(n,r),window.dispatchEvent(new CustomEvent(`kobo:open-diff`,{detail:{workspaceId:n,path:r}})))}return c(()=>t.item.result?.isError===!0,e=>{e&&(o.value=!0)},{immediate:!0}),(t,a)=>d.value?(l(),x(`div`,{key:0,class:r([`tool-row`,{"tool-row-expanded":o.value}])},[C(`div`,{class:`tool-header`,onClick:te},[m(k,{name:y.value,size:`14px`,class:`tool-icon`},null,8,[`name`]),C(`span`,We,i(d.value.toolName===`Bash:rm`?`Bash`:d.value.toolName),1),C(`span`,{class:`tool-path`,title:d.value.filePath},i(f.value),9,Ge),m(ee,{flat:``,dense:``,round:``,size:`xs`,icon:`open_in_new`,class:`tool-open-diff`,onClick:A(N,[`stop`])},{default:p(()=>[m(W,null,{default:p(()=>[_(i(n(u)(`chat.openInGitDiff`)),1)]),_:1})]),_:1}),d.value.additions>0?(l(),x(`span`,Ke,`+`+i(d.value.additions),1)):g(``,!0),d.value.deletions>0?(l(),x(`span`,qe,`-`+i(d.value.deletions),1)):g(``,!0),e.item.result?.isError?(l(),h(k,{key:2,name:`error_outline`,color:`negative`,size:`xs`,class:`q-ml-xs`})):e.item.result?(l(),h(k,{key:3,name:`check`,color:`positive`,size:`xs`,class:`q-ml-xs`})):g(``,!0),m(k,{name:o.value?`expand_less`:`expand_more`,size:`xs`,class:`q-ml-auto text-kobo-3`},null,8,[`name`])]),o.value&&D.value?(l(),x(`div`,{key:0,class:`tool-diff`,onClick:a[0]||=A(()=>{},[`stop`])},[(l(!0),x(S,null,T(D.value,(e,t)=>(l(),x(`div`,{key:t,class:r([`diff-line`,{"diff-del":e.type===`del`,"diff-add":e.type===`add`,"diff-context":e.type===`context`}])},[C(`span`,Je,i(e.type===`del`?`-`:e.type===`add`?`+`:` `),1),_(i(e.content),1)],2))),128))])):g(``,!0)],2)):(l(),x(`div`,{key:1,class:r([`tool-row tool-row-generic`,{"tool-row-expanded":o.value,"tool-row--toggleable":M.value}])},[C(`div`,{class:`tool-header`,onClick:a[1]||=e=>M.value&&te()},[m(k,{name:y.value,size:`14px`,class:`tool-icon`},null,8,[`name`]),C(`span`,Ye,i(e.item.name),1),w.value?(l(),x(`span`,{key:0,class:`tool-arg`,title:E(e.item.input)||w.value},i(w.value),9,Xe)):g(``,!0),e.item.result?.isError?(l(),h(k,{key:1,name:`error_outline`,color:`negative`,size:`xs`,class:`q-ml-auto`})):e.item.result?(l(),h(k,{key:2,name:`check`,color:`positive`,size:`xs`,class:`q-ml-auto`})):g(``,!0),M.value?(l(),h(k,{key:3,name:o.value?`expand_less`:`expand_more`,size:`xs`,class:`q-ml-xs text-kobo-3`},null,8,[`name`])):g(``,!0)]),o.value&&M.value?(l(),x(`div`,{key:0,class:`tool-output`,onClick:a[2]||=A(()=>{},[`stop`])},i(O.value),1)):g(``,!0)],2))}}),[[`__scopeId`,`data-v-2482adfe`]]);function Qe(e,t){return t?e.replace(/\[image:\s+([^\]]+)\]/g,(e,n)=>{let r=String(n).trim();return/^(\.ai\/images\/|images\/)/.test(r)?`}/images/file?path=${encodeURIComponent(r)}`})`:e}):e}var $e=[`innerHTML`],et=[`innerHTML`],tt=[`src`],nt=U(w({__name:`UserMessageItem`,props:{item:{}},setup(e){let t=e,n=I(),r=b(()=>t.item.sender===`system-prompt`),i=b(()=>{let e=Qe(t.item.content,n.selectedWorkspaceId??``);return G(e)}),o=a(null),s=a(!1);function c(e){let t=e.target;if(t?.tagName!==`IMG`)return;let n=t;n.src&&(o.value=n.src,s.value=!0)}return(e,t)=>(l(),x(S,null,[r.value?(l(),h(le,{key:0,dense:``,"dense-toggle":``,label:e.$t(`chat.systemPrompt`),"header-class":`text-kobo-2 text-caption`},{default:p(()=>[C(`div`,{class:`q-py-xs markdown-user-prompt`,innerHTML:i.value},null,8,$e)]),_:1},8,[`label`])):(l(),x(`div`,{key:1,class:`markdown-message`,onClick:c},[C(`div`,{innerHTML:i.value},null,8,et)])),m(P,{modelValue:s.value,"onUpdate:modelValue":t[1]||=e=>s.value=e},{default:p(()=>[o.value?(l(),x(`img`,{key:0,src:o.value,alt:``,class:`image-lightbox-img`,onClick:t[0]||=e=>s.value=!1},null,8,tt)):g(``,!0)]),_:1},8,[`modelValue`])],64))}}),[[`__scopeId`,`data-v-7687eb7e`]]),rt=[`data-event-ids`],it={class:`turn-header`},at={key:0,class:`turn-time`},ot={class:`turn-time turn-time-updated`},st={key:2,class:`turn-actions`},ct={class:`turn-body`},lt={key:0,class:`turn-scroll-top`},ut=U(w({__name:`TurnCard`,props:{turn:{},highlighted:{type:Boolean}},emits:[`scrollTo`],setup(e,{emit:o}){let s=e,c=o,{t:u}=L(),d=a(null);function f(){let e=d.value;if(!e)return;let t=e.closest(`.q-scrollarea`)?.querySelector(`.q-scrollarea__content`);if(!t){e.scrollIntoView({behavior:`smooth`,block:`start`});return}let n=e.getBoundingClientRect().top-t.getBoundingClientRect().top;c(`scrollTo`,Math.max(0,n-8))}let v=b(()=>{switch(s.turn.speaker){case`user`:return{label:u(`chat.you`),accent:`var(--kobo-turn-user)`,badgeClass:`turn-badge-user`};case`agent`:return{label:u(`chat.agent`),accent:`var(--kobo-turn-agent)`,badgeClass:`turn-badge-agent`};case`system-prompt`:return{label:u(`chat.systemPrompt`),accent:`var(--kobo-text-3)`,badgeClass:`turn-badge-system`};case`session`:return{label:u(`chat.session`),accent:`var(--kobo-text-3)`,badgeClass:`turn-badge-session`};case`script`:{let e=s.turn.items[0],t=e?.type===`user`?e.sender:``;return{label:re(t)?u(`chat.hookScript`,{event:t.slice(5)}):u(t===`archive`?`chat.archiveScript`:t===`setup`?`chat.setupScript`:`chat.cleanupScript`),accent:`var(--kobo-success)`,badgeClass:`turn-badge-script`}}}});function y(e,t=!1){if(!e)return``;let n=new Date(e);return Number.isNaN(n.getTime())?``:n.toLocaleTimeString(void 0,t?{hour:`2-digit`,minute:`2-digit`,second:`2-digit`}:{hour:`2-digit`,minute:`2-digit`})}let w=b(()=>y(s.turn.ts)),E=b(()=>s.turn.items.flatMap(e=>e.eventIds??[]).join(` `)),D=b(()=>{let e=s.turn.items;if(e.length===0)return null;for(let t=e.length-1;t>=0;t--){let n=e[t].ts;if(n)return n}return null}),O=b(()=>{let e=s.turn.ts,t=D.value;if(!t||!e||t===e)return``;let n=new Date(e).getTime(),r=new Date(t).getTime();return Number.isNaN(n)||Number.isNaN(r)||r<=n?``:y(t,r-n<6e4)}),A=b(()=>O.value!==``),j=b(()=>s.turn.items.filter(e=>e.type===`tool`).length);return(a,o)=>(l(),x(`div`,{ref_key:`cardEl`,ref:d,class:r([`turn-card`,{"turn-card--user":e.turn.speaker===`user`,"turn-card--highlighted":e.highlighted}]),"data-event-ids":E.value,style:t({"--turn-accent":v.value.accent})},[C(`div`,it,[C(`span`,{class:r([`turn-badge`,v.value.badgeClass])},i(v.value.label),3),w.value?(l(),x(`span`,at,i(w.value),1)):g(``,!0),A.value?(l(),x(S,{key:1},[m(k,{name:`arrow_forward`,size:`10px`,color:`kobo-3`,class:`turn-time-arrow`}),C(`span`,ot,[_(i(O.value)+` `,1),m(W,null,{default:p(()=>[_(i(n(u)(`chat.lastUpdatedAt`,{time:O.value})),1)]),_:1})])],64)):g(``,!0),j.value>0?(l(),x(`span`,st,` · `+i(n(u)(`chat.nActions`,{n:j.value})),1)):g(``,!0)]),C(`div`,ct,[(l(!0),x(S,null,T(e.turn.items,e=>(l(),x(S,{key:n(we)(e)},[e.type===`text`?(l(),h(Fe,{key:0,item:e},null,8,[`item`])):e.type===`thinking`?(l(),h(q,{key:1,item:e},null,8,[`item`])):e.type===`tool`?(l(),h(Ze,{key:2,item:e},null,8,[`item`])):e.type===`user`?(l(),h(nt,{key:3,item:e},null,8,[`item`])):e.type===`session`?(l(),h(Z,{key:4,item:e},null,8,[`item`])):e.type===`error`?(l(),h(X,{key:5,item:e},null,8,[`item`])):g(``,!0)],64))),128))]),e.turn.items.length>4?(l(),x(`div`,lt,[m(ee,{flat:``,round:``,dense:``,size:`xs`,icon:`arrow_upward`,color:`kobo-3`,class:`turn-scroll-top-btn`,onClick:f},{default:p(()=>[m(W,null,{default:p(()=>[_(i(n(u)(`chat.scrollToTurnTop`)),1)]),_:1})]),_:1})])):g(``,!0)],14,rt))}}),[[`__scopeId`,`data-v-6e61835e`]]),dt={key:0,class:`activity-feed-switching`},ft={key:1,class:`activity-feed-wrap`,"data-tour":`ws-chat`},pt=[`aria-label`],mt={key:0,class:`activity-feed-compacting`},ht={class:`text-caption text-kobo-2`},gt={key:0,class:`text-center q-py-sm text-caption text-kobo-3`},_t={key:1,class:`activity-feed-empty q-pa-md`},vt={class:`activity-feed-empty__title`},yt={class:`activity-feed-empty__hint`},bt={key:2,class:`q-px-md q-pb-md`},xt={class:`activity-feed-nav-cluster`},St=300,Ct=60,wt=200,Tt=200,Et=400,Dt=200,Ot=180,kt=1200,At=U(w({__name:`ActivityFeed`,props:{workspaceId:{}},setup(e){let{t}=L(),r=e,o=oe(),u=ie(),v=I(),y=b(()=>o.isCompacting(r.workspaceId)),w=b(()=>v.selectedSessionId),E=b(()=>v.sessions.find(e=>e.id===w.value)?.engineSessionId??null),D=b(()=>{let e=v.sessions;return e.length!==0&&w.value===e[e.length-1].id});function A(e){return w.value?e?e===w.value||e===E.value:D.value:!0}let j=b(()=>(v.activityFeeds[r.workspaceId]??[]).filter(e=>e.type===`text`&&typeof e.content==`string`&&A(e.sessionId)).map(e=>({content:e.content,sender:e.meta?.sender??`user`,ts:e.timestamp,sessionId:e.sessionId,eventIds:[e.id]}))),te=b(()=>{let e=v.workspaces.find(e=>e.id===r.workspaceId);return ae(e?.status)}),N=_e(),ne=``,P=b(()=>{let e=o.eventsFor(r.workspaceId),t=o.timestampsFor(r.workspaceId),n=o.sessionIdsFor(r.workspaceId),i=o.eventIdsFor(r.workspaceId),a=[],s=[],c=[];for(let r=0;r<e.length;r++)A(n[r])&&(a.push(e[r]),s.push(t[r]),c.push(i[r]??null));let l=`${r.workspaceId}::${w.value??`*`}`;l!==ne&&(N=_e(),ne=l);let d=he(N,a,s,te.value,c);return Ce(ge(d,j.value).filter(e=>e.type===`thinking`?!1:e.type===`session`?e.kind===`ended`?u.showVerboseSystemMessages||!me(e.detail):u.showVerboseSystemMessages:!0))}),F=b(()=>u.showVerboseSystemMessages?o.eventsFor(r.workspaceId).filter(e=>e.kind===`message:raw`).map(e=>e.content):[]),re=b(()=>{for(let e=P.value.length-1;e>=0;e--){let n=P.value[e]?.items??[];for(let e=n.length-1;e>=0;e--){let r=n[e];if(r){if(r.type===`text`&&!r.streaming)return{key:`text:${r.messageId}`,text:r.text};if(r.type===`tool`)return r.result?{key:`tool:${r.toolCallId}:end`,text:r.result.isError?t(`activity.a11y.toolFailed`,{name:r.name}):t(`activity.a11y.toolFinished`,{name:r.name})}:{key:`tool:${r.toolCallId}:start`,text:t(`activity.a11y.toolStarted`,{name:r.name})}}}}return null}),ce=a(``);c(re,(e,n)=>{if(!e||e.key===n?.key)return;let r=e.key.startsWith(`text:`)?t(`activity.a11y.agentSaid`,{text:e.text.slice(0,St)}):e.text;ce.value=r});let R=a(null),z=a(!0),B=a(!1),V=a(null),H=!1,U=a(!0),W=a(new Map);function de(e){let t=e.verticalSize-e.verticalPosition-e.verticalContainerSize;z.value=t<=Ct,H&&e.verticalPosition<=wt&&!B.value&&fe()&&ve()}function G(e,t){return`${e}:${t}`}function fe(){let e=w.value;return e?W.value.get(G(r.workspaceId,e))??!0:o.hasMoreOlderFor(r.workspaceId)}function K(e,t,n){W.value.set(G(e,t),n)}function pe(e){if(!w.value)return o.oldestIdFor(e);let t=o.eventIdsFor(e),n=o.sessionIdsFor(e);for(let e=0;e<t.length;e++){if(!A(n[e]))continue;let r=t[e];if(r)return r}}let q=null;function ve(){return q||(q=ye().finally(()=>{q=null}),q)}async function ye(){let e=r.workspaceId,t=w.value,n=pe(e);if(!n)return;B.value=!0;let i=Date.now();try{let r=R.value;await d();let i=r?.getScroll().verticalSize??0,a=r?.getScroll().verticalPosition??0,s=new URLSearchParams({before:n,limit:`200`});t&&s.set(`session`,t);let c=fetch(`/api/workspaces/${e}/events?${s.toString()}`),l=new Promise(e=>setTimeout(e,Tt)),[u]=await Promise.all([c,l]);if(!u.ok){t?K(e,t,!1):o.prepend(e,[],[],{oldestId:n,hasMoreOlder:!1});return}let f=await u.json(),p=f.events??[],m=p.filter(t=>t.type===`agent:event`&&t.workspaceId===e),h=p.filter(t=>t.type===`user:message`&&t.workspaceId===e),g=m.map(e=>e.payload),_=m.map(e=>e.createdAt),y=m.map(e=>e.sessionId??null),b=m.map(e=>e.id),x=p.length>0?p[0].id:n;t&&K(e,t,f.hasMore),o.prepend(e,g,_,{oldestId:x,hasMoreOlder:t?o.hasMoreOlderFor(e):f.hasMore,sessionIds:y,eventIds:b});for(let t of h){let n=t.payload;typeof n.content==`string`&&v.addActivityItem(e,{id:t.id,type:`text`,content:n.content,timestamp:t.createdAt,sessionId:t.sessionId??void 0,meta:{sender:n.sender??`user`}})}if(await d(),r){let e=r.getScroll().verticalSize-i;if(e>0){let t=Math.max(a+e,250);r.setScrollPosition(`vertical`,t,0)}}}catch(e){console.error(`[ActivityFeed] failed to load older events:`,e)}finally{let e=Date.now()-i,t=Math.max(0,Tt-e);await new Promise(e=>setTimeout(e,t+Et)),B.value=!1}}async function J(e=0){await d();let t=R.value;if(!t)return;let n=t.getScroll();t.setScrollPosition(`vertical`,n.verticalSize,e)}let Y=null,xe=0;function Se(){if(Y!=null)return;let e=performance.now()-xe<Ot;Y=requestAnimationFrame(()=>{Y=null,xe=performance.now(),J(e?0:180)})}function we(e){let t=R.value;t&&t.setScrollPosition(`vertical`,Math.max(0,e),250)}function Oe(){let e=window.location.hash,t=e.indexOf(`?`);if(t===-1||!new URLSearchParams(e.slice(t+1)).has(`eventId`))return;let n=e.slice(0,t);window.history.replaceState(window.history.state,``,`${window.location.pathname}${window.location.search}${n}`)}async function ke(e){let t=e.detail;if(t?.workspaceId===r.workspaceId&&t.eventId)try{let e=new URLSearchParams({around:t.eventId,limit:`200`});t.sessionId&&e.set(`session`,t.sessionId);let n=await fetch(`/api/workspaces/${r.workspaceId}/events?${e}`);if(!n.ok)return;let i=await n.json(),a=i.events??[],s=a.filter(e=>e.type===`agent:event`&&e.workspaceId===r.workspaceId),c=a.filter(e=>e.type===`user:message`&&e.workspaceId===r.workspaceId);o.reset(r.workspaceId,s.map(e=>e.payload),s.map(e=>e.createdAt),{oldestId:a[0]?.id,hasMoreOlder:i.hasMore??!1,sessionIds:s.map(e=>e.sessionId),eventIds:s.map(e=>e.id)});for(let e of c){let t=e.payload;typeof t.content==`string`&&v.addActivityItem(r.workspaceId,{id:e.id,type:`text`,content:t.content,timestamp:e.createdAt,sessionId:e.sessionId??void 0,meta:{sender:t.sender??`user`}})}await De(U,e=>!e,5e3),await d(),await new Promise(e=>requestAnimationFrame(()=>requestAnimationFrame(()=>e())));let l=P.value.findIndex(e=>e.items.some(e=>e.eventIds?.includes(t.eventId)??!1));if(l<0)return;V.value=t.eventId,Ae.value?.scrollTo(l,`center`),X.value=l,window.setTimeout(()=>{V.value===t.eventId&&(V.value=null)},1800)}catch(e){console.error(`[ActivityFeed] failed to focus history event:`,e)}}let Ae=a(null),je=a(null),X=a(0);function Me(e){X.value=e.index}let Z=a(!1);async function Ne(){if(Z.value)return;Z.value=!0;let e=r.workspaceId;try{let t=Ee(P.value,X.value);if(t<0)for(let n=0;n<15;n++){if(e!==r.workspaceId)return;if(!fe())break;let n=P.value.length;if(await ve(),await d(),e!==r.workspaceId)return;if(X.value+=P.value.length-n,t=Ee(P.value,X.value),t>=0)break}t>=0&&e===r.workspaceId&&(Ae.value?.scrollTo(t,`start`),X.value=t)}finally{Z.value=!1}}async function Q(){H=!1,await d(),await J(0),await new Promise(e=>requestAnimationFrame(()=>requestAnimationFrame(()=>e()))),await J(0),H=!0}let $=b(()=>{let e=o.sessionIdsFor(r.workspaceId);if(!w.value)return e.length;let t=0;for(let n of e)A(n)&&t++;return t}),Pe=b(()=>o.eventsFor(r.workspaceId).length);async function Fe(){U.value=!0;let e=Date.now();await new Promise(e=>setTimeout(e,Dt));let t=Math.max(0,e+kt-Date.now());await De(Pe,e=>e>0,t),U.value=!1}c(U,async e=>{!e&&$.value>0&&await Q(),!e&&$.value===0&&w.value&&Re(),e||d(()=>{je.value=R.value?.getScrollTarget()??null})}),f(()=>{window.addEventListener(`kobo:focus-history-event`,ke),d(()=>{je.value=R.value?.getScrollTarget()??null}),Fe(),$.value>0&&Q(),w.value&&Re();let e=new URLSearchParams(window.location.hash.split(`?`)[1]??``).get(`eventId`);e&&(ke(new CustomEvent(`kobo:focus-history-event`,{detail:{workspaceId:r.workspaceId,sessionId:w.value,eventId:e}})),Oe())});let Ie=$.value>0;c($,async(e,t)=>{if(!B.value){if(!Ie&&e>0){Ie=!0,await Q();return}e>t&&z.value&&!B.value&&Se()}}),s(()=>{window.removeEventListener(`kobo:focus-history-event`,ke),Y!=null&&(cancelAnimationFrame(Y),Y=null)}),c(()=>r.workspaceId,()=>{z.value=!0,Ie=$.value>0,H=!1,Fe(),$.value>0&&Q()}),c(()=>v.selectedSessionId,async()=>{z.value=!0,H=!1,await Q(),Re()});let Le=new Set;async function Re(){let e=w.value;if(!e||$.value>0)return;let t=G(r.workspaceId,e);if(!Le.has(t)){Le.add(t);try{let t=await fetch(`/api/workspaces/${r.workspaceId}/events?session=${encodeURIComponent(e)}&limit=500`);if(!t.ok)return;let n=await t.json(),i=n.events??[];if(i.length===0)return;let a=i.filter(e=>e.type===`agent:event`&&e.workspaceId===r.workspaceId),s=i.filter(e=>e.type===`user:message`&&e.workspaceId===r.workspaceId),c=a.map(e=>e.payload),l=a.map(e=>e.createdAt),u=a.map(e=>e.sessionId??null),f=a.map(e=>e.id);K(r.workspaceId,e,n.hasMore),c.length>0&&o.prepend(r.workspaceId,c,l,{oldestId:i[0].id,hasMoreOlder:o.hasMoreOlderFor(r.workspaceId),sessionIds:u,eventIds:f});for(let e of s){let t=e.payload;typeof t.content==`string`&&v.addActivityItem(r.workspaceId,{id:e.id,type:`text`,content:t.content,timestamp:e.createdAt,sessionId:e.sessionId??void 0,meta:{sender:t.sender??`user`}})}await d(),await J(0)}catch(e){console.error(`[ActivityFeed] fetchSessionIfMissing failed:`,e),Le.delete(t)}}}let ze=b(()=>j.value.filter(e=>e.sender!==`system-prompt`).length);c(ze,async(e,t)=>{e>t&&(z.value=!0,await J(180))});async function Be(){z.value=!0,await J(250)}return(e,t)=>U.value?(l(),x(`div`,dt,[m(se,{size:`40px`,color:`primary`})])):(l(),x(`div`,ft,[C(`div`,{"data-testid":`activity-live-region`,class:`sr-only`,role:`log`,"aria-live":`polite`,"aria-atomic":`true`,"aria-label":e.$t(`activity.a11y.region`)},i(ce.value),9,pt),m(O,{name:`fade`},{default:p(()=>[y.value?(l(),x(`div`,mt,[m(se,{size:`18px`,color:`primary`}),C(`span`,ht,i(e.$t(`activity.compacting`)),1)])):g(``,!0)]),_:1}),m(ue,{ref_key:`scrollRef`,ref:R,class:`activity-feed-scroll`,onScroll:de},{default:p(()=>[B.value?(l(),x(`div`,gt,[m(M,{size:`sm`}),_(` `+i(e.$t(`activity.loading_older`)),1)])):g(``,!0),m(be,{ref_key:`virtualScrollRef`,ref:Ae,class:`q-pa-md`,items:P.value,"scroll-target":je.value??void 0,"virtual-scroll-item-size":160,"virtual-scroll-slice-size":20,onVirtualScroll:Me},{default:p(({item:e,index:t})=>[(l(),h(ut,{key:n(Te)(e),turn:e,"data-turn-index":t,highlighted:e.items.some(e=>e.eventIds?.includes(V.value??``)??!1),onScrollTo:we},null,8,[`turn`,`data-turn-index`,`highlighted`]))]),_:1},8,[`items`,`scroll-target`]),P.value.length===0&&F.value.length===0&&!B.value?(l(),x(`div`,_t,[m(k,{name:`forum`,size:`28px`}),C(`div`,vt,i(e.$t(`activity.empty`)),1),C(`div`,yt,i(e.$t(`activity.emptyHint`)),1)])):g(``,!0),F.value.length?(l(),x(`div`,bt,[m(le,{label:e.$t(`activity.raw_lines`,{n:F.value.length}),dense:``},{default:p(()=>[(l(!0),x(S,null,T(F.value,(e,t)=>(l(),x(`div`,{key:t,class:`text-caption text-kobo-3 q-pa-xs`},i(e),1))),128))]),_:1},8,[`label`])])):g(``,!0)]),_:1},512),C(`div`,xt,[z.value?g(``,!0):(l(),h(ee,{key:0,round:``,dense:``,unelevated:``,color:`kobo-surface-2`,"text-color":`kobo-1`,icon:`arrow_downward`,size:`sm`,class:`activity-feed-nav-btn`,title:e.$t(`activity.scroll_to_bottom`),"aria-label":e.$t(`activity.scroll_to_bottom`),onClick:Be},null,8,[`title`,`aria-label`])),m(ee,{round:``,dense:``,unelevated:``,color:`kobo-surface-2`,"text-color":`kobo-1`,icon:`arrow_upward`,size:`sm`,class:`activity-feed-nav-btn`,title:e.$t(`activity.prev_user_message`),"aria-label":e.$t(`activity.prev_user_message`),loading:Z.value,disable:Z.value,onClick:Ne},null,8,[`title`,`aria-label`,`loading`,`disable`])])]))}}),[[`__scopeId`,`data-v-01cd9f2a`]]);export{At as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{j as e,ut as t,v as n}from"./reactivity.esm-bundler-DEJj9cFv.js";import{L as r,P as i,Y as a,_ as o,d as s,f as c,p as l,r as u,u as d,y as f,z as p}from"./runtime-core.esm-bundler-CDnVUfeZ.js";import{t as m}from"./QBtn-CZaBclNw.js";import{Lt as h,n as g,zt as _}from"./index-CIP08MpU.js";import{a as v,i as y,n as b}from"./use-tours-DhLhglwr.js";import{n as x}from"./render-chat-markdown-Cb15kvox.js";import{t as S}from"./QChip-owciRuZu.js";import{t as C}from"./QPage-DaX9sMQC.js";import{t as w}from"./DrawerToggleButton-C_bNtZsE.js";import{t as T}from"./TourReplayButton-CdHpGBdr.js";var E={class:`row items-center q-mb-md`},D={class:`text-h6 q-ml-sm`},O={key:0,class:`text-kobo-3 text-center q-pa-lg`},k={key:1,class:`text-negative text-center q-pa-lg`},A={key:2,class:`text-kobo-3 text-center q-pa-lg`},j={key:3,class:`column q-gutter-md`,"data-tour":`changelog-list`},M={key:0,class:`text-caption text-kobo-3`},N={class:`row items-center q-mb-sm`},P={class:`text-subtitle1 text-kobo-2`,style:{"font-family":`var(--kobo-font-mono, monospace)`}},F=[`innerHTML`],I=v(f({__name:`ChangelogPage`,setup(f){let v=g(),{scheduleAutoRun:I}=b(),L=n([]),R=n(``),z=n(!1),B=n(null);function V(e){return x(e)}async function H(){z.value=!0,B.value=null;try{let e=await fetch(`/api/changelog`);if(!e.ok)throw Error(`HTTP ${e.status}`);let t=await e.json();R.value=t.currentVersion??``,L.value=t.versions??[]}catch(e){B.value=e instanceof Error?e.message:String(e)}finally{z.value=!1}}return i(()=>{H(),I(`changelog`)}),(n,i)=>(r(),s(C,{class:`q-pa-md`,style:{"max-width":`900px`,margin:`0 auto`}},{default:a(()=>[d(`div`,E,[o(w,{class:`q-mr-xs`}),o(m,{flat:``,dense:``,round:``,icon:`arrow_back`,onClick:i[0]||=t=>e(v).back()}),d(`div`,D,t(n.$t(`changelog.title`)),1),o(y),o(m,{flat:``,dense:``,icon:`refresh`,loading:z.value,label:n.$t(`common.refresh`),onClick:H},null,8,[`loading`,`label`]),o(T,{"tour-id":`changelog`,class:`q-ml-xs`})]),z.value&&L.value.length===0?(r(),l(`div`,O,t(n.$t(`common.loading`)),1)):B.value?(r(),l(`div`,k,t(B.value),1)):L.value.length===0?(r(),l(`div`,A,t(n.$t(`changelog.empty`)),1)):(r(),l(`div`,j,[R.value?(r(),l(`div`,M,t(n.$t(`changelog.currentVersion`,{version:R.value})),1)):c(``,!0),(r(!0),l(u,null,p(L.value,e=>(r(),s(h,{key:e.version,dark:``,flat:``,bordered:``},{default:a(()=>[o(_,null,{default:a(()=>[d(`div`,N,[d(`div`,P,` v`+t(e.version),1),e.version===R.value?(r(),s(S,{key:0,dense:``,size:`sm`,color:`primary`,"text-color":`kobo-1`,label:n.$t(`changelog.current`),class:`q-ml-sm`},null,8,[`label`])):c(``,!0)]),d(`div`,{class:`changelog-notes`,innerHTML:V(e.notes)},null,8,F)]),_:2},1024)]),_:2},1024))),128))]))]),_:1}))}}),[[`__scopeId`,`data-v-6137cb7c`]]);export{I as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{V as e,nt as t}from"./QBtn-CZaBclNw.js";import{m as n,p as r}from"./use-id-BIuaWKkB.js";function i(e){return e===!1?0:e===!0||e===void 0?1:Number.parseInt(e,10)||0}function a(e){let t=e.currentTarget,i=t.__qclosepopup;i!==0&&setTimeout(()=>{let a=n(t);a!==void 0&&r(a,e,i)},0)}function o(t){e(t,13)&&a(t)}var s=t({name:`close-popup`,beforeMount(e,{value:t}){e.__qclosepopup=i(t),e.addEventListener(`click`,a),e.addEventListener(`keyup`,o)},updated(e,{value:t,oldValue:n}){t!==n&&e.__qclosepopup!==void 0&&(e.__qclosepopup=i(t))},beforeUnmount(e){e.__qclosepopup!==void 0&&(e.removeEventListener(`click`,a),e.removeEventListener(`keyup`,o),e.__qclosepopup=void 0)}});export{s as t};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{ot as e,ut as t,v as n}from"./reactivity.esm-bundler-DEJj9cFv.js";import{J as r,L as i,P as a,Y as ee,_ as o,d as te,f as s,g as ne,l as c,p as l,r as u,u as d,y as f,z as re}from"./runtime-core.esm-bundler-CDnVUfeZ.js";import{t as p}from"./QBtn-CZaBclNw.js";import{n as ie,p as ae,xt as oe}from"./index-CIP08MpU.js";import{t as se}from"./QMarkupTable-B9QuKtK_.js";import{a as m,i as h}from"./use-tours-DhLhglwr.js";var g=[`workspace`,`engine`,`model`,`status`,`branch`,`tasks_done`,`tasks_total`,`commits`,`files_changed`,`insertions`,`deletions`,`agent_messages`,`user_messages`,`injected_prompts`,`questions`,`tool_calls`,`errors`,`input_tokens`,`output_tokens`,`sessions`,`duration_seconds`];function _(e){if(e==null)return``;let t=String(e);return/[",\r\n]/.test(t)?`"${t.replace(/"/g,`""`)}"`:t}function v(e){let t=e.map(e=>{let t=e.gitStats,n=e.activity;return[e.workspace.name,e.workspace.engine,e.workspace.model,e.workspace.status,e.workspace.workingBranch,e.tasks.done,e.tasks.total,t?.commitCount??null,t?.filesChanged??null,t?.insertions??null,t?.deletions??null,n.agentMessages,n.userMessages,n.injectedPrompts,n.questions,n.toolCalls,n.errors,n.inputTokens,n.outputTokens,n.sessions,Math.round(n.durationMs/1e3)].map(_).join(`,`)});return`${[g.join(`,`),...t].join(`\r
|
|
2
|
+
`)}`}var y={key:0,class:`comparison-panel q-pa-md`},b={class:`row items-center q-mb-sm`},x={class:`text-subtitle2`},S={class:`text-caption text-kobo-3 q-mb-sm`},C={class:`text-left`},w={class:`text-left`},T={class:`text-right`},E={class:`text-right`},D={class:`text-right`},O={class:`text-right`},k={class:`text-right`},A={class:`text-right`},j={class:`text-right`},M={class:`text-right`},N={class:`text-right`},P={class:`text-right`},F={class:`text-right`},I={class:`text-caption text-kobo-3 comparison-mono`},L={key:0,class:`text-caption text-kobo-3`},R={class:`text-right comparison-mono`},z={class:`text-right`},B={class:`text-right`},V={class:`text-right`},ce={key:0,class:`comparison-mono`},H={class:`text-kobo-success`},U={class:`text-kobo-danger q-ml-xs`},W={key:1,class:`text-kobo-3`},G={class:`text-right comparison-mono`},le=[`title`],ue={class:`text-right`},de={class:`text-right`},fe={class:`text-right comparison-mono`},pe=[`title`],me={class:`text-right`},he={class:`text-right comparison-mono`},ge={class:`text-right`},_e={key:1,class:`text-caption text-kobo-3`},ve={key:0,class:`text-caption text-kobo-warning q-mt-sm`},K=m(f({__name:`ComparisonPanel`,props:{workspaceId:{}},setup(f){let m=f;function g(e){return e<1e3?String(e):e<1e6?`${(e/1e3).toFixed(+(e<1e4))}k`:`${(e/1e6).toFixed(1)}M`}function _(e){if(e<=0)return`-`;let t=Math.round(e/1e3);if(t<60)return q(`reliability.durationSeconds`,{s:t});let n=Math.floor(t/60);return n<60?q(`reliability.durationMinutes`,{m:n,s:t%60}):q(`reliability.durationHours`,{h:Math.floor(n/60),m:n%60})}let K=ie(),ye=ae(),{t:q}=oe(),J=n([]),Y=n(!1),be={created:`workspaceStatus.created`,extracting:`workspaceStatus.extracting`,brainstorming:`workspaceStatus.brainstorming`,executing:`workspaceStatus.executing`,"awaiting-user":`workspaceStatus.awaitingUser`,completed:`workspaceStatus.completed`,idle:`workspaceStatus.idle`,error:`workspaceStatus.error`,quota:`workspaceStatus.quota`,retrying:`workspaceStatus.retrying`},X=n({});function xe(e){return X.value[e]??e}async function Se(){try{let e=await fetch(`/api/engines`);if(!e.ok)return;let t=await e.json();X.value=Object.fromEntries(t.map(e=>[e.id,e.displayName]))}catch{}}function Z(e){let t=be[e];return t?q(t):e}let Ce=c(()=>m.workspaceId);function Q(e){return e.workspace.id===Ce.value}function we(e){ye.selectWorkspace(e),K.push({name:`workspace`,params:{id:e}})}function Te(){let e=v(J.value),t=new Blob([e],{type:`text/csv;charset=utf-8`}),n=URL.createObjectURL(t),r=document.createElement(`a`);r.href=n,r.download=`kobo-comparison-${J.value[0]?.workspace.comparisonId??`comparison`}.csv`,r.click(),URL.revokeObjectURL(n)}async function $(){Y.value=!0;try{let e=await fetch(`/api/workspaces/${m.workspaceId}/comparison`);if(!e.ok){J.value=[];return}let t=await e.json();J.value=t.members??[]}catch{J.value=[]}finally{Y.value=!1}}return a(()=>{Se(),$()}),r(()=>m.workspaceId,$),(n,r)=>J.value.length>0?(i(),l(`div`,y,[d(`div`,b,[d(`div`,x,t(n.$t(`comparison.title`)),1),o(h),o(p,{flat:``,dense:``,round:``,size:`sm`,icon:`download`,title:n.$t(`comparison.exportCsv`),onClick:Te},null,8,[`title`]),o(p,{flat:``,dense:``,round:``,size:`sm`,icon:`refresh`,loading:Y.value,onClick:$},null,8,[`loading`])]),d(`div`,S,t(n.$t(`comparison.hint`)),1),o(se,{flat:``,dense:``,dark:``,class:`comparison-table`},{default:ee(()=>[d(`thead`,null,[d(`tr`,null,[d(`th`,C,t(n.$t(`comparison.engine`)),1),d(`th`,w,t(n.$t(`comparison.status`)),1),d(`th`,T,t(n.$t(`comparison.tasks`)),1),d(`th`,E,t(n.$t(`comparison.commits`)),1),d(`th`,D,t(n.$t(`comparison.files`)),1),d(`th`,O,t(n.$t(`comparison.diff`)),1),d(`th`,k,t(n.$t(`comparison.messages`)),1),d(`th`,A,t(n.$t(`comparison.questions`)),1),d(`th`,j,t(n.$t(`comparison.tools`)),1),d(`th`,M,t(n.$t(`comparison.errors`)),1),d(`th`,N,t(n.$t(`comparison.tokens`)),1),d(`th`,P,t(n.$t(`comparison.sessions`)),1),d(`th`,F,t(n.$t(`comparison.duration`)),1),r[0]||=d(`th`,null,null,-1)])]),d(`tbody`,null,[(i(!0),l(u,null,re(J.value,r=>(i(),l(`tr`,{key:r.workspace.id,class:e({"comparison-current":Q(r)})},[d(`td`,null,[d(`div`,null,t(xe(r.workspace.engine)),1),d(`div`,I,t(r.workspace.model),1)]),d(`td`,null,[ne(t(Z(r.workspace.status))+` `,1),r.workspace.archivedAt?(i(),l(`span`,L,` · `+t(n.$t(`comparison.archived`)),1)):s(``,!0)]),d(`td`,R,t(r.tasks.done)+`/`+t(r.tasks.total),1),d(`td`,z,t(r.gitStats?.commitCount??`-`),1),d(`td`,B,t(r.gitStats?.filesChanged??`-`),1),d(`td`,V,[r.gitStats?(i(),l(`span`,ce,[d(`span`,H,`+`+t(r.gitStats.insertions),1),d(`span`,U,`-`+t(r.gitStats.deletions),1)])):(i(),l(`span`,W,`-`))]),d(`td`,G,[d(`span`,{title:n.$t(`comparison.messagesTitle`)},t(r.activity.agentMessages)+` / `+t(r.activity.userMessages),9,le)]),d(`td`,ue,t(r.activity.questions),1),d(`td`,de,t(r.activity.toolCalls),1),d(`td`,{class:e([`text-right`,r.activity.errors>0?`text-kobo-danger`:``])},t(r.activity.errors),3),d(`td`,fe,[d(`span`,{title:n.$t(`comparison.tokensTitle`)},t(g(r.activity.inputTokens))+` / `+t(g(r.activity.outputTokens)),9,pe)]),d(`td`,me,t(r.activity.sessions),1),d(`td`,he,t(_(r.activity.durationMs)),1),d(`td`,ge,[Q(r)?(i(),l(`span`,_e,t(n.$t(`comparison.current`)),1)):(i(),te(p,{key:0,flat:``,dense:``,"no-caps":``,size:`sm`,label:n.$t(`comparison.open`),onClick:e=>we(r.workspace.id)},null,8,[`label`,`onClick`]))])],2))),128))])]),_:1}),J.value.length<2?(i(),l(`div`,ve,t(n.$t(`comparison.incomplete`)),1)):s(``,!0)])):s(``,!0)}}),[[`__scopeId`,`data-v-95e8c6b5`]]);export{K as default};
|