@loicngr/kobo 2.1.25 → 2.1.28
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 +30 -3
- package/CHANGELOG.md +15 -0
- package/CONFIGURATION.md +156 -49
- package/README.md +2 -1
- package/SECURITY.md +33 -6
- package/dist/mcp-server/kobo-tasks-server.js +36 -1021
- package/dist/mcp-server/managed-mcp-dispatcher.js +960 -0
- package/dist/mcp-server/workspace-dialogue-client.js +26 -1
- package/dist/server/index.js +52 -19
- package/dist/server/middleware/host-check-middleware.js +8 -5
- package/dist/server/middleware/network-auth-middleware.js +51 -29
- package/dist/server/middleware/security-headers-middleware.js +19 -0
- package/dist/server/middleware/traffic-limits-middleware.js +74 -0
- package/dist/server/routes/auth.js +73 -0
- package/dist/server/routes/internal-mcp.js +137 -0
- package/dist/server/routes/mcp-credentials.js +30 -0
- package/dist/server/routes/mcp.js +156 -76
- package/dist/server/routes/usage.js +24 -0
- package/dist/server/routes/workspaces.js +19 -3
- package/dist/server/services/agent/orchestrator.js +27 -17
- package/dist/server/services/external-mcp-credentials-service.js +110 -0
- package/dist/server/services/forge/github/provider.js +15 -3
- package/dist/server/services/forge/types.js +8 -0
- package/dist/server/services/internal-mcp-capability-service.js +98 -0
- package/dist/server/services/mcp-connection-info-service.js +1 -1
- package/dist/server/services/memory-mcp-service.js +9 -7
- package/dist/server/services/memory-service.js +15 -0
- package/dist/server/services/network-access-service.js +43 -73
- package/dist/server/services/network-session-service.js +182 -0
- package/dist/server/services/provider-accounts-service.js +202 -0
- package/dist/server/services/settings-service.js +29 -7
- package/dist/server/services/terminal-websocket-service.js +5 -0
- package/dist/server/services/usage/poller.js +44 -3
- package/dist/server/services/websocket-service.js +5 -0
- package/dist/server/utils/paths.js +2 -1
- package/dist/server/utils/startup-display.js +1 -2
- package/dist/shared/models.js +6 -0
- package/dist/shared/provider-accounts.js +1 -0
- package/docker-compose.vps.yml +6 -0
- package/docs/deployment-vps.md +24 -12
- package/package.json +1 -1
- package/src/client/dist/pwa/assets/ActivityFeed-D58VS9o2.js +2 -0
- package/src/client/dist/pwa/assets/{AutoLoopFinalReviewSettings-Dafi7Tue.js → AutoLoopFinalReviewSettings-CK3r_fVB.js} +1 -1
- package/src/client/dist/pwa/assets/{ChangelogPage-DH-zUtlf.js → ChangelogPage-CAAUEn5X.js} +1 -1
- package/src/client/dist/pwa/assets/ComparisonPanel-BCb7vB5E.js +2 -0
- package/src/client/dist/pwa/assets/CreatePage-DasH1Uk4.js +2 -0
- package/src/client/dist/pwa/assets/{DashboardPage-B8gTooRi.js → DashboardPage-XE52EABP.js} +1 -1
- package/src/client/dist/pwa/assets/DiffViewer-CncKlXne.js +8 -0
- package/src/client/dist/pwa/assets/{DocumentsPanel-DXjjhGUg.js → DocumentsPanel-L4pbQfz1.js} +1 -1
- package/src/client/dist/pwa/assets/{DrawerToggleButton-CiVNMTbq.js → DrawerToggleButton-DiOJm5he.js} +1 -1
- package/src/client/dist/pwa/assets/GitPanel-WJlzy2VJ.js +3 -0
- package/src/client/dist/pwa/assets/{GitPanel-CRaLJzjc.css → GitPanel-yHvU38dp.css} +1 -1
- package/src/client/dist/pwa/assets/{HealthPage-lc2Cuuks.js → HealthPage-hJY5MurK.js} +1 -1
- package/src/client/dist/pwa/assets/MainLayout-DgWElK7w.js +2 -0
- package/src/client/dist/pwa/assets/{MemoryPanel-SrR94UVW.js → MemoryPanel-DtEvh_b7.js} +1 -1
- package/src/client/dist/pwa/assets/{MemoryProposalList-B5uVf3KU.js → MemoryProposalList-kgFCm9zz.js} +1 -1
- package/src/client/dist/pwa/assets/{QBtnDropdown-Cxr3lBPB.js → QBtnDropdown-CWPIugs8.js} +1 -1
- package/src/client/dist/pwa/assets/{QExpansionItem-xIXDevKz.js → QExpansionItem-D-EGL2b3.js} +1 -1
- package/src/client/dist/pwa/assets/{QScrollArea-DeczMHVs.js → QScrollArea-BMrqK-xd.js} +1 -1
- package/src/client/dist/pwa/assets/QSelect-DFWyDWCE.js +1 -0
- package/src/client/dist/pwa/assets/{QTooltip-be9mqhmB.js → QTooltip-Bk8dLUtU.js} +1 -1
- package/src/client/dist/pwa/assets/{SchedulePanel-R6H-D-m5.js → SchedulePanel-CuxmxCKi.js} +1 -1
- package/src/client/dist/pwa/assets/{SearchPage-CaeYoIDw.js → SearchPage-DKnQPUqY.js} +1 -1
- package/src/client/dist/pwa/assets/{SettingsPage-Dcq1Jeoo.css → SettingsPage-B4rmd2WX.css} +1 -1
- package/src/client/dist/pwa/assets/SettingsPage-CO3vIpPd.js +9 -0
- package/src/client/dist/pwa/assets/{SplitWorkspacePage-Dw1qUIFU.js → SplitWorkspacePage-WhQyX8fn.js} +1 -1
- package/src/client/dist/pwa/assets/{TerminalPanel-Dqw88QqH.js → TerminalPanel-BY0lPq6k.js} +10 -10
- package/src/client/dist/pwa/assets/{TerminalPanel-Dz9EE-Ph.css → TerminalPanel-BcbwuFVR.css} +1 -1
- package/src/client/dist/pwa/assets/{ToolCallItem-BMn7eela.js → ToolCallItem-DYubi-J0.js} +3 -3
- package/src/client/dist/pwa/assets/{TouchPan-BwCpe_Oh.js → TouchPan-DDotyYLN.js} +1 -1
- package/src/client/dist/pwa/assets/{TourReplayButton-yeGYf6xT.js → TourReplayButton-kAnEMcy2.js} +1 -1
- package/src/client/dist/pwa/assets/{WorkspaceAttentionLabels-BJPw65vU.js → WorkspaceAttentionLabels-DiYt3rP3.js} +1 -1
- package/src/client/dist/pwa/assets/WorkspacePage-BEBwwr81.css +1 -0
- package/src/client/dist/pwa/assets/WorkspacePage-K-1ce50d.js +4 -0
- package/src/client/dist/pwa/assets/{browser-C9U-mSDm.js → browser-DVYLH8vl.js} +1 -1
- package/src/client/dist/pwa/assets/{build-path-tree-DYdRW2T4.js → build-path-tree-DEuOdIIk.js} +1 -1
- package/src/client/dist/pwa/assets/custom-sounds-Ggw6V9iF.js +1 -0
- package/src/client/dist/pwa/assets/de-Bk3zQrin.js +17 -0
- package/src/client/dist/pwa/assets/{editor.api-iu5YkEl3.js → editor.api-BC38eXAS.js} +1 -1
- package/src/client/dist/pwa/assets/{efforts-ja6uJadn.js → efforts-TGacUPcJ.js} +1 -1
- package/src/client/dist/pwa/assets/es-f3xuAWA9.js +17 -0
- package/src/client/dist/pwa/assets/{expand-template-BbgnJ8YH.js → expand-template-DM8gq3Mo.js} +1 -1
- package/src/client/dist/pwa/assets/{format-CLXLy6Ov.js → format-OwxXBnDJ.js} +1 -1
- package/src/client/dist/pwa/assets/{formatters-DeKgxYJe.js → formatters-YgaCZcwJ.js} +1 -1
- package/src/client/dist/pwa/assets/fr-B6c21SG3.js +17 -0
- package/src/client/dist/pwa/assets/{freemarker2-2IrnDDvu.js → freemarker2-l7R42fI2.js} +1 -1
- package/src/client/dist/pwa/assets/{handlebars-CeLN7FCP.js → handlebars-DFmPrgjH.js} +1 -1
- package/src/client/dist/pwa/assets/{html-Bn3rhcp3.js → html-CERIAlPM.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-WaE4cZK9.js +1 -0
- package/src/client/dist/pwa/assets/index-CpWBGKdM.js +20 -0
- package/src/client/dist/pwa/assets/it-DUstDpC2.js +17 -0
- package/src/client/dist/pwa/assets/{javascript-BMvON8oT.js → javascript-BBYncplF.js} +1 -1
- package/src/client/dist/pwa/assets/{liquid-D_AQLvQl.js → liquid-C1HqYyYU.js} +1 -1
- package/src/client/dist/pwa/assets/{mdx-DDJzMB8Q.js → mdx-E74dsP22.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco-contributions-DFKwR-_X.js → monaco-contributions-DHvm-kp5.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco.contribution-4AwFLnjV.js → monaco.contribution-DYdn9CwR.js} +2 -2
- package/src/client/dist/pwa/assets/network-auth-DTZUKbny.js +1 -0
- package/src/client/dist/pwa/assets/{notify-theme-CMzdjL91.js → notify-theme-FuTL7HuS.js} +1 -1
- package/src/client/dist/pwa/assets/{permissionModes-DLWiBfdb.js → permissionModes-D-Nexnoo.js} +1 -1
- package/src/client/dist/pwa/assets/{project-color-C5mEKdlH.js → project-color-_snXWYUp.js} +1 -1
- package/src/client/dist/pwa/assets/{python-BOqcp9xc.js → python-DNAAGBcX.js} +1 -1
- package/src/client/dist/pwa/assets/{razor-BrGwBzd7.js → razor-CB3g5p_T.js} +1 -1
- package/src/client/dist/pwa/assets/{typescript-mOGf86uY.js → typescript-0kpz8bqP.js} +1 -1
- package/src/client/dist/pwa/assets/{use-search-index-status-P7O7EaV_.js → use-search-index-status-D38yFOD_.js} +1 -1
- package/src/client/dist/pwa/assets/{use-tours-CMFPIW6f.js → use-tours-BWkHp34X.js} +2 -2
- package/src/client/dist/pwa/assets/{use-worktree-restore-DF6P7h8g.js → use-worktree-restore-DUjZr44N.js} +1 -1
- package/src/client/dist/pwa/assets/{workspace-templates-C_bgIQvs.js → workspace-templates-D-lPJQ4G.js} +1 -1
- package/src/client/dist/pwa/assets/{xml-YcMBzYAm.js → xml-BbOuqdUe.js} +1 -1
- package/src/client/dist/pwa/assets/{yaml-BP_67Sds.js → yaml-gasFQtfk.js} +1 -1
- package/src/client/dist/pwa/index.html +1 -1
- package/src/client/dist/pwa/sw.js +1 -1
- package/src/mcp-server/README.md +13 -19
- package/src/mcp-server/kobo-tasks-server.ts +42 -1146
- package/src/mcp-server/managed-mcp-dispatcher.ts +1071 -0
- package/src/mcp-server/workspace-dialogue-client.ts +26 -1
- package/src/client/dist/pwa/assets/ActivityFeed-xJMPnXKA.js +0 -2
- package/src/client/dist/pwa/assets/ComparisonPanel-DwaQyEgb.js +0 -2
- package/src/client/dist/pwa/assets/CreatePage-DyF_efbs.js +0 -2
- package/src/client/dist/pwa/assets/DiffViewer-BivWNITj.js +0 -8
- package/src/client/dist/pwa/assets/GitPanel-DQjgGtoN.js +0 -3
- package/src/client/dist/pwa/assets/MainLayout-D3pGcTx8.js +0 -2
- package/src/client/dist/pwa/assets/QSelect-GQAsf7pB.js +0 -1
- package/src/client/dist/pwa/assets/SettingsPage-BCWdQHS5.js +0 -9
- package/src/client/dist/pwa/assets/WorkspacePage-BHO1Q8PG.js +0 -4
- package/src/client/dist/pwa/assets/WorkspacePage-BnklZAYZ.css +0 -1
- package/src/client/dist/pwa/assets/custom-sounds-DPsfYErA.js +0 -1
- package/src/client/dist/pwa/assets/de-Bqil7RJE.js +0 -17
- package/src/client/dist/pwa/assets/es-CNcMm1Zz.js +0 -17
- package/src/client/dist/pwa/assets/fr-BObHEohg.js +0 -17
- package/src/client/dist/pwa/assets/i18n-Df_y96N2.js +0 -1
- package/src/client/dist/pwa/assets/index-CH4JpI2a.js +0 -20
- package/src/client/dist/pwa/assets/it-DDMC7Emk.js +0 -17
- package/src/client/dist/pwa/assets/network-auth-CMERg4Qk.js +0 -1
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { withUsageAccountSwitch } from './usage/poller.js';
|
|
5
|
+
const ERRORS = {
|
|
6
|
+
accounts_unknown_provider: [404, 'Unknown account provider.'],
|
|
7
|
+
accounts_invalid_account: [400, 'Invalid account selection.'],
|
|
8
|
+
accounts_not_found: [404, 'Account is no longer available.'],
|
|
9
|
+
accounts_disabled: [409, 'Account is disabled.'],
|
|
10
|
+
accounts_unavailable: [503, 'Account switching is unavailable.'],
|
|
11
|
+
accounts_unsupported_environment: [409, 'Account switching is unsupported in this runtime environment.'],
|
|
12
|
+
accounts_command_failed: [502, 'Account operation failed.'],
|
|
13
|
+
accounts_busy: [429, 'Account operations are busy.'],
|
|
14
|
+
};
|
|
15
|
+
export class ProviderAccountsError extends Error {
|
|
16
|
+
code;
|
|
17
|
+
status;
|
|
18
|
+
constructor(code) {
|
|
19
|
+
super(ERRORS[code][1]);
|
|
20
|
+
this.code = code;
|
|
21
|
+
this.status = ERRORS[code][0];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// ccswap shares backup/config state across providers. Keep validation and the
|
|
25
|
+
// post-switch read in the same queue, not just the mutating child process.
|
|
26
|
+
let operations = Promise.resolve();
|
|
27
|
+
let queued = 0;
|
|
28
|
+
function serialize(operation) {
|
|
29
|
+
if (queued >= 32)
|
|
30
|
+
return Promise.reject(new ProviderAccountsError('accounts_busy'));
|
|
31
|
+
queued++;
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
let expired = false;
|
|
34
|
+
const timeout = setTimeout(() => {
|
|
35
|
+
expired = true;
|
|
36
|
+
reject(new ProviderAccountsError('accounts_busy'));
|
|
37
|
+
}, 5_000);
|
|
38
|
+
const result = operations
|
|
39
|
+
.then(async () => {
|
|
40
|
+
clearTimeout(timeout);
|
|
41
|
+
// The caller has already received busy; never perform a delayed switch.
|
|
42
|
+
if (expired)
|
|
43
|
+
return;
|
|
44
|
+
resolve(await operation());
|
|
45
|
+
})
|
|
46
|
+
.catch(reject);
|
|
47
|
+
operations = result.finally(() => {
|
|
48
|
+
queued--;
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function providerId(value) {
|
|
53
|
+
if (value !== 'claude-code' && value !== 'codex')
|
|
54
|
+
throw new ProviderAccountsError('accounts_unknown_provider');
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
function assertRuntime(provider) {
|
|
58
|
+
if (provider !== 'claude-code')
|
|
59
|
+
return;
|
|
60
|
+
// ccswap 0.37 writes the regular active store, not Claude's separate secure
|
|
61
|
+
// store override or macOS's hashed Keychain service for ANY nonempty
|
|
62
|
+
// CLAUDE_CONFIG_DIR, even when it explicitly names the default directory.
|
|
63
|
+
const config = process.env.CLAUDE_CONFIG_DIR;
|
|
64
|
+
if (process.env.CLAUDE_SECURESTORAGE_CONFIG_DIR !== undefined || (process.platform === 'darwin' && config)) {
|
|
65
|
+
throw new ProviderAccountsError('accounts_unsupported_environment');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function run(args, allowEmpty = false) {
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
// Inherit the actual server/container identity and homes. Never invoke a
|
|
71
|
+
// shell, docker, SSH, or resolve a binary relative to a workspace checkout.
|
|
72
|
+
execFile('ccswap', args, {
|
|
73
|
+
encoding: 'utf8',
|
|
74
|
+
shell: false,
|
|
75
|
+
killSignal: 'SIGKILL',
|
|
76
|
+
timeout: 30_000,
|
|
77
|
+
maxBuffer: 1024 * 1024,
|
|
78
|
+
cwd: os.homedir(),
|
|
79
|
+
env: process.env,
|
|
80
|
+
}, (error, stdout) => {
|
|
81
|
+
if (error) {
|
|
82
|
+
reject(new ProviderAccountsError(error.code === 'ENOENT' ? 'accounts_unavailable' : 'accounts_command_failed'));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (allowEmpty && !stdout.trim()) {
|
|
86
|
+
resolve(null);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
resolve(JSON.parse(stdout));
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
reject(new ProviderAccountsError('accounts_command_failed'));
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function object(value) {
|
|
99
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
100
|
+
}
|
|
101
|
+
function display(value) {
|
|
102
|
+
return typeof value === 'string'
|
|
103
|
+
? value
|
|
104
|
+
.replace(/[\p{Cc}\p{Cf}]/gu, '')
|
|
105
|
+
.trim()
|
|
106
|
+
.slice(0, 320)
|
|
107
|
+
: '';
|
|
108
|
+
}
|
|
109
|
+
async function list(provider) {
|
|
110
|
+
const home = provider === 'codex' ? process.env.CODEX_HOME : process.env.CLAUDE_CONFIG_DIR;
|
|
111
|
+
// Agent subprocesses run in their worktrees, the quota provider in the server
|
|
112
|
+
// cwd, and ccswap in HOME. Relative (including empty) overrides cannot name
|
|
113
|
+
// the same credential store reliably. Require an absolute path or no override.
|
|
114
|
+
if (home !== undefined && !path.isAbsolute(home)) {
|
|
115
|
+
throw new ProviderAccountsError('accounts_unsupported_environment');
|
|
116
|
+
}
|
|
117
|
+
const raw = await run(provider === 'codex' ? ['codex', 'list', '--json'] : ['list', '--provider', 'claude', '--json']);
|
|
118
|
+
if (!object(raw) ||
|
|
119
|
+
!Array.isArray(raw.accounts) ||
|
|
120
|
+
raw.accounts.length > 1000 ||
|
|
121
|
+
(provider === 'claude-code' ? raw.schemaVersion !== 1 : raw.provider !== 'codex')) {
|
|
122
|
+
throw new ProviderAccountsError('accounts_command_failed');
|
|
123
|
+
}
|
|
124
|
+
const seen = new Set();
|
|
125
|
+
const accounts = raw.accounts.map((row) => {
|
|
126
|
+
if (!object(row) ||
|
|
127
|
+
typeof row.number !== 'number' ||
|
|
128
|
+
!Number.isSafeInteger(row.number) ||
|
|
129
|
+
row.number < 1 ||
|
|
130
|
+
typeof row.active !== 'boolean' ||
|
|
131
|
+
(row.disabled !== undefined && typeof row.disabled !== 'boolean')) {
|
|
132
|
+
throw new ProviderAccountsError('accounts_command_failed');
|
|
133
|
+
}
|
|
134
|
+
const id = String(row.number);
|
|
135
|
+
if (seen.has(id))
|
|
136
|
+
throw new ProviderAccountsError('accounts_command_failed');
|
|
137
|
+
seen.add(id);
|
|
138
|
+
return {
|
|
139
|
+
id,
|
|
140
|
+
email: display(row.email),
|
|
141
|
+
label: display(provider === 'codex' ? row.label : row.alias) || display(row.organizationName),
|
|
142
|
+
active: row.active,
|
|
143
|
+
disabled: row.disabled === true,
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
return { available: true, accounts };
|
|
147
|
+
}
|
|
148
|
+
export async function listProviderAccounts(value) {
|
|
149
|
+
const provider = providerId(value);
|
|
150
|
+
return serialize(async () => {
|
|
151
|
+
try {
|
|
152
|
+
return await list(provider);
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
if (error instanceof ProviderAccountsError && error.code === 'accounts_unavailable')
|
|
156
|
+
return { available: false, accounts: [] };
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
export function validateAccountId(accountId) {
|
|
162
|
+
if (typeof accountId !== 'string' || !/^[1-9]\d{0,15}$/.test(accountId) || !Number.isSafeInteger(Number(accountId))) {
|
|
163
|
+
throw new ProviderAccountsError('accounts_invalid_account');
|
|
164
|
+
}
|
|
165
|
+
return accountId;
|
|
166
|
+
}
|
|
167
|
+
export async function switchProviderAccount(value, accountId) {
|
|
168
|
+
const provider = providerId(value);
|
|
169
|
+
const selectedId = validateAccountId(accountId);
|
|
170
|
+
return serialize(async () => {
|
|
171
|
+
assertRuntime(provider);
|
|
172
|
+
const current = await list(provider);
|
|
173
|
+
const selected = current.accounts.find((account) => account.id === selectedId);
|
|
174
|
+
if (!selected)
|
|
175
|
+
throw new ProviderAccountsError('accounts_not_found');
|
|
176
|
+
if (selected.disabled)
|
|
177
|
+
throw new ProviderAccountsError('accounts_disabled');
|
|
178
|
+
const change = async () => {
|
|
179
|
+
const raw = await run(provider === 'codex' ? ['codex', 'switch', selectedId, '--json'] : ['switch', selectedId, '--json'], provider === 'codex');
|
|
180
|
+
// ccswap 0.37's Codex dispatcher discards the switch return value.
|
|
181
|
+
// Empty stdout is accepted only here; the following list must confirm it.
|
|
182
|
+
if (provider === 'codex' && raw === null)
|
|
183
|
+
return;
|
|
184
|
+
if (!object(raw) ||
|
|
185
|
+
typeof raw.switched !== 'boolean' ||
|
|
186
|
+
!object(raw.to) ||
|
|
187
|
+
raw.to.number !== Number(selectedId) ||
|
|
188
|
+
(provider === 'claude-code' && raw.schemaVersion !== 1)) {
|
|
189
|
+
throw new ProviderAccountsError('accounts_command_failed');
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
if (provider === 'claude-code')
|
|
193
|
+
await withUsageAccountSwitch(provider, change);
|
|
194
|
+
else
|
|
195
|
+
await change();
|
|
196
|
+
const updated = await list(provider);
|
|
197
|
+
if (!updated.accounts.some((account) => account.id === selectedId && account.active)) {
|
|
198
|
+
throw new ProviderAccountsError('accounts_command_failed');
|
|
199
|
+
}
|
|
200
|
+
return updated;
|
|
201
|
+
});
|
|
202
|
+
}
|
|
@@ -12,6 +12,7 @@ import { getSettingsPath } from '../utils/paths.js';
|
|
|
12
12
|
import { InvalidWorktreesPathError, resolveGlobalWorktreesRoot, sanitizeWorktreesPath, validateWorktreesPath, } from '../utils/worktree-paths.js';
|
|
13
13
|
import { DEFAULT_NOTION_INITIAL_PROMPT, DEFAULT_SENTRY_INITIAL_PROMPT } from './initial-prompt-template-service.js';
|
|
14
14
|
import { LEGACY_AGNOSTIC_AUTO_LOOP_REVIEW_GATE, LEGACY_AGNOSTIC_REVIEW_TEMPLATE, LEGACY_CI_FIX_PROMPT_TEMPLATE, LEGACY_FINALIZATION_PROMPT, LEGACY_NOTION_INITIAL_PROMPT, LEGACY_PR_PROMPT_TEMPLATE, LEGACY_SENTRY_INITIAL_PROMPT, } from './legacy-public-prompts.js';
|
|
15
|
+
import { revokeNetworkSessions } from './network-session-service.js';
|
|
15
16
|
import { DEFAULT_REVIEW_PROMPT_TEMPLATE } from './review-template-service.js';
|
|
16
17
|
import { DEFAULT_CHANGE_SOURCE_BRANCH_SCRIPT, DEFAULT_MODEL_BY_ENGINE, FRESH_MODEL_BY_ENGINE, } from './settings-defaults.js';
|
|
17
18
|
import { AGNOSTIC_PROMPTS } from './skill-suite-prompts.js';
|
|
@@ -1293,6 +1294,7 @@ function readSettings() {
|
|
|
1293
1294
|
}
|
|
1294
1295
|
function readSettingsFromDisk() {
|
|
1295
1296
|
ensureSettingsPathInitialized();
|
|
1297
|
+
protectSettingsFiles();
|
|
1296
1298
|
if (!fs.existsSync(settingsFilePath)) {
|
|
1297
1299
|
const defaults = defaultSettings();
|
|
1298
1300
|
writeSettings(defaults);
|
|
@@ -1334,6 +1336,22 @@ function readSettingsFromDisk() {
|
|
|
1334
1336
|
}
|
|
1335
1337
|
/** Number of `settings.json.backup-*` files kept; older ones are rotated out. */
|
|
1336
1338
|
const SETTINGS_BACKUP_KEEP = 5;
|
|
1339
|
+
/** Repair legacy permissions before reading secrets, without following file symlinks. */
|
|
1340
|
+
function protectSettingsFiles() {
|
|
1341
|
+
const dir = path.dirname(settingsFilePath);
|
|
1342
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
1343
|
+
fs.chmodSync(dir, 0o700);
|
|
1344
|
+
const basename = path.basename(settingsFilePath);
|
|
1345
|
+
for (const name of fs.readdirSync(dir)) {
|
|
1346
|
+
if (name !== basename && name !== `${basename}.tmp` && !name.startsWith(`${basename}.backup-`))
|
|
1347
|
+
continue;
|
|
1348
|
+
const file = path.join(dir, name);
|
|
1349
|
+
const stat = fs.lstatSync(file);
|
|
1350
|
+
if (!stat.isFile() || stat.nlink !== 1)
|
|
1351
|
+
throw new Error('[settings] Refusing unsafe settings file');
|
|
1352
|
+
fs.chmodSync(file, 0o600);
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1337
1355
|
/**
|
|
1338
1356
|
* Keep only the `SETTINGS_BACKUP_KEEP` most recent `settings.json.backup-*`
|
|
1339
1357
|
* files in `dir`, deleting the rest. Best-effort — never throws.
|
|
@@ -1368,7 +1386,7 @@ function createSettingsBackupIfPresent() {
|
|
|
1368
1386
|
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
1369
1387
|
settingsBackupSequence += 1;
|
|
1370
1388
|
const backupPath = path.join(dir, `settings.json.backup-${stamp}-${settingsBackupSequence}`);
|
|
1371
|
-
fs.
|
|
1389
|
+
fs.writeFileSync(backupPath, fs.readFileSync(settingsFilePath), { mode: 0o600, flag: 'wx' });
|
|
1372
1390
|
pruneSettingsBackups(dir);
|
|
1373
1391
|
}
|
|
1374
1392
|
function writeSettings(settings, options) {
|
|
@@ -1376,14 +1394,11 @@ function writeSettings(settings, options) {
|
|
|
1376
1394
|
invalidateSettingsCache();
|
|
1377
1395
|
ensureSettingsPathInitialized();
|
|
1378
1396
|
const tmpPath = `${settingsFilePath}.tmp`;
|
|
1379
|
-
|
|
1380
|
-
if (!fs.existsSync(dir)) {
|
|
1381
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
1382
|
-
}
|
|
1397
|
+
protectSettingsFiles();
|
|
1383
1398
|
if (options?.backup) {
|
|
1384
1399
|
createSettingsBackupIfPresent();
|
|
1385
1400
|
}
|
|
1386
|
-
fs.writeFileSync(tmpPath, JSON.stringify(settings, null, 2), 'utf-8');
|
|
1401
|
+
fs.writeFileSync(tmpPath, JSON.stringify(settings, null, 2), { encoding: 'utf-8', mode: 0o600 });
|
|
1387
1402
|
fs.renameSync(tmpPath, settingsFilePath);
|
|
1388
1403
|
}
|
|
1389
1404
|
/** Read and return the full settings object, creating defaults if missing. */
|
|
@@ -1823,7 +1838,9 @@ export function updateGlobalSettings(input) {
|
|
|
1823
1838
|
* (which would leave the server bound wide but unauthenticatable after a restart).
|
|
1824
1839
|
*/
|
|
1825
1840
|
export function updateNetworkAccessSettings(patch) {
|
|
1826
|
-
const
|
|
1841
|
+
const current = readSettings();
|
|
1842
|
+
// Keep cached policy intact until persistence succeeds.
|
|
1843
|
+
const settings = { ...current, global: { ...current.global } };
|
|
1827
1844
|
if (typeof patch.networkAccessEnabled === 'boolean') {
|
|
1828
1845
|
settings.global.networkAccessEnabled = patch.networkAccessEnabled;
|
|
1829
1846
|
}
|
|
@@ -1834,6 +1851,11 @@ export function updateNetworkAccessSettings(patch) {
|
|
|
1834
1851
|
settings.global.networkAccessBehindProxy = patch.networkAccessBehindProxy;
|
|
1835
1852
|
}
|
|
1836
1853
|
writeSettings(settings, { backup: true });
|
|
1854
|
+
if (current.global.networkAccessEnabled !== settings.global.networkAccessEnabled ||
|
|
1855
|
+
current.global.networkAccessToken !== settings.global.networkAccessToken ||
|
|
1856
|
+
current.global.networkAccessBehindProxy !== settings.global.networkAccessBehindProxy) {
|
|
1857
|
+
revokeNetworkSessions();
|
|
1858
|
+
}
|
|
1837
1859
|
return settings.global;
|
|
1838
1860
|
}
|
|
1839
1861
|
function ensureGlobalWorktreesRootExists(worktreesPath) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { assertWorkspaceLifecycleAvailable, isWorkspaceLifecycleBusy } from '../utils/workspace-lifecycle-guard.js';
|
|
2
|
+
import { acceptNetworkSocketMessage, isNetworkSocketAuthorized } from './network-session-service.js';
|
|
2
3
|
import { createTerminal, getTerminal } from './terminal-service.js';
|
|
3
4
|
import { getWorkspace } from './workspace-service.js';
|
|
4
5
|
const MAX_BUFFERED_BYTES = 1024 * 1024;
|
|
@@ -31,6 +32,8 @@ export function handleTerminalConnection(ws, workspaceId, isShuttingDown = () =>
|
|
|
31
32
|
ws.terminate();
|
|
32
33
|
}
|
|
33
34
|
function send(data) {
|
|
35
|
+
if (!isNetworkSocketAuthorized(ws))
|
|
36
|
+
return false;
|
|
34
37
|
if (disposed || ws.readyState !== 1)
|
|
35
38
|
return false;
|
|
36
39
|
if (ws.bufferedAmount + Buffer.byteLength(data) > MAX_BUFFERED_BYTES) {
|
|
@@ -72,6 +75,8 @@ export function handleTerminalConnection(ws, workspaceId, isShuttingDown = () =>
|
|
|
72
75
|
ws.on('close', dispose);
|
|
73
76
|
ws.on('error', disconnect);
|
|
74
77
|
ws.on('message', (data, isBinary) => {
|
|
78
|
+
if (!acceptNetworkSocketMessage(ws))
|
|
79
|
+
return;
|
|
75
80
|
if (disposed || ws.readyState !== 1 || isShuttingDown())
|
|
76
81
|
return;
|
|
77
82
|
if (isBinary) {
|
|
@@ -8,17 +8,23 @@ export const POLL_INTERVAL_MS = 5 * 60_000;
|
|
|
8
8
|
const DEFAULT_PROVIDERS = [createClaudeCodeProvider()];
|
|
9
9
|
let providers = DEFAULT_PROVIDERS;
|
|
10
10
|
let intervalHandle = null;
|
|
11
|
+
const generations = new Map();
|
|
12
|
+
const switchingProviders = new Set();
|
|
13
|
+
const latest = new Map();
|
|
11
14
|
function persistAndBroadcast(snap) {
|
|
12
15
|
upsertUsageSnapshot(snap);
|
|
16
|
+
latest.set(snap.providerId, snap);
|
|
13
17
|
broadcastAll('usage:snapshot', { providerId: snap.providerId, snapshot: snap });
|
|
14
18
|
}
|
|
15
19
|
async function tick() {
|
|
16
20
|
for (const provider of providers) {
|
|
17
21
|
try {
|
|
18
|
-
|
|
22
|
+
const generation = generations.get(provider.id) ?? 0;
|
|
23
|
+
if (switchingProviders.has(provider.id) || !(await provider.isAvailable()))
|
|
19
24
|
continue;
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
if (generation !== (generations.get(provider.id) ?? 0))
|
|
26
|
+
continue;
|
|
27
|
+
await refreshNow(provider.id);
|
|
22
28
|
}
|
|
23
29
|
catch (err) {
|
|
24
30
|
console.error('[usage-poller] tick failed for provider', provider.id, err);
|
|
@@ -45,11 +51,46 @@ export async function refreshNow(providerId) {
|
|
|
45
51
|
const provider = providers.find((p) => p.id === providerId);
|
|
46
52
|
if (!provider)
|
|
47
53
|
return null;
|
|
54
|
+
if (switchingProviders.has(providerId))
|
|
55
|
+
return latest.get(providerId) ?? null;
|
|
56
|
+
const generation = generations.get(providerId) ?? 0;
|
|
48
57
|
const snap = await provider.fetchSnapshot();
|
|
58
|
+
// A request started with old credentials must not restore them in SQLite,
|
|
59
|
+
// WebSocket clients, or its own delayed HTTP refresh response.
|
|
60
|
+
if (generation !== (generations.get(providerId) ?? 0))
|
|
61
|
+
return latest.get(providerId) ?? null;
|
|
49
62
|
persistAndBroadcast(snap);
|
|
50
63
|
return snap;
|
|
51
64
|
}
|
|
65
|
+
/** Invalidate before changing credentials, including uncertain CLI failures. */
|
|
66
|
+
export async function withUsageAccountSwitch(providerId, operation) {
|
|
67
|
+
generations.set(providerId, (generations.get(providerId) ?? 0) + 1);
|
|
68
|
+
switchingProviders.add(providerId);
|
|
69
|
+
try {
|
|
70
|
+
persistAndBroadcast({
|
|
71
|
+
providerId,
|
|
72
|
+
status: 'error',
|
|
73
|
+
buckets: [],
|
|
74
|
+
fetchedAt: new Date().toISOString(),
|
|
75
|
+
errorMessage: 'Account credentials are changing. Refresh usage after the switch.',
|
|
76
|
+
});
|
|
77
|
+
return await operation();
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
switchingProviders.delete(providerId);
|
|
81
|
+
try {
|
|
82
|
+
await refreshNow(providerId);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Quota refresh failure must not turn a successful account mutation into
|
|
86
|
+
// a failed switch or replace the original command error. Keep empty data.
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
52
90
|
// Test seam — pass `null` to restore the default provider list.
|
|
53
91
|
export function _setProvidersForTest(list) {
|
|
54
92
|
providers = list ?? DEFAULT_PROVIDERS;
|
|
93
|
+
generations.clear();
|
|
94
|
+
switchingProviders.clear();
|
|
95
|
+
latest.clear();
|
|
55
96
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { nanoid } from 'nanoid';
|
|
2
2
|
import { getDb } from '../db/index.js';
|
|
3
3
|
import { recordActivity } from './activity-service.js';
|
|
4
|
+
import { acceptNetworkSocketMessage, isNetworkSocketAuthorized } from './network-session-service.js';
|
|
4
5
|
import { getAllPersistedSnapshots } from './usage/db.js';
|
|
5
6
|
// ── State ──────────────────────────────────────────────────────────────────────
|
|
6
7
|
/** Maps each WS client to the set of workspaceIds they are subscribed to */
|
|
@@ -11,6 +12,8 @@ const clients = new Map();
|
|
|
11
12
|
const MAX_BUFFERED_BYTES = 1024 * 1024;
|
|
12
13
|
/** A dropped frame must end the connection so its replay cursor cannot skip it. */
|
|
13
14
|
function sendFrame(ws, message) {
|
|
15
|
+
if (!isNetworkSocketAuthorized(ws))
|
|
16
|
+
return;
|
|
14
17
|
if (ws.readyState !== 1)
|
|
15
18
|
return;
|
|
16
19
|
if (ws.bufferedAmount > MAX_BUFFERED_BYTES) {
|
|
@@ -92,6 +95,8 @@ export function handleConnection(ws) {
|
|
|
92
95
|
console.error('[ws] usage hydration failed:', err);
|
|
93
96
|
}
|
|
94
97
|
ws.on('message', (data) => {
|
|
98
|
+
if (!acceptNetworkSocketMessage(ws))
|
|
99
|
+
return;
|
|
95
100
|
let msg;
|
|
96
101
|
try {
|
|
97
102
|
const parsed = JSON.parse(data.toString());
|
|
@@ -67,8 +67,9 @@ export function getKoboHome() {
|
|
|
67
67
|
export function ensureKoboHome() {
|
|
68
68
|
const dir = getKoboHome();
|
|
69
69
|
if (!fs.existsSync(dir)) {
|
|
70
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
70
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
71
71
|
}
|
|
72
|
+
fs.chmodSync(dir, 0o700);
|
|
72
73
|
return dir;
|
|
73
74
|
}
|
|
74
75
|
/**
|
|
@@ -32,8 +32,7 @@ export function formatStartupBanner(options) {
|
|
|
32
32
|
if (options.networkEnabled) {
|
|
33
33
|
for (const lanUrl of options.lanUrls ?? [])
|
|
34
34
|
lines.push(` LAN: ${lanUrl}`);
|
|
35
|
-
|
|
36
|
-
lines.push(` Token: ${options.token}`);
|
|
35
|
+
lines.push(' Authentication required; retrieve the token from local settings.');
|
|
37
36
|
}
|
|
38
37
|
const release = parseChangelog(options.changelog ?? '').find((entry) => entry.version === options.version);
|
|
39
38
|
const highlights = (release?.notes ?? '')
|
package/dist/shared/models.js
CHANGED
|
@@ -35,6 +35,12 @@ export const CLAUDE_MODELS = [
|
|
|
35
35
|
i18nLabelKey: 'model.sonnet55',
|
|
36
36
|
i18nDescriptionKey: 'model.sonnet55Description',
|
|
37
37
|
},
|
|
38
|
+
{
|
|
39
|
+
id: 'claude-haiku-5-5',
|
|
40
|
+
label: 'Haiku 5.5',
|
|
41
|
+
i18nLabelKey: 'model.haiku55',
|
|
42
|
+
i18nDescriptionKey: 'model.haiku55Description',
|
|
43
|
+
},
|
|
38
44
|
{
|
|
39
45
|
id: 'claude-haiku-4-5-20251001',
|
|
40
46
|
label: 'Haiku 4.5',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/docker-compose.vps.yml
CHANGED
|
@@ -46,6 +46,12 @@ services:
|
|
|
46
46
|
traefik.http.routers.kobo.entrypoints: ${TRAEFIK_ENTRYPOINT:-websecure}
|
|
47
47
|
traefik.http.routers.kobo.tls: "true"
|
|
48
48
|
traefik.http.routers.kobo.tls.certresolver: ${TRAEFIK_CERTRESOLVER:-letsencrypt}
|
|
49
|
+
traefik.http.routers.kobo.middlewares: kobo-security
|
|
50
|
+
traefik.http.middlewares.kobo-security.headers.stsseconds: "31536000"
|
|
51
|
+
traefik.http.middlewares.kobo-security.headers.contenttypenosniff: "true"
|
|
52
|
+
traefik.http.middlewares.kobo-security.headers.referrerpolicy: no-referrer
|
|
53
|
+
traefik.http.middlewares.kobo-security.headers.customframeoptionsvalue: SAMEORIGIN
|
|
54
|
+
traefik.http.middlewares.kobo-security.headers.contentsecuritypolicy: "frame-ancestors 'self'"
|
|
49
55
|
traefik.http.services.kobo.loadbalancer.server.port: "3000"
|
|
50
56
|
# No published HTTP or SSH ports. Traefik handles HTTP and WebSockets.
|
|
51
57
|
stop_grace_period: 180s
|
package/docs/deployment-vps.md
CHANGED
|
@@ -191,13 +191,21 @@ From `/opt/kobo`:
|
|
|
191
191
|
docker network inspect web # use your TRAEFIK_NETWORK value
|
|
192
192
|
docker compose config --quiet
|
|
193
193
|
docker compose up -d --wait --wait-timeout 180 kobo
|
|
194
|
-
docker compose
|
|
194
|
+
docker compose exec kobo node -e 'const fs = require("node:fs"); console.log(JSON.parse(fs.readFileSync(process.env.KOBO_HOME + "/settings.json", "utf8")).global.networkAccessToken)'
|
|
195
195
|
```
|
|
196
196
|
|
|
197
|
-
Open `https://kobo.example.com
|
|
198
|
-
|
|
197
|
+
Open `https://kobo.example.com/` on your PC and paste the token into the login
|
|
198
|
+
dialog. The retrieval command deliberately displays a secret in your local
|
|
199
|
+
terminal; do not copy its output into logs or shared transcripts. Startup logs
|
|
200
|
+
never contain the token. After upgrading from a version that logged it, rotate
|
|
201
|
+
the token and apply your existing log-retention policy to old copies.
|
|
199
202
|
`KOBO_NETWORK_ACCESS_PROXY_HOST` is populated from `KOBO_DOMAIN` and must match
|
|
200
203
|
the public hostname. Both network access and behind-proxy mode are enabled.
|
|
204
|
+
Missing or invalid proxy hostname configuration rejects public traffic. Browser
|
|
205
|
+
origins must match the configured scheme and port (HTTPS is the default).
|
|
206
|
+
The Compose example attaches HSTS and browser security headers at the TLS router;
|
|
207
|
+
same-origin split panes remain supported. Keep the Docker socket unmounted unless
|
|
208
|
+
project workflows need its root-equivalent host access.
|
|
201
209
|
|
|
202
210
|
This example publishes no HTTP or SSH ports. Use SSH to the VPS followed by
|
|
203
211
|
`docker compose exec kobo kobo-vps-entrypoint --exec bash` for a container shell. The VPS launcher prepares the home permissions, then drops root before starting Node. It does not start the image's optional SSH daemon. `--exec` runs administrative shell/CLI commands as the same non-root user without repeating home migration.
|
|
@@ -211,15 +219,19 @@ curl -s -o /dev/null -w '%{http_code}\n' https://kobo.example.com/api/workspaces
|
|
|
211
219
|
|
|
212
220
|
Expect health JSON with `status: "ok"` and the installed version, then `401`
|
|
213
221
|
without a token. `/api/health` is intentionally public. Ordinary authenticated
|
|
214
|
-
REST
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
222
|
+
REST scripts use the operator token in `X-Kobo-Token`; browser login creates a
|
|
223
|
+
12-hour HttpOnly session cookie. External MCP clients require a dedicated key,
|
|
224
|
+
with explicitly granted tools/workspaces, even on loopback. See
|
|
225
|
+
[external MCP credentials](../CONFIGURATION.md#external-llm-access-through-mcp).
|
|
226
|
+
No additional Traefik route is needed for WebSocket upgrades: the browser obtains
|
|
227
|
+
a 60-second, single-use ticket instead of placing the operator token in the URL.
|
|
228
|
+
|
|
229
|
+
Rotating the operator token, disabling network access or changing proxy mode
|
|
230
|
+
revokes browser sessions and existing WebSockets, including terminal connections.
|
|
231
|
+
Managed agent MCP processes use a revocable per-launch capability through a
|
|
232
|
+
loopback-only internal endpoint, not the operator token. Plan/read-only launches
|
|
233
|
+
cannot mutate workspace state; only their narrow progress/report exceptions
|
|
234
|
+
remain. Agent processes are not stopped by browser credential revocation.
|
|
223
235
|
|
|
224
236
|
## 3. Authenticate and choose a project
|
|
225
237
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loicngr/kobo",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.28",
|
|
4
4
|
"description": "Kōbō — local multi-workspace orchestrator for Claude Code and OpenAI Codex, with isolated Git worktrees, live review and optional automation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "GPL-3.0-or-later",
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{M as e,S as t,dt as n,lt as r,p as i,pt as a,v as o}from"./reactivity.esm-bundler-DY1JkAEA.js";import{C as s,F as c,J as l,L as u,N as d,O as f,P as p,T as m,Y as h,_ as g,d as _,f as v,g as y,j as ee,k as b,l as x,p as S,r as C,u as w,y as T,z as te}from"./runtime-core.esm-bundler-Bqg71Vll.js";import{$ as ne,K as re,M as E,f as ie,z as ae}from"./dom-BtYTvLeN.js";import{l as oe,t as D,u as se}from"./QBtn-BUSdPaf4.js";import{Gt as ce,Jt as O,O as le,_t as ue,at as de,en as fe,j as pe,m as me,mt as he,pn as ge,pt as _e,rn as k,st as ve,un as A,v as ye}from"./index-CpWBGKdM.js";import{t as be}from"./QSpinnerDots-xdWyaE1i.js";import{t as xe}from"./QList-Cx1UfaUm.js";import{t as Se}from"./QMarkupTable-UhP7fVUp.js";import{c as j,l as M,s as N}from"./QSelect-DFWyDWCE.js";import{t as P}from"./QItemSection-MdJ1_ZK7.js";import{t as Ce}from"./QExpansionItem-D-EGL2b3.js";import{a as F,c as we,r as I,t as L}from"./ToolCallItem-DYubi-J0.js";import{t as Te}from"./QScrollArea-BMrqK-xd.js";import{a as R}from"./use-tours-BWkHp34X.js";import{f as Ee,g as z,m as De,p as Oe,u as ke}from"./use-worktree-restore-DUjZr44N.js";import{t as B}from"./QTooltip-Bk8dLUtU.js";import{n as V}from"./render-chat-markdown-CafzCcmu.js";import{n as H}from"./WorkspacePage-K-1ce50d.js";function Ae(e,t){return s(`div`,e,[s(`table`,{class:`q-table`},t)])}var je={list:xe,table:Se},U=[`list`,`table`,`__qtable`],Me=ae({name:`QVirtualScroll`,props:{...M,type:{type:String,default:`list`,validator:e=>U.includes(e)},items:{type:Array,default:()=>[]},itemsFn:Function,itemsSize:Number,scrollTarget:ge},setup(e,{slots:n,attrs:r}){let i,a=t(null),o=x(()=>e.itemsSize>=0&&e.itemsFn!==void 0?Number.parseInt(e.itemsSize,10):Array.isArray(e.items)?e.items.length:0),{virtualScrollSliceRange:c,localResetVirtualScroll:u,padVirtualScroll:f,onVirtualScrollEvt:m}=j({virtualScrollLength:o,getVirtualScrollTarget:y,getVirtualScrollEl:v}),h=x(()=>{if(o.value===0)return[];let t=(e,t)=>({index:c.value.from+t,item:e});return e.itemsFn===void 0?e.items.slice(c.value.from,c.value.to).map(t):e.itemsFn(c.value.from,c.value.to-c.value.from).map(t)}),g=x(()=>`q-virtual-scroll q-virtual-scroll`+(e.virtualScrollHorizontal?`--horizontal`:`--vertical`)+(e.scrollTarget===void 0?` scroll`:``)),_=x(()=>e.scrollTarget===void 0?{tabindex:0}:{});l(o,()=>{u()}),l(()=>e.scrollTarget,()=>{C(),S()});function v(){let e=a.value;return e===null?null:e.$el||e}function y(){return i}function S(){let t=v();t!==null&&(i=A(t,e.scrollTarget),i.addEventListener(`scroll`,m,E.passive))}function C(){i!==void 0&&(i.removeEventListener(`scroll`,m,E.passive),i=void 0)}function w(){let t=f(e.type===`list`?`div`:`tbody`,h.value.map(n.default));return n.before!==void 0&&(t=n.before().concat(t)),ie(n.after,t)}return u(),p(()=>{S()}),b(()=>{S()}),d(()=>{C()}),ee(()=>{C()}),()=>{if(n.default===void 0){console.error(`QVirtualScroll: default scoped slot is required for rendering`);return}return e.type===`__qtable`?Ae({ref:a,class:`q-table__middle `+g.value,tabindex:0},w()):s(je[e.type],{...r,ref:a,class:[r.class,g.value],..._.value},w)}}}),Ne=new Set([`setup`,`cleanup`,`archive`]);function Pe(e){switch(e.type){case`user`:return e.sender===`system-prompt`?`system-prompt`:Ne.has(e.sender)||le(e.sender)?`script`:`user`;case`session`:return`session`;default:return`agent`}}function Fe(e){let t=[],n=null,r=null;for(let i of e){let e=Pe(i),a=e===`session`||e===`system-prompt`,o=e===`script`&&i.type===`user`?`script:${i.sender}`:e===`user`&&i.type===`user`&&i.source?JSON.stringify([`mcp`,i.source.transport,i.source.clientName]):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 W(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 Ie(e){let t=e.items[0];return`${e.speaker}:${t?W(t):`empty`}`}function Le(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 Re(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=l(e,e=>{t(e)&&s(!0)},{immediate:!0}),i){o();return}a=setTimeout(()=>s(!1),n)})}var ze={class:`agent-error-item`},Be={class:`agent-error-item__body`},Ve={class:`agent-error-item__title`},G={class:`agent-error-item__message`},He=R(T({__name:`AgentErrorItem`,props:{item:{}},setup(e){let t=e,{te:n}=fe(),r=x(()=>{let e=`agent.error.${t.item.category}`;return n(e)?e:`agent.error.other`});return(t,n)=>(u(),S(`div`,ze,[g(oe,{name:`error_outline`,size:`16px`,class:`agent-error-item__icon`}),w(`div`,Be,[w(`div`,Ve,a(t.$t(r.value)),1),w(`div`,G,a(e.item.message),1)])]))}}),[[`__scopeId`,`data-v-e98c3c02`]]),Ue={class:`text-caption text-kobo-3`},We=T({__name:`SessionEventItem`,props:{item:{}},setup(e){let t=e,n=x(()=>{switch(t.item.kind){case`started`:return`session.started`;case`ended`:return z(t.item.detail);case`compacted`:return`session.compacted`;default:return`session.started`}});return(e,t)=>(u(),S(`span`,Ue,a(e.$t(n.value)),1))}}),K=new Map;function q(){for(let e of K.values())e.controller.abort(),e.blobUrl&&URL.revokeObjectURL(e.blobUrl);K.clear()}typeof window<`u`&&(window.addEventListener(`kobo:auth-token-changed`,q),window.addEventListener(`storage`,e=>{(e.key===`kobo:network-token`||e.key===null)&&q()}));function J(e){if(!/^\/api\/workspaces\/[^/]+\/images\/file\?path=/.test(e))throw Error(`Invalid workspace image URL`);let t=K.get(e);if(!t){let n=new AbortController,r={references:0,controller:n,ready:Promise.resolve(``)};r.ready=ce(e,{signal:n.signal}).then(async e=>{if(!e.ok)throw await O(e);let t=await e.blob();if(n.signal.aborted)throw new DOMException(`Aborted`,`AbortError`);return r.blobUrl=URL.createObjectURL(t),r.blobUrl}),t=r,K.set(e,t)}t.references++;let n=!1,r=t;return{ready:r.ready,release(){n||(n=!0,!(--r.references>0)&&(r.controller.abort(),r.blobUrl&&URL.revokeObjectURL(r.blobUrl),K.get(e)===r&&K.delete(e)))}}}function Ge(e,t){let n=o(``),r=o(0),a=()=>{r.value++},s=e=>{(e.key===`kobo:network-token`||e.key===null)&&a()};return window.addEventListener(`kobo:auth-token-changed`,a),window.addEventListener(`storage`,s),i(()=>{window.removeEventListener(`kobo:auth-token-changed`,a),window.removeEventListener(`storage`,s)}),l([e,r],([e],r,i)=>{let a=!0,o=[],s=document.createElement(`template`);s.innerHTML=e;for(let e of s.content.querySelectorAll(`img`)){let r=e.getAttribute(`src`)??``;if(e.removeAttribute(`data-kobo-image-url`),!/^\/api\/workspaces\/[^/]+\/images\/file\?path=/.test(r))continue;e.removeAttribute(`src`);let i=J(r);o.push(i),i.ready.then(t=>{a&&(e.setAttribute(`src`,t),e.setAttribute(`data-kobo-image-url`,r),n.value=s.innerHTML)}).catch(()=>{a&&(e.setAttribute(`title`,t()),n.value=s.innerHTML)})}n.value=s.innerHTML,i(()=>{a=!1;for(let e of o)e.release()})},{immediate:!0}),n}function Ke(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 qe=[`innerHTML`],Je=[`innerHTML`],Ye=[`src`],Y=R(T({__name:`UserMessageItem`,props:{item:{},workspaceId:{}},setup(t){let n=t,{t:r}=fe(),a=x(()=>n.item.sender===`system-prompt`),s=Ge(x(()=>{let e=Ke(n.item.content,n.workspaceId);return V(e)}),()=>r(`network.login.unreachable`)),c=o(null),d=o(!1),f;function p(){d.value=!1,c.value=null,f?.release(),f=void 0}l(d,e=>{e||p()}),l(()=>n.workspaceId,p);let m=e=>{(e.key===`kobo:network-token`||e.key===null)&&p()};window.addEventListener(`storage`,m),window.addEventListener(`kobo:auth-token-changed`,p),i(()=>{p(),window.removeEventListener(`kobo:auth-token-changed`,p),window.removeEventListener(`storage`,m)});function y(e){let t=e.target;if(t?.tagName!==`IMG`)return;let n=t;n.src&&(p(),n.dataset.koboImageUrl&&(f=J(n.dataset.koboImageUrl),f.ready.catch(p)),c.value=n.src,d.value=!0)}return(t,n)=>(u(),S(C,null,[a.value?(u(),_(Ce,{key:0,dense:``,"dense-toggle":``,label:t.$t(`chat.systemPrompt`),"header-class":`text-kobo-2 text-caption`},{default:h(()=>[w(`div`,{class:`q-py-xs markdown-message markdown-user-prompt`,innerHTML:e(s)},null,8,qe)]),_:1},8,[`label`])):(u(),S(`div`,{key:1,class:`markdown-message`,onClick:y},[w(`div`,{innerHTML:e(s)},null,8,Je)])),g(k,{modelValue:d.value,"onUpdate:modelValue":n[1]||=e=>d.value=e},{default:h(()=>[c.value?(u(),S(`img`,{key:0,src:c.value,alt:``,class:`image-lightbox-img`,onClick:n[0]||=e=>d.value=!1},null,8,Ye)):v(``,!0)]),_:1},8,[`modelValue`])],64))}}),[[`__scopeId`,`data-v-965e6297`]]),Xe=[`data-event-ids`],X={class:`turn-header`},Ze={key:0,class:`turn-time`},Qe={class:`turn-time turn-time-updated`},Z={key:2,class:`turn-actions`},$e={class:`turn-body`},et={class:`ellipsis`},tt={class:`q-px-sm q-pb-sm`},nt={key:0,class:`turn-scroll-top`},rt=R(T({__name:`TurnCard`,props:{turn:{},workspaceId:{},highlighted:{type:Boolean}},emits:[`scrollTo`],setup(t,{emit:i}){let s=t,c=i,{t:l}=fe(),d=de(),f=m(F),p=o(null);function ee(){let e=p.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 b=x(()=>{switch(s.turn.speaker){case`user`:{let e=s.turn.items[0],t=e?.type===`user`?e.source:void 0;return{label:t?l(`chat.externalLlm`,{name:t.clientName}):l(`chat.you`),accent:`var(--kobo-turn-user)`,badgeClass:`turn-badge-user`}}case`agent`:return{label:l(`chat.agent`),accent:`var(--kobo-turn-agent)`,badgeClass:`turn-badge-agent`};case`system-prompt`:return{label:l(`chat.systemPrompt`),accent:`var(--kobo-text-3)`,badgeClass:`turn-badge-system`};case`session`:return{label:l(`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:le(t)?l(`chat.hookScript`,{event:t.slice(5)}):l(t===`archive`?`chat.archiveScript`:t===`setup`?`chat.setupScript`:`chat.cleanupScript`),accent:`var(--kobo-success)`,badgeClass:`turn-badge-script`}}}});function T(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 re=x(()=>T(s.turn.ts)),E=x(()=>s.turn.items.flatMap(e=>e.eventIds??[]).join(` `)),ie=x(()=>{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}),ae=x(()=>{let e=s.turn.ts,t=ie.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?``:T(t,r-n<6e4)}),se=x(()=>ae.value!==``);function ce(e){return we(e.origin,Object.values(d.subagents[s.workspaceId]??{}),e.type===`tool`?e.toolCallId:void 0)}function O(e){return e||l(`chat.subagentActivity`)}function ue(e){return(e.type===`text`||e.type===`thinking`||e.type===`tool`)&&e.origin?.kind===`subagent`}let pe=x(()=>{let e=[],t=new Map;for(let n of s.turn.items){if(!ue(n)){e.push({type:`item`,key:W(n),item:n});continue}let r=ce(n),i=r?.toolUseId??n.origin?.toolCallId??n.origin?.threadId??W(n),a=t.get(i);if(a){a.items.push(n);continue}let o={type:`subagent`,key:`subagent:${W(n)}`,items:[n],subagentKey:i,subagentName:r?.description||null};t.set(i,o),e.push(o)}return e});function me(e){e.origin&&f?.({origin:e.origin})}let he=x(()=>s.turn.items.filter(e=>e.type===`tool`).length);return(i,o)=>(u(),S(`div`,{ref_key:`cardEl`,ref:p,class:r([`turn-card`,{"turn-card--user":t.turn.speaker===`user`,"turn-card--highlighted":t.highlighted}]),"data-event-ids":E.value,style:n({"--turn-accent":b.value.accent})},[w(`div`,X,[w(`span`,{class:r([`turn-badge`,b.value.badgeClass])},a(b.value.label),3),re.value?(u(),S(`span`,Ze,a(re.value),1)):v(``,!0),se.value?(u(),S(C,{key:1},[g(oe,{name:`arrow_forward`,size:`10px`,color:`kobo-3`,class:`turn-time-arrow`}),w(`span`,Qe,[y(a(ae.value)+` `,1),g(B,null,{default:h(()=>[y(a(e(l)(`chat.lastUpdatedAt`,{time:ae.value})),1)]),_:1})])],64)):v(``,!0),he.value>0?(u(),S(`span`,Z,` · `+a(e(l)(`chat.nActions`,{n:he.value})),1)):v(``,!0)]),w(`div`,$e,[(u(!0),S(C,null,te(pe.value,n=>(u(),S(C,{key:n.key},[n.type===`subagent`?(u(),_(Ce,{key:0,class:`turn-subagent-activity`,dense:``,label:O(n.subagentName),caption:e(l)(`chat.nActions`,{n:n.items.length}),"default-opened":t.highlighted&&n.items.some(e=>e.eventIds?.some(e=>E.value.includes(e)))},{header:h(()=>[g(P,null,{default:h(()=>[g(N,{class:`row items-center no-wrap`,title:n.subagentName??void 0},{default:h(()=>[g(oe,{name:`hub`,size:`16px`,class:`q-mr-xs`},{default:h(()=>[g(B,null,{default:h(()=>[y(a(e(l)(`chat.subagentActivity`)),1)]),_:1})]),_:1}),w(`span`,et,a(O(n.subagentName)),1)]),_:2},1032,[`title`]),g(N,{caption:``},{default:h(()=>[y(a(e(l)(`chat.nActions`,{n:n.items.length})),1)]),_:2},1024)]),_:2},1024),g(P,{side:``},{default:h(()=>[g(D,{flat:``,round:``,dense:``,size:`sm`,icon:`open_in_new`,"aria-label":e(l)(`chat.openSubagentActivity`),onClick:ne(e=>me(n.items[0]),[`stop`])},{default:h(()=>[g(B,null,{default:h(()=>[y(a(e(l)(`chat.openSubagentActivity`)),1)]),_:1})]),_:1},8,[`aria-label`,`onClick`])]),_:2},1024)]),default:h(()=>[w(`div`,tt,[(u(!0),S(C,null,te(n.items,t=>(u(),S(C,{key:e(W)(t)},[t.type===`text`?(u(),_(I,{key:0,item:t},null,8,[`item`])):t.type===`thinking`?(u(),_(H,{key:1,item:t},null,8,[`item`])):t.type===`tool`?(u(),_(L,{key:2,item:t},null,8,[`item`])):v(``,!0)],64))),128))])]),_:2},1032,[`label`,`caption`,`default-opened`])):(u(),S(C,{key:1},[n.item.type===`text`?(u(),_(I,{key:0,item:n.item},null,8,[`item`])):n.item.type===`thinking`?(u(),_(H,{key:1,item:n.item},null,8,[`item`])):n.item.type===`tool`?(u(),_(L,{key:2,item:n.item},null,8,[`item`])):n.item.type===`user`?(u(),_(Y,{key:3,item:n.item,"workspace-id":t.workspaceId},null,8,[`item`,`workspace-id`])):n.item.type===`session`?(u(),_(We,{key:4,item:n.item},null,8,[`item`])):n.item.type===`error`?(u(),_(He,{key:5,item:n.item},null,8,[`item`])):v(``,!0)],64))],64))),128))]),t.turn.items.length>4?(u(),S(`div`,nt,[g(D,{flat:``,round:``,dense:``,size:`xs`,icon:`arrow_upward`,color:`kobo-3`,class:`turn-scroll-top-btn`,onClick:ee},{default:h(()=>[g(B,null,{default:h(()=>[y(a(e(l)(`chat.scrollToTurnTop`)),1)]),_:1})]),_:1})])):v(``,!0)],14,Xe))}}),[[`__scopeId`,`data-v-13bebb3d`]]),it={key:0,class:`activity-feed-switching`},at={key:1,class:`activity-feed-wrap`,"data-tour":`ws-chat`},ot={key:0,role:`alert`,class:`q-pa-sm text-negative`},st={key:1,"data-testid":`history-load-error`,role:`alert`,class:`q-pa-sm text-negative`},ct=[`aria-label`],lt={key:0,class:`activity-feed-compacting`},ut={class:`text-caption text-kobo-2`},dt={key:0,class:`text-center q-py-sm text-caption text-kobo-3`},ft={key:1,class:`activity-feed-empty q-pa-md`},pt={class:`activity-feed-empty__title`},mt={class:`activity-feed-empty__hint`},ht={key:2,class:`q-px-md q-pb-md`},gt=[`title`],_t={class:`ellipsis`},vt={class:`activity-feed-nav-cluster`},yt=300,bt=60,xt=200,St=180,Ct=255,wt=3,Tt=60,Et=R(T({__name:`ActivityFeed`,props:{workspaceId:{}},setup(t){let{t:n}=fe(),r=t,i=ue(),s=pe(),d=de(),m=me(),ee=x(()=>m.isCompacting(r.workspaceId)),b=x(()=>d.selectedSessionId),T=x(()=>d.sessions.find(e=>e.id===b.value)?.engineSessionId??null),ne=x(()=>{let e=d.sessions;return e.length!==0&&b.value===e[e.length-1].id});function E(e){return b.value?e?e===b.value||e===T.value:ne.value:!0}let ie=x(()=>(d.activityFeeds[r.workspaceId]??[]).filter(e=>e.type===`text`&&typeof e.content==`string`&&E(e.sessionId)).map(e=>({content:e.content,sender:e.meta?.sender??`user`,source:ye(e.meta?.source),ts:e.timestamp,sessionId:e.sessionId,eventIds:[e.id]}))),ae=x(()=>{let e=d.workspaces.find(e=>e.id===r.workspaceId);return ve(e?.status)}),le=ke(),ge=``,k=x(()=>{let e=i.eventsFor(r.workspaceId),t=i.timestampsFor(r.workspaceId),n=i.sessionIdsFor(r.workspaceId),a=i.eventIdsFor(r.workspaceId),o=[],c=[],l=[];for(let r=0;r<e.length;r++)E(n[r])&&(o.push(e[r]),c.push(t[r]),l.push(a[r]??null));let u=`${r.workspaceId}::${b.value??`*`}`;u!==ge&&(le=ke(),ge=u);let d=Ee(le,o,c,ae.value,l);return Fe(De(d,ie.value).filter(e=>e.type===`thinking`?!1:e.type===`session`?e.kind===`ended`?s.showVerboseSystemMessages||!Oe(e.detail):s.showVerboseSystemMessages:!0))}),A=x(()=>s.showVerboseSystemMessages?i.eventsFor(r.workspaceId).filter(e=>e.kind===`message:raw`).map(e=>e.content):[]),xe=x(()=>{for(let e=k.value.length-1;e>=0;e--){let t=k.value[e]?.items??[];for(let e=t.length-1;e>=0;e--){let r=t[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?n(`activity.a11y.toolFailed`,{name:r.name}):n(`activity.a11y.toolFinished`,{name:r.name})}:{key:`tool:${r.toolCallId}:start`,text:n(`activity.a11y.toolStarted`,{name:r.name})}}}}return null}),Se=o(``);l(xe,(e,t)=>{if(!e||e.key===t?.key)return;let r=e.key.startsWith(`text:`)?n(`activity.a11y.agentSaid`,{text:e.text.slice(0,yt)}):e.text;Se.value=r});let j=o(null),M=o(!0),N=o(!1),P=o(``),F=_e(),we=_e(),I=_e(),L=o(null),R=!1,z=!0,B=null,V=o(new Set),H=new Map,Ae=x(()=>{if(k.value.length>0||A.value.length>0)return!1;let e=r.workspaceId,t=b.value;return!!d.loadingSessions[e]||m.isSyncPending(e)||t!==null&&V.value.has(W(e,t))}),je=o(new Map),U=x(()=>k.value.length>0||A.value.length>0),Ne=x(()=>!!d.loadingSessions[r.workspaceId]||m.isSyncPending(r.workspaceId)||b.value!==null&&V.value.has(W(r.workspaceId,b.value)));function Pe(e){et();let t=B;if(B=e.verticalSize,t!==null&&e.verticalSize>t&&M.value&&!N.value){q(0);return}let n=e.verticalSize-e.verticalPosition-e.verticalContainerSize;M.value=n<=bt,R&&e.verticalPosition<=xt&&!N.value&&!P.value&&ze()&&He()}function W(e,t){return`${e}:${t}`}function ze(){let e=b.value;return e?je.value.get(W(r.workspaceId,e))??!0:i.hasMoreOlderFor(r.workspaceId)}function Be(e,t,n){je.value.set(W(e,t),n)}function Ve(e){if(!b.value)return i.oldestIdFor(e);let t=i.eventIdsFor(e),n=i.sessionIdsFor(e);for(let e=0;e<t.length;e++){if(!E(n[e]))continue;let r=t[e];if(r)return r}}let G=null;function He(){if(G)return G;let e=K().finally(()=>{G===e&&(G=null)});return G=e,G}function Ue(){F.abort(),we.abort(),I.abort(),N.value=!1,G=null;for(let e of V.value)Ft.delete(e);H.clear(),V.value.clear(),P.value=``}function We(){P.value=``,Mt.value===0&&b.value?It():He()}l(()=>[r.workspaceId,b.value],Ue,{flush:`sync`});async function K(){let e=r.workspaceId,t=b.value,n=Ve(e);if(!n)return;let a=F.begin();P.value=``,N.value=!0;try{let r=j.value;if(await f(),!F.isCurrent(a))return;let o=r?.getScroll().verticalSize??0,s=r?.getScroll().verticalPosition??0,c=new URLSearchParams({before:n,limit:`200`});t&&c.set(`session`,t);let l=await ce(`/api/workspaces/${e}/events?${c.toString()}`,{signal:a});if(!l.ok)throw await O(l);let u=await l.json();if(!F.isCurrent(a))return;let p=u.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),v=m.map(e=>e.sessionId??null),y=m.map(e=>e.id),ee=p.length>0?p[0].id:n;t&&Be(e,t,u.hasMore),i.prepend(e,g,_,{oldestId:ee,hasMoreOlder:t?i.hasMoreOlderFor(e):u.hasMore,sessionIds:v,eventIds:y});for(let t of h){let n=t.payload;typeof n.content==`string`&&d.addActivityItem(e,{id:t.id,type:`text`,content:n.content,timestamp:t.createdAt,sessionId:t.sessionId??void 0,meta:{sender:n.sender??`user`,source:ye(n.source)}})}if(await f(),r&&F.isCurrent(a)){let e=r.getScroll().verticalSize-o;if(e>0){let t=Math.max(s+e,250);r.setScrollPosition(`vertical`,t,0)}}}catch(e){if(he(e)||!F.isCurrent(a))return;P.value=e instanceof Error?e.message:String(e),console.error(`[ActivityFeed] failed to load older events:`,e)}finally{F.isCurrent(a)&&(N.value=!1)}}async function q(e=0){let t=Pt();if(await f(),t!==Pt())return;let n=j.value;if(!n)return;let r=n.getScroll();n.setScrollPosition(`vertical`,r.verticalSize,e)}let J=null,Ge=0;function Ke(){if(J!=null)return;let e=performance.now()-Ge<St,t=Pt();J=requestAnimationFrame(()=>{J=null,t===Pt()&&(Ge=performance.now(),q(e?0:180))})}function qe(e){$(),M.value=!1;let t=j.value;t&&t.setScrollPosition(`vertical`,Math.max(0,e),250)}function Je(){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 Ye(e){let t=e.detail;if(t?.workspaceId!==r.workspaceId||!t.eventId)return;$(),M.value=!1;let n=I.begin(),a=r.workspaceId;try{let e=new URLSearchParams({around:t.eventId,limit:`200`});t.sessionId&&e.set(`session`,t.sessionId);let o=await ce(`/api/workspaces/${a}/events?${e}`,{signal:n});if(!o.ok)throw await O(o);let s=await o.json();if(!I.isCurrent(n))return;let c=s.events??[],l=c.filter(e=>e.type===`agent:event`&&e.workspaceId===r.workspaceId),u=c.filter(e=>e.type===`user:message`&&e.workspaceId===r.workspaceId);i.reset(r.workspaceId,l.map(e=>e.payload),l.map(e=>e.createdAt),{oldestId:c[0]?.id,hasMoreOlder:s.hasMore??!1,sessionIds:l.map(e=>e.sessionId),eventIds:l.map(e=>e.id)});for(let e of u){let t=e.payload;typeof t.content==`string`&&d.addActivityItem(r.workspaceId,{id:e.id,type:`text`,content:t.content,timestamp:e.createdAt,sessionId:e.sessionId??void 0,meta:{sender:t.sender??`user`,source:ye(t.source)}})}if(await Re(Ae,e=>!e,5e3),await f(),await new Promise(e=>requestAnimationFrame(()=>requestAnimationFrame(()=>e()))),!I.isCurrent(n))return;let p=k.value.findIndex(e=>e.items.some(e=>e.eventIds?.includes(t.eventId)??!1));if(p<0)return;L.value=t.eventId,Y.value?.scrollTo(p,`center`),X.value=p,window.setTimeout(()=>{L.value===t.eventId&&(L.value=null)},1800)}catch(e){if(he(e)||!I.isCurrent(n))return;P.value=e instanceof Error?e.message:String(e),console.error(`[ActivityFeed] failed to focus history event:`,e)}}let Y=o(null),Xe=o(null),X=o(0),Ze=o(!1),Qe=x(()=>{for(let e=k.value.length-1;e>=0;e--)if(k.value[e]?.speaker===`user`)return e;return-1}),Z=null;function $e(){let e=Qe.value,t=j.value?.getScrollTarget(),n=t?.querySelector(`[data-turn-index="${e}"]`);if(!n||!t){Ze.value=!1;return}let r=t.getBoundingClientRect(),i=n.getBoundingClientRect();Ze.value=i.bottom>r.top&&i.top<r.bottom}function et(){Z===null&&(Z=requestAnimationFrame(()=>{Z=null,$e()}))}function tt(e){return e.length<=Ct?e:`${e.slice(0,252)}...`}let nt=x(()=>{let e=Qe.value,t=k.value[e];if(!t||X.value<=e||Ze.value)return null;let n=t.items.filter(e=>e.type===`user`).map(e=>e.content).join(`
|
|
2
|
+
`).trim();return n?{index:e,content:tt(n)}:null});function Et(e){X.value=e.index,et()}function Dt(){let e=nt.value;e&&($(),M.value=!1,Y.value?.scrollTo(e.index,`start`),X.value=e.index)}let Ot=o(!1);async function kt(){if(Ot.value)return;$(),M.value=!1,Ot.value=!0;let e=r.workspaceId;try{let t=Le(k.value,X.value);if(t<0)for(let n=0;n<15;n++){if(e!==r.workspaceId)return;if(!ze())break;let n=k.value.length;if(await He(),await f(),e!==r.workspaceId)return;if(X.value+=k.value.length-n,t=Le(k.value,X.value),t>=0)break}t>=0&&e===r.workspaceId&&(Y.value?.scrollTo(t,`start`),X.value=t)}finally{Ot.value=!1}}let Q=0;function At(){return new Promise(e=>requestAnimationFrame(()=>e()))}async function jt(){if(!z||!U.value||Ae.value)return;R=!1;let e=++Q;if(await f(),e!==Q||!z||(Xe.value=j.value?.getScrollTarget()??null,await f(),e!==Q||!z))return;k.value.length>0&&Y.value?.scrollTo(k.value.length-1,`end-force`),await At();let t=-1,n=0;for(let r=0;r<Tt&&n<wt;r++){if(e!==Q||!z)return;await q(0),await At();let r=j.value?.getScroll().verticalSize??0;B=r,n=r===t?n+1:0,t=r}e===Q&&!Ne.value&&(R=!0,z=!1)}function $(){z=!1,!R&&(Q++,R=!0)}let Mt=x(()=>{let e=i.sessionIdsFor(r.workspaceId);if(!b.value)return e.length;let t=0;for(let n of e)E(n)&&t++;return t});l(Ae,async e=>{!e&&U.value&&await jt(),e||f(()=>{Xe.value=j.value?.getScrollTarget()??null,et()})}),l(Ne,(e,t)=>{!e&&t&&z&&U.value&&jt()}),p(()=>{window.addEventListener(`kobo:focus-history-event`,Ye),f(()=>{Xe.value=j.value?.getScrollTarget()??null,et()}),U.value&&jt(),b.value&&It();let e=new URLSearchParams(window.location.hash.split(`?`)[1]??``).get(`eventId`);e&&(Ye(new CustomEvent(`kobo:focus-history-event`,{detail:{workspaceId:r.workspaceId,sessionId:b.value,eventId:e}})),Je())}),l([Mt,()=>ie.value.length,()=>A.value.length],async([e,t,n])=>{N.value||z&&e+t+n>0&&await jt()}),l([Qe,()=>k.value.length],()=>{f(et)});let Nt=x(()=>i.liveAppendCountFor(r.workspaceId,E)),Pt=()=>`${r.workspaceId}\u0000${d.selectedSessionId??``}`;l([Nt,Pt],([e,t],[n,r])=>{t===r&&e>n&&M.value&&!N.value&&Ke()}),c(()=>{Q++,z=!1,Ue(),window.removeEventListener(`kobo:focus-history-event`,Ye),J!=null&&(cancelAnimationFrame(J),J=null),Z!==null&&cancelAnimationFrame(Z)}),l([()=>r.workspaceId,()=>d.selectedSessionId],()=>{Q++,z=!0,B=null,M.value=!0,R=!1,It(),U.value&&jt()});let Ft=new Set;async function It(){let e=r.workspaceId,t=b.value;if(!t||Mt.value>0)return;let n=W(e,t);if(Ft.has(n))return;Ft.add(n),V.value.add(n);let a=we.begin();H.set(n,a),P.value=``;try{let n=await ce(`/api/workspaces/${e}/events?session=${encodeURIComponent(t)}&limit=500`,{signal:a});if(!n.ok)throw await O(n);let r=await n.json();if(!we.isCurrent(a))return;let o=r.events??[];if(o.length===0)return;let s=o.filter(t=>t.type===`agent:event`&&t.workspaceId===e),c=o.filter(t=>t.type===`user:message`&&t.workspaceId===e),l=s.map(e=>e.payload),u=s.map(e=>e.createdAt),f=s.map(e=>e.sessionId??null),p=s.map(e=>e.id);Be(e,t,r.hasMore),l.length>0&&i.prepend(e,l,u,{oldestId:o[0].id,hasMoreOlder:i.hasMoreOlderFor(e),sessionIds:f,eventIds:p});for(let t of c){let n=t.payload;typeof n.content==`string`&&d.addActivityItem(e,{id:t.id,type:`text`,content:n.content,timestamp:t.createdAt,sessionId:t.sessionId??void 0,meta:{sender:n.sender??`user`,source:ye(n.source)}})}}catch(e){if(he(e)||!we.isCurrent(a)){(!H.has(n)||H.get(n)===a)&&Ft.delete(n);return}P.value=e instanceof Error?e.message:String(e),console.error(`[ActivityFeed] fetchSessionIfMissing failed:`,e),Ft.delete(n)}finally{H.get(n)===a&&(H.delete(n),V.value.delete(n))}}let Lt=x(()=>ie.value.filter(e=>e.sender===`user`).length),Rt=x(()=>(d.activityFeeds[r.workspaceId]??[]).filter(e=>e.meta?.sender===`user`&&e.meta.pending&&E(e.sessionId)).length);l([Lt,Rt,Pt],async([e,t,n],[r,i,a])=>{let o=t>i;n===a&&(o||e>r&&M.value&&!N.value&&!Ne.value)&&(M.value=!0,await q(o?180:0))});async function zt(){M.value=!0,await q(250)}return(t,n)=>Ae.value?(u(),S(`div`,it,[g(be,{size:`40px`,color:`primary`})])):(u(),S(`div`,at,[e(d).sessionsErrors[r.workspaceId]?(u(),S(`div`,ot,[y(a(t.$t(`activity.sessionsError`,{message:e(d).sessionsErrors[r.workspaceId]}))+` `,1),g(D,{flat:``,dense:``,label:t.$t(`common.retry`),onClick:n[0]||=t=>e(d).fetchSessions(r.workspaceId)},null,8,[`label`])])):v(``,!0),P.value?(u(),S(`div`,st,[y(a(t.$t(`activity.historyError`,{message:P.value}))+` `,1),g(D,{flat:``,dense:``,label:t.$t(`common.retry`),onClick:We},null,8,[`label`])])):v(``,!0),w(`div`,{"data-testid":`activity-live-region`,class:`sr-only`,role:`log`,"aria-live":`polite`,"aria-atomic":`true`,"aria-label":t.$t(`activity.a11y.region`)},a(Se.value),9,ct),g(re,{name:`fade`},{default:h(()=>[ee.value?(u(),S(`div`,lt,[g(be,{size:`18px`,color:`primary`}),w(`span`,ut,a(t.$t(`activity.compacting`)),1)])):v(``,!0)]),_:1}),g(Te,{ref_key:`scrollRef`,ref:j,class:`activity-feed-scroll`,onScroll:Pe,onWheelPassive:$,onTouchstartPassive:$,onKeydown:$},{default:h(()=>[N.value?(u(),S(`div`,dt,[g(se,{size:`sm`}),y(` `+a(t.$t(`activity.loading_older`)),1)])):v(``,!0),g(Me,{ref_key:`virtualScrollRef`,ref:Y,class:`q-pa-md`,items:k.value,"scroll-target":Xe.value??void 0,"virtual-scroll-item-size":160,"virtual-scroll-slice-size":20,onVirtualScroll:Et},{default:h(({item:t,index:n})=>[(u(),_(rt,{key:e(Ie)(t),turn:t,"workspace-id":r.workspaceId,"data-turn-index":n,highlighted:t.items.some(e=>e.eventIds?.includes(L.value??``)??!1),onScrollTo:qe},null,8,[`turn`,`workspace-id`,`data-turn-index`,`highlighted`]))]),_:1},8,[`items`,`scroll-target`]),k.value.length===0&&A.value.length===0&&!N.value?(u(),S(`div`,ft,[g(oe,{name:`forum`,size:`28px`}),w(`div`,pt,a(t.$t(`activity.empty`)),1),w(`div`,mt,a(t.$t(`activity.emptyHint`)),1)])):v(``,!0),A.value.length?(u(),S(`div`,ht,[g(Ce,{label:t.$t(`activity.raw_lines`,{n:A.value.length}),dense:``},{default:h(()=>[(u(!0),S(C,null,te(A.value,(e,t)=>(u(),S(`div`,{key:t,class:`text-caption text-kobo-3 q-pa-xs`},a(e),1))),128))]),_:1},8,[`label`])])):v(``,!0)]),_:1},512),nt.value?(u(),S(`button`,{key:2,type:`button`,class:`activity-feed-last-user`,"data-testid":`latest-user-message`,title:nt.value.content,onClick:Dt},[g(oe,{name:`person`,size:`16px`}),w(`span`,_t,a(nt.value.content),1)],8,gt)):v(``,!0),w(`div`,vt,[M.value?v(``,!0):(u(),_(D,{key:0,round:``,dense:``,unelevated:``,color:`kobo-surface-2`,"text-color":`kobo-1`,icon:`arrow_downward`,size:`sm`,class:`activity-feed-nav-btn`,title:t.$t(`activity.scroll_to_bottom`),"aria-label":t.$t(`activity.scroll_to_bottom`),onClick:zt},null,8,[`title`,`aria-label`])),g(D,{round:``,dense:``,unelevated:``,color:`kobo-surface-2`,"text-color":`kobo-1`,icon:`arrow_upward`,size:`sm`,class:`activity-feed-nav-btn`,title:t.$t(`activity.prev_user_message`),"aria-label":t.$t(`activity.prev_user_message`),loading:Ot.value,disable:Ot.value,onClick:kt},null,8,[`title`,`aria-label`,`loading`,`disable`])])]))}}),[[`__scopeId`,`data-v-58324159`]]);export{Et as default};
|