@loicngr/kobo 1.11.28 → 1.11.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/AGENTS.md +6 -0
  2. package/CHANGELOG.md +9 -0
  3. package/dist/server/index.js +2 -0
  4. package/dist/server/routes/workspace-templates.js +69 -0
  5. package/dist/server/routes/workspaces.js +32 -6
  6. package/dist/server/services/comparison-stats-service.js +43 -0
  7. package/dist/server/services/settings-defaults.js +10 -0
  8. package/dist/server/services/settings-service.js +20 -2
  9. package/dist/server/services/workspace-template-service.js +201 -0
  10. package/dist/server/utils/paths.js +4 -0
  11. package/dist/shared/codex-models.js +9 -3
  12. package/package.json +1 -1
  13. package/src/client/dist/pwa/assets/{ActivityFeed-LDzg5XPx.js → ActivityFeed-Cx1rS3ci.js} +1 -1
  14. package/src/client/dist/pwa/assets/{ChangelogPage-CjiWI9Qa.js → ChangelogPage-2OaGEKe9.js} +1 -1
  15. package/src/client/dist/pwa/assets/{ClosePopup-CK6wvsMy.js → ClosePopup-CvV-0nzX.js} +1 -1
  16. package/src/client/dist/pwa/assets/ComparisonPanel-BmUrmK4m.js +2 -0
  17. package/src/client/dist/pwa/assets/ComparisonPanel-x1iuICgd.css +1 -0
  18. package/src/client/dist/pwa/assets/CreatePage-C0uUaDXT.css +1 -0
  19. package/src/client/dist/pwa/assets/CreatePage-Hp3O5al9.js +2 -0
  20. package/src/client/dist/pwa/assets/{DashboardPage-Xev4suQ7.js → DashboardPage-Mgx1FG2b.js} +1 -1
  21. package/src/client/dist/pwa/assets/{DiffViewer-D0BQ5Dip.js → DiffViewer-aMjhTpVF.js} +3 -3
  22. package/src/client/dist/pwa/assets/{DocumentsPanel-7hMCYUTd.js → DocumentsPanel-Cdr5b21_.js} +1 -1
  23. package/src/client/dist/pwa/assets/{DrawerToggleButton-BASeszPg.js → DrawerToggleButton-VEVxQ39S.js} +1 -1
  24. package/src/client/dist/pwa/assets/{GitPanel-D-rwHnFn.js → GitPanel-DUJ15ikk.js} +3 -3
  25. package/src/client/dist/pwa/assets/{HealthPage-BKXqP1li.js → HealthPage-v_jKOByf.js} +1 -1
  26. package/src/client/dist/pwa/assets/{MainLayout-ExB0xYHB.css → MainLayout-DxYQV-Ni.css} +1 -1
  27. package/src/client/dist/pwa/assets/MainLayout-jRXQQzPm.js +2 -0
  28. package/src/client/dist/pwa/assets/{QBtnDropdown-52YHuBbx.js → QBtnDropdown-pyNCwmSZ.js} +1 -1
  29. package/src/client/dist/pwa/assets/{SchedulePanel-C7NK1hIK.js → SchedulePanel-DBc1j1k3.js} +1 -1
  30. package/src/client/dist/pwa/assets/{SearchPage-Cm8chltA.js → SearchPage-CWca6d5o.js} +1 -1
  31. package/src/client/dist/pwa/assets/{SettingsPage-2vdOZu1C.css → SettingsPage-DHFMKY_1.css} +1 -1
  32. package/src/client/dist/pwa/assets/SettingsPage-qKa3e6W-.js +10 -0
  33. package/src/client/dist/pwa/assets/{TerminalPanel-CSPk8QtK.js → TerminalPanel-Dv_1sNtU.js} +1 -1
  34. package/src/client/dist/pwa/assets/{TouchPan-CpO61wPy.js → TouchPan-DPBRkGyO.js} +1 -1
  35. package/src/client/dist/pwa/assets/{WorkspaceAttentionLabels-DZrbJ0vD.js → WorkspaceAttentionLabels-CevQAXwk.js} +1 -1
  36. package/src/client/dist/pwa/assets/{WorkspacePage-D0-FkRVJ.js → WorkspacePage-CEwILZ6V.js} +3 -3
  37. package/src/client/dist/pwa/assets/{build-path-tree-DH4SSsEz.js → build-path-tree-Cx5bCcQs.js} +1 -1
  38. package/src/client/dist/pwa/assets/de-BUPxnXau.js +17 -0
  39. package/src/client/dist/pwa/assets/{documents-CDZqd_g5.js → documents-CN6TlZH7.js} +1 -1
  40. package/src/client/dist/pwa/assets/{editor.api-CMsYdZTX.js → editor.api-vup96nzB.js} +1 -1
  41. package/src/client/dist/pwa/assets/{efforts-Db4w0alg.js → efforts-BcV6XR1p.js} +1 -1
  42. package/src/client/dist/pwa/assets/{engineFeatures-BjtQR0vB.js → engineFeatures-D9IQa3bt.js} +1 -1
  43. package/src/client/dist/pwa/assets/es-DMnEKZ17.js +17 -0
  44. package/src/client/dist/pwa/assets/{expand-template-DY8ntqUb.js → expand-template-Cxl3iYaV.js} +1 -1
  45. package/src/client/dist/pwa/assets/{format-C-9jMgWb.js → format-DSh3Ghaw.js} +1 -1
  46. package/src/client/dist/pwa/assets/fr-BTolHkCj.js +17 -0
  47. package/src/client/dist/pwa/assets/{freemarker2-KxB1BtrL.js → freemarker2-BdMCo-ui.js} +1 -1
  48. package/src/client/dist/pwa/assets/{handlebars-7TByd_Zc.js → handlebars-CVUpmZRG.js} +1 -1
  49. package/src/client/dist/pwa/assets/{html-BoVZ_gre.js → html-B9nx87id.js} +1 -1
  50. package/src/client/dist/pwa/assets/{i18n-BubYp85n.js → i18n-B_ietr0X.js} +1 -1
  51. package/src/client/dist/pwa/assets/{index-Ck1_qrff.js → index-BePGTdV0.js} +4 -4
  52. package/src/client/dist/pwa/assets/it-BPFSv6Pp.js +17 -0
  53. package/src/client/dist/pwa/assets/{javascript-BLL8vPQy.js → javascript-B0F3ZRYP.js} +1 -1
  54. package/src/client/dist/pwa/assets/{layout-CFuk4mdG.js → layout-DE4C3CX3.js} +1 -1
  55. package/src/client/dist/pwa/assets/{liquid-DzcT-R6k.js → liquid-C2jkK9Te.js} +1 -1
  56. package/src/client/dist/pwa/assets/{mdx-Bguo999w.js → mdx-B9CyGsGZ.js} +1 -1
  57. package/src/client/dist/pwa/assets/{monaco-contributions-CM7FH-iZ.js → monaco-contributions-BZH2_u8S.js} +1 -1
  58. package/src/client/dist/pwa/assets/{monaco.contribution-Dkq0qtWA.js → monaco.contribution--NRhv52L.js} +2 -2
  59. package/src/client/dist/pwa/assets/{network-auth-C0moem8_.js → network-auth-CXNehcyv.js} +1 -1
  60. package/src/client/dist/pwa/assets/{notify-theme-C7mNmzKx.js → notify-theme-DzIPRvEu.js} +1 -1
  61. package/src/client/dist/pwa/assets/{pending-diff-open-BaQ_SNEF.js → pending-diff-open-DoKaz8uO.js} +1 -1
  62. package/src/client/dist/pwa/assets/{permissionModes-CJZsT9Fl.js → permissionModes-D58HKBN0.js} +2 -2
  63. package/src/client/dist/pwa/assets/{project-color-54JZHVy2.js → project-color-D8UVi4-G.js} +1 -1
  64. package/src/client/dist/pwa/assets/{python-D5RQNiPF.js → python-D6494OxU.js} +1 -1
  65. package/src/client/dist/pwa/assets/{razor-CVwoDJol.js → razor-uuwEcrBe.js} +1 -1
  66. package/src/client/dist/pwa/assets/{render-chat-markdown-NAmPVIQp.js → render-chat-markdown-BTcSOgEc.js} +1 -1
  67. package/src/client/dist/pwa/assets/{typescript-qdgwClKe.js → typescript-umL7JrOg.js} +1 -1
  68. package/src/client/dist/pwa/assets/{use-onboarding-DbYF8ZX_.js → use-onboarding-BE2bb17E.js} +1 -1
  69. package/src/client/dist/pwa/assets/{use-quasar-B4eIgSfZ.js → use-quasar-0nZoDKhp.js} +1 -1
  70. package/src/client/dist/pwa/assets/workspace-templates-CkKJmfTo.js +1 -0
  71. package/src/client/dist/pwa/assets/{xml-DNSfQSlk.js → xml-Nu9IP_9Z.js} +1 -1
  72. package/src/client/dist/pwa/assets/{yaml-6gnX2NnP.js → yaml-D_Py0JsF.js} +1 -1
  73. package/src/client/dist/pwa/index.html +1 -1
  74. package/src/client/dist/pwa/sw.js +1 -1
  75. package/src/client/dist/pwa/assets/ComparisonPanel-C0hI2D1O.css +0 -1
  76. package/src/client/dist/pwa/assets/ComparisonPanel-D6eEciwQ.js +0 -1
  77. package/src/client/dist/pwa/assets/CreatePage-DHBa7ZL5.js +0 -2
  78. package/src/client/dist/pwa/assets/CreatePage-DeKYK6uC.css +0 -1
  79. package/src/client/dist/pwa/assets/MainLayout-DnPtpWgP.js +0 -2
  80. package/src/client/dist/pwa/assets/SettingsPage-B4xs1RUl.js +0 -10
  81. package/src/client/dist/pwa/assets/de-Bf_OsgDw.js +0 -17
  82. package/src/client/dist/pwa/assets/es-1ib0e9jm.js +0 -17
  83. package/src/client/dist/pwa/assets/fr-nj0BrM3X.js +0 -17
  84. package/src/client/dist/pwa/assets/it-B9MEgvg1.js +0 -17
package/AGENTS.md CHANGED
@@ -70,6 +70,7 @@ src/
70
70
  │ │ ├── settings-defaults.ts # DEFAULT_* constants for opt-in settings (e.g. change-source-branch script)
71
71
  │ │ ├── lifecycle-hook-service.ts # user shell hooks on session-ended / pr-merged / autoloop-disabled
72
72
  │ │ ├── awaiting-user-reminder-service.ts # reminds about workspaces stuck in `awaiting-user`
73
+ │ │ ├── workspace-template-service.ts # create-form presets (workspace-templates.json) + presetFromWorkspace
73
74
  │ │ ├── content-migration-service.ts # runtime legacy ws_events → normalised AgentEvent migration
74
75
  │ │ ├── templates-service.ts # prompt templates CRUD (JSON file persistence, seeding)
75
76
  │ │ ├── dev-server-service.ts # per-workspace dev server lifecycle (docker or npm process)
@@ -81,6 +82,7 @@ src/
81
82
  │ ├── routes/ # Hono handlers, thin layer over services
82
83
  │ │ ├── workspaces.ts # /api/workspaces/* — the main surface
83
84
  │ │ ├── templates.ts # /api/templates — prompt templates CRUD
85
+ │ │ ├── workspace-templates.ts # /api/workspace-templates - saved create-form presets
84
86
  │ │ ├── plans.ts # /api/workspaces/:id/plans — plan file browser (read-only)
85
87
  │ │ ├── dev-server.ts, git.ts, notion.ts, settings.ts
86
88
  │ ├── utils/
@@ -244,6 +246,10 @@ One task, two engines, two sibling worktrees. `POST /api/workspaces` accepts a `
244
246
 
245
247
  `computeDueReminders` holds the interval arithmetic as a near-pure function over an injected `Map<id, {firstSeenAt, remindersSent}>`, so it is unit-tested without a timer or a DB. Two rules it encodes: a workspace that leaves `awaiting-user` is dropped from the map (answering resets the clock), and a tick that finds itself several intervals behind sends **one** reminder, not one per missed interval. State is in-memory only, like the pr-watcher's caches: a restart restarts the clock.
246
248
 
249
+ ### Workspace templates and duplication
250
+
251
+ `workspace-template-service.ts` persists named presets of the create form in `<KOBO_HOME>/workspace-templates.json` (same JSON pattern as `templates.json`, no SQLite migration). `sanitizePreset` keeps known keys with the right type and drops the rest, so a hand-edited file degrades to unset fields rather than errors. `presetFromWorkspace(id)` derives the same shape from an existing workspace (tasks as titles, statuses dropped); `GET /api/workspaces/:id/preset` exposes it read-only and "Duplicate" opens `/create?from=<id>` with it. On the client, `utils/workspace-preset.ts` (`capturePreset` / `applyPreset`, pure, round-trip tested) is the single bridge between the form and a preset; the create page applies the engine first and waits a tick so its engine watchers normalise model / effort / permission mode before the preset's values land. See [CONFIGURATION.md → Workspace templates and duplication](CONFIGURATION.md#workspace-templates-and-duplication).
252
+
247
253
  ### Worktree purge
248
254
 
249
255
  `src/server/services/worktree-purge-service.ts` removes a workspace's worktree from disk while preserving the chat history and PR metadata. Triggered manually via `POST /api/workspaces/:id/purge-worktree` from the workspace context menu, or automatically by the pr-watcher when a PR transitions to MERGED **and** `global.autoPurgeOnPrMerged` is enabled (Settings → Worktrees toggle, settings migration v36).
package/CHANGELOG.md CHANGED
@@ -4,6 +4,15 @@ All notable changes to Kōbō are documented here. The format is based on
4
4
  [Keep a Changelog](https://keepachangelog.com/). Each release is an `## <version>`
5
5
  section — the in-app "What's new" dialog reads this file.
6
6
 
7
+ ## 1.11.30
8
+
9
+ - feat: workspace templates, duplication and per-engine default models
10
+ - feat(codex): add GPT-6 Astra to the model catalogue
11
+
12
+ ## 1.11.29
13
+
14
+ - feat(comparison): task progress, activity stats and CSV export
15
+
7
16
  ## 1.11.28
8
17
 
9
18
  - feat: lifecycle hooks, reminders, engine comparison and audit follow-ups
@@ -26,6 +26,7 @@ import settingsRouter from './routes/settings.js';
26
26
  import templatesRouter from './routes/templates.js';
27
27
  import usageRoutes from './routes/usage.js';
28
28
  import voiceRouter from './routes/voice.js';
29
+ import workspaceTemplatesRouter from './routes/workspace-templates.js';
29
30
  import workspacesRouter from './routes/workspaces.js';
30
31
  import { getAvailableSkills, isAgentUnavailableError, reconcileOrphanSessions, restoreRetryCountsFromDb, sendMessage, setBackendPort, startAgent, startWatchdog, stopAgent, stopAllAgents, stopWatchdog, } from './services/agent/orchestrator.js';
31
32
  import * as autoLoopService from './services/auto-loop-service.js';
@@ -164,6 +165,7 @@ app.route('/api/changelog', changelogRouter);
164
165
  app.route('/api/settings', settingsRouter);
165
166
  app.route('/api/dev-server', devServerRouter);
166
167
  app.route('/api/templates', templatesRouter);
168
+ app.route('/api/workspace-templates', workspaceTemplatesRouter);
167
169
  app.route('/api/usage', usageRoutes);
168
170
  app.route('/api/workspaces', documentsRouter);
169
171
  app.route('/api/workspaces', exportRouter);
@@ -0,0 +1,69 @@
1
+ import { Hono } from 'hono';
2
+ import * as templates from '../services/workspace-template-service.js';
3
+ /** Saved presets of the create-workspace form. */
4
+ const app = new Hono();
5
+ /** Service errors carry their meaning in the message; everything else is a 500. */
6
+ function statusFor(message) {
7
+ if (message.startsWith('Invalid template name'))
8
+ return 400;
9
+ if (/^Template '.*' already exists$/.test(message))
10
+ return 409;
11
+ if (message.startsWith('Too many templates'))
12
+ return 422;
13
+ return 500;
14
+ }
15
+ app.get('/', (c) => {
16
+ try {
17
+ return c.json({ templates: templates.listWorkspaceTemplates() });
18
+ }
19
+ catch (err) {
20
+ const message = err instanceof Error ? err.message : String(err);
21
+ return c.json({ error: message }, 500);
22
+ }
23
+ });
24
+ app.post('/', async (c) => {
25
+ try {
26
+ const body = await c.req.json().catch(() => null);
27
+ if (!body || typeof body.name !== 'string')
28
+ return c.json({ error: 'name is required' }, 400);
29
+ const template = templates.createWorkspaceTemplate({ name: body.name, preset: body.preset ?? {} });
30
+ return c.json({ template }, 201);
31
+ }
32
+ catch (err) {
33
+ const message = err instanceof Error ? err.message : String(err);
34
+ return c.json({ error: message }, statusFor(message));
35
+ }
36
+ });
37
+ app.put('/:id', async (c) => {
38
+ try {
39
+ const body = await c.req.json().catch(() => null);
40
+ if (!body)
41
+ return c.json({ error: 'Invalid JSON body' }, 400);
42
+ const updates = {};
43
+ if (typeof body.name === 'string')
44
+ updates.name = body.name;
45
+ if (body.preset !== undefined)
46
+ updates.preset = body.preset;
47
+ const template = templates.updateWorkspaceTemplate(c.req.param('id'), updates);
48
+ if (!template)
49
+ return c.json({ error: `Template '${c.req.param('id')}' not found` }, 404);
50
+ return c.json({ template });
51
+ }
52
+ catch (err) {
53
+ const message = err instanceof Error ? err.message : String(err);
54
+ return c.json({ error: message }, statusFor(message));
55
+ }
56
+ });
57
+ app.delete('/:id', (c) => {
58
+ try {
59
+ if (!templates.deleteWorkspaceTemplate(c.req.param('id'))) {
60
+ return c.json({ error: `Template '${c.req.param('id')}' not found` }, 404);
61
+ }
62
+ return c.body(null, 204);
63
+ }
64
+ catch (err) {
65
+ const message = err instanceof Error ? err.message : String(err);
66
+ return c.json({ error: message }, 500);
67
+ }
68
+ });
69
+ export default app;
@@ -14,6 +14,7 @@ import * as autoLoopService from '../services/auto-loop-service.js';
14
14
  import { changeSourceBranch } from '../services/change-source-branch-service.js';
15
15
  import { listChatHistory, pushChatHistory } from '../services/chat-history-service.js';
16
16
  import { renderCiFixTemplate } from '../services/ci-fix-template-service.js';
17
+ import { computeWorkspaceActivityStats } from '../services/comparison-stats-service.js';
17
18
  import * as cronService from '../services/cron-service.js';
18
19
  import * as devServerService from '../services/dev-server-service.js';
19
20
  import { buildEngineHandoff } from '../services/engine-handoff-service.js';
@@ -37,6 +38,7 @@ import * as wakeupService from '../services/wakeup-service.js';
37
38
  import * as wsService from '../services/websocket-service.js';
38
39
  import * as permissionPolicyService from '../services/workspace-permission-policy-service.js';
39
40
  import * as workspaceService from '../services/workspace-service.js';
41
+ import { presetFromWorkspace } from '../services/workspace-template-service.js';
40
42
  import * as purgeWorktreeService from '../services/worktree-purge-service.js';
41
43
  import * as worktreeService from '../services/worktree-service.js';
42
44
  import { resolveUniqueBranchAndPath } from '../utils/branch-resolver.js';
@@ -1426,12 +1428,21 @@ app.get('/:id/comparison', (c) => {
1426
1428
  if (!workspace.comparisonId)
1427
1429
  return c.json({ comparisonId: null, members: [] });
1428
1430
  const stats = getAllGitStats();
1429
- const members = workspaceService.listComparisonMembers(workspace.comparisonId).map((member) => ({
1430
- workspace: member,
1431
- // Null, not zeros: "not measured yet" and "changed nothing" are
1432
- // different answers, and only one of them is true here.
1433
- gitStats: stats[member.id] ?? null,
1434
- }));
1431
+ const members = workspaceService.listComparisonMembers(workspace.comparisonId).map((member) => {
1432
+ // Same count as the auto-loop badge: every task, acceptance criteria
1433
+ // included. How finely each engine planned the task is visible before
1434
+ // a single line of code is, and it colours every later number.
1435
+ const tasks = workspaceService.listTasks(member.id);
1436
+ return {
1437
+ workspace: member,
1438
+ // Null, not zeros: "not measured yet" and "changed nothing" are
1439
+ // different answers, and only one of them is true here.
1440
+ gitStats: stats[member.id] ?? null,
1441
+ tasks: { done: tasks.filter((t) => t.status === 'done').length, total: tasks.length },
1442
+ // What it took: messages, questions, tools, tokens, over every session.
1443
+ activity: computeWorkspaceActivityStats(getDb(), member.id),
1444
+ };
1445
+ });
1435
1446
  return c.json({ comparisonId: workspace.comparisonId, members });
1436
1447
  }
1437
1448
  catch (err) {
@@ -1439,6 +1450,21 @@ app.get('/:id/comparison', (c) => {
1439
1450
  return c.json({ error: message }, 500);
1440
1451
  }
1441
1452
  });
1453
+ // GET /api/workspaces/:id/preset — the create-form preset this workspace would
1454
+ // have been created from. Read-only: "Duplicate" prefills the form with it.
1455
+ app.get('/:id/preset', (c) => {
1456
+ const id = c.req.param('id');
1457
+ try {
1458
+ const preset = presetFromWorkspace(id);
1459
+ if (!preset)
1460
+ return c.json({ error: `Workspace '${id}' not found` }, 404);
1461
+ return c.json({ preset });
1462
+ }
1463
+ catch (err) {
1464
+ const message = err instanceof Error ? err.message : String(err);
1465
+ return c.json({ error: message }, 500);
1466
+ }
1467
+ });
1442
1468
  // POST /api/workspaces/pr-snapshot/refresh/:id — on-demand refresh of a single
1443
1469
  // workspace's PR snapshot, driven by the Git tab refresh button. Static prefix
1444
1470
  // keeps it ahead of `/:id` in the Hono matcher.
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The tools an engine calls to ask the user a question. The persisted
3
+ * `session:user-input-requested` event is purged once answered, so the tool
4
+ * call — which stays — is what gets counted.
5
+ */
6
+ const QUESTION_TOOLS = ['AskUserQuestion', 'request_user_input'];
7
+ export function computeWorkspaceActivityStats(db, workspaceId) {
8
+ const sessions = db
9
+ .prepare(`SELECT COUNT(*) AS count,
10
+ COALESCE(SUM(CASE WHEN ended_at IS NOT NULL
11
+ THEN MAX(0, (julianday(ended_at) - julianday(started_at)) * 86400000.0)
12
+ ELSE 0 END), 0) AS duration_ms
13
+ FROM agent_sessions WHERE workspace_id = ?`)
14
+ .get(workspaceId);
15
+ const metrics = db
16
+ .prepare(`SELECT COALESCE(SUM(tool_calls), 0) AS tool_calls, COALESCE(SUM(errors), 0) AS errors,
17
+ COALESCE(SUM(input_tokens), 0) AS input_tokens, COALESCE(SUM(output_tokens), 0) AS output_tokens
18
+ FROM session_event_metrics WHERE workspace_id = ?`)
19
+ .get(workspaceId);
20
+ const messages = db
21
+ .prepare(`SELECT
22
+ SUM(CASE WHEN type = 'user:message' AND json_extract(payload, '$.sender') = 'user' THEN 1 ELSE 0 END) AS user_messages,
23
+ SUM(CASE WHEN type = 'user:message' AND json_extract(payload, '$.sender') != 'user' THEN 1 ELSE 0 END) AS injected,
24
+ COUNT(DISTINCT CASE WHEN type = 'agent:event' AND json_extract(payload, '$.kind') = 'message:text'
25
+ THEN json_extract(payload, '$.messageId') END) AS agent_messages,
26
+ SUM(CASE WHEN type = 'agent:event' AND json_extract(payload, '$.kind') = 'tool:call'
27
+ AND json_extract(payload, '$.name') IN (${QUESTION_TOOLS.map(() => '?').join(', ')})
28
+ THEN 1 ELSE 0 END) AS questions
29
+ FROM ws_events WHERE workspace_id = ? AND json_valid(payload)`)
30
+ .get(...QUESTION_TOOLS, workspaceId);
31
+ return {
32
+ sessions: sessions.count,
33
+ durationMs: Math.round(sessions.duration_ms),
34
+ userMessages: messages.user_messages ?? 0,
35
+ injectedPrompts: messages.injected ?? 0,
36
+ agentMessages: messages.agent_messages,
37
+ questions: messages.questions ?? 0,
38
+ toolCalls: metrics.tool_calls,
39
+ errors: metrics.errors,
40
+ inputTokens: metrics.input_tokens,
41
+ outputTokens: metrics.output_tokens,
42
+ };
43
+ }
@@ -75,3 +75,13 @@ export function getSettingsDefaults() {
75
75
  changeSourceBranchScript: DEFAULT_CHANGE_SOURCE_BRANCH_SCRIPT,
76
76
  };
77
77
  }
78
+ /**
79
+ * Model each engine starts on when nothing else is configured. `auto` used to
80
+ * be the seed (let the CLI pick), but a workspace created without thinking
81
+ * about it should land on a known, current model, not on whatever the CLI's own
82
+ * default happens to be that week.
83
+ */
84
+ export const DEFAULT_MODEL_BY_ENGINE = {
85
+ 'claude-code': 'claude-sonnet-5',
86
+ codex: 'gpt-5.6-terra',
87
+ };
@@ -9,7 +9,7 @@ import { getSettingsPath } from '../utils/paths.js';
9
9
  import { InvalidWorktreesPathError, resolveGlobalWorktreesRoot, sanitizeWorktreesPath, validateWorktreesPath, } from '../utils/worktree-paths.js';
10
10
  import { DEFAULT_NOTION_INITIAL_PROMPT, DEFAULT_SENTRY_INITIAL_PROMPT } from './initial-prompt-template-service.js';
11
11
  import { DEFAULT_REVIEW_PROMPT_TEMPLATE } from './review-template-service.js';
12
- import { DEFAULT_CHANGE_SOURCE_BRANCH_SCRIPT } from './settings-defaults.js';
12
+ import { DEFAULT_CHANGE_SOURCE_BRANCH_SCRIPT, DEFAULT_MODEL_BY_ENGINE } from './settings-defaults.js';
13
13
  import { AGNOSTIC_PROMPTS } from './skill-suite-prompts.js';
14
14
  export const DEFAULT_GIT_CONVENTIONS = `# Git conventions
15
15
 
@@ -883,6 +883,24 @@ const settingsMigrations = [
883
883
  }
884
884
  },
885
885
  },
886
+ {
887
+ version: 57,
888
+ name: 'seed-default-model-per-engine',
889
+ migrate: ({ global }) => {
890
+ // Only an engine still on the seeded `auto` (or with no entry) moves to
891
+ // the shipped default; a model the user picked is theirs to keep.
892
+ const existing = global.defaultModelByEngine;
893
+ const map = typeof existing === 'object' && existing !== null && !Array.isArray(existing)
894
+ ? existing
895
+ : {};
896
+ for (const [engine, model] of Object.entries(DEFAULT_MODEL_BY_ENGINE)) {
897
+ const current = map[engine];
898
+ if (typeof current !== 'string' || current.length === 0 || current === 'auto')
899
+ map[engine] = model;
900
+ }
901
+ global.defaultModelByEngine = map;
902
+ },
903
+ },
886
904
  ];
887
905
  /** Current settings schema version — always equals the highest migration version. */
888
906
  export const SETTINGS_SCHEMA_VERSION = settingsMigrations.length > 0 ? settingsMigrations[settingsMigrations.length - 1].version : 0;
@@ -912,7 +930,7 @@ function defaultSettings() {
912
930
  return {
913
931
  schemaVersion: SETTINGS_SCHEMA_VERSION,
914
932
  global: {
915
- defaultModelByEngine: { 'claude-code': 'auto', codex: 'auto' },
933
+ defaultModelByEngine: { ...DEFAULT_MODEL_BY_ENGINE },
916
934
  dangerouslySkipPermissions: true,
917
935
  prPromptTemplate: DEFAULT_PR_PROMPT_TEMPLATE,
918
936
  reviewPromptTemplate: DEFAULT_REVIEW_PROMPT_TEMPLATE,
@@ -0,0 +1,201 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { nanoid } from 'nanoid';
4
+ import { getDb } from '../db/index.js';
5
+ import { getWorkspaceTemplatesPath } from '../utils/paths.js';
6
+ const FILE_VERSION = 1;
7
+ export const MAX_WORKSPACE_TEMPLATES = 100;
8
+ const MAX_NAME_LENGTH = 80;
9
+ const STRING_KEYS = [
10
+ 'projectPath',
11
+ 'sourceBranch',
12
+ 'branchType',
13
+ 'engine',
14
+ 'model',
15
+ 'reasoningEffort',
16
+ 'brainstormModel',
17
+ 'brainstormReasoningEffort',
18
+ 'description',
19
+ ];
20
+ const BOOLEAN_KEYS = ['autoLoop', 'skipSetupScript'];
21
+ const LIST_KEYS = ['tasks', 'acceptanceCriteria'];
22
+ const PERMISSION_MODES = ['plan', 'bypass', 'strict', 'interactive'];
23
+ const SESSION_MODES = ['per_task', 'continuous'];
24
+ /**
25
+ * Keep the known keys that carry the right type, drop everything else. An
26
+ * unknown or malformed field is not a reason to refuse a whole template — a
27
+ * hand-edited file or an older client should degrade to "that field is
28
+ * unset", not to an error.
29
+ */
30
+ export function sanitizePreset(input) {
31
+ if (typeof input !== 'object' || input === null || Array.isArray(input))
32
+ return {};
33
+ const raw = input;
34
+ const preset = {};
35
+ for (const key of STRING_KEYS) {
36
+ if (typeof raw[key] === 'string')
37
+ preset[key] = raw[key];
38
+ }
39
+ for (const key of BOOLEAN_KEYS) {
40
+ if (typeof raw[key] === 'boolean')
41
+ preset[key] = raw[key];
42
+ }
43
+ for (const key of LIST_KEYS) {
44
+ if (Array.isArray(raw[key]))
45
+ preset[key] = raw[key].filter((v) => typeof v === 'string');
46
+ }
47
+ if (typeof raw.agentPermissionMode === 'string' &&
48
+ PERMISSION_MODES.includes(raw.agentPermissionMode)) {
49
+ preset.agentPermissionMode = raw.agentPermissionMode;
50
+ }
51
+ if (typeof raw.autoLoopSessionMode === 'string' &&
52
+ SESSION_MODES.includes(raw.autoLoopSessionMode)) {
53
+ preset.autoLoopSessionMode = raw.autoLoopSessionMode;
54
+ }
55
+ return preset;
56
+ }
57
+ function normalizeName(name) {
58
+ return name.trim();
59
+ }
60
+ function assertValidName(name) {
61
+ if (name.length === 0 || name.length > MAX_NAME_LENGTH) {
62
+ throw new Error(`Invalid template name: must be 1 to ${MAX_NAME_LENGTH} characters`);
63
+ }
64
+ }
65
+ function sameName(a, b) {
66
+ return a.toLowerCase() === b.toLowerCase();
67
+ }
68
+ function readFile() {
69
+ const filePath = getWorkspaceTemplatesPath();
70
+ if (!existsSync(filePath))
71
+ return [];
72
+ try {
73
+ const parsed = JSON.parse(readFileSync(filePath, 'utf-8'));
74
+ if (parsed.version !== FILE_VERSION) {
75
+ console.warn(`[workspace-template-service] workspace-templates.json has version ${parsed.version}, expected ${FILE_VERSION}. Reading best-effort.`);
76
+ }
77
+ if (!Array.isArray(parsed.templates))
78
+ return [];
79
+ // Re-sanitise on read: the file may have been edited by hand.
80
+ return parsed.templates
81
+ .filter((t) => t &&
82
+ typeof t.id === 'string' &&
83
+ typeof t.name === 'string' &&
84
+ typeof t.createdAt === 'string' &&
85
+ typeof t.updatedAt === 'string')
86
+ .map((t) => ({ ...t, preset: sanitizePreset(t.preset) }));
87
+ }
88
+ catch (err) {
89
+ // Treated as empty, never overwritten here: the next successful write is
90
+ // the user's, after they have seen the log line.
91
+ console.error('[workspace-template-service] Failed to read workspace-templates.json:', err);
92
+ return [];
93
+ }
94
+ }
95
+ function writeFile(templates) {
96
+ const filePath = getWorkspaceTemplatesPath();
97
+ mkdirSync(path.dirname(filePath), { recursive: true });
98
+ const file = { version: FILE_VERSION, templates };
99
+ writeFileSync(filePath, JSON.stringify(file, null, 2), 'utf-8');
100
+ }
101
+ export function listWorkspaceTemplates() {
102
+ return readFile();
103
+ }
104
+ /** Throws `Invalid template name`, `already exists` or `Too many templates`. */
105
+ export function createWorkspaceTemplate(input) {
106
+ const name = normalizeName(input.name);
107
+ assertValidName(name);
108
+ const templates = readFile();
109
+ if (templates.some((t) => sameName(t.name, name))) {
110
+ throw new Error(`Template '${name}' already exists`);
111
+ }
112
+ if (templates.length >= MAX_WORKSPACE_TEMPLATES) {
113
+ throw new Error(`Too many templates: the limit is ${MAX_WORKSPACE_TEMPLATES}`);
114
+ }
115
+ const now = new Date().toISOString();
116
+ const template = {
117
+ id: nanoid(),
118
+ name,
119
+ createdAt: now,
120
+ updatedAt: now,
121
+ preset: sanitizePreset(input.preset),
122
+ };
123
+ writeFile([...templates, template]);
124
+ return template;
125
+ }
126
+ /** Null for an unknown id. Throws on an invalid or duplicate name. */
127
+ export function updateWorkspaceTemplate(id, updates) {
128
+ const templates = readFile();
129
+ const index = templates.findIndex((t) => t.id === id);
130
+ if (index < 0)
131
+ return null;
132
+ const current = templates[index];
133
+ let name = current.name;
134
+ if (updates.name !== undefined) {
135
+ name = normalizeName(updates.name);
136
+ assertValidName(name);
137
+ if (templates.some((t) => t.id !== id && sameName(t.name, name))) {
138
+ throw new Error(`Template '${name}' already exists`);
139
+ }
140
+ }
141
+ const updated = {
142
+ ...current,
143
+ name,
144
+ preset: updates.preset !== undefined ? sanitizePreset(updates.preset) : current.preset,
145
+ updatedAt: new Date().toISOString(),
146
+ };
147
+ templates[index] = updated;
148
+ writeFile(templates);
149
+ return updated;
150
+ }
151
+ /** False for an unknown id. */
152
+ export function deleteWorkspaceTemplate(id) {
153
+ const templates = readFile();
154
+ const remaining = templates.filter((t) => t.id !== id);
155
+ if (remaining.length === templates.length)
156
+ return false;
157
+ writeFile(remaining);
158
+ return true;
159
+ }
160
+ /**
161
+ * The preset an existing workspace would have been created from. Used by
162
+ * "Duplicate": the create form is prefilled with it, nothing is written.
163
+ * Tasks and criteria come back as titles in their order, statuses dropped — a
164
+ * preset describes work to do, not work done.
165
+ */
166
+ export function presetFromWorkspace(workspaceId) {
167
+ const db = getDb();
168
+ const row = db
169
+ .prepare(`SELECT project_path, source_branch, working_branch, engine, model, reasoning_effort,
170
+ agent_permission_mode, auto_loop, auto_loop_session_mode, brainstorm_model, description
171
+ FROM workspaces WHERE id = ?`)
172
+ .get(workspaceId);
173
+ if (!row)
174
+ return null;
175
+ const tasks = db
176
+ .prepare('SELECT title, is_acceptance_criterion FROM tasks WHERE workspace_id = ? ORDER BY sort_order ASC')
177
+ .all(workspaceId);
178
+ const slash = row.working_branch.indexOf('/');
179
+ const preset = {
180
+ projectPath: row.project_path,
181
+ sourceBranch: row.source_branch,
182
+ ...(slash > 0 ? { branchType: row.working_branch.slice(0, slash) } : {}),
183
+ engine: row.engine,
184
+ model: row.model,
185
+ reasoningEffort: row.reasoning_effort,
186
+ autoLoop: row.auto_loop === 1,
187
+ ...(row.brainstorm_model ? { brainstormModel: row.brainstorm_model } : {}),
188
+ ...(row.description ? { description: row.description } : {}),
189
+ tasks: tasks.filter((t) => t.is_acceptance_criterion !== 1).map((t) => t.title),
190
+ acceptanceCriteria: tasks.filter((t) => t.is_acceptance_criterion === 1).map((t) => t.title),
191
+ };
192
+ // Route the enum-like columns through the sanitiser so a legacy value in the
193
+ // DB cannot leak an out-of-range mode into the form.
194
+ return {
195
+ ...preset,
196
+ ...sanitizePreset({
197
+ agentPermissionMode: row.agent_permission_mode,
198
+ autoLoopSessionMode: row.auto_loop_session_mode,
199
+ }),
200
+ };
201
+ }
@@ -97,6 +97,10 @@ export function getSkillsPath() {
97
97
  export function getTemplatesPath() {
98
98
  return path.join(getKoboHome(), 'templates.json');
99
99
  }
100
+ /** Saved create-form presets. Same directory as templates.json and settings.json. */
101
+ export function getWorkspaceTemplatesPath() {
102
+ return path.join(getKoboHome(), 'workspace-templates.json');
103
+ }
100
104
  /**
101
105
  * Absolute path to the compiled MCP server entry (shipped in the published
102
106
  * package as dist/mcp-server/kobo-tasks-server.js). Returns null if not
@@ -5,9 +5,9 @@
5
5
  * editing the workspace `model` field directly. This list reflects the
6
6
  * recommended set surfaced in the create-workspace selector.
7
7
  *
8
- * Auth caveat: the `gpt-5.6-*` family and `gpt-5.5` are currently only
9
- * reachable when authenticated via ChatGPT (Plus/Pro/Team/Enterprise).
10
- * API-key auth is limited to `gpt-5.4` and below.
8
+ * Auth caveat: `gpt-6-astra`, the `gpt-5.6-*` family and `gpt-5.5` are
9
+ * currently only reachable when authenticated via ChatGPT
10
+ * (Plus/Pro/Team/Enterprise). API-key auth is limited to `gpt-5.4` and below.
11
11
  */
12
12
  export const CODEX_MODELS = [
13
13
  {
@@ -16,6 +16,12 @@ export const CODEX_MODELS = [
16
16
  i18nLabelKey: 'model.auto',
17
17
  i18nDescriptionKey: 'model.autoDescription',
18
18
  },
19
+ {
20
+ id: 'gpt-6-astra',
21
+ label: 'GPT-6 Astra',
22
+ i18nLabelKey: 'model.gpt6astra',
23
+ i18nDescriptionKey: 'model.gpt6astraDescription',
24
+ },
19
25
  {
20
26
  id: 'gpt-5.6-sol',
21
27
  label: 'GPT-5.6 Sol',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loicngr/kobo",
3
- "version": "1.11.28",
3
+ "version": "1.11.30",
4
4
  "description": "Kōbō — multi-workspace agent manager for Claude Code. Orchestrates isolated git worktrees with dev servers, Notion integration, and MCP tools.",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0-or-later",
@@ -1,4 +1,4 @@
1
- import{B as e,C as t,St as n,wt as r,yt as i}from"./reactivity.esm-bundler-DtSZKRQB.js";import{$ as a,A as o,At as s,B as c,Dt as l,Et as u,H as d,It as f,Mt as p,Ot as m,Rt as h,Tt as g,U as _,V as v,W as y,Wt as b,X as x,Z as S,_n as C,fn as w,t as T,ut as E,w as D}from"./runtime-dom.esm-bundler-DgevF6KV.js";import{n as O}from"./vue-i18n-DtajJy_8.js";import{W as k,Z as A,b as j,l as M,t as N,w as P}from"./QBtn-B7WFdfmy.js";import{C as F,Et as ee,H as te,T as ne,kt as I,m as re,p as L,vt as R,w as ie}from"./index-Ck1_qrff.js";import{t as ae}from"./QSpinnerDots-DWOFEezR.js";import{r as oe}from"./QItemSection-CEUi4Tz0.js";import{t as se}from"./QMarkupTable-DcoQpjPu.js";import{_ as ce,b as z,x as B}from"./permissionModes-CJZsT9Fl.js";import{r as le,t as V}from"./pending-diff-open-BaQ_SNEF.js";import{t as H}from"./documents-CDZqd_g5.js";import{n as U,r as W,t as ue}from"./use-quasar-B4eIgSfZ.js";import{i as de,n as G,o as fe,r as K,t as pe}from"./render-chat-markdown-NAmPVIQp.js";import{a as me,i as he,n as q,o as ge,r as _e,s as ve}from"./WorkspacePage-D0-FkRVJ.js";function ye(e,t){return E(`div`,e,[E(`table`,{class:`q-table`},t)])}var J={list:oe,table:se},Y=[`list`,`table`,`__qtable`],be=A({name:`QVirtualScroll`,props:{...B,type:{type:String,default:`list`,validator:e=>Y.includes(e)},items:{type:Array,default:()=>[]},itemsFn:Function,itemsSize:Number,scrollTarget:I},setup(e,{slots:n,attrs:r}){let i,a=t(null),o=c(()=>e.itemsSize>=0&&e.itemsFn!==void 0?Number.parseInt(e.itemsSize,10):Array.isArray(e.items)?e.items.length:0),{virtualScrollSliceRange:d,localResetVirtualScroll:f,padVirtualScroll:h,onVirtualScrollEvt:g}=z({virtualScrollLength:o,getVirtualScrollTarget:x,getVirtualScrollEl:b}),_=c(()=>{if(o.value===0)return[];let t=(e,t)=>({index:d.value.from+t,item:e});return e.itemsFn===void 0?e.items.slice(d.value.from,d.value.to).map(t):e.itemsFn(d.value.from,d.value.to-d.value.from).map(t)}),v=c(()=>`q-virtual-scroll q-virtual-scroll`+(e.virtualScrollHorizontal?`--horizontal`:`--vertical`)+(e.scrollTarget===void 0?` scroll`:``)),y=c(()=>e.scrollTarget===void 0?{tabindex:0}:{});w(o,()=>{f()}),w(()=>e.scrollTarget,()=>{C(),S()});function b(){let e=a.value;return e===null?null:e.$el||e}function x(){return i}function S(){let t=b();t!==null&&(i=ee(t,e.scrollTarget),i.addEventListener(`scroll`,g,k.passive))}function C(){i!==void 0&&(i.removeEventListener(`scroll`,g,k.passive),i=void 0)}function T(){let t=h(e.type===`list`?`div`:`tbody`,_.value.map(n.default));return n.before!==void 0&&(t=n.before().concat(t)),j(n.after,t)}return l(()=>{f()}),p(()=>{S()}),u(()=>{S()}),s(()=>{C()}),m(()=>{C()}),()=>{if(n.default===void 0){console.error(`QVirtualScroll: default scoped slot is required for rendering`);return}return e.type===`__qtable`?ye({ref:a,class:`q-table__middle `+v.value},T()):E(J[e.type],{...r,ref:a,class:[r.class,v.value],...y.value},T)}}}),xe=new Set([`setup`,`cleanup`,`archive`]);function Se(e){switch(e.type){case`user`:return e.sender===`system-prompt`?`system-prompt`:xe.has(e.sender)||F(e.sender)?`script`:`user`;case`session`:return`session`;default:return`agent`}}function Ce(e){let t=[],n=null,r=null;for(let i of e){let e=Se(i),a=e===`session`||e===`system-prompt`,o=e===`script`&&i.type===`user`?`script:${i.sender}`:e;!n||r!==o||a?(n={speaker:e,ts:i.ts,items:[i]},r=o,t.push(n),a&&(n=null)):n.items.push(i)}return t}function we(e){switch(e.type){case`text`:return`text:${e.messageId}`;case`thinking`:return`thinking:${e.messageId}:${e.eventIds?.[0]??e.ts??``}`;case`tool`:return`tool:${e.toolCallId}`;case`session`:return`session:${e.kind}:${e.eventIds?.[0]??e.ts??``}`;case`user`:return`user:${e.eventIds?.[0]??`${e.sender}:${e.ts??``}`}`;case`error`:return`error:${e.eventIds?.[0]??`${e.category}:${e.ts??``}`}`}}function Te(e){let t=e.items[0];return`${e.speaker}:${t?we(t):`empty`}`}function Ee(e,t){let n=Math.min(t,e.length)-1;for(let t=n;t>=0;t--)if(e[t]?.speaker===`user`)return t;return-1}function De(e,t,n){return new Promise(r=>{let i=!1,a=null,o=null,s=e=>{i||(i=!0,a!==null&&clearTimeout(a),a=null,o?.(),r(e))};if(o=w(e,e=>{t(e)&&s(!0)},{immediate:!0}),i){o();return}a=setTimeout(()=>s(!1),n)})}var Oe={class:`agent-error-item`},ke={class:`agent-error-item__body`},Ae={class:`agent-error-item__title`},je={class:`agent-error-item__message`},X=U(a({__name:`AgentErrorItem`,props:{item:{}},setup(e){let t=e,{te:n}=O(),i=c(()=>{let e=`agent.error.${t.item.category}`;return n(e)?e:`agent.error.other`});return(t,n)=>(h(),y(`div`,Oe,[S(M,{name:`error_outline`,size:`16px`,class:`agent-error-item__icon`}),v(`div`,ke,[v(`div`,Ae,r(t.$t(i.value)),1),v(`div`,je,r(e.item.message),1)])]))}}),[[`__scopeId`,`data-v-e98c3c02`]]),Me={class:`text-caption text-kobo-3`},Z=a({__name:`SessionEventItem`,props:{item:{}},setup(e){let t=e,n=c(()=>{switch(t.item.kind){case`started`:return`session.started`;case`ended`:return ve(t.item.detail);case`compacted`:return`session.compacted`;default:return`session.started`}});return(e,t)=>(h(),y(`span`,Me,r(e.$t(n.value)),1))}});function Ne(e,t){if(t.length===0||e.length===0)return e;let n=[...t].sort((e,t)=>t.length-e.length),r=new DOMParser().parseFromString(`<div>${e}</div>`,`text/html`),i=r.body.firstChild;if(!i)return e;function a(e){if(e.nodeType===Node.TEXT_NODE){Q(e,n,r);return}if(e.nodeName===`A`)return;let t=Array.from(e.childNodes);for(let e of t)a(e)}return a(i),i.innerHTML}function Q(e,t,n){let r=e.textContent??``;if(!t.some(e=>r.includes(e)))return;let i=n.createDocumentFragment(),a=0;for(;a<r.length;){let e=$(r,a,t);if(!e){i.appendChild(n.createTextNode(r.slice(a)));break}e.index>a&&i.appendChild(n.createTextNode(r.slice(a,e.index)));let o=n.createElement(`a`);o.className=`document-link`,o.setAttribute(`data-document-path`,e.path),o.setAttribute(`href`,`#`),o.textContent=e.path,i.appendChild(o),a=e.index+e.path.length}e.parentNode?.replaceChild(i,e)}function $(e,t,n){let r=null;for(let i of n){let n=e.indexOf(i,t);n<0||(!r||n<r.index||n===r.index&&i.length>r.path.length)&&(r={index:n,path:i})}return r}var Pe=[`innerHTML`],Fe=U(a({__name:`TextMessageItem`,props:{item:{}},setup(e){let t=e,n=H(),r=L(),i=ue(),{t:a}=O(),o=c(()=>{let e=r.selectedWorkspaceId;return e?n.documentsFor(e).map(e=>e.path):[]}),s=c(()=>{let e=t.item;if(e.streaming)return pe(e.text);let n=o.value;return K(`${e.messageId}|${n.join(`,`)}`,e.text,e=>{let t=Ne(fe.parse(e,{async:!1,breaks:!0,gfm:!0}),n);return de(t,{addAttr:[`data-document-path`]})})});function l(e){let t=e.target?.closest(`.document-link`);if(!t)return;e.preventDefault();let o=t.getAttribute(`data-document-path`),s=r.selectedWorkspaceId;!o||!s||n.openDocumentByPath(s,o).catch(e=>{console.error(`[TextMessageItem] openDocumentByPath failed:`,e),i.notify({type:`negative`,message:a(`documents.loadFailed`),position:`top`})})}return(t,n)=>(h(),y(`div`,{class:`markdown-message`,onClick:l},[v(`div`,{innerHTML:s.value},null,8,Pe),e.item.streaming?(h(),d(P,{key:0,size:`xs`,class:`q-ml-xs`})):_(``,!0)]))}}),[[`__scopeId`,`data-v-99371035`]]);function Ie(e,t){let n=e.split(`
1
+ import{B as e,C as t,St as n,wt as r,yt as i}from"./reactivity.esm-bundler-DtSZKRQB.js";import{$ as a,A as o,At as s,B as c,Dt as l,Et as u,H as d,It as f,Mt as p,Ot as m,Rt as h,Tt as g,U as _,V as v,W as y,Wt as b,X as x,Z as S,_n as C,fn as w,t as T,ut as E,w as D}from"./runtime-dom.esm-bundler-DgevF6KV.js";import{n as O}from"./vue-i18n-DtajJy_8.js";import{W as k,Z as A,b as j,l as M,t as N,w as P}from"./QBtn-B7WFdfmy.js";import{C as F,Et as ee,H as te,T as ne,kt as I,m as re,p as L,vt as R,w as ie}from"./index-BePGTdV0.js";import{t as ae}from"./QSpinnerDots-DWOFEezR.js";import{r as oe}from"./QItemSection-CEUi4Tz0.js";import{t as se}from"./QMarkupTable-DcoQpjPu.js";import{_ as ce,b as z,x as B}from"./permissionModes-D58HKBN0.js";import{r as le,t as V}from"./pending-diff-open-DoKaz8uO.js";import{t as H}from"./documents-CN6TlZH7.js";import{n as U,r as W,t as ue}from"./use-quasar-0nZoDKhp.js";import{i as de,n as G,o as fe,r as K,t as pe}from"./render-chat-markdown-BTcSOgEc.js";import{a as me,i as he,n as q,o as ge,r as _e,s as ve}from"./WorkspacePage-CEwILZ6V.js";function ye(e,t){return E(`div`,e,[E(`table`,{class:`q-table`},t)])}var J={list:oe,table:se},Y=[`list`,`table`,`__qtable`],be=A({name:`QVirtualScroll`,props:{...B,type:{type:String,default:`list`,validator:e=>Y.includes(e)},items:{type:Array,default:()=>[]},itemsFn:Function,itemsSize:Number,scrollTarget:I},setup(e,{slots:n,attrs:r}){let i,a=t(null),o=c(()=>e.itemsSize>=0&&e.itemsFn!==void 0?Number.parseInt(e.itemsSize,10):Array.isArray(e.items)?e.items.length:0),{virtualScrollSliceRange:d,localResetVirtualScroll:f,padVirtualScroll:h,onVirtualScrollEvt:g}=z({virtualScrollLength:o,getVirtualScrollTarget:x,getVirtualScrollEl:b}),_=c(()=>{if(o.value===0)return[];let t=(e,t)=>({index:d.value.from+t,item:e});return e.itemsFn===void 0?e.items.slice(d.value.from,d.value.to).map(t):e.itemsFn(d.value.from,d.value.to-d.value.from).map(t)}),v=c(()=>`q-virtual-scroll q-virtual-scroll`+(e.virtualScrollHorizontal?`--horizontal`:`--vertical`)+(e.scrollTarget===void 0?` scroll`:``)),y=c(()=>e.scrollTarget===void 0?{tabindex:0}:{});w(o,()=>{f()}),w(()=>e.scrollTarget,()=>{C(),S()});function b(){let e=a.value;return e===null?null:e.$el||e}function x(){return i}function S(){let t=b();t!==null&&(i=ee(t,e.scrollTarget),i.addEventListener(`scroll`,g,k.passive))}function C(){i!==void 0&&(i.removeEventListener(`scroll`,g,k.passive),i=void 0)}function T(){let t=h(e.type===`list`?`div`:`tbody`,_.value.map(n.default));return n.before!==void 0&&(t=n.before().concat(t)),j(n.after,t)}return l(()=>{f()}),p(()=>{S()}),u(()=>{S()}),s(()=>{C()}),m(()=>{C()}),()=>{if(n.default===void 0){console.error(`QVirtualScroll: default scoped slot is required for rendering`);return}return e.type===`__qtable`?ye({ref:a,class:`q-table__middle `+v.value},T()):E(J[e.type],{...r,ref:a,class:[r.class,v.value],...y.value},T)}}}),xe=new Set([`setup`,`cleanup`,`archive`]);function Se(e){switch(e.type){case`user`:return e.sender===`system-prompt`?`system-prompt`:xe.has(e.sender)||F(e.sender)?`script`:`user`;case`session`:return`session`;default:return`agent`}}function Ce(e){let t=[],n=null,r=null;for(let i of e){let e=Se(i),a=e===`session`||e===`system-prompt`,o=e===`script`&&i.type===`user`?`script:${i.sender}`:e;!n||r!==o||a?(n={speaker:e,ts:i.ts,items:[i]},r=o,t.push(n),a&&(n=null)):n.items.push(i)}return t}function we(e){switch(e.type){case`text`:return`text:${e.messageId}`;case`thinking`:return`thinking:${e.messageId}:${e.eventIds?.[0]??e.ts??``}`;case`tool`:return`tool:${e.toolCallId}`;case`session`:return`session:${e.kind}:${e.eventIds?.[0]??e.ts??``}`;case`user`:return`user:${e.eventIds?.[0]??`${e.sender}:${e.ts??``}`}`;case`error`:return`error:${e.eventIds?.[0]??`${e.category}:${e.ts??``}`}`}}function Te(e){let t=e.items[0];return`${e.speaker}:${t?we(t):`empty`}`}function Ee(e,t){let n=Math.min(t,e.length)-1;for(let t=n;t>=0;t--)if(e[t]?.speaker===`user`)return t;return-1}function De(e,t,n){return new Promise(r=>{let i=!1,a=null,o=null,s=e=>{i||(i=!0,a!==null&&clearTimeout(a),a=null,o?.(),r(e))};if(o=w(e,e=>{t(e)&&s(!0)},{immediate:!0}),i){o();return}a=setTimeout(()=>s(!1),n)})}var Oe={class:`agent-error-item`},ke={class:`agent-error-item__body`},Ae={class:`agent-error-item__title`},je={class:`agent-error-item__message`},X=U(a({__name:`AgentErrorItem`,props:{item:{}},setup(e){let t=e,{te:n}=O(),i=c(()=>{let e=`agent.error.${t.item.category}`;return n(e)?e:`agent.error.other`});return(t,n)=>(h(),y(`div`,Oe,[S(M,{name:`error_outline`,size:`16px`,class:`agent-error-item__icon`}),v(`div`,ke,[v(`div`,Ae,r(t.$t(i.value)),1),v(`div`,je,r(e.item.message),1)])]))}}),[[`__scopeId`,`data-v-e98c3c02`]]),Me={class:`text-caption text-kobo-3`},Z=a({__name:`SessionEventItem`,props:{item:{}},setup(e){let t=e,n=c(()=>{switch(t.item.kind){case`started`:return`session.started`;case`ended`:return ve(t.item.detail);case`compacted`:return`session.compacted`;default:return`session.started`}});return(e,t)=>(h(),y(`span`,Me,r(e.$t(n.value)),1))}});function Ne(e,t){if(t.length===0||e.length===0)return e;let n=[...t].sort((e,t)=>t.length-e.length),r=new DOMParser().parseFromString(`<div>${e}</div>`,`text/html`),i=r.body.firstChild;if(!i)return e;function a(e){if(e.nodeType===Node.TEXT_NODE){Q(e,n,r);return}if(e.nodeName===`A`)return;let t=Array.from(e.childNodes);for(let e of t)a(e)}return a(i),i.innerHTML}function Q(e,t,n){let r=e.textContent??``;if(!t.some(e=>r.includes(e)))return;let i=n.createDocumentFragment(),a=0;for(;a<r.length;){let e=$(r,a,t);if(!e){i.appendChild(n.createTextNode(r.slice(a)));break}e.index>a&&i.appendChild(n.createTextNode(r.slice(a,e.index)));let o=n.createElement(`a`);o.className=`document-link`,o.setAttribute(`data-document-path`,e.path),o.setAttribute(`href`,`#`),o.textContent=e.path,i.appendChild(o),a=e.index+e.path.length}e.parentNode?.replaceChild(i,e)}function $(e,t,n){let r=null;for(let i of n){let n=e.indexOf(i,t);n<0||(!r||n<r.index||n===r.index&&i.length>r.path.length)&&(r={index:n,path:i})}return r}var Pe=[`innerHTML`],Fe=U(a({__name:`TextMessageItem`,props:{item:{}},setup(e){let t=e,n=H(),r=L(),i=ue(),{t:a}=O(),o=c(()=>{let e=r.selectedWorkspaceId;return e?n.documentsFor(e).map(e=>e.path):[]}),s=c(()=>{let e=t.item;if(e.streaming)return pe(e.text);let n=o.value;return K(`${e.messageId}|${n.join(`,`)}`,e.text,e=>{let t=Ne(fe.parse(e,{async:!1,breaks:!0,gfm:!0}),n);return de(t,{addAttr:[`data-document-path`]})})});function l(e){let t=e.target?.closest(`.document-link`);if(!t)return;e.preventDefault();let o=t.getAttribute(`data-document-path`),s=r.selectedWorkspaceId;!o||!s||n.openDocumentByPath(s,o).catch(e=>{console.error(`[TextMessageItem] openDocumentByPath failed:`,e),i.notify({type:`negative`,message:a(`documents.loadFailed`),position:`top`})})}return(t,n)=>(h(),y(`div`,{class:`markdown-message`,onClick:l},[v(`div`,{innerHTML:s.value},null,8,Pe),e.item.streaming?(h(),d(P,{key:0,size:`xs`,class:`q-ml-xs`})):_(``,!0)]))}}),[[`__scopeId`,`data-v-99371035`]]);function Ie(e,t){let n=e.split(`
2
2
  `),r=t.split(`
3
3
  `),i=0;for(;i<n.length&&i<r.length&&n[i]===r[i];)i++;let a=n.length,o=r.length;for(;a>i&&o>i&&n[a-1]===r[o-1];)a--,o--;let s=n.slice(0,i).map(e=>({type:`context`,content:e})),c=n.slice(a).map(e=>({type:`context`,content:e})),l=n.slice(i,a),u=r.slice(i,o);return l.length>800||u.length>800?[...s,...l.map(e=>({type:`del`,content:e})),...u.map(e=>({type:`add`,content:e})),...c]:[...s,...Le(l,u),...c]}function Le(e,t){let n=e.length,r=t.length;if(n===0&&r===0)return[];if(n===0)return t.map(e=>({type:`add`,content:e}));if(r===0)return e.map(e=>({type:`del`,content:e}));let i=r+1,a=new Int32Array((n+1)*i);for(let o=n-1;o>=0;o--)for(let n=r-1;n>=0;n--)a[o*i+n]=e[o]===t[n]?a[(o+1)*i+(n+1)]+1:Math.max(a[(o+1)*i+n],a[o*i+(n+1)]);let o=[],s=0,c=0;for(;s<n&&c<r;)e[s]===t[c]?(o.push({type:`context`,content:e[s]}),s++,c++):a[(s+1)*i+c]>=a[s*i+(c+1)]?(o.push({type:`del`,content:e[s]}),s++):(o.push({type:`add`,content:t[c]}),c++);for(;s<n;)o.push({type:`del`,content:e[s++]});for(;c<r;)o.push({type:`add`,content:t[c++]});return o}function Re(e){let t=e.split(`
4
4
  `),n=[],r=!1;for(let e of t){if(e.startsWith(`@@`)){r=!0;continue}!r&&(e.startsWith(`+++`)||e.startsWith(`---`))||(e.startsWith(`+`)?n.push({type:`add`,content:e.slice(1)}):e.startsWith(`-`)?n.push({type:`del`,content:e.slice(1)}):e.startsWith(` `)?n.push({type:`context`,content:e.slice(1)}):e.length>0&&n.push({type:`context`,content:e}))}return n}function ze(e,t){if(!t||typeof t!=`object`)return null;let n=t;if(e===`Edit`){let e=n.file_path;if(!e)return null;let t=n.old_string??``,r=n.new_string??``,i=typeof n.diff==`string`?n.diff:``;if(!t&&!r&&i.length>0){let t=Re(i);return{toolName:`Edit`,filePath:e,additions:t.filter(e=>e.type===`add`).length,deletions:t.filter(e=>e.type===`del`).length,diffLines:t}}return{toolName:`Edit`,filePath:e,oldString:t,newString:r,replaceAll:n.replace_all??!1,additions:r?r.split(`
@@ -1 +1 @@
1
- import{B as e,C as t,wt as n}from"./reactivity.esm-bundler-DtSZKRQB.js";import{$ as r,A as i,H as a,Mt as o,Rt as s,U as c,V as l,W as u,Wt as d,Z as f,_n as p}from"./runtime-dom.esm-bundler-DgevF6KV.js";import{t as m}from"./QBtn-B7WFdfmy.js";import{Jt as h,Kt as g,n as _}from"./index-Ck1_qrff.js";import{n as v}from"./use-quasar-B4eIgSfZ.js";import{n as y}from"./render-chat-markdown-NAmPVIQp.js";import{t as b}from"./QSpace-D3PNLwa4.js";import{t as x}from"./QChip-DpUYTCOJ.js";import{t as S}from"./QPage-RpDxx5ah.js";import{t as C}from"./DrawerToggleButton-BASeszPg.js";var w={class:`row items-center q-mb-md`},T={class:`text-h6 q-ml-sm`},E={key:0,class:`text-kobo-3 text-center q-pa-lg`},D={key:1,class:`text-negative text-center q-pa-lg`},O={key:2,class:`text-kobo-3 text-center q-pa-lg`},k={key:3,class:`column q-gutter-md`},A={key:0,class:`text-caption text-kobo-3`},j={class:`row items-center q-mb-sm`},M={class:`text-subtitle1 text-kobo-2`,style:{"font-family":`var(--kobo-font-mono, monospace)`}},N=[`innerHTML`],P=v(r({__name:`ChangelogPage`,setup(r){let v=_(),P=t([]),F=t(``),I=t(!1),L=t(null);function R(e){return y(e)}async function z(){I.value=!0,L.value=null;try{let e=await fetch(`/api/changelog`);if(!e.ok)throw Error(`HTTP ${e.status}`);let t=await e.json();F.value=t.currentVersion??``,P.value=t.versions??[]}catch(e){L.value=e instanceof Error?e.message:String(e)}finally{I.value=!1}}return o(z),(t,r)=>(s(),a(S,{class:`q-pa-md`,style:{"max-width":`900px`,margin:`0 auto`}},{default:p(()=>[l(`div`,w,[f(C,{class:`q-mr-xs`}),f(m,{flat:``,dense:``,round:``,icon:`arrow_back`,onClick:r[0]||=t=>e(v).back()}),l(`div`,T,n(t.$t(`changelog.title`)),1),f(b),f(m,{flat:``,dense:``,icon:`refresh`,loading:I.value,label:t.$t(`common.refresh`),onClick:z},null,8,[`loading`,`label`])]),I.value&&P.value.length===0?(s(),u(`div`,E,n(t.$t(`common.loading`)),1)):L.value?(s(),u(`div`,D,n(L.value),1)):P.value.length===0?(s(),u(`div`,O,n(t.$t(`changelog.empty`)),1)):(s(),u(`div`,k,[F.value?(s(),u(`div`,A,n(t.$t(`changelog.currentVersion`,{version:F.value})),1)):c(``,!0),(s(!0),u(i,null,d(P.value,e=>(s(),a(g,{key:e.version,dark:``,flat:``,bordered:``},{default:p(()=>[f(h,null,{default:p(()=>[l(`div`,j,[l(`div`,M,` v`+n(e.version),1),e.version===F.value?(s(),a(x,{key:0,dense:``,size:`sm`,color:`primary`,"text-color":`kobo-1`,label:t.$t(`changelog.current`),class:`q-ml-sm`},null,8,[`label`])):c(``,!0)]),l(`div`,{class:`changelog-notes`,innerHTML:R(e.notes)},null,8,N)]),_:2},1024)]),_:2},1024))),128))]))]),_:1}))}}),[[`__scopeId`,`data-v-303054e6`]]);export{P as default};
1
+ import{B as e,C as t,wt as n}from"./reactivity.esm-bundler-DtSZKRQB.js";import{$ as r,A as i,H as a,Mt as o,Rt as s,U as c,V as l,W as u,Wt as d,Z as f,_n as p}from"./runtime-dom.esm-bundler-DgevF6KV.js";import{t as m}from"./QBtn-B7WFdfmy.js";import{Jt as h,Kt as g,n as _}from"./index-BePGTdV0.js";import{n as v}from"./use-quasar-0nZoDKhp.js";import{n as y}from"./render-chat-markdown-BTcSOgEc.js";import{t as b}from"./QSpace-D3PNLwa4.js";import{t as x}from"./QChip-DpUYTCOJ.js";import{t as S}from"./QPage-RpDxx5ah.js";import{t as C}from"./DrawerToggleButton-VEVxQ39S.js";var w={class:`row items-center q-mb-md`},T={class:`text-h6 q-ml-sm`},E={key:0,class:`text-kobo-3 text-center q-pa-lg`},D={key:1,class:`text-negative text-center q-pa-lg`},O={key:2,class:`text-kobo-3 text-center q-pa-lg`},k={key:3,class:`column q-gutter-md`},A={key:0,class:`text-caption text-kobo-3`},j={class:`row items-center q-mb-sm`},M={class:`text-subtitle1 text-kobo-2`,style:{"font-family":`var(--kobo-font-mono, monospace)`}},N=[`innerHTML`],P=v(r({__name:`ChangelogPage`,setup(r){let v=_(),P=t([]),F=t(``),I=t(!1),L=t(null);function R(e){return y(e)}async function z(){I.value=!0,L.value=null;try{let e=await fetch(`/api/changelog`);if(!e.ok)throw Error(`HTTP ${e.status}`);let t=await e.json();F.value=t.currentVersion??``,P.value=t.versions??[]}catch(e){L.value=e instanceof Error?e.message:String(e)}finally{I.value=!1}}return o(z),(t,r)=>(s(),a(S,{class:`q-pa-md`,style:{"max-width":`900px`,margin:`0 auto`}},{default:p(()=>[l(`div`,w,[f(C,{class:`q-mr-xs`}),f(m,{flat:``,dense:``,round:``,icon:`arrow_back`,onClick:r[0]||=t=>e(v).back()}),l(`div`,T,n(t.$t(`changelog.title`)),1),f(b),f(m,{flat:``,dense:``,icon:`refresh`,loading:I.value,label:t.$t(`common.refresh`),onClick:z},null,8,[`loading`,`label`])]),I.value&&P.value.length===0?(s(),u(`div`,E,n(t.$t(`common.loading`)),1)):L.value?(s(),u(`div`,D,n(L.value),1)):P.value.length===0?(s(),u(`div`,O,n(t.$t(`changelog.empty`)),1)):(s(),u(`div`,k,[F.value?(s(),u(`div`,A,n(t.$t(`changelog.currentVersion`,{version:F.value})),1)):c(``,!0),(s(!0),u(i,null,d(P.value,e=>(s(),a(g,{key:e.version,dark:``,flat:``,bordered:``},{default:p(()=>[f(h,null,{default:p(()=>[l(`div`,j,[l(`div`,M,` v`+n(e.version),1),e.version===F.value?(s(),a(x,{key:0,dense:``,size:`sm`,color:`primary`,"text-color":`kobo-1`,label:t.$t(`changelog.current`),class:`q-ml-sm`},null,8,[`label`])):c(``,!0)]),l(`div`,{class:`changelog-notes`,innerHTML:R(e.notes)},null,8,N)]),_:2},1024)]),_:2},1024))),128))]))]),_:1}))}}),[[`__scopeId`,`data-v-303054e6`]]);export{P as default};
@@ -1 +1 @@
1
- import{Q as e,R as t}from"./QBtn-B7WFdfmy.js";import{Ft as n,It as r}from"./index-Ck1_qrff.js";function i(e){return e===!1?0:e===!0||e===void 0?1:Number.parseInt(e,10)||0}var a=e({name:`close-popup`,beforeMount(e,{value:a}){let o={depth:i(a),handler(t){o.depth!==0&&setTimeout(()=>{let i=r(e);i!==void 0&&n(i,t,o.depth)},0)},handlerKey(e){t(e,13)&&o.handler(e)}};e.__qclosepopup=o,e.addEventListener(`click`,o.handler),e.addEventListener(`keyup`,o.handlerKey)},updated(e,{value:t,oldValue:n}){t!==n&&(e.__qclosepopup.depth=i(t))},beforeUnmount(e){let t=e.__qclosepopup;e.removeEventListener(`click`,t.handler),e.removeEventListener(`keyup`,t.handlerKey),delete e.__qclosepopup}});export{a as t};
1
+ import{Q as e,R as t}from"./QBtn-B7WFdfmy.js";import{Ft as n,It as r}from"./index-BePGTdV0.js";function i(e){return e===!1?0:e===!0||e===void 0?1:Number.parseInt(e,10)||0}var a=e({name:`close-popup`,beforeMount(e,{value:a}){let o={depth:i(a),handler(t){o.depth!==0&&setTimeout(()=>{let i=r(e);i!==void 0&&n(i,t,o.depth)},0)},handlerKey(e){t(e,13)&&o.handler(e)}};e.__qclosepopup=o,e.addEventListener(`click`,o.handler),e.addEventListener(`keyup`,o.handlerKey)},updated(e,{value:t,oldValue:n}){t!==n&&(e.__qclosepopup.depth=i(t))},beforeUnmount(e){let t=e.__qclosepopup;e.removeEventListener(`click`,t.handler),e.removeEventListener(`keyup`,t.handlerKey),delete e.__qclosepopup}});export{a as t};
@@ -0,0 +1,2 @@
1
+ import{C as e,wt as t,yt as n}from"./reactivity.esm-bundler-DtSZKRQB.js";import{$ as r,A as ee,B as te,H as i,Mt as a,Rt as o,U as s,V as c,W as l,Wt as u,X as d,Z as f,_n as ne,fn as re}from"./runtime-dom.esm-bundler-DgevF6KV.js";import{n as ie}from"./vue-i18n-DtajJy_8.js";import{t as p}from"./QBtn-B7WFdfmy.js";import{n as ae,p as oe}from"./index-BePGTdV0.js";import{t as m}from"./QMarkupTable-DcoQpjPu.js";import{n as h}from"./use-quasar-0nZoDKhp.js";import{t as g}from"./QSpace-D3PNLwa4.js";var _=[`workspace`,`engine`,`model`,`status`,`branch`,`tasks_done`,`tasks_total`,`commits`,`files_changed`,`insertions`,`deletions`,`agent_messages`,`user_messages`,`injected_prompts`,`questions`,`tool_calls`,`errors`,`input_tokens`,`output_tokens`,`sessions`,`duration_seconds`];function v(e){if(e==null)return``;let t=String(e);return/[",\r\n]/.test(t)?`"${t.replace(/"/g,`""`)}"`:t}function y(e){let t=e.map(e=>{let t=e.gitStats,n=e.activity;return[e.workspace.name,e.workspace.engine,e.workspace.model,e.workspace.status,e.workspace.workingBranch,e.tasks.done,e.tasks.total,t?.commitCount??null,t?.filesChanged??null,t?.insertions??null,t?.deletions??null,n.agentMessages,n.userMessages,n.injectedPrompts,n.questions,n.toolCalls,n.errors,n.inputTokens,n.outputTokens,n.sessions,Math.round(n.durationMs/1e3)].map(v).join(`,`)});return`${[_.join(`,`),...t].join(`\r
2
+ `)}`}var b={key:0,class:`comparison-panel q-pa-md`},x={class:`row items-center q-mb-sm`},S={class:`text-subtitle2`},C={class:`text-caption text-kobo-3 q-mb-sm`},w={class:`text-left`},T={class:`text-left`},E={class:`text-right`},D={class:`text-right`},O={class:`text-right`},k={class:`text-right`},A={class:`text-right`},j={class:`text-right`},M={class:`text-right`},N={class:`text-right`},P={class:`text-right`},F={class:`text-right`},I={class:`text-right`},L={class:`text-caption text-kobo-3 comparison-mono`},R={key:0,class:`text-caption text-kobo-3`},z={class:`text-right comparison-mono`},B={class:`text-right`},V={class:`text-right`},se={class:`text-right`},H={key:0,class:`comparison-mono`},U={class:`text-kobo-success`},W={class:`text-kobo-danger q-ml-xs`},G={key:1,class:`text-kobo-3`},K={class:`text-right comparison-mono`},ce=[`title`],le={class:`text-right`},ue={class:`text-right`},de={class:`text-right comparison-mono`},fe=[`title`],pe={class:`text-right`},me={class:`text-right comparison-mono`},he={class:`text-right`},ge={key:1,class:`text-caption text-kobo-3`},_e={key:0,class:`text-caption text-kobo-warning q-mt-sm`},q=h(r({__name:`ComparisonPanel`,props:{workspaceId:{}},setup(r){let h=r;function _(e){return e<1e3?String(e):e<1e6?`${(e/1e3).toFixed(+(e<1e4))}k`:`${(e/1e6).toFixed(1)}M`}function v(e){if(e<=0)return`-`;let t=Math.round(e/1e3);if(t<60)return J(`reliability.durationSeconds`,{s:t});let n=Math.floor(t/60);return n<60?J(`reliability.durationMinutes`,{m:n,s:t%60}):J(`reliability.durationHours`,{h:Math.floor(n/60),m:n%60})}let q=ae(),ve=oe(),{t:J}=ie(),Y=e([]),X=e(!1),ye={created:`workspaceStatus.created`,extracting:`workspaceStatus.extracting`,brainstorming:`workspaceStatus.brainstorming`,executing:`workspaceStatus.executing`,"awaiting-user":`workspaceStatus.awaitingUser`,completed:`workspaceStatus.completed`,idle:`workspaceStatus.idle`,error:`workspaceStatus.error`,quota:`workspaceStatus.quota`,retrying:`workspaceStatus.retrying`},Z=e({});function be(e){return Z.value[e]??e}async function xe(){try{let e=await fetch(`/api/engines`);if(!e.ok)return;let t=await e.json();Z.value=Object.fromEntries(t.map(e=>[e.id,e.displayName]))}catch{}}function Se(e){let t=ye[e];return t?J(t):e}let Ce=te(()=>h.workspaceId);function Q(e){return e.workspace.id===Ce.value}function we(e){ve.selectWorkspace(e),q.push({name:`workspace`,params:{id:e}})}function Te(){let e=y(Y.value),t=new Blob([e],{type:`text/csv;charset=utf-8`}),n=URL.createObjectURL(t),r=document.createElement(`a`);r.href=n,r.download=`kobo-comparison-${Y.value[0]?.workspace.comparisonId??`comparison`}.csv`,r.click(),URL.revokeObjectURL(n)}async function $(){X.value=!0;try{let e=await fetch(`/api/workspaces/${h.workspaceId}/comparison`);if(!e.ok){Y.value=[];return}let t=await e.json();Y.value=t.members??[]}catch{Y.value=[]}finally{X.value=!1}}return a(()=>{xe(),$()}),re(()=>h.workspaceId,$),(e,r)=>Y.value.length>0?(o(),l(`div`,b,[c(`div`,x,[c(`div`,S,t(e.$t(`comparison.title`)),1),f(g),f(p,{flat:``,dense:``,round:``,size:`sm`,icon:`download`,title:e.$t(`comparison.exportCsv`),onClick:Te},null,8,[`title`]),f(p,{flat:``,dense:``,round:``,size:`sm`,icon:`refresh`,loading:X.value,onClick:$},null,8,[`loading`])]),c(`div`,C,t(e.$t(`comparison.hint`)),1),f(m,{flat:``,dense:``,dark:``,class:`comparison-table`},{default:ne(()=>[c(`thead`,null,[c(`tr`,null,[c(`th`,w,t(e.$t(`comparison.engine`)),1),c(`th`,T,t(e.$t(`comparison.status`)),1),c(`th`,E,t(e.$t(`comparison.tasks`)),1),c(`th`,D,t(e.$t(`comparison.commits`)),1),c(`th`,O,t(e.$t(`comparison.files`)),1),c(`th`,k,t(e.$t(`comparison.diff`)),1),c(`th`,A,t(e.$t(`comparison.messages`)),1),c(`th`,j,t(e.$t(`comparison.questions`)),1),c(`th`,M,t(e.$t(`comparison.tools`)),1),c(`th`,N,t(e.$t(`comparison.errors`)),1),c(`th`,P,t(e.$t(`comparison.tokens`)),1),c(`th`,F,t(e.$t(`comparison.sessions`)),1),c(`th`,I,t(e.$t(`comparison.duration`)),1),r[0]||=c(`th`,null,null,-1)])]),c(`tbody`,null,[(o(!0),l(ee,null,u(Y.value,r=>(o(),l(`tr`,{key:r.workspace.id,class:n({"comparison-current":Q(r)})},[c(`td`,null,[c(`div`,null,t(be(r.workspace.engine)),1),c(`div`,L,t(r.workspace.model),1)]),c(`td`,null,[d(t(Se(r.workspace.status))+` `,1),r.workspace.archivedAt?(o(),l(`span`,R,` · `+t(e.$t(`comparison.archived`)),1)):s(``,!0)]),c(`td`,z,t(r.tasks.done)+`/`+t(r.tasks.total),1),c(`td`,B,t(r.gitStats?.commitCount??`-`),1),c(`td`,V,t(r.gitStats?.filesChanged??`-`),1),c(`td`,se,[r.gitStats?(o(),l(`span`,H,[c(`span`,U,`+`+t(r.gitStats.insertions),1),c(`span`,W,`-`+t(r.gitStats.deletions),1)])):(o(),l(`span`,G,`-`))]),c(`td`,K,[c(`span`,{title:e.$t(`comparison.messagesTitle`)},t(r.activity.agentMessages)+` / `+t(r.activity.userMessages),9,ce)]),c(`td`,le,t(r.activity.questions),1),c(`td`,ue,t(r.activity.toolCalls),1),c(`td`,{class:n([`text-right`,r.activity.errors>0?`text-kobo-danger`:``])},t(r.activity.errors),3),c(`td`,de,[c(`span`,{title:e.$t(`comparison.tokensTitle`)},t(_(r.activity.inputTokens))+` / `+t(_(r.activity.outputTokens)),9,fe)]),c(`td`,pe,t(r.activity.sessions),1),c(`td`,me,t(v(r.activity.durationMs)),1),c(`td`,he,[Q(r)?(o(),l(`span`,ge,t(e.$t(`comparison.current`)),1)):(o(),i(p,{key:0,flat:``,dense:``,"no-caps":``,size:`sm`,label:e.$t(`comparison.open`),onClick:e=>we(r.workspace.id)},null,8,[`label`,`onClick`]))])],2))),128))])]),_:1}),Y.value.length<2?(o(),l(`div`,_e,t(e.$t(`comparison.incomplete`)),1)):s(``,!0)])):s(``,!0)}}),[[`__scopeId`,`data-v-95e8c6b5`]]);export{q as default};
@@ -0,0 +1 @@
1
+ .comparison-panel[data-v-95e8c6b5]{border-bottom:1px solid var(--kobo-border)}.comparison-table[data-v-95e8c6b5]{background:var(--kobo-surface);border:1px solid var(--kobo-border);border-radius:var(--kobo-radius-md)}.comparison-current[data-v-95e8c6b5]{background:var(--kobo-surface-2)}.comparison-mono[data-v-95e8c6b5]{font-family:var(--kobo-font-mono)}