@loicngr/kobo 2.1.15 → 2.1.18
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 +61 -0
- package/CHANGELOG.md +12 -0
- package/CONFIGURATION.md +74 -17
- package/README.md +7 -3
- package/dist/mcp-server/kobo-tasks-server.js +19 -104
- package/dist/mcp-server/workspace-dialogue-client.js +15 -3
- package/dist/server/db/migrations.js +2 -0
- package/dist/server/db/schema.js +2 -0
- package/dist/server/db/workspace-message-batch-schema.js +9 -0
- package/dist/server/index.js +8 -0
- package/dist/server/middleware/workspace-creation-maintenance.js +13 -0
- package/dist/server/routes/maintenance.js +32 -0
- package/dist/server/routes/mcp.js +69 -3
- package/dist/server/routes/pull-requests.js +2 -1
- package/dist/server/routes/workspace-messages.js +22 -0
- package/dist/server/routes/workspaces.js +60 -4
- package/dist/server/services/agent/orchestrator.js +21 -6
- package/dist/server/services/agent/session-controller.js +4 -1
- package/dist/server/services/cron-service.js +7 -0
- package/dist/server/services/pr-checkout-rollback-service.js +51 -0
- package/dist/server/services/pr-checkout-service.js +21 -1
- package/dist/server/services/quota-backoff-service.js +7 -0
- package/dist/server/services/update-maintenance-service.js +44 -0
- package/dist/server/services/wakeup-service.js +21 -3
- package/dist/server/services/workspace-creation-mcp-service.js +214 -0
- package/dist/server/services/workspace-group-message-mcp-service.js +58 -0
- package/dist/server/services/workspace-group-message-service.js +182 -0
- package/dist/server/services/workspace-lifecycle-mcp-service.js +72 -0
- package/dist/server/services/workspace-message-service.js +5 -0
- package/dist/shared/pr-checkout-steps.js +86 -0
- package/dist/shared/workspace-creation-tools.js +182 -0
- package/dist/shared/workspace-group-message-tools.js +134 -0
- package/dist/shared/workspace-group-messages.js +27 -0
- package/dist/shared/workspace-lifecycle-tools.js +112 -0
- package/docker-compose.vps.yml +60 -0
- package/docs/deployment-vps.md +506 -0
- package/docs/getting-started.md +9 -0
- package/package.json +8 -1
- package/scripts/kobo-gh +9 -0
- package/scripts/kobo-github-app-token +190 -0
- package/scripts/kobo-install +392 -0
- package/scripts/kobo-update +499 -0
- package/src/client/dist/pwa/assets/ActivityFeed-S3XGw3WW.js +2 -0
- package/src/client/dist/pwa/assets/{AutoLoopFinalReviewSettings-lKjfaC6V.js → AutoLoopFinalReviewSettings-CC8ICe_I.js} +1 -1
- package/src/client/dist/pwa/assets/{ChangelogPage-BplM7qEB.js → ChangelogPage-C1aiPBoi.js} +1 -1
- package/src/client/dist/pwa/assets/{ComparisonPanel-DxK5v9w_.js → ComparisonPanel-DElbn4ac.js} +1 -1
- package/src/client/dist/pwa/assets/CreatePage-Bx58t4YW.js +2 -0
- package/src/client/dist/pwa/assets/{DashboardPage-BsP-MATS.js → DashboardPage-D1S3QGp4.js} +1 -1
- package/src/client/dist/pwa/assets/DiffViewer-BH4X9ru1.js +8 -0
- package/src/client/dist/pwa/assets/{DocumentsPanel-CGrf8Q3H.js → DocumentsPanel-C4AvyZF3.js} +1 -1
- package/src/client/dist/pwa/assets/{DrawerToggleButton-BANeLSsR.js → DrawerToggleButton-LzIEeeJY.js} +1 -1
- package/src/client/dist/pwa/assets/GitPanel-BP3N_4nh.js +3 -0
- package/src/client/dist/pwa/assets/{HealthPage-BVTNx-92.js → HealthPage-Cm13vxOi.js} +1 -1
- package/src/client/dist/pwa/assets/MainLayout-DGmMkfuw.js +2 -0
- package/src/client/dist/pwa/assets/{MainLayout-C6nvIQjU.css → MainLayout-DoAEgANU.css} +1 -1
- package/src/client/dist/pwa/assets/{MemoryPanel-C71rTpv9.js → MemoryPanel-C6tzCv6D.js} +1 -1
- package/src/client/dist/pwa/assets/{MemoryProposalList-CoqhUGrv.js → MemoryProposalList-CzpzIirK.js} +1 -1
- package/src/client/dist/pwa/assets/{QBtnDropdown-U9Ey5W8h.js → QBtnDropdown-BnuKPevV.js} +1 -1
- package/src/client/dist/pwa/assets/{QExpansionItem-Be_A1MT1.js → QExpansionItem-DjaUhgjt.js} +1 -1
- package/src/client/dist/pwa/assets/{QScrollArea-D4lYzAPQ.js → QScrollArea-Dpksslru.js} +1 -1
- package/src/client/dist/pwa/assets/QSelect-Dh4CjliE.js +1 -0
- package/src/client/dist/pwa/assets/{QTooltip-kStOgY0X.js → QTooltip-CH45NuVs.js} +1 -1
- package/src/client/dist/pwa/assets/{SchedulePanel-C7nTGdWE.js → SchedulePanel-QAjXuTIN.js} +1 -1
- package/src/client/dist/pwa/assets/{SearchPage-C6J72pkQ.js → SearchPage-B1pbwDej.js} +1 -1
- package/src/client/dist/pwa/assets/SettingsPage-CUQefsDh.js +9 -0
- package/src/client/dist/pwa/assets/{SplitWorkspacePage-Bqn4gdVw.js → SplitWorkspacePage-CJ7e-KPh.js} +1 -1
- package/src/client/dist/pwa/assets/{TerminalPanel-zLeDCReI.js → TerminalPanel-DVomz3oo.js} +10 -10
- package/src/client/dist/pwa/assets/{ToolCallItem-B8b_Sagj.js → ToolCallItem-BntDHc6p.js} +3 -3
- package/src/client/dist/pwa/assets/{TouchPan-Bl8VzIDK.js → TouchPan-DwqfsAxH.js} +1 -1
- package/src/client/dist/pwa/assets/{TourReplayButton-pL0HCboU.js → TourReplayButton-OIavXdUg.js} +1 -1
- package/src/client/dist/pwa/assets/{WorkspaceAttentionLabels-9Eomx0rQ.js → WorkspaceAttentionLabels-CYwN1-Rf.js} +1 -1
- package/src/client/dist/pwa/assets/WorkspacePage-CAsY7Hl-.js +4 -0
- package/src/client/dist/pwa/assets/{browser-C-PJ4alw.js → browser-Cl0d1gbu.js} +1 -1
- package/src/client/dist/pwa/assets/{build-path-tree-SW9rj7B_.js → build-path-tree-J3YQeMuV.js} +1 -1
- package/src/client/dist/pwa/assets/{custom-sounds-toz-4drX.js → custom-sounds-D-T5rpgv.js} +1 -1
- package/src/client/dist/pwa/assets/de-C-RtXLqL.js +17 -0
- package/src/client/dist/pwa/assets/{editor.api-BJTPhnh8.js → editor.api-BoFiCjBZ.js} +1 -1
- package/src/client/dist/pwa/assets/{efforts-BO9xGn9q.js → efforts-Y6XotW97.js} +1 -1
- package/src/client/dist/pwa/assets/es-CBlW91ZK.js +17 -0
- package/src/client/dist/pwa/assets/{expand-template-BOZMtWau.js → expand-template-sfOHWudd.js} +1 -1
- package/src/client/dist/pwa/assets/{format-hEnpRyit.js → format-mWov137C.js} +1 -1
- package/src/client/dist/pwa/assets/{formatters-DD_eFc4z.js → formatters-B_XsveM0.js} +1 -1
- package/src/client/dist/pwa/assets/fr-Hfzp_sF5.js +17 -0
- package/src/client/dist/pwa/assets/{freemarker2-B6nUofvT.js → freemarker2-2pGNakhH.js} +1 -1
- package/src/client/dist/pwa/assets/{handlebars-4P3gtM8p.js → handlebars-Bn2V6Liw.js} +1 -1
- package/src/client/dist/pwa/assets/{html-C9ZzWo-y.js → html-BBhkJkf2.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-sBCWwf8Y.js +1 -0
- package/src/client/dist/pwa/assets/index-DCpe-Tfm.js +20 -0
- package/src/client/dist/pwa/assets/it-Bl-v27qg.js +17 -0
- package/src/client/dist/pwa/assets/{javascript-BGEC5ggd.js → javascript-Ba0G68U2.js} +1 -1
- package/src/client/dist/pwa/assets/{liquid-DNtF630O.js → liquid-C5VrzHr_.js} +1 -1
- package/src/client/dist/pwa/assets/{mdx-CICwyOd0.js → mdx-DrPRiuKb.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco-contributions-BkuwvgrT.js → monaco-contributions-qQUY6VS7.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco.contribution-GX8XA-PE.js → monaco.contribution-Dtq2RsyB.js} +2 -2
- package/src/client/dist/pwa/assets/network-auth-BPJpXK4L.js +1 -0
- package/src/client/dist/pwa/assets/notify-theme-Bl9iRsZB.js +1 -0
- package/src/client/dist/pwa/assets/{project-color-B1SMFjSe.js → project-color-Dzvvmaml.js} +1 -1
- package/src/client/dist/pwa/assets/{python-n5uPdTVS.js → python-Cuf_7umq.js} +1 -1
- package/src/client/dist/pwa/assets/{razor-AOj0nsUt.js → razor-DcVpxFeh.js} +1 -1
- package/src/client/dist/pwa/assets/{typescript-Uhkrqihh.js → typescript-Bjcc3kYC.js} +1 -1
- package/src/client/dist/pwa/assets/{use-search-index-status-CW0BdpBZ.js → use-search-index-status-Bwf40BEI.js} +1 -1
- package/src/client/dist/pwa/assets/use-tours-CWafZt-m.js +2 -0
- package/src/client/dist/pwa/assets/{use-worktree-restore-BZxibZS5.js → use-worktree-restore-DkcM2FAZ.js} +1 -1
- package/src/client/dist/pwa/assets/{workspace-templates-W557e1-m.js → workspace-templates-DL_Cs7mD.js} +1 -1
- package/src/client/dist/pwa/assets/{xml-B-hnMgEs.js → xml-BmJwqJOz.js} +1 -1
- package/src/client/dist/pwa/assets/{yaml-DqXkTvgm.js → yaml-DAssfLhS.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 +130 -12
- package/src/mcp-server/kobo-tasks-server.ts +27 -105
- package/src/mcp-server/workspace-dialogue-client.ts +18 -3
- package/src/client/dist/pwa/assets/ActivityFeed-D7jouiuP.js +0 -2
- package/src/client/dist/pwa/assets/CreatePage-D5ilP5kt.js +0 -2
- package/src/client/dist/pwa/assets/DiffViewer-Cd15vFWj.js +0 -8
- package/src/client/dist/pwa/assets/GitPanel-CEPV5Qw0.js +0 -3
- package/src/client/dist/pwa/assets/MainLayout-DBKtwilW.js +0 -2
- package/src/client/dist/pwa/assets/QSelect-fS0DLOd1.js +0 -1
- package/src/client/dist/pwa/assets/SettingsPage-SuH-aIzv.js +0 -9
- package/src/client/dist/pwa/assets/WorkspacePage-B6clgURl.js +0 -4
- package/src/client/dist/pwa/assets/de-Ddxe8cc6.js +0 -17
- package/src/client/dist/pwa/assets/es-DBJIQeWW.js +0 -17
- package/src/client/dist/pwa/assets/fr-yBzIIPaQ.js +0 -17
- package/src/client/dist/pwa/assets/i18n-2-ULabk2.js +0 -1
- package/src/client/dist/pwa/assets/index-Vk99jtxD.js +0 -20
- package/src/client/dist/pwa/assets/it-D44U-PT1.js +0 -17
- package/src/client/dist/pwa/assets/network-auth-KnWzjYIc.js +0 -1
- package/src/client/dist/pwa/assets/notify-theme-CWuZTz4Z.js +0 -1
- package/src/client/dist/pwa/assets/use-tours-CndFlyL-.js +0 -2
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Update a standalone Kōbō Compose deployment. Python 3.10+; standard library only."""
|
|
3
|
+
import argparse
|
|
4
|
+
import fcntl
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import re
|
|
9
|
+
import shutil
|
|
10
|
+
import signal
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
import tarfile
|
|
14
|
+
import tempfile
|
|
15
|
+
import time
|
|
16
|
+
import urllib.error
|
|
17
|
+
import urllib.request
|
|
18
|
+
import uuid
|
|
19
|
+
from datetime import datetime, timezone
|
|
20
|
+
|
|
21
|
+
REPO = 'https://github.com/loicngr/kobo.git'
|
|
22
|
+
VERSION = re.compile(r'\d+\.\d+\.\d+')
|
|
23
|
+
COMPOSE_ENV_KEYS = ('KOBO_DOMAIN', 'KOBO_PROJECTS_DIR', 'TRAEFIK_NETWORK',
|
|
24
|
+
'TRAEFIK_ENTRYPOINT', 'TRAEFIK_CERTRESOLVER')
|
|
25
|
+
BUSY_JS = r"""
|
|
26
|
+
const db = new (require('better-sqlite3'))('/root/.config/kobo/kobo.db', {readonly:true});
|
|
27
|
+
const sessions = db.prepare("SELECT count(*) n FROM agent_sessions WHERE status = 'running'").get().n;
|
|
28
|
+
const workspaces = db.prepare("SELECT count(*) n FROM workspaces WHERE status IN ('extracting','brainstorming','executing','compacting')").get().n;
|
|
29
|
+
console.log(JSON.stringify({sessions, workspaces}));
|
|
30
|
+
"""
|
|
31
|
+
MIGRATION_JS = r"""
|
|
32
|
+
import Database from 'better-sqlite3';
|
|
33
|
+
import { runMigrations } from './dist/server/db/migrations.js';
|
|
34
|
+
const db = new Database('/check/kobo.db');
|
|
35
|
+
db.pragma('foreign_keys = ON');
|
|
36
|
+
runMigrations(db);
|
|
37
|
+
if (db.pragma('integrity_check', {simple:true}) !== 'ok') throw new Error('SQLite integrity check failed');
|
|
38
|
+
if (db.pragma('foreign_key_check').length) throw new Error('SQLite foreign key check failed');
|
|
39
|
+
db.close();
|
|
40
|
+
"""
|
|
41
|
+
API_JS = r"""
|
|
42
|
+
(async () => {
|
|
43
|
+
const fs = require('fs');
|
|
44
|
+
const token = JSON.parse(fs.readFileSync('/root/.config/kobo/settings.json','utf8')).global.networkAccessToken;
|
|
45
|
+
if (!token) throw new Error('Missing network token');
|
|
46
|
+
const headers = {'X-Kobo-Token': token};
|
|
47
|
+
const r = await fetch(`http://127.0.0.1:${process.env.SERVER_PORT || 3000}/api/workspaces`,{headers,signal:AbortSignal.timeout(10000)});
|
|
48
|
+
if (!r.ok) throw new Error('Authenticated API check failed');
|
|
49
|
+
console.log('API authenticated: OK');
|
|
50
|
+
})().catch(() => process.exit(1));
|
|
51
|
+
"""
|
|
52
|
+
MAINTENANCE_JS = r"""
|
|
53
|
+
(async () => {
|
|
54
|
+
const fs = require('fs');
|
|
55
|
+
const token = JSON.parse(fs.readFileSync('/root/.config/kobo/settings.json','utf8')).global.networkAccessToken;
|
|
56
|
+
if (!token) throw new Error('Missing network token');
|
|
57
|
+
const method = process.argv[1];
|
|
58
|
+
const options = {method, headers:{'X-Kobo-Token':token,'Content-Type':'application/json'}, signal:AbortSignal.timeout(10000)};
|
|
59
|
+
if (method !== 'GET') options.body = JSON.stringify({owner:process.argv[2]});
|
|
60
|
+
const r = await fetch(`http://127.0.0.1:${process.env.SERVER_PORT || 3000}/api/maintenance/update`,options);
|
|
61
|
+
const body = await r.json().catch(() => ({}));
|
|
62
|
+
console.log(JSON.stringify({status:r.status, protocol:body.protocol}));
|
|
63
|
+
})().catch(() => process.exit(1));
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class UpdateError(Exception):
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def atomic_write(path, data, mode=0o600):
|
|
72
|
+
fd, name = tempfile.mkstemp(prefix='.' + path.name + '.', dir=path.parent)
|
|
73
|
+
try:
|
|
74
|
+
with os.fdopen(fd, 'wb') as f:
|
|
75
|
+
f.write(data)
|
|
76
|
+
f.flush()
|
|
77
|
+
os.fsync(f.fileno())
|
|
78
|
+
os.chmod(name, mode)
|
|
79
|
+
os.replace(name, path)
|
|
80
|
+
parent_fd = os.open(path.parent, os.O_DIRECTORY)
|
|
81
|
+
try:
|
|
82
|
+
os.fsync(parent_fd)
|
|
83
|
+
finally:
|
|
84
|
+
os.close(parent_fd)
|
|
85
|
+
finally:
|
|
86
|
+
if os.path.exists(name):
|
|
87
|
+
os.unlink(name)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def image_config(text, image):
|
|
91
|
+
# Refuse an unexpected/multi-service structure instead of modifying other services.
|
|
92
|
+
pattern = r'(?m)^ image: [^\n]+$'
|
|
93
|
+
if len(re.findall(pattern, text)) != 1:
|
|
94
|
+
raise UpdateError('Unexpected Compose layout: exactly one indented image entry is required.')
|
|
95
|
+
return re.sub(pattern, ' image: ' + image, text)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def normalize_version(value):
|
|
99
|
+
value = value.removeprefix('v')
|
|
100
|
+
if not VERSION.fullmatch(value):
|
|
101
|
+
raise UpdateError('Expected a stable version such as 2.1.16 (or latest).')
|
|
102
|
+
return value
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def say(message):
|
|
106
|
+
print(message, flush=True)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class Updater:
|
|
110
|
+
def __init__(self, stack):
|
|
111
|
+
self.stack = stack.resolve()
|
|
112
|
+
self.compose_file = self.stack / 'compose.yaml'
|
|
113
|
+
self.log = None
|
|
114
|
+
self.job = None
|
|
115
|
+
self.state = {}
|
|
116
|
+
self.container = None
|
|
117
|
+
self.project_name = None
|
|
118
|
+
self.public_url = None
|
|
119
|
+
self.maintenance_owner = uuid.uuid4().hex
|
|
120
|
+
|
|
121
|
+
def command_environment(self, args):
|
|
122
|
+
env = os.environ.copy()
|
|
123
|
+
if args[:2] == ['docker', 'compose']:
|
|
124
|
+
# Deployment inputs come from the selected stack's .env, never an
|
|
125
|
+
# unrelated caller shell. Keep Docker context and authentication.
|
|
126
|
+
for key in list(env):
|
|
127
|
+
if key in COMPOSE_ENV_KEYS or key.startswith('COMPOSE_'):
|
|
128
|
+
del env[key]
|
|
129
|
+
return env
|
|
130
|
+
|
|
131
|
+
def run(self, args, timeout=120, data=None):
|
|
132
|
+
try:
|
|
133
|
+
result = subprocess.run(args, input=data, stdout=subprocess.PIPE,
|
|
134
|
+
stderr=subprocess.PIPE, timeout=timeout, check=False,
|
|
135
|
+
env=self.command_environment(args))
|
|
136
|
+
except subprocess.TimeoutExpired as e:
|
|
137
|
+
raise UpdateError(f'Timeout: {args[0]}') from e
|
|
138
|
+
if result.returncode:
|
|
139
|
+
if self.log:
|
|
140
|
+
with self.log.open('ab') as f:
|
|
141
|
+
f.write(result.stderr)
|
|
142
|
+
raise UpdateError(f'Failed: {args[0]} (code {result.returncode}). See the private update log.')
|
|
143
|
+
return result.stdout.decode().strip()
|
|
144
|
+
|
|
145
|
+
def logged(self, args, timeout=3600):
|
|
146
|
+
with self.log.open('ab') as f:
|
|
147
|
+
try:
|
|
148
|
+
subprocess.run(args, stdout=f, stderr=subprocess.STDOUT,
|
|
149
|
+
timeout=timeout, check=True, env=self.command_environment(args))
|
|
150
|
+
except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as e:
|
|
151
|
+
raise UpdateError(f'Failed: {args[0]}. Log: {self.log}') from e
|
|
152
|
+
|
|
153
|
+
def compose(self, *args, file=None):
|
|
154
|
+
project = ['-p', self.project_name] if self.project_name else []
|
|
155
|
+
return ['docker', 'compose', '--project-directory', str(self.stack),
|
|
156
|
+
'--env-file', str(self.stack / '.env'),
|
|
157
|
+
*project, '-f', str(file or self.compose_file), *args]
|
|
158
|
+
|
|
159
|
+
def record(self, phase, **values):
|
|
160
|
+
self.state.update(values, phase=phase)
|
|
161
|
+
atomic_write(self.job / 'state.json', json.dumps(self.state, indent=2).encode())
|
|
162
|
+
|
|
163
|
+
def idle(self):
|
|
164
|
+
counts = json.loads(self.run(['docker', 'exec', self.container, 'node', '-e', BUSY_JS]))
|
|
165
|
+
if counts['sessions'] or counts['workspaces']:
|
|
166
|
+
raise UpdateError('Kōbō is busy: wait for agents or stop them in the UI, then retry.')
|
|
167
|
+
|
|
168
|
+
def maintenance(self, method):
|
|
169
|
+
reply = json.loads(self.run(['docker', 'exec', self.container, 'node', '-e',
|
|
170
|
+
MAINTENANCE_JS, method, self.maintenance_owner]))
|
|
171
|
+
if method == 'DELETE' and reply['status'] == 409:
|
|
172
|
+
return # Our reservation was never acquired or has already been released.
|
|
173
|
+
if reply['status'] == 404 or (reply['status'] == 200 and reply.get('protocol') != 1):
|
|
174
|
+
raise UpdateError('Runtime lacks atomic update reservation. Use --prepare and perform the first upgrade manually; see the VPS guide.')
|
|
175
|
+
if reply['status'] != 200:
|
|
176
|
+
raise UpdateError(f'Update reservation {method} refused (HTTP {reply["status"]}).')
|
|
177
|
+
|
|
178
|
+
def inspect(self):
|
|
179
|
+
ids = self.run(self.compose('ps', '-q', 'kobo')).splitlines()
|
|
180
|
+
if len(ids) != 1:
|
|
181
|
+
raise UpdateError('Exactly one running Kōbō container is required.')
|
|
182
|
+
self.container = ids[0]
|
|
183
|
+
info = json.loads(self.run(['docker', 'inspect', self.container]))[0]
|
|
184
|
+
if not info['State']['Running']:
|
|
185
|
+
raise UpdateError('The Kōbō container is stopped.')
|
|
186
|
+
mounts = [m for m in info['Mounts'] if m['Destination'] == '/root']
|
|
187
|
+
if len(mounts) != 1 or mounts[0]['Type'] != 'volume':
|
|
188
|
+
raise UpdateError('Expected a named /root volume; no changes made.')
|
|
189
|
+
if any(m['Destination'].startswith('/root/') for m in info['Mounts']):
|
|
190
|
+
raise UpdateError('Nested /root mounts would escape the home backup. This layout is not supported.')
|
|
191
|
+
config = json.loads(self.run(self.compose('config', '--format', 'json')))
|
|
192
|
+
self.config_snapshot = config
|
|
193
|
+
if set(config['services']) != {'kobo'}:
|
|
194
|
+
raise UpdateError('This script requires a standalone stack containing only the kobo service.')
|
|
195
|
+
configured_mounts = config['services']['kobo'].get('volumes', [])
|
|
196
|
+
home = [v for v in configured_mounts if v['target'] == '/root']
|
|
197
|
+
if (len(home) != 1 or home[0]['type'] != 'volume'
|
|
198
|
+
or config.get('volumes', {}).get(home[0]['source'], {}).get('name') != mounts[0]['Name']
|
|
199
|
+
or any(v['target'].startswith('/root/') for v in configured_mounts)):
|
|
200
|
+
raise UpdateError('Compose home mounts do not match the running persistent volume.')
|
|
201
|
+
self.project_name = config['name']
|
|
202
|
+
env = config['services']['kobo'].get('environment', {})
|
|
203
|
+
host = env.get('KOBO_NETWORK_ACCESS_PROXY_HOST', '')
|
|
204
|
+
if not re.fullmatch(r'[A-Za-z0-9.-]+(?::[0-9]+)?', host):
|
|
205
|
+
raise UpdateError('Set KOBO_NETWORK_ACCESS_PROXY_HOST to your HTTPS hostname.')
|
|
206
|
+
if env.get('KOBO_HOME') != '/root/.config/kobo':
|
|
207
|
+
raise UpdateError('This updater requires KOBO_HOME=/root/.config/kobo and a named /root volume.')
|
|
208
|
+
self.public_url = 'https://' + host
|
|
209
|
+
if config['services']['kobo']['image'] != info['Config']['Image']:
|
|
210
|
+
raise UpdateError('Compose and the running container use different images. Reconcile them before updating.')
|
|
211
|
+
self.old_image = info['Image'] # immutable digest, for backup/migration helpers
|
|
212
|
+
self.volume = mounts[0]['Name']
|
|
213
|
+
self.original = self.compose_file.read_bytes()
|
|
214
|
+
self.original_mode = self.compose_file.stat().st_mode & 0o777
|
|
215
|
+
image_config(self.original.decode(), 'kobo:validation')
|
|
216
|
+
self.installed = normalize_version(self.run(['docker', 'exec', self.container,
|
|
217
|
+
'node', '-p', 'require("./package.json").version']))
|
|
218
|
+
self.idle()
|
|
219
|
+
self.run(['docker', 'compose', 'version'])
|
|
220
|
+
used_kib = int(self.run(['docker', 'exec', self.container, 'du', '-sk', '/root']).split()[0])
|
|
221
|
+
# Sources/build layers plus archive + migration copy; compression is not assumed.
|
|
222
|
+
required = 6 * 1024**3 + used_kib * 1024 * 2
|
|
223
|
+
docker_root = self.run(['docker', 'info', '--format', '{{.DockerRootDir}}'])
|
|
224
|
+
docker_free = int(self.run(['docker', 'run', '--rm', '--network', 'none',
|
|
225
|
+
'--read-only', '--entrypoint', 'node', '--mount',
|
|
226
|
+
f'type=bind,src={docker_root},dst=/docker,readonly', self.old_image,
|
|
227
|
+
'-e', "const s=require('fs').statfsSync('/docker');console.log(s.bavail*s.bsize)"]))
|
|
228
|
+
if min(shutil.disk_usage(self.stack).free, docker_free) < required:
|
|
229
|
+
raise UpdateError(f'Insufficient disk space: need at least {required / 1024**3:.1f} GiB free.')
|
|
230
|
+
return info['Config']['Image']
|
|
231
|
+
|
|
232
|
+
def resolve(self, requested):
|
|
233
|
+
if requested == 'latest':
|
|
234
|
+
with urllib.request.urlopen('https://registry.npmjs.org/@loicngr%2fkobo/latest', timeout=30) as r:
|
|
235
|
+
requested = json.load(r)['version']
|
|
236
|
+
version = normalize_version(requested)
|
|
237
|
+
if tuple(map(int, version.split('.'))) < tuple(map(int, self.installed.split('.'))):
|
|
238
|
+
raise UpdateError('Downgrade refused: an older image may be incompatible with the current database.')
|
|
239
|
+
# Public repository: no App token is needed to discover/retrieve a release.
|
|
240
|
+
refs = self.run(['git', '-c', 'credential.helper=', 'ls-remote', '--exit-code',
|
|
241
|
+
REPO, f'refs/tags/v{version}', f'refs/tags/v{version}^{{}}'])
|
|
242
|
+
pairs = [line.split() for line in refs.splitlines()]
|
|
243
|
+
sha = next((sha for sha, ref in pairs if ref.endswith('^{}')), pairs[0][0])
|
|
244
|
+
if not re.fullmatch('[a-f0-9]{40}', sha):
|
|
245
|
+
raise UpdateError('Invalid release reference.')
|
|
246
|
+
return version, sha
|
|
247
|
+
|
|
248
|
+
def build(self, version, sha):
|
|
249
|
+
source = self.job / 'source'
|
|
250
|
+
source.mkdir()
|
|
251
|
+
self.logged(['git', 'init', str(source)])
|
|
252
|
+
self.logged(['git', '-C', str(source), '-c', 'credential.helper=', 'fetch',
|
|
253
|
+
'--depth=1', REPO, f'refs/tags/v{version}'])
|
|
254
|
+
self.logged(['git', '-C', str(source), 'checkout', '--detach', 'FETCH_HEAD'])
|
|
255
|
+
actual = self.run(['git', '-C', str(source), 'rev-parse', 'HEAD'])
|
|
256
|
+
if actual != sha or json.loads((source / 'package.json').read_text())['version'] != version:
|
|
257
|
+
raise UpdateError('Release changed or package.json does not match the tag.')
|
|
258
|
+
# Never overwrite a tag still used by production or a rollback configuration.
|
|
259
|
+
build_id = self.job.name.lower()
|
|
260
|
+
base = f'kobo:{version}-{sha[:12]}-base-{build_id}'
|
|
261
|
+
image = f'kobo:{version}-{sha[:12]}-vps-{build_id}'
|
|
262
|
+
self.logged(['docker', 'build', '--progress=plain', '-t', base, str(source)])
|
|
263
|
+
overlay = self.job / 'overlay'
|
|
264
|
+
overlay.mkdir()
|
|
265
|
+
(overlay / 'Dockerfile').write_text(
|
|
266
|
+
f'FROM {base}\nRUN docker compose version >/dev/null 2>&1 || '
|
|
267
|
+
'(apt-get update && apt-get install -y --no-install-recommends docker-compose-plugin '
|
|
268
|
+
'&& rm -rf /var/lib/apt/lists/*)\n')
|
|
269
|
+
self.logged(['docker', 'build', '--progress=plain', '-t', image, str(overlay)])
|
|
270
|
+
self.run(['docker', 'run', '--rm', '--network', 'none', '--entrypoint', 'docker',
|
|
271
|
+
image, 'compose', 'version'])
|
|
272
|
+
self.run(['docker', 'run', '--rm', '--network', 'none', '--entrypoint', 'node', image,
|
|
273
|
+
'-e', "require('better-sqlite3')(':memory:').close();require('node-pty')"])
|
|
274
|
+
return image
|
|
275
|
+
|
|
276
|
+
def backup(self):
|
|
277
|
+
archive = self.job / 'home.tar.gz'
|
|
278
|
+
with archive.open('xb') as f, self.log.open('ab') as err:
|
|
279
|
+
try:
|
|
280
|
+
subprocess.run(['docker', 'run', '--rm', '--network', 'none', '--read-only',
|
|
281
|
+
'--mount', f'type=volume,src={self.volume},dst=/data,readonly',
|
|
282
|
+
'--entrypoint', 'tar', self.old_image, '-C', '/data', '-czf', '-', '.'],
|
|
283
|
+
stdout=f, stderr=err, check=True, timeout=3600)
|
|
284
|
+
f.flush()
|
|
285
|
+
os.fsync(f.fileno())
|
|
286
|
+
except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as e:
|
|
287
|
+
raise UpdateError('Failed: backup. The previous version will be restarted.') from e
|
|
288
|
+
check = self.job / 'migration-check'
|
|
289
|
+
check.mkdir()
|
|
290
|
+
# Read the complete gzip stream; extract only DB files as regular files, never symlinks.
|
|
291
|
+
with tarfile.open(archive, 'r|gz') as tar:
|
|
292
|
+
for member in tar:
|
|
293
|
+
if member.name in {f'./.config/kobo/{name}' for name in ('kobo.db','kobo.db-wal','kobo.db-shm')}:
|
|
294
|
+
if not member.isfile():
|
|
295
|
+
raise UpdateError('Unexpected SQLite file in backup.')
|
|
296
|
+
with tar.extractfile(member) as src, (check / Path(member.name).name).open('wb') as dst:
|
|
297
|
+
shutil.copyfileobj(src, dst)
|
|
298
|
+
if not (check / 'kobo.db').is_file():
|
|
299
|
+
raise UpdateError('Backup does not contain the Kōbō database.')
|
|
300
|
+
return check
|
|
301
|
+
|
|
302
|
+
def verify(self, version):
|
|
303
|
+
self.container = self.run(self.compose('ps', '-q', 'kobo'))
|
|
304
|
+
self.run(['docker', 'exec', self.container, 'node', '-e', API_JS])
|
|
305
|
+
last_error = None
|
|
306
|
+
for attempt in range(15):
|
|
307
|
+
try:
|
|
308
|
+
with urllib.request.urlopen(self.public_url + '/api/health', timeout=10) as r:
|
|
309
|
+
health = json.load(r)
|
|
310
|
+
if health.get('version') != version or health.get('status') != 'ok':
|
|
311
|
+
raise UpdateError('Unexpected HTTPS version or health status.')
|
|
312
|
+
try:
|
|
313
|
+
with urllib.request.urlopen(self.public_url + '/api/workspaces', timeout=10):
|
|
314
|
+
raise UpdateError('The public API no longer requires authentication.')
|
|
315
|
+
except urllib.error.HTTPError as e:
|
|
316
|
+
if e.code != 401:
|
|
317
|
+
raise UpdateError('Unexpected public API authentication response.') from e
|
|
318
|
+
return
|
|
319
|
+
except Exception as e:
|
|
320
|
+
last_error = e
|
|
321
|
+
if attempt < 14:
|
|
322
|
+
time.sleep(2)
|
|
323
|
+
raise UpdateError('HTTPS verification failed.') from last_error
|
|
324
|
+
|
|
325
|
+
def execute(self, requested, check_only=False, prepare_only=False):
|
|
326
|
+
for state in (self.stack / 'updates').glob('*/state.json'):
|
|
327
|
+
if json.loads(state.read_text())['phase'] not in {'complete','prepared','aborted'}:
|
|
328
|
+
raise UpdateError(f'Interrupted update requires review: {state}. See RECOVERY.md in that directory.')
|
|
329
|
+
old_tag = self.inspect()
|
|
330
|
+
version, sha = self.resolve(requested)
|
|
331
|
+
say(f'Installed: {self.installed} — target: {version} ({sha[:12]}).')
|
|
332
|
+
if not prepare_only:
|
|
333
|
+
self.maintenance('GET')
|
|
334
|
+
if check_only:
|
|
335
|
+
say('Checks passed. No shutdown or update performed.')
|
|
336
|
+
return
|
|
337
|
+
if version == self.installed and not prepare_only:
|
|
338
|
+
say('This version is already installed. Nothing to do.')
|
|
339
|
+
return
|
|
340
|
+
updates = self.stack / 'updates'
|
|
341
|
+
updates.mkdir(mode=0o700, exist_ok=True)
|
|
342
|
+
self.job = Path(tempfile.mkdtemp(prefix=datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ-'), dir=updates))
|
|
343
|
+
self.log = self.job / 'update.log'
|
|
344
|
+
self.log.touch(mode=0o600)
|
|
345
|
+
(self.job / 'compose.before.yaml').write_bytes(self.original)
|
|
346
|
+
self.record('building', previous_image=old_tag, previous_digest=self.old_image,
|
|
347
|
+
volume=self.volume, version=version, sha=sha,
|
|
348
|
+
maintenance_owner=self.maintenance_owner,
|
|
349
|
+
backup_complete=False, new_version_start_attempted=False)
|
|
350
|
+
self.recovery_notes()
|
|
351
|
+
stopped = False
|
|
352
|
+
launching = False
|
|
353
|
+
maintenance_requested = False
|
|
354
|
+
try:
|
|
355
|
+
say(f'Building the image; Kōbō remains available. Log: {self.log}')
|
|
356
|
+
image = self.build(version, sha)
|
|
357
|
+
candidate = self.job / 'compose.next.yaml'
|
|
358
|
+
candidate.write_text(image_config(self.original.decode(), image))
|
|
359
|
+
self.run(self.compose('config', '--quiet', file=candidate))
|
|
360
|
+
if prepare_only:
|
|
361
|
+
self.record('prepared', image=image)
|
|
362
|
+
say(f'Image ready: {image}. Service was not interrupted.')
|
|
363
|
+
return
|
|
364
|
+
self.idle() # Agents may have started while the build ran.
|
|
365
|
+
if self.compose_file.read_bytes() != self.original:
|
|
366
|
+
raise UpdateError('Compose changed during the build. Aborting without deployment.')
|
|
367
|
+
if json.loads(self.run(self.compose('config', '--format', 'json'))) != self.config_snapshot:
|
|
368
|
+
raise UpdateError('Resolved Compose configuration changed during the build (environment or files). Aborting.')
|
|
369
|
+
self.record('reserving', image=image)
|
|
370
|
+
# Set before sending: a lost response can still mean the server acquired our reservation.
|
|
371
|
+
maintenance_requested = True
|
|
372
|
+
self.maintenance('POST')
|
|
373
|
+
self.record('stopping')
|
|
374
|
+
stopped = True # Also recover if the stop command itself fails/interruption arrives.
|
|
375
|
+
say('Stopping Kōbō, backing up /root and testing migrations on a database copy…')
|
|
376
|
+
self.run(self.compose('stop', '-t', '60', 'kobo'))
|
|
377
|
+
info = json.loads(self.run(['docker', 'inspect', self.container]))[0]
|
|
378
|
+
if info['State']['Running']:
|
|
379
|
+
raise UpdateError('The container did not stop.')
|
|
380
|
+
check = self.backup()
|
|
381
|
+
self.record('backed-up', backup_complete=True)
|
|
382
|
+
self.logged(['docker', 'run', '--rm', '--network', 'none',
|
|
383
|
+
'--mount', f'type=bind,src={check},dst=/check', '--entrypoint', 'node',
|
|
384
|
+
image, '--input-type=module', '-e', MIGRATION_JS], timeout=180)
|
|
385
|
+
atomic_write(self.compose_file, candidate.read_bytes(), self.original_mode)
|
|
386
|
+
# From here migrations/automatic jobs may have run. Never automatically restore old data.
|
|
387
|
+
launching = True
|
|
388
|
+
self.record('launching', new_version_start_attempted=True)
|
|
389
|
+
say('Starting the new version and checking the API and HTTPS…')
|
|
390
|
+
self.logged(self.compose('up', '-d', '--no-deps', '--wait', '--wait-timeout', '180', 'kobo'), timeout=240)
|
|
391
|
+
self.verify(version)
|
|
392
|
+
self.record('complete')
|
|
393
|
+
say(f'Kōbō {version} is up to date. Backup: {self.job / "home.tar.gz"}')
|
|
394
|
+
except BaseException:
|
|
395
|
+
if stopped and not launching:
|
|
396
|
+
# Only safe before any process from the new version has used production data.
|
|
397
|
+
try:
|
|
398
|
+
# A failed/interrupted CLI does not prove the daemon's stop completed.
|
|
399
|
+
# Keep admission reserved until the old process is confirmed gone.
|
|
400
|
+
info = json.loads(self.run(['docker', 'inspect', self.container]))[0]
|
|
401
|
+
if info['State']['Running']:
|
|
402
|
+
self.run(['docker', 'stop', '-t', '60', self.container])
|
|
403
|
+
info = json.loads(self.run(['docker', 'inspect', self.container]))[0]
|
|
404
|
+
if info['State']['Running']:
|
|
405
|
+
raise UpdateError('Previous process shutdown is unconfirmed.')
|
|
406
|
+
atomic_write(self.compose_file, self.original, self.original_mode)
|
|
407
|
+
self.logged(self.compose('up', '-d', '--no-deps', '--wait', '--wait-timeout', '180', 'kobo'), timeout=240)
|
|
408
|
+
self.record('aborted')
|
|
409
|
+
say('Update aborted; previous version restarted, original database unchanged.')
|
|
410
|
+
except BaseException:
|
|
411
|
+
self.record('recovery-required')
|
|
412
|
+
say(f'Restart requires review. Instructions: {self.job / "RECOVERY.md"}')
|
|
413
|
+
elif launching:
|
|
414
|
+
self.record('recovery-required')
|
|
415
|
+
say(f'New version requires diagnosis. Data retained; no automatic database rollback. See {self.job / "RECOVERY.md"}')
|
|
416
|
+
else:
|
|
417
|
+
try:
|
|
418
|
+
if maintenance_requested:
|
|
419
|
+
self.maintenance('DELETE')
|
|
420
|
+
self.record('aborted')
|
|
421
|
+
except BaseException:
|
|
422
|
+
self.record('recovery-required')
|
|
423
|
+
say(f'Update reservation may still be active. Instructions: {self.job / "RECOVERY.md"}')
|
|
424
|
+
raise
|
|
425
|
+
|
|
426
|
+
def recovery_notes(self):
|
|
427
|
+
import shlex
|
|
428
|
+
restart = shlex.join(self.compose('up', '-d', '--no-deps', '--wait', 'kobo'))
|
|
429
|
+
(self.job / 'RECOVERY.md').write_text(f'''# Kōbō update recovery
|
|
430
|
+
|
|
431
|
+
Private log: `{self.job / 'update.log'}`. Durable phase: `state.json`.
|
|
432
|
+
Previous configuration: `compose.before.yaml`.
|
|
433
|
+
Previous immutable image: `{self.old_image}`.
|
|
434
|
+
Original volume: `{self.volume}`.
|
|
435
|
+
The archive `home.tar.gz` is validated only when backup_complete=true.
|
|
436
|
+
new_version_start_attempted indicates whether a new-version launch was attempted.
|
|
437
|
+
Projects outside /root are NOT included in this archive; back them up separately.
|
|
438
|
+
Old images, volumes and backups are never automatically deleted.
|
|
439
|
+
|
|
440
|
+
## Interrupted admission reservation
|
|
441
|
+
The runtime reservation blocks agent starts and defers scheduled deliveries until
|
|
442
|
+
the container stops or the owner explicitly releases it. It does not expire.
|
|
443
|
+
If phase is reserving/stopping/recovery-required, inspect whether the old process
|
|
444
|
+
is still running. Before any new-version launch, stop it with Docker and confirm
|
|
445
|
+
it has exited before restarting the previous configuration below; this clears
|
|
446
|
+
the in-memory reservation. Do not release admission while a Docker stop may still
|
|
447
|
+
be in progress. The reservation identifier is state.json's maintenance_owner.
|
|
448
|
+
|
|
449
|
+
## Before a new-version launch
|
|
450
|
+
If no new-version launch was attempted, restore compose.before.yaml to
|
|
451
|
+
`{self.compose_file}` and run:
|
|
452
|
+
|
|
453
|
+
```sh
|
|
454
|
+
{restart}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
Verify service health before setting state.json phase to aborted to allow a retry.
|
|
458
|
+
|
|
459
|
+
## After a new-version launch (launching/recovery-required)
|
|
460
|
+
Do not run an older image against a potentially migrated database.
|
|
461
|
+
Inspect the private log and Compose logs (which may contain credentials).
|
|
462
|
+
A proxy error can leave a working backend: check for new messages and resumed
|
|
463
|
+
work before considering a restore.
|
|
464
|
+
To roll back fully, stop Kōbō, back up the current volume too, extract home.tar.gz
|
|
465
|
+
into a NEW empty Docker volume and adapt the previous Compose configuration to
|
|
466
|
+
use that restored volume and the previous immutable image above. Keep the current
|
|
467
|
+
volume. Database rollback does not undo external agent or project changes.
|
|
468
|
+
After recovery and health verification, set state.json phase to complete or aborted.
|
|
469
|
+
''')
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def main():
|
|
473
|
+
parser = argparse.ArgumentParser(description='Update a Kōbō VPS Compose stack (latest stable release by default).')
|
|
474
|
+
parser.add_argument('version', nargs='?', default='latest', help='latest, 2.1.16 or v2.1.16')
|
|
475
|
+
modes = parser.add_mutually_exclusive_group()
|
|
476
|
+
modes.add_argument('--check', action='store_true', help='check without building or interrupting the service')
|
|
477
|
+
modes.add_argument('--prepare', action='store_true', help='build and test the image without deploying')
|
|
478
|
+
parser.add_argument('--directory', type=Path, default=Path.cwd(),
|
|
479
|
+
help='deployment directory containing compose.yaml and .env (default: current directory)')
|
|
480
|
+
args = parser.parse_args()
|
|
481
|
+
os.umask(0o077)
|
|
482
|
+
for sig in (signal.SIGTERM, signal.SIGHUP):
|
|
483
|
+
signal.signal(sig, lambda *_: (_ for _ in ()).throw(KeyboardInterrupt()))
|
|
484
|
+
try:
|
|
485
|
+
with (args.directory / '.update.lock').open('a') as lock:
|
|
486
|
+
try:
|
|
487
|
+
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
488
|
+
except BlockingIOError:
|
|
489
|
+
raise UpdateError('Another update is already running.')
|
|
490
|
+
Updater(args.directory).execute(args.version, args.check, args.prepare)
|
|
491
|
+
except (Exception, KeyboardInterrupt) as e:
|
|
492
|
+
message = str(e) if isinstance(e, UpdateError) else type(e).__name__
|
|
493
|
+
say(f'Stopped: {message}.')
|
|
494
|
+
return 1
|
|
495
|
+
return 0
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
if __name__ == '__main__':
|
|
499
|
+
sys.exit(main())
|
|
@@ -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,Ht as O,O as le,Zt as ue,at as de,en as fe,gt as pe,j as me,m as he,mt as ge,pt as _e,sn as k,st as ve,un as A,v as ye}from"./index-DCpe-Tfm.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-Dh4CjliE.js";import{t as P}from"./QItemSection-MdJ1_ZK7.js";import{t as Ce}from"./QExpansionItem-DjaUhgjt.js";import{a as F,c as we,r as I,t as L}from"./ToolCallItem-BntDHc6p.js";import{t as Te}from"./QScrollArea-Dpksslru.js";import{a as R}from"./use-tours-CWafZt-m.js";import{f as Ee,g as z,m as De,p as Oe,u as ke}from"./use-worktree-restore-DkcM2FAZ.js";import{t as B}from"./QTooltip-CH45NuVs.js";import{n as V}from"./render-chat-markdown-CafzCcmu.js";import{n as H}from"./WorkspacePage-CAsY7Hl-.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:A},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=k(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}=ue(),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=O(e,{signal:n.signal}).then(async e=>{if(!e.ok)throw await ce(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}=ue(),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-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(fe,{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-db2fc622`]]),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}=ue(),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 fe(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(!fe(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}=ue(),r=t,i=pe(),s=me(),d=de(),m=he(),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(),fe=``,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!==fe&&(le=ke(),fe=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 O(`/api/workspaces/${e}/events?${c.toString()}`,{signal:a});if(!l.ok)throw await ce(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(ge(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 O(`/api/workspaces/${a}/events?${e}`,{signal:n});if(!o.ok)throw await ce(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(ge(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 O(`/api/workspaces/${e}/events?session=${encodeURIComponent(t)}&limit=500`,{signal:a});if(!n.ok)throw await ce(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(ge(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};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{M as e,pt as t,v as n}from"./reactivity.esm-bundler-DY1JkAEA.js";import{J as r,L as i,Y as a,_ as o,d as s,f as c,l,p as u,r as d,u as f,y as p}from"./runtime-core.esm-bundler-Bqg71Vll.js";import{t as m}from"./QBtn-BUSdPaf4.js";import{
|
|
1
|
+
import{M as e,pt as t,v as n}from"./reactivity.esm-bundler-DY1JkAEA.js";import{J as r,L as i,Y as a,_ as o,d as s,f as c,l,p as u,r as d,u as f,y as p}from"./runtime-core.esm-bundler-Bqg71Vll.js";import{t as m}from"./QBtn-BUSdPaf4.js";import{At as h,Zt as g,_n as _,en as v,f as y,hn as b,j as x,jt as S,l as C}from"./index-DCpe-Tfm.js";import{t as w}from"./QSelect-Dh4CjliE.js";import{a as T}from"./use-tours-CWafZt-m.js";import{i as E,t as D}from"./permissionModes-DLWiBfdb.js";import{t as O}from"./efforts-Y6XotW97.js";function k(e,t){return e.engine!==t.engine||e.model!==t.model||e.reasoningEffort!==t.reasoningEffort||e.agentPermissionMode!==t.agentPermissionMode}var A={class:`text-h6`},ee={class:`text-body2 text-kobo-2 q-mt-xs`},te={class:`row q-col-gutter-sm q-mb-md`},ne={class:`col-12 col-sm-6`},j={class:`col-12 col-sm-6`},M={class:`col-12 col-sm-6`},N={class:`col-12 col-sm-6`},P={key:0,class:`text-caption text-kobo-3 q-mt-xs`},F={class:`text-caption text-kobo-3 q-mt-xs`},I={class:`text-caption text-kobo-3 q-mt-xs`},L=T(p({__name:`StartReviewDialog`,props:{modelValue:{type:Boolean},loading:{type:Boolean},workspace:{},canReturnToSession:{type:Boolean},currentSession:{},mode:{},scheduledConfiguration:{}},emits:[`update:modelValue`,`submit`],setup(e,{emit:p}){let T=e,L=p,{t:R}=g(),z=x(),B=l(()=>T.mode===`auto-loop`),V=n(``),H=n(!1),U=n(!1),W=n(`claude-code`),G=n(`auto`),K=n(`auto`),q=n(`bypass`),J=l(()=>B.value||U.value?`plan`:q.value),Y=l(()=>({engine:W.value,model:G.value,reasoningEffort:K.value,agentPermissionMode:J.value})),X=l(()=>{let e=T.currentSession;return B.value||U.value||!!T.workspace&&k(Y.value,T.workspace)||!!e?.engine&&e.engine!==W.value||G.value!==`auto`&&!!e?.model&&e.model!==G.value}),Z=l({get:()=>H.value||X.value,set:e=>{H.value=e}}),re=l(()=>[{value:`claude-code`,label:R(`workspacePage.engineClaude`)},{value:`codex`,label:R(`workspacePage.engineCodex`)}]),Q=l(()=>{let e=(E[W.value]??[]).map(e=>({value:e.value,label:R(e.i18nLabelKey)}));T.workspace?.engine===W.value&&!e.some(e=>e.value===T.workspace?.model)&&e.push({value:T.workspace.model,label:T.workspace.model});let t=T.scheduledConfiguration;return t?.engine===W.value&&!e.some(e=>e.value===t.model)&&e.push({value:t.model,label:t.model}),e}),ie=l(()=>(O[W.value]??[]).map(e=>({value:e.value,label:R(e.i18nLabelKey)}))),$=l(()=>(D[W.value]??[]).map(e=>({value:e,label:R(`agentPermissionMode.${e}`)})));r(W,()=>{let e=T.workspace,t=z.global.defaultModelByEngine[W.value];G.value=e?.engine===W.value?e.model:Q.value.some(e=>e.value===t)?t:`auto`,K.value=e?.engine===W.value?e.reasoningEffort:`auto`,$.value.some(e=>e.value===q.value)||(q.value=`plan`)},{flush:`sync`}),r([()=>T.modelValue,()=>T.workspace?.id],([e])=>{if(!e)return;let t=B.value?T.scheduledConfiguration:null;V.value=t?.additionalInstructions??``,H.value=!1,U.value=B.value,T.workspace&&(W.value=T.workspace.engine,G.value=T.workspace.model,K.value=T.workspace.reasoningEffort,q.value=T.workspace.agentPermissionMode),t&&(W.value=t.engine,G.value=t.model,K.value=t.reasoningEffort)},{immediate:!0}),r(()=>T.canReturnToSession,e=>{!e&&!B.value&&(U.value=!1)});function ae(){T.loading||L(`update:modelValue`,!1)}function oe(){!T.loading&&T.workspace&&L(`submit`,{...Y.value,additionalInstructions:V.value,newSession:Z.value,returnToSession:B.value||U.value&&T.canReturnToSession})}return(n,r)=>(i(),s(v,{"model-value":e.modelValue,persistent:e.loading,"onUpdate:modelValue":r[7]||=e=>L(`update:modelValue`,e)},{default:a(()=>[o(b,{class:`review-card text-kobo-1`},{default:a(()=>[o(_,null,{default:a(()=>[f(`div`,A,t(n.$t(B.value?`autoLoop.finalReview.title`:`review.title`)),1),f(`div`,ee,t(n.$t(B.value?`autoLoop.finalReview.description`:`review.subtitle`)),1)]),_:1}),o(y,{dark:``}),o(_,{class:`q-pt-md`},{default:a(()=>[f(`div`,te,[f(`div`,ne,[o(w,{modelValue:W.value,"onUpdate:modelValue":r[0]||=e=>W.value=e,options:re.value,label:n.$t(`engine.select`),disable:e.loading,"emit-value":``,"map-options":``,outlined:``,dark:``,dense:``},null,8,[`modelValue`,`options`,`label`,`disable`])]),f(`div`,j,[o(w,{modelValue:G.value,"onUpdate:modelValue":r[1]||=e=>G.value=e,options:Q.value,label:n.$t(`engine.model`),disable:e.loading,"emit-value":``,"map-options":``,outlined:``,dark:``,dense:``},null,8,[`modelValue`,`options`,`label`,`disable`])]),f(`div`,M,[o(w,{modelValue:K.value,"onUpdate:modelValue":r[2]||=e=>K.value=e,options:ie.value,label:n.$t(`engine.effort`),disable:e.loading,"emit-value":``,"map-options":``,outlined:``,dark:``,dense:``},null,8,[`modelValue`,`options`,`label`,`disable`])]),f(`div`,N,[o(w,{"model-value":J.value,options:$.value,label:n.$t(`agentPermissionMode.label`),disable:e.loading||U.value,"emit-value":``,"map-options":``,outlined:``,dark:``,dense:``,"onUpdate:modelValue":r[3]||=e=>q.value=e},null,8,[`model-value`,`options`,`label`,`disable`]),U.value?(i(),u(`div`,P,t(n.$t(`review.readOnlyForced`)),1)):c(``,!0)])]),o(S,{modelValue:V.value,"onUpdate:modelValue":r[4]||=e=>V.value=e,type:`textarea`,disable:e.loading,label:n.$t(`review.additionalInstructions`),placeholder:n.$t(`review.additionalInstructionsPlaceholder`),rows:4,outlined:``,dark:``,color:`primary`},null,8,[`modelValue`,`disable`,`label`,`placeholder`])]),_:1}),B.value?c(``,!0):(i(),s(_,{key:0,class:`q-pt-none`},{default:a(()=>[o(C,{modelValue:Z.value,"onUpdate:modelValue":r[5]||=e=>Z.value=e,disable:e.loading||X.value,label:n.$t(`review.newSession`),color:`primary`,dark:``},null,8,[`modelValue`,`disable`,`label`]),f(`div`,F,t(n.$t(X.value?`review.newSessionRequired`:`review.newSessionHint`)),1),Z.value?(i(),u(d,{key:0},[o(C,{modelValue:U.value,"onUpdate:modelValue":r[6]||=e=>U.value=e,disable:e.loading||!e.canReturnToSession,label:n.$t(`review.returnToSession`),color:`primary`,dark:``,class:`q-mt-md`},null,8,[`modelValue`,`disable`,`label`]),f(`div`,I,t(n.$t(e.canReturnToSession?`review.returnToSessionHint`:`review.returnUnavailable`)),1)],64)):c(``,!0)]),_:1})),o(h,{align:`right`,class:`q-pa-md`},{default:a(()=>[o(m,{flat:``,"no-caps":``,label:n.$t(`review.cancel`),color:`kobo-2`,disable:e.loading,onClick:ae},null,8,[`label`,`disable`]),o(m,{"no-caps":``,color:`primary`,label:n.$t(B.value?`autoLoop.finalReview.save`:`review.start`),loading:e.loading,disable:!e.workspace||e.loading,onClick:oe},null,8,[`label`,`loading`,`disable`])]),_:1})]),_:1})]),_:1},8,[`model-value`,`persistent`]))}}),[[`__scopeId`,`data-v-a4419350`]]),R={class:`final-review-settings`},z={class:`text-caption text-kobo-3`},B=T(p({__name:`AutoLoopFinalReviewSettings`,props:{modelValue:{},workspace:{},disabled:{type:Boolean},loading:{type:Boolean}},emits:[`update:modelValue`],setup(a,{emit:l}){let d=a,p=l,{t:h}=g(),_=n(!1),v=n(null);r(()=>d.modelValue,e=>{JSON.stringify(e)===JSON.stringify(v.value)&&(v.value=null)}),r(()=>d.workspace.id,()=>{_.value=!1,v.value=null});function y(e){d.disabled||d.loading||(e?_.value=!0:p(`update:modelValue`,null))}function b(e){!d.disabled&&!d.loading&&e.engine&&e.model&&e.reasoningEffort&&(v.value={engine:e.engine,model:e.model,reasoningEffort:e.reasoningEffort,additionalInstructions:e.additionalInstructions??``},p(`update:modelValue`,v.value),_.value=!1)}return(n,r)=>(i(),u(`div`,R,[o(C,{"model-value":!!a.modelValue,disable:a.disabled||a.loading,color:`primary`,label:e(h)(`autoLoop.finalReview.enabled`),"onUpdate:modelValue":y},null,8,[`model-value`,`disable`,`label`]),f(`p`,z,t(e(h)(`autoLoop.finalReview.hint`)),1),a.modelValue?(i(),s(m,{key:0,flat:``,dense:``,"no-caps":``,disable:a.disabled||a.loading,label:e(h)(`autoLoop.finalReview.configure`),onClick:r[0]||=e=>_.value=!0},null,8,[`disable`,`label`])):c(``,!0),o(L,{modelValue:_.value,"onUpdate:modelValue":r[1]||=e=>_.value=e,mode:`auto-loop`,loading:a.loading??!1,workspace:a.workspace,"scheduled-configuration":v.value??a.modelValue,"can-return-to-session":!0,onSubmit:b},null,8,[`modelValue`,`loading`,`workspace`,`scheduled-configuration`])]))}}),[[`__scopeId`,`data-v-b7f36f83`]]);export{L as n,B as t};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{M as e,pt as t,v as n}from"./reactivity.esm-bundler-DY1JkAEA.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-Bqg71Vll.js";import{t as m}from"./QBtn-BUSdPaf4.js";import{
|
|
1
|
+
import{M as e,pt as t,v as n}from"./reactivity.esm-bundler-DY1JkAEA.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-Bqg71Vll.js";import{t as m}from"./QBtn-BUSdPaf4.js";import{Gt as h,Ht as g,_n as _,a as v,hn as y}from"./index-DCpe-Tfm.js";import{a as b,i as x,n as S}from"./use-tours-CWafZt-m.js";import{n as C}from"./render-chat-markdown-CafzCcmu.js";import{t as w}from"./QChip-Be16M4Ig.js";import{t as T}from"./QPage-CqkGHokx.js";import{t as E}from"./DrawerToggleButton-LzIEeeJY.js";import{t as D}from"./TourReplayButton-OIavXdUg.js";var O={class:`row items-center q-mb-md`},k={class:`text-h6 q-ml-sm`},A={key:0,class:`text-kobo-3 text-center q-pa-lg`},j={key:1,class:`text-negative text-center q-pa-lg`},M={key:2,class:`text-kobo-3 text-center q-pa-lg`},N={key:3,class:`column q-gutter-md`,"data-tour":`changelog-list`},P={key:0,class:`text-caption text-kobo-3`},F={class:`row items-center q-mb-sm`},I={class:`text-subtitle1 text-kobo-2`,style:{"font-family":`var(--kobo-font-mono, monospace)`}},L=[`innerHTML`],R=b(f({__name:`ChangelogPage`,setup(f){let b=v(),{scheduleAutoRun:R}=S(),z=n([]),B=n(``),V=n(!1),H=n(null);function U(e){return C(e)}async function W(){V.value=!0,H.value=null;try{let e=await g(`/api/changelog`);if(!e.ok)throw await h(e);let t=await e.json();B.value=t.currentVersion??``,z.value=t.versions??[]}catch(e){H.value=e instanceof Error?e.message:String(e)}finally{V.value=!1}}return i(()=>{W(),R(`changelog`)}),(n,i)=>(r(),s(T,{class:`q-pa-md`,style:{"max-width":`900px`,margin:`0 auto`}},{default:a(()=>[d(`div`,O,[o(E,{class:`q-mr-xs`}),o(m,{flat:``,dense:``,round:``,icon:`arrow_back`,onClick:i[0]||=t=>e(b).back()}),d(`div`,k,t(n.$t(`changelog.title`)),1),o(x),o(m,{flat:``,dense:``,icon:`refresh`,loading:V.value,label:n.$t(`common.refresh`),onClick:W},null,8,[`loading`,`label`]),o(D,{"tour-id":`changelog`,class:`q-ml-xs`})]),V.value&&z.value.length===0?(r(),l(`div`,A,t(n.$t(`common.loading`)),1)):H.value?(r(),l(`div`,j,t(H.value),1)):z.value.length===0?(r(),l(`div`,M,t(n.$t(`changelog.empty`)),1)):(r(),l(`div`,N,[B.value?(r(),l(`div`,P,t(n.$t(`changelog.currentVersion`,{version:B.value})),1)):c(``,!0),(r(!0),l(u,null,p(z.value,e=>(r(),s(y,{key:e.version,dark:``,flat:``,bordered:``},{default:a(()=>[o(_,null,{default:a(()=>[d(`div`,F,[d(`div`,I,` v`+t(e.version),1),e.version===B.value?(r(),s(w,{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:U(e.notes)},null,8,L)]),_:2},1024)]),_:2},1024))),128))]))]),_:1}))}}),[[`__scopeId`,`data-v-4032778f`]]);export{R as default};
|