@loicngr/kobo 2.1.24 → 2.1.25

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 +15 -0
  2. package/CHANGELOG.md +4 -0
  3. package/CONFIGURATION.md +48 -0
  4. package/dist/server/routes/workspaces.js +41 -7
  5. package/dist/server/services/agent/engines/claude-code/engine.js +2 -0
  6. package/dist/server/services/agent/engines/claude-code/progress-reporting-hook.js +99 -0
  7. package/dist/server/services/forge/github/provider.js +4 -2
  8. package/dist/server/services/forge/types.js +0 -1
  9. package/dist/server/utils/git-ops.js +29 -20
  10. package/dist/shared/forge-merge.js +5 -0
  11. package/dist/shared/progress-bar-prompts.js +1 -1
  12. package/package.json +1 -1
  13. package/src/client/dist/pwa/assets/{ActivityFeed-BYspOKKg.js → ActivityFeed-xJMPnXKA.js} +1 -1
  14. package/src/client/dist/pwa/assets/{AutoLoopFinalReviewSettings-DXX3CqrY.js → AutoLoopFinalReviewSettings-Dafi7Tue.js} +1 -1
  15. package/src/client/dist/pwa/assets/{ChangelogPage-DKkI5IU-.js → ChangelogPage-DH-zUtlf.js} +1 -1
  16. package/src/client/dist/pwa/assets/{ComparisonPanel-Cw5Dkan0.js → ComparisonPanel-DwaQyEgb.js} +1 -1
  17. package/src/client/dist/pwa/assets/{CreatePage-B7PLFOH6.js → CreatePage-DyF_efbs.js} +1 -1
  18. package/src/client/dist/pwa/assets/{DashboardPage-Dw8nfkYR.js → DashboardPage-B8gTooRi.js} +1 -1
  19. package/src/client/dist/pwa/assets/DiffViewer-BivWNITj.js +8 -0
  20. package/src/client/dist/pwa/assets/{DiffViewer-DEPOa3D7.css → DiffViewer-XzCvEKLJ.css} +1 -1
  21. package/src/client/dist/pwa/assets/{DocumentsPanel-CH0KzdJV.js → DocumentsPanel-DXjjhGUg.js} +1 -1
  22. package/src/client/dist/pwa/assets/{DrawerToggleButton-CZVpU8HH.js → DrawerToggleButton-CiVNMTbq.js} +1 -1
  23. package/src/client/dist/pwa/assets/{GitPanel-CNFtj6k0.css → GitPanel-CRaLJzjc.css} +1 -1
  24. package/src/client/dist/pwa/assets/GitPanel-DQjgGtoN.js +3 -0
  25. package/src/client/dist/pwa/assets/{HealthPage-DnPp8GSx.js → HealthPage-lc2Cuuks.js} +1 -1
  26. package/src/client/dist/pwa/assets/{MainLayout-1PaKfzdy.js → MainLayout-D3pGcTx8.js} +2 -2
  27. package/src/client/dist/pwa/assets/{MemoryPanel-BarliK2r.js → MemoryPanel-SrR94UVW.js} +1 -1
  28. package/src/client/dist/pwa/assets/{MemoryProposalList-BtVjVPp7.js → MemoryProposalList-B5uVf3KU.js} +1 -1
  29. package/src/client/dist/pwa/assets/{QBtnDropdown-Rzurb7nO.js → QBtnDropdown-Cxr3lBPB.js} +1 -1
  30. package/src/client/dist/pwa/assets/{QExpansionItem-CaKEBcyx.js → QExpansionItem-xIXDevKz.js} +1 -1
  31. package/src/client/dist/pwa/assets/{QScrollArea-hrsAyyJf.js → QScrollArea-DeczMHVs.js} +1 -1
  32. package/src/client/dist/pwa/assets/{QSelect-Dr7rAt6u.js → QSelect-GQAsf7pB.js} +1 -1
  33. package/src/client/dist/pwa/assets/{QTooltip-CNMsXTmD.js → QTooltip-be9mqhmB.js} +1 -1
  34. package/src/client/dist/pwa/assets/{SchedulePanel-nDw2eZ9K.js → SchedulePanel-R6H-D-m5.js} +1 -1
  35. package/src/client/dist/pwa/assets/{SearchPage-BXJ6iVt1.js → SearchPage-CaeYoIDw.js} +1 -1
  36. package/src/client/dist/pwa/assets/{SettingsPage-SHpE2q9Z.js → SettingsPage-BCWdQHS5.js} +1 -1
  37. package/src/client/dist/pwa/assets/{SplitWorkspacePage-tfZ1kAdZ.js → SplitWorkspacePage-Dw1qUIFU.js} +1 -1
  38. package/src/client/dist/pwa/assets/{TerminalPanel-Cw8R7lAw.js → TerminalPanel-Dqw88QqH.js} +1 -1
  39. package/src/client/dist/pwa/assets/{ToolCallItem-DEKhFJEv.js → ToolCallItem-BMn7eela.js} +1 -1
  40. package/src/client/dist/pwa/assets/{TouchPan-DybGzRRK.js → TouchPan-BwCpe_Oh.js} +1 -1
  41. package/src/client/dist/pwa/assets/{TourReplayButton-BNdlAZUR.js → TourReplayButton-yeGYf6xT.js} +1 -1
  42. package/src/client/dist/pwa/assets/{WorkspaceAttentionLabels-B0H9omcK.js → WorkspaceAttentionLabels-BJPw65vU.js} +1 -1
  43. package/src/client/dist/pwa/assets/{WorkspacePage-tWGXnmrz.js → WorkspacePage-BHO1Q8PG.js} +3 -3
  44. package/src/client/dist/pwa/assets/{WorkspacePage-B-82Rw9T.css → WorkspacePage-BnklZAYZ.css} +1 -1
  45. package/src/client/dist/pwa/assets/{browser-DUTZhU-w.js → browser-C9U-mSDm.js} +1 -1
  46. package/src/client/dist/pwa/assets/{build-path-tree-ZThcsIN5.js → build-path-tree-DYdRW2T4.js} +1 -1
  47. package/src/client/dist/pwa/assets/{custom-sounds-CquHn2gw.js → custom-sounds-DPsfYErA.js} +1 -1
  48. package/src/client/dist/pwa/assets/{de-B3H0T-ft.js → de-Bqil7RJE.js} +2 -2
  49. package/src/client/dist/pwa/assets/{editor.api-pSvhgban.js → editor.api-iu5YkEl3.js} +1 -1
  50. package/src/client/dist/pwa/assets/{efforts-DJaFsLgd.js → efforts-ja6uJadn.js} +1 -1
  51. package/src/client/dist/pwa/assets/{es-B4lACW6k.js → es-CNcMm1Zz.js} +2 -2
  52. package/src/client/dist/pwa/assets/{expand-template-C5D_p5eV.js → expand-template-BbgnJ8YH.js} +1 -1
  53. package/src/client/dist/pwa/assets/{format-DRfnkeRP.js → format-CLXLy6Ov.js} +1 -1
  54. package/src/client/dist/pwa/assets/{formatters-Cog2icjt.js → formatters-DeKgxYJe.js} +1 -1
  55. package/src/client/dist/pwa/assets/{fr-3uNi02Jz.js → fr-BObHEohg.js} +2 -2
  56. package/src/client/dist/pwa/assets/{freemarker2-W-OLsqOk.js → freemarker2-2IrnDDvu.js} +1 -1
  57. package/src/client/dist/pwa/assets/{handlebars-Kt5RbWuO.js → handlebars-CeLN7FCP.js} +1 -1
  58. package/src/client/dist/pwa/assets/{html-D5VKPBbB.js → html-Bn3rhcp3.js} +1 -1
  59. package/src/client/dist/pwa/assets/i18n-Df_y96N2.js +1 -0
  60. package/src/client/dist/pwa/assets/{index-BZwhT2-I.js → index-CH4JpI2a.js} +3 -3
  61. package/src/client/dist/pwa/assets/{it-DtFWyLHb.js → it-DDMC7Emk.js} +2 -2
  62. package/src/client/dist/pwa/assets/{javascript-DBjx8Bg2.js → javascript-BMvON8oT.js} +1 -1
  63. package/src/client/dist/pwa/assets/{liquid-DYT4a7D7.js → liquid-D_AQLvQl.js} +1 -1
  64. package/src/client/dist/pwa/assets/{mdx-9L1kysEY.js → mdx-DDJzMB8Q.js} +1 -1
  65. package/src/client/dist/pwa/assets/{monaco-contributions-DAme5TZS.js → monaco-contributions-DFKwR-_X.js} +1 -1
  66. package/src/client/dist/pwa/assets/{monaco.contribution-C58k7TPw.js → monaco.contribution-4AwFLnjV.js} +2 -2
  67. package/src/client/dist/pwa/assets/{network-auth-BDUlgV-U.js → network-auth-CMERg4Qk.js} +1 -1
  68. package/src/client/dist/pwa/assets/{notify-theme-DrQB-mrb.js → notify-theme-CMzdjL91.js} +1 -1
  69. package/src/client/dist/pwa/assets/{project-color-BlV5PUUW.js → project-color-C5mEKdlH.js} +1 -1
  70. package/src/client/dist/pwa/assets/{python-DJIlKr-Y.js → python-BOqcp9xc.js} +1 -1
  71. package/src/client/dist/pwa/assets/{razor-CC3uqdaH.js → razor-BrGwBzd7.js} +1 -1
  72. package/src/client/dist/pwa/assets/{typescript-BnnkT9oE.js → typescript-mOGf86uY.js} +1 -1
  73. package/src/client/dist/pwa/assets/{use-search-index-status-CExdHIar.js → use-search-index-status-P7O7EaV_.js} +1 -1
  74. package/src/client/dist/pwa/assets/{use-tours-DOUjqO2f.js → use-tours-CMFPIW6f.js} +1 -1
  75. package/src/client/dist/pwa/assets/{use-worktree-restore-zIOYNIcb.js → use-worktree-restore-DF6P7h8g.js} +1 -1
  76. package/src/client/dist/pwa/assets/{workspace-templates-DXJG6flv.js → workspace-templates-C_bgIQvs.js} +1 -1
  77. package/src/client/dist/pwa/assets/{xml-DJtVmsJT.js → xml-YcMBzYAm.js} +1 -1
  78. package/src/client/dist/pwa/assets/{yaml-Dwuw8FcP.js → yaml-BP_67Sds.js} +1 -1
  79. package/src/client/dist/pwa/index.html +1 -1
  80. package/src/client/dist/pwa/sw.js +1 -1
  81. package/src/mcp-server/README.md +8 -0
  82. package/src/client/dist/pwa/assets/DiffViewer-DFlDwkmd.js +0 -8
  83. package/src/client/dist/pwa/assets/GitPanel-DjKL30gM.js +0 -3
  84. package/src/client/dist/pwa/assets/i18n-CEzCwMir.js +0 -1
package/AGENTS.md CHANGED
@@ -223,6 +223,13 @@ reopen a closed database. Startup reconciles lost internal reporters while
223
223
  preserving external ones. An interrupted reporter is not proof of cancelled
224
224
  background work. Operational reporting never satisfies auto-loop verification.
225
225
 
226
+ Progress without measured counts is labelled explicitly, and the panel shows the
227
+ age of a report after two minutes without a newer one. This local clock must not
228
+ poll or mutate progress. Claude's per-launch PostToolUse advisory tracks only
229
+ observed managed progress responses, separates parent/subagent producers, and
230
+ limits reminders by action count and elapsed time. Do not infer percentages,
231
+ automatically complete bars, or block agent shutdown for missing reports.
232
+
226
233
  Retain scoped activity-key fingerprints, producer sequences and dismissed rows
227
234
  for retries; subsequent updates cannot resurrect a dismissed indicator or undo a
228
235
  terminal state. Bound labels, steps, page size, active/retained records and update
@@ -747,6 +754,14 @@ Snapshot each target and its delivery mode before scheduling: auto-loop uses `ne
747
754
 
748
755
  ### File editing in the diff viewer
749
756
 
757
+ The diff defaults to inline view. Busy workspace status or known live controller disables
758
+ Edit with an explanatory tooltip, and updates Monaco to read-only without discarding
759
+ the current buffer. Historical commit comparisons remain read-only. Unpushed mode
760
+ lists local commits plus working-tree changes (including non-ignored untracked files)
761
+ against the branch remote ref, falling back to the source branch before the first push.
762
+ The list and file-content routes must use the same baseline semantics; failed Git
763
+ reads must not be reported as an empty, clean diff.
764
+
750
765
  The right panel of `DiffViewer.vue` is editable when the workspace agent is stopped and the file is not in `deleted` status. `Ctrl/Cmd+S` or the explicit Save button persists the file via `POST /api/workspaces/:id/save-file`, sending `{ path, content, baseSha }` where `baseSha` is the sha256 captured at `GET /diff-file` time. The route refuses with **412 Precondition Failed** + `{ currentSha }` if the on-disk content has changed; the client shows a Reload / Keep mine dialog. Worktree-traversal guards (including parent-symlink escapes) and a 1 MB size cap live in `file-editor-service.ts`. Changing files in the tree while dirty pops an "Unsaved changes" prompt.
751
766
 
752
767
  ## Code conventions
package/CHANGELOG.md CHANGED
@@ -4,6 +4,10 @@ All notable changes to Kōbō are documented here. The format is based on
4
4
  [Keep a Changelog](https://keepachangelog.com/). Each release is an `## <version>`
5
5
  section — the in-app "What's new" dialog reads this file.
6
6
 
7
+ ## 2.1.25
8
+
9
+ - feat: improve merge options, diff controls and progress reporting
10
+
7
11
  ## 2.1.24
8
12
 
9
13
  - feat: add MCP attachments and immediate chat delivery
package/CONFIGURATION.md CHANGED
@@ -949,6 +949,20 @@ resume, not retroactively to an already running agent.
949
949
 
950
950
  The service retains at most 1,000 indicators per session, with at most 32 running
951
951
  at once. Hidden records are retained so retries cannot recreate dismissed bars.
952
+ When a bar has no numeric total, the UI explicitly says **Progress not quantified**.
953
+ After two minutes without a newer report it shows **No update for N min**. This
954
+ is the age of the last received report, not proof that work stopped. The display
955
+ clock does not poll the server or manufacture progress.
956
+
957
+ Claude launches with progress tools also receive a bounded `PostToolUse` reminder:
958
+ once on creation, then after at least eight subsequent tool completions and one
959
+ minute without a report. Reminders are scoped to the producing agent/subagent,
960
+ use the last observed sequence, and follow an explicit reporting handoff. They
961
+ are advisory: no extra tool calls, automatic percentages or blocking stop hooks.
962
+ Codex and external clients retain the shared MCP reporting guidance. Agents must
963
+ supply measured counts when a real test total or fixed checklist is available;
964
+ step-only updates correctly change the description without filling the gauge.
965
+
952
966
  The [MCP progress guide](src/mcp-server/README.md#session-progress-tools) documents
953
967
  arguments, ordering, retry behavior and update limits.
954
968
 
@@ -1788,6 +1802,40 @@ Adapt the port stride, the `APP_NAME`, and any extra `*_PORT` keys to your stack
1788
1802
 
1789
1803
  Kōbō can open pull requests (GitHub or Bitbucket) or merge requests (GitLab) directly from the Git panel. The **forge** setting controls which service is used for a given project.
1790
1804
 
1805
+ ### Review and edit a diff
1806
+
1807
+ The diff viewer opens in **Inline** view. Use **Side by side** to change the layout.
1808
+ While an agent is working, **Edit** is disabled and its tooltip explains why;
1809
+ the diff remains readable and review comments remain available. The editor also
1810
+ becomes read-only if an agent starts while the diff is open. Stop the agent to
1811
+ resume editing. Server-side save checks remain in force.
1812
+
1813
+ **Unpushed** compares the current working tree with `origin/<workingBranch>`:
1814
+ it includes local commits, staged and unstaged changes, deletions, and non-ignored
1815
+ untracked files. Before the first push, it uses the source branch instead. The
1816
+ header shows the comparison base. **Branch** retains its separate opt-in for
1817
+ untracked files. Comparing historical commits remains read-only.
1818
+
1819
+ ### Merge a pull request
1820
+
1821
+ In the Git panel, **Merge PR** opens a confirmation. For GitHub, choose:
1822
+
1823
+ - **Merge commit** (default): preserve the commits and create a merge commit.
1824
+ - **Squash and merge**: combine the changes into one commit on the target branch.
1825
+ - **Rebase and merge**: replay the commits on the target branch without a merge commit.
1826
+
1827
+ The selection applies to this operation only. Kōbō rechecks PR readiness before
1828
+ calling the forge. Repository policies still apply: a disallowed method is
1829
+ reported as an error, with no automatic fallback or protection bypass. The
1830
+ source branch is kept; after a confirmed merge, remote deletion remains a
1831
+ separate confirmation. GitLab and Bitbucket retain their existing merge behavior.
1832
+
1833
+ `POST /api/workspaces/:id/merge-pr` accepts optional JSON
1834
+ `{ "method": "merge" | "squash" | "rebase" }` for integrations advertising the
1835
+ corresponding `forge.capabilities.mergeMethods`. An omitted body keeps the
1836
+ existing provider default. Invalid values return 400; unsupported methods return
1837
+ 409 before any merge command runs.
1838
+
1791
1839
  ### Modes
1792
1840
 
1793
1841
  | Value | Behaviour |
@@ -1,6 +1,7 @@
1
1
  import { execFile as execFileCb, execFileSync, spawn } from 'node:child_process';
2
2
  import { promisify } from 'node:util';
3
3
  import { nanoid } from 'nanoid';
4
+ import { isMergeMethod } from '../../shared/forge-merge.js';
4
5
  import { isWorkflowPolicy, resolveWorkflowPolicy, WORKFLOW_ACTIONS, } from '../../shared/workflow-policy.js';
5
6
  import * as finalReviewService from '../services/auto-loop-final-review-service.js';
6
7
  import { getReviewReturnStatus } from '../services/review-return-service.js';
@@ -3483,15 +3484,14 @@ app.get('/:id/git-stats', async (c) => {
3483
3484
  // `origin/<sourceBranch>` (which can be ahead of the local ref when
3484
3485
  // upstream landed merges Kōbō hasn't pulled). The handler refreshes
3485
3486
  // `origin/<sourceBranch>` synchronously so the diff is never stale.
3486
- // - `unpushed`: committed-only changes vs `origin/<workingBranch>`,
3487
- // i.e. what the next `git push` will send.
3487
+ // - `unpushed`: local commits + current worktree changes (including untracked)
3488
+ // vs `origin/<workingBranch>`, falling back to the source branch before the first push.
3488
3489
  app.get('/:id/diff', async (c) => {
3489
3490
  try {
3490
3491
  const id = c.req.param('id');
3491
3492
  const rawMode = c.req.query('mode');
3492
3493
  const mode = rawMode === 'unpushed' ? 'unpushed' : rawMode === 'commits' ? 'commits' : 'branch';
3493
- // Opt-in flag from the diff viewer toggle. Only meaningful in `branch`
3494
- // mode — `unpushed` is committed-only by definition.
3494
+ // Branch mode keeps its untracked opt-in; unpushed always includes local untracked files.
3495
3495
  const includeUntracked = c.req.query('includeUntracked') === '1';
3496
3496
  const workspace = workspaceService.getWorkspace(id);
3497
3497
  if (!workspace) {
@@ -3525,7 +3525,7 @@ app.get('/:id/diff', async (c) => {
3525
3525
  await gitOps.fetchSourceBranchAsync(worktreePath, workspace.sourceBranch);
3526
3526
  }
3527
3527
  const files = mode === 'unpushed'
3528
- ? await gitOps.getUnpushedChangedFilesAsync(worktreePath, workspace.workingBranch)
3528
+ ? await gitOps.getUnpushedChangedFilesAsync(worktreePath, workspace.workingBranch, 'origin', workspace.sourceBranch)
3529
3529
  : await gitOps.getChangedFilesAsync(worktreePath, workspace.sourceBranch, includeUntracked);
3530
3530
  c.header('Cache-Control', 'no-store');
3531
3531
  return c.json({
@@ -3533,6 +3533,11 @@ app.get('/:id/diff', async (c) => {
3533
3533
  mode,
3534
3534
  sourceBranch: workspace.sourceBranch,
3535
3535
  workingBranch: workspace.workingBranch,
3536
+ ...(mode === 'unpushed'
3537
+ ? {
3538
+ baseRef: await gitOps.resolveUnpushedBaseAsync(worktreePath, workspace.workingBranch, workspace.sourceBranch),
3539
+ }
3540
+ : {}),
3536
3541
  });
3537
3542
  }
3538
3543
  catch (err) {
@@ -3591,7 +3596,9 @@ app.get('/:id/diff-file', async (c) => {
3591
3596
  to,
3592
3597
  });
3593
3598
  }
3594
- const baseRef = mode === 'unpushed' ? `origin/${workspace.workingBranch}` : workspace.sourceBranch;
3599
+ const baseRef = mode === 'unpushed'
3600
+ ? await gitOps.resolveUnpushedBaseAsync(worktreePath, workspace.workingBranch, workspace.sourceBranch)
3601
+ : workspace.sourceBranch;
3595
3602
  const original = await gitOps.getFileAtRefAsync(worktreePath, baseRef, filePath);
3596
3603
  const modified = gitOps.getFileContent(worktreePath, filePath);
3597
3604
  c.header('Cache-Control', 'no-store');
@@ -4211,10 +4218,32 @@ app.post('/:id/merge-pr', async (c) => {
4211
4218
  const workspace = workspaceService.getWorkspace(id);
4212
4219
  if (!workspace)
4213
4220
  return c.json({ error: `Workspace '${id}' not found` }, 404);
4221
+ const rawBody = await c.req.text();
4222
+ let body = {};
4223
+ if (rawBody.length > 0) {
4224
+ let parsed;
4225
+ try {
4226
+ parsed = JSON.parse(rawBody);
4227
+ }
4228
+ catch {
4229
+ return c.json({ error: 'Request body must be valid JSON' }, 400);
4230
+ }
4231
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
4232
+ return c.json({ error: 'Request body must be a JSON object' }, 400);
4233
+ }
4234
+ body = parsed;
4235
+ }
4236
+ const method = body.method;
4237
+ if (method !== undefined && !isMergeMethod(method)) {
4238
+ return c.json({ error: 'method must be merge, squash or rebase' }, 400);
4239
+ }
4214
4240
  const provider = getForgeProvider(resolveForge(workspace.projectPath));
4215
4241
  if (!provider.capabilities.canMergeRequest) {
4216
4242
  return c.json({ error: 'This project has no forge that can merge the PR', code: 'forge_unsupported' }, 409);
4217
4243
  }
4244
+ if (method !== undefined && !provider.capabilities.mergeMethods?.includes(method)) {
4245
+ return c.json({ error: 'This forge does not support the requested merge method', code: 'merge_method_unsupported' }, 409);
4246
+ }
4218
4247
  const availability = await provider.isAvailable(workspace.worktreePath);
4219
4248
  if (!availability.available) {
4220
4249
  return c.json({
@@ -4229,7 +4258,12 @@ app.post('/:id/merge-pr', async (c) => {
4229
4258
  if (!snapshot.readyToMerge) {
4230
4259
  return c.json({ error: 'This PR is not ready to merge', code: 'pr_not_ready' }, 409);
4231
4260
  }
4232
- await provider.mergeRequest(workspace.worktreePath, snapshot.number);
4261
+ if (method === undefined) {
4262
+ await provider.mergeRequest(workspace.worktreePath, snapshot.number);
4263
+ }
4264
+ else {
4265
+ await provider.mergeRequest(workspace.worktreePath, snapshot.number, method);
4266
+ }
4233
4267
  const merged = await provider.getPrStatus(workspace.worktreePath, workspace.workingBranch);
4234
4268
  return c.json({ success: true, merged: merged?.state === 'MERGED', branch: workspace.workingBranch });
4235
4269
  }
@@ -14,6 +14,7 @@ import { CLAUDE_CODE_CAPABILITIES } from './capabilities.js';
14
14
  import { createMapperState, mapSdkMessage, QUOTA_PATTERN, tryEmitQuota } from './event-mapper.js';
15
15
  import { buildClaudeOptions } from './options-builder.js';
16
16
  import { buildCompactionSessionStartOutput } from './precompact-hook.js';
17
+ import { createProgressReportingHook } from './progress-reporting-hook.js';
17
18
  import { resolveClaudeBinaryPath } from './resolve-binary.js';
18
19
  import { buildStopHookOutput } from './stop-hook.js';
19
20
  /**
@@ -341,6 +342,7 @@ export function createClaudeCodeEngine() {
341
342
  const hooks = {
342
343
  ...(options.mcpServers?.some((server) => server.name === 'kobo-tasks' && server.env.KOBO_PROGRESS_SESSION_TOKEN)
343
344
  ? {
345
+ PostToolUse: [{ hooks: [createProgressReportingHook()] }],
344
346
  SubagentStart: [
345
347
  {
346
348
  hooks: [
@@ -0,0 +1,99 @@
1
+ import { PROGRESS_MAX_RETAINED, PROGRESS_MAX_RUNNING } from '../../../../../shared/progress-bars.js';
2
+ function object(value) {
3
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
4
+ }
5
+ function progressResponse(value) {
6
+ if (typeof value === 'string') {
7
+ if (value.length > 16_384)
8
+ return null;
9
+ try {
10
+ value = JSON.parse(value);
11
+ }
12
+ catch {
13
+ return null;
14
+ }
15
+ }
16
+ const result = object(value);
17
+ if (result?.isError === true)
18
+ return null;
19
+ if (result && object(result.structuredContent))
20
+ return object(result.structuredContent);
21
+ const blocks = Array.isArray(value) ? value : result?.content;
22
+ if (Array.isArray(blocks)) {
23
+ for (const entry of blocks.slice(0, 4)) {
24
+ const item = object(entry);
25
+ if (item?.type !== 'text' || typeof item.text !== 'string' || item.text.length > 16_384)
26
+ continue;
27
+ try {
28
+ return object(JSON.parse(item.text));
29
+ }
30
+ catch {
31
+ /* Not a JSON progress response. */
32
+ }
33
+ }
34
+ return null;
35
+ }
36
+ return result;
37
+ }
38
+ /** Per-launch advisory only: no timers, tool calls, writes or stop-blocking loops. */
39
+ export function createProgressReportingHook() {
40
+ const bars = new Map();
41
+ const retired = new Set();
42
+ const reminder = (entries) => ({
43
+ hookSpecificOutput: {
44
+ hookEventName: 'PostToolUse',
45
+ additionalContext: `Kōbō progress reporting: keep these existing activities current at actual milestones. Next update arguments: ${entries.map(([id, state]) => JSON.stringify({ progress_id: id, sequence: state.sequence + 1 })).join('; ')}. Add step and measured current/total when known; otherwise report step only. Call update_progress_bar after meaningful work, and finish_progress_bar with the known outcome before your final report. Do not invent progress, poll, repeat unchanged updates, recreate dismissed bars or finish background work that is still running.`,
46
+ },
47
+ });
48
+ return async (input) => {
49
+ if (input.hook_event_name !== 'PostToolUse' || retired.size >= PROGRESS_MAX_RETAINED)
50
+ return {};
51
+ const producer = JSON.stringify([input.session_id, input.agent_id ?? null]);
52
+ const tool = input.tool_name;
53
+ if (/^mcp__kobo-tasks__(create|update|finish)_progress_bar$/.test(tool)) {
54
+ const bar = progressResponse(input.tool_response);
55
+ if (!bar ||
56
+ typeof bar.id !== 'string' ||
57
+ !/^[\w-]{1,200}$/.test(bar.id) ||
58
+ !Number.isSafeInteger(bar.sequence) ||
59
+ bar.sequence < 0 ||
60
+ !['running', 'succeeded', 'failed', 'interrupted'].includes(bar.status))
61
+ return {};
62
+ if (retired.has(bar.id))
63
+ return {};
64
+ const existing = bars.get(bar.id);
65
+ if (existing && bar.sequence < existing.sequence)
66
+ return {};
67
+ if (bar.dismissed === true || bar.status !== 'running') {
68
+ bars.delete(bar.id);
69
+ // Parallel hook results may arrive after completion; never resurrect a reporter.
70
+ retired.add(bar.id);
71
+ return {};
72
+ }
73
+ // A retry is not progress. A real update may hand reporting to a delegate.
74
+ if (existing && bar.sequence <= existing.sequence)
75
+ return {};
76
+ if (!existing && !tool.endsWith('create_progress_bar'))
77
+ return {};
78
+ if (!existing && bars.size >= PROGRESS_MAX_RUNNING)
79
+ return {};
80
+ const state = { producer, sequence: bar.sequence, actions: 0, remindedAt: Date.now() };
81
+ bars.set(bar.id, state);
82
+ return existing ? {} : reminder([[bar.id, state]]);
83
+ }
84
+ if (tool.startsWith('mcp__kobo-tasks__') && tool.endsWith('progress_bars'))
85
+ return {};
86
+ const due = [];
87
+ for (const [id, state] of bars) {
88
+ if (state.producer !== producer)
89
+ continue;
90
+ state.actions++;
91
+ if (state.actions >= 8 && Date.now() - state.remindedAt >= 60_000 && due.length < 3) {
92
+ state.actions = 0;
93
+ state.remindedAt = Date.now();
94
+ due.push([id, state]);
95
+ }
96
+ }
97
+ return due.length ? reminder(due) : {};
98
+ };
99
+ }
@@ -1,6 +1,7 @@
1
1
  // src/server/services/forge/github/provider.ts
2
2
  import { execFile } from 'node:child_process';
3
3
  import { promisify } from 'node:util';
4
+ import { MERGE_METHODS } from '../../../../shared/forge-merge.js';
4
5
  import { parseCliJson } from '../parse-cli-json.js';
5
6
  import { deriveReadyToMerge, truncatePrBody } from '../types.js';
6
7
  const execFileAsync = promisify(execFile);
@@ -157,6 +158,7 @@ export const githubProvider = {
157
158
  canCreatePr: true,
158
159
  canChangePrBase: true,
159
160
  canMergeRequest: true,
161
+ mergeMethods: MERGE_METHODS,
160
162
  canDeleteRemoteBranch: true,
161
163
  canListPullRequests: true,
162
164
  requestTermShort: 'PR',
@@ -192,8 +194,8 @@ export const githubProvider = {
192
194
  async changePrBase(repoPath, base) {
193
195
  await execFileAsync('gh', ['pr', 'edit', '--base', base], cliOptions(repoPath));
194
196
  },
195
- async mergeRequest(repoPath, number) {
196
- await execFileAsync('gh', ['pr', 'merge', String(number), '--merge'], cliOptions(repoPath));
197
+ async mergeRequest(repoPath, number, method = 'merge') {
198
+ await execFileAsync('gh', ['pr', 'merge', String(number), `--${method}`], cliOptions(repoPath));
197
199
  },
198
200
  async deleteRemoteBranch(repoPath, branch) {
199
201
  await execFileAsync('git', ['push', 'origin', '--delete', branch], cliOptions(repoPath));
@@ -1,4 +1,3 @@
1
- // src/server/services/forge/types.ts
2
1
  /**
3
2
  * True when nothing blocks merging the PR: it is open, CI is not failing or
4
3
  * in-flight (green `SUCCESS` or absent `null` — matching GitHub's "Ready to
@@ -1045,27 +1045,27 @@ export function getChangedFiles(repoPath, base, includeUntracked = false) {
1045
1045
  }
1046
1046
  return files;
1047
1047
  }
1048
- /**
1049
- * List committed files between `origin/<branch>` and local HEAD — the set
1050
- * of files the next `git push` would send. Working tree changes are NOT
1051
- * included: uncommitted edits aren't about to be pushed. Returns an empty
1052
- * list if there is no remote tracking branch yet.
1053
- */
1054
- export function getUnpushedChangedFiles(repoPath, branchName, remote = 'origin') {
1055
- const remoteRef = `${remote}/${branchName}`;
1056
- // Bail out cleanly if the remote branch doesn't exist (branch never pushed).
1048
+ /** Local changes relative to the pushed branch, including staged and working-tree edits. */
1049
+ export function getUnpushedChangedFiles(repoPath, branchName, remote = 'origin', sourceBranch = 'HEAD') {
1050
+ let ref = `${remote}/${branchName}`;
1057
1051
  try {
1058
- git(repoPath, ['rev-parse', '--verify', remoteRef]);
1052
+ git(repoPath, ['rev-parse', '--verify', ref]);
1059
1053
  }
1060
1054
  catch {
1061
- return [];
1062
- }
1063
- const files = [];
1064
- try {
1065
- files.push(...parseNameStatus(git(repoPath, ['diff', '--name-status', '-z', `${remoteRef}..HEAD`])));
1055
+ ref = resolveBase(repoPath, sourceBranch);
1066
1056
  }
1067
- catch {
1068
- // Unlikely after the rev-parse check, but keep the happy path robust.
1057
+ const files = parseNameStatus(git(repoPath, ['diff', '--name-status', '-z', ref, '--']));
1058
+ const untracked = git(repoPath, ['ls-files', '--others', '--exclude-standard', '-z']);
1059
+ return withUntrackedFiles(files, untracked);
1060
+ }
1061
+ /** Preserve NUL-delimited filenames and deduplicate tracked/untracked transitions. */
1062
+ function withUntrackedFiles(files, output) {
1063
+ const seen = new Set(files.map((file) => file.path));
1064
+ for (const filePath of output.split('\0')) {
1065
+ if (filePath && !seen.has(filePath)) {
1066
+ files.push({ path: filePath, status: 'untracked' });
1067
+ seen.add(filePath);
1068
+ }
1069
1069
  }
1070
1070
  return files;
1071
1071
  }
@@ -1555,16 +1555,25 @@ export async function getChangedFilesBetweenAsync(repoPath, fromRef, toRef) {
1555
1555
  return [];
1556
1556
  }
1557
1557
  }
1558
- export async function getUnpushedChangedFilesAsync(repoPath, branchName, remote = 'origin') {
1558
+ /** Use the source branch until this working branch has been pushed for the first time. */
1559
+ export async function resolveUnpushedBaseAsync(repoPath, branchName, sourceBranch = 'HEAD', remote = 'origin') {
1559
1560
  const ref = `${remote}/${branchName}`;
1560
1561
  try {
1561
1562
  await gitAsync(repoPath, ['rev-parse', '--verify', ref]);
1562
- return parseNameStatus(await gitAsync(repoPath, ['diff', '--name-status', '-z', `${ref}..HEAD`]));
1563
+ return ref;
1563
1564
  }
1564
1565
  catch {
1565
- return [];
1566
+ return resolveBaseAsync(repoPath, sourceBranch);
1566
1567
  }
1567
1568
  }
1569
+ export async function getUnpushedChangedFilesAsync(repoPath, branchName, remote = 'origin', sourceBranch = 'HEAD') {
1570
+ const ref = await resolveUnpushedBaseAsync(repoPath, branchName, sourceBranch, remote);
1571
+ const [changes, untracked] = await Promise.all([
1572
+ gitAsync(repoPath, ['diff', '--name-status', '-z', ref, '--']),
1573
+ gitAsync(repoPath, ['ls-files', '--others', '--exclude-standard', '-z']),
1574
+ ]);
1575
+ return withUntrackedFiles(parseNameStatus(changes), untracked);
1576
+ }
1568
1577
  export async function getChangedFilesAsync(repoPath, base, includeUntracked = false) {
1569
1578
  const ref = await resolveBaseAsync(repoPath, base);
1570
1579
  const [committed, working] = await Promise.allSettled([
@@ -0,0 +1,5 @@
1
+ /** Explicit PR merge strategies. Availability is declared by each forge provider. */
2
+ export const MERGE_METHODS = ['merge', 'squash', 'rebase'];
3
+ export function isMergeMethod(value) {
4
+ return typeof value === 'string' && MERGE_METHODS.some((method) => method === value);
5
+ }
@@ -4,7 +4,7 @@ export const PROGRESS_BAR_PROMPT = `<kobo-progress-reporting>
4
4
  ${PROGRESS_BAR_PLAN_EXCEPTION}
5
5
  For each long-running activity (tests, audits, builds or delegated work), follow this reporting lifecycle:
6
6
  1. Call create_progress_bar with a clear label, current step and stable activity_key. Save its returned id and sequence; creating a bar is only the start of reporting.
7
- 2. Call update_progress_bar with that id and an increasing sequence: update the same bar at each actual phase change or meaningful completed milestone, including during a long phase. Even without a measured percentage, report the current step; omit both current and total for step-only updates. Use measured numbers only when known. Never invent percentages, poll, send unchanged updates or start timer-driven reporting loops; follow tool retry guidance.
7
+ 2. Call update_progress_bar with that id and an increasing sequence: update the same bar at each actual phase change or meaningful completed milestone, including during a long phase. Even without a measured percentage, report the current step; omit both current and total for step-only updates. Use measured numbers only when known: when tests report a total or your activity has a fixed checklist, provide current and total together and advance current only as those items actually finish. A step-only update changes the label, not the gauge; null numbers mean no percentage is available. Never invent percentages, poll, send unchanged updates or start timer-driven reporting loops; follow tool retry guidance.
8
8
  3. Call finish_progress_bar on that id before your final report (or return to the parent) when the activity's succeeded/failed outcome is known. Do not declare ongoing background work finished merely because you are yielding control.
9
9
  Pass this requirement to all subagents when delegating: each reports and finishes its own long activity through inherited Kōbō MCP tools; the parent must not duplicate it. If a custom subagent lacks these tools, the parent reports its observed milestones and outcome instead. Subagents must not schedule or replace workspace wakeups; the parent coordinates follow-up.
10
10
  A dismissed bar stays hidden: continue without recreating it; dismissal is not cancellation. Reporting never substitutes for verification, and an interrupted reporter does not prove background work stopped. After resume, use new activity keys for new work; do not reopen finished activities.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loicngr/kobo",
3
- "version": "2.1.24",
3
+ "version": "2.1.25",
4
4
  "description": "Kōbō — local multi-workspace orchestrator for Claude Code and OpenAI Codex, with isolated Git worktrees, live review and optional automation.",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0-or-later",
@@ -1,2 +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{Kt as ce,O as le,Qt as ue,Ut as O,_t as de,at as fe,cn as pe,dn as k,j as me,m as he,mt as ge,pt as _e,st as ve,tn as A,v as ye}from"./index-BZwhT2-I.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-Dr7rAt6u.js";import{t as P}from"./QItemSection-MdJ1_ZK7.js";import{t as Ce}from"./QExpansionItem-CaKEBcyx.js";import{a as F,c as we,r as I,t as L}from"./ToolCallItem-DEKhFJEv.js";import{t as Te}from"./QScrollArea-hrsAyyJf.js";import{a as R}from"./use-tours-DOUjqO2f.js";import{f as Ee,g as z,m as De,p as Oe,u as ke}from"./use-worktree-restore-zIOYNIcb.js";import{t as B}from"./QTooltip-CNMsXTmD.js";import{n as V}from"./render-chat-markdown-CafzCcmu.js";import{n as H}from"./WorkspacePage-tWGXnmrz.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:k},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=pe(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)?`![${r}](${`/api/workspaces/${encodeURIComponent(t)}/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-message markdown-user-prompt`,innerHTML:e(s)},null,8,qe)]),_:1},8,[`label`])):(u(),S(`div`,{key:1,class:`markdown-message`,onClick:y},[w(`div`,{innerHTML:e(s)},null,8,Je)])),g(A,{modelValue:d.value,"onUpdate:modelValue":n[1]||=e=>d.value=e},{default:h(()=>[c.value?(u(),S(`img`,{key:0,src:c.value,alt:``,class:`image-lightbox-img`,onClick:n[0]||=e=>d.value=!1},null,8,Ye)):v(``,!0)]),_:1},8,[`modelValue`])],64))}}),[[`__scopeId`,`data-v-965e6297`]]),Xe=[`data-event-ids`],X={class:`turn-header`},Ze={key:0,class:`turn-time`},Qe={class:`turn-time turn-time-updated`},$e={key:2,class:`turn-actions`},et={class:`turn-body`},tt={class:`ellipsis`},nt={class:`q-px-sm q-pb-sm`},rt={key:0,class:`turn-scroll-top`},it=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=fe(),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 de(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(!de(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 k(e){e.origin&&f?.({origin:e.origin})}let me=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),me.value>0?(u(),S(`span`,$e,` · `+a(e(l)(`chat.nActions`,{n:me.value})),1)):v(``,!0)]),w(`div`,et,[(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`,tt,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=>k(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`,nt,[(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`,rt,[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`]]),at={key:0,class:`activity-feed-switching`},ot={key:1,class:`activity-feed-wrap`,"data-tour":`ws-chat`},st={key:0,role:`alert`,class:`q-pa-sm text-negative`},ct={key:1,"data-testid":`history-load-error`,role:`alert`,class:`q-pa-sm text-negative`},lt=[`aria-label`],ut={key:0,class:`activity-feed-compacting`},dt={class:`text-caption text-kobo-2`},ft={key:0,class:`text-center q-py-sm text-caption text-kobo-3`},pt={key:1,class:`activity-feed-empty q-pa-md`},mt={class:`activity-feed-empty__title`},ht={class:`activity-feed-empty__hint`},gt={key:2,class:`q-px-md q-pb-md`},_t=[`title`],vt={class:`ellipsis`},yt={class:`activity-feed-nav-cluster`},bt=300,xt=60,St=200,Ct=180,wt=255,Tt=3,Et=60,Dt=R(T({__name:`ActivityFeed`,props:{workspaceId:{}},setup(t){let{t:n}=ue(),r=t,i=de(),s=me(),d=fe(),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(),pe=``,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!==pe&&(le=ke(),pe=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,bt)}):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){tt();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<=xt,R&&e.verticalPosition<=St&&!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=``,Nt.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=$();if(await f(),t!==$())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<Ct,t=$();J=requestAnimationFrame(()=>{J=null,t===$()&&(Ge=performance.now(),q(e?0:180))})}function qe(e){Q(),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;Q(),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}),$e=null;function et(){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 tt(){$e===null&&($e=requestAnimationFrame(()=>{$e=null,et()}))}function nt(e){return e.length<=wt?e:`${e.slice(0,252)}...`}let rt=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(`
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{Kt as ce,O as le,Qt as ue,Ut as O,_t as de,at as fe,cn as pe,dn as k,j as me,m as he,mt as ge,pt as _e,st as ve,tn as A,v as ye}from"./index-CH4JpI2a.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-GQAsf7pB.js";import{t as P}from"./QItemSection-MdJ1_ZK7.js";import{t as Ce}from"./QExpansionItem-xIXDevKz.js";import{a as F,c as we,r as I,t as L}from"./ToolCallItem-BMn7eela.js";import{t as Te}from"./QScrollArea-DeczMHVs.js";import{a as R}from"./use-tours-CMFPIW6f.js";import{f as Ee,g as z,m as De,p as Oe,u as ke}from"./use-worktree-restore-DF6P7h8g.js";import{t as B}from"./QTooltip-be9mqhmB.js";import{n as V}from"./render-chat-markdown-CafzCcmu.js";import{n as H}from"./WorkspacePage-BHO1Q8PG.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:k},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=pe(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)?`![${r}](${`/api/workspaces/${encodeURIComponent(t)}/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-message markdown-user-prompt`,innerHTML:e(s)},null,8,qe)]),_:1},8,[`label`])):(u(),S(`div`,{key:1,class:`markdown-message`,onClick:y},[w(`div`,{innerHTML:e(s)},null,8,Je)])),g(A,{modelValue:d.value,"onUpdate:modelValue":n[1]||=e=>d.value=e},{default:h(()=>[c.value?(u(),S(`img`,{key:0,src:c.value,alt:``,class:`image-lightbox-img`,onClick:n[0]||=e=>d.value=!1},null,8,Ye)):v(``,!0)]),_:1},8,[`modelValue`])],64))}}),[[`__scopeId`,`data-v-965e6297`]]),Xe=[`data-event-ids`],X={class:`turn-header`},Ze={key:0,class:`turn-time`},Qe={class:`turn-time turn-time-updated`},$e={key:2,class:`turn-actions`},et={class:`turn-body`},tt={class:`ellipsis`},nt={class:`q-px-sm q-pb-sm`},rt={key:0,class:`turn-scroll-top`},it=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=fe(),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 de(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(!de(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 k(e){e.origin&&f?.({origin:e.origin})}let me=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),me.value>0?(u(),S(`span`,$e,` · `+a(e(l)(`chat.nActions`,{n:me.value})),1)):v(``,!0)]),w(`div`,et,[(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`,tt,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=>k(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`,nt,[(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`,rt,[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`]]),at={key:0,class:`activity-feed-switching`},ot={key:1,class:`activity-feed-wrap`,"data-tour":`ws-chat`},st={key:0,role:`alert`,class:`q-pa-sm text-negative`},ct={key:1,"data-testid":`history-load-error`,role:`alert`,class:`q-pa-sm text-negative`},lt=[`aria-label`],ut={key:0,class:`activity-feed-compacting`},dt={class:`text-caption text-kobo-2`},ft={key:0,class:`text-center q-py-sm text-caption text-kobo-3`},pt={key:1,class:`activity-feed-empty q-pa-md`},mt={class:`activity-feed-empty__title`},ht={class:`activity-feed-empty__hint`},gt={key:2,class:`q-px-md q-pb-md`},_t=[`title`],vt={class:`ellipsis`},yt={class:`activity-feed-nav-cluster`},bt=300,xt=60,St=200,Ct=180,wt=255,Tt=3,Et=60,Dt=R(T({__name:`ActivityFeed`,props:{workspaceId:{}},setup(t){let{t:n}=ue(),r=t,i=de(),s=me(),d=fe(),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(),pe=``,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!==pe&&(le=ke(),pe=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,bt)}):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){tt();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<=xt,R&&e.verticalPosition<=St&&!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=``,Nt.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=$();if(await f(),t!==$())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<Ct,t=$();J=requestAnimationFrame(()=>{J=null,t===$()&&(Ge=performance.now(),q(e?0:180))})}function qe(e){Q(),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;Q(),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}),$e=null;function et(){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 tt(){$e===null&&($e=requestAnimationFrame(()=>{$e=null,et()}))}function nt(e){return e.length<=wt?e:`${e.slice(0,252)}...`}let rt=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
2
  `).trim();return n?{index:e,content:nt(n)}:null});function Dt(e){X.value=e.index,tt()}function Ot(){let e=rt.value;e&&(Q(),M.value=!1,Y.value?.scrollTo(e.index,`start`),X.value=e.index)}let kt=o(!1);async function At(){if(kt.value)return;Q(),M.value=!1,kt.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{kt.value=!1}}let Z=0;function jt(){return new Promise(e=>requestAnimationFrame(()=>e()))}async function Mt(){if(!z||!U.value||Ae.value)return;R=!1;let e=++Z;if(await f(),e!==Z||!z||(Xe.value=j.value?.getScrollTarget()??null,await f(),e!==Z||!z))return;k.value.length>0&&Y.value?.scrollTo(k.value.length-1,`end-force`),await jt();let t=-1,n=0;for(let r=0;r<Et&&n<Tt;r++){if(e!==Z||!z)return;await q(0),await jt();let r=j.value?.getScroll().verticalSize??0;B=r,n=r===t?n+1:0,t=r}e===Z&&!Ne.value&&(R=!0,z=!1)}function Q(){z=!1,!R&&(Z++,R=!0)}let Nt=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 Mt(),e||f(()=>{Xe.value=j.value?.getScrollTarget()??null,tt()})}),l(Ne,(e,t)=>{!e&&t&&z&&U.value&&Mt()}),p(()=>{window.addEventListener(`kobo:focus-history-event`,Ye),f(()=>{Xe.value=j.value?.getScrollTarget()??null,tt()}),U.value&&Mt(),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([Nt,()=>ie.value.length,()=>A.value.length],async([e,t,n])=>{N.value||z&&e+t+n>0&&await Mt()}),l([Qe,()=>k.value.length],()=>{f(tt)});let Pt=x(()=>i.liveAppendCountFor(r.workspaceId,E)),$=()=>`${r.workspaceId}\u0000${d.selectedSessionId??``}`;l([Pt,$],([e,t],[n,r])=>{t===r&&e>n&&M.value&&!N.value&&Ke()}),c(()=>{Z++,z=!1,Ue(),window.removeEventListener(`kobo:focus-history-event`,Ye),J!=null&&(cancelAnimationFrame(J),J=null),$e!==null&&cancelAnimationFrame($e)}),l([()=>r.workspaceId,()=>d.selectedSessionId],()=>{Z++,z=!0,B=null,M.value=!0,R=!1,It(),U.value&&Mt()});let Ft=new Set;async function It(){let e=r.workspaceId,t=b.value;if(!t||Nt.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,$],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`,at,[g(be,{size:`40px`,color:`primary`})])):(u(),S(`div`,ot,[e(d).sessionsErrors[r.workspaceId]?(u(),S(`div`,st,[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`,ct,[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,lt),g(re,{name:`fade`},{default:h(()=>[ee.value?(u(),S(`div`,ut,[g(be,{size:`18px`,color:`primary`}),w(`span`,dt,a(t.$t(`activity.compacting`)),1)])):v(``,!0)]),_:1}),g(Te,{ref_key:`scrollRef`,ref:j,class:`activity-feed-scroll`,onScroll:Pe,onWheelPassive:Q,onTouchstartPassive:Q,onKeydown:Q},{default:h(()=>[N.value?(u(),S(`div`,ft,[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:Dt},{default:h(({item:t,index:n})=>[(u(),_(it,{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`,pt,[g(oe,{name:`forum`,size:`28px`}),w(`div`,mt,a(t.$t(`activity.empty`)),1),w(`div`,ht,a(t.$t(`activity.emptyHint`)),1)])):v(``,!0),A.value.length?(u(),S(`div`,gt,[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),rt.value?(u(),S(`button`,{key:2,type:`button`,class:`activity-feed-last-user`,"data-testid":`latest-user-message`,title:rt.value.content,onClick:Ot},[g(oe,{name:`person`,size:`16px`}),w(`span`,vt,a(rt.value.content),1)],8,_t)):v(``,!0),w(`div`,yt,[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:kt.value,disable:kt.value,onClick:At},null,8,[`title`,`aria-label`,`loading`,`disable`])])]))}}),[[`__scopeId`,`data-v-58324159`]]);export{Dt as default};