@loicngr/kobo 2.1.4 → 2.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/CONFIGURATION.md +11 -3
- package/dist/server/index.js +24 -91
- package/dist/server/routes/git.js +6 -7
- package/dist/server/routes/settings.js +21 -11
- package/dist/server/routes/workspaces.js +84 -74
- package/dist/server/services/agent/engines/claude-code/event-mapper.js +33 -0
- package/dist/server/services/agent/engines/codex/engine.js +10 -1
- package/dist/server/services/agent/engines/codex/event-mapper.js +2 -0
- package/dist/server/services/agent/integration-mcp.js +4 -3
- package/dist/server/services/agent/orchestrator.js +1 -1
- package/dist/server/services/agent/streaming-batcher.js +14 -3
- package/dist/server/services/engine-handoff-service.js +120 -24
- package/dist/server/services/pr-checkout-service.js +90 -53
- package/dist/server/services/pr-watcher-service.js +13 -7
- package/dist/server/services/retention/worker.js +40 -0
- package/dist/server/services/review-service.js +8 -4
- package/dist/server/services/session-event-metrics.js +43 -0
- package/dist/server/services/session-handoff-service.js +6 -4
- package/dist/server/services/templates-service.js +67 -49
- package/dist/server/services/terminal-service.js +45 -14
- package/dist/server/services/terminal-websocket-service.js +143 -0
- package/dist/server/services/wakeup-service.js +23 -21
- package/dist/server/services/workspace-service.js +3 -44
- package/dist/server/services/workspace-template-service.js +16 -18
- package/dist/server/services/worktree-purge-service.js +1 -4
- package/dist/server/services/ws-events-retention-service.js +197 -104
- package/dist/server/services/ws-events-retention-worker-service.js +112 -0
- package/dist/server/utils/atomic-json-file.js +23 -0
- package/dist/server/utils/git-ops.js +262 -60
- package/dist/shared/codex-models.js +20 -3
- package/dist/shared/models.js +12 -0
- package/package.json +6 -6
- package/src/client/dist/pwa/assets/ActivityFeed-BO1mKMJ0.js +1 -0
- package/src/client/dist/pwa/assets/ActivityFeed-ZTxXVmNM.css +1 -0
- package/src/client/dist/pwa/assets/ChangelogPage-BXNz7PGb.js +1 -0
- package/src/client/dist/pwa/assets/ChangelogPage-DVyx9ZjV.css +1 -0
- package/src/client/dist/pwa/assets/ComparisonPanel-ByVbqYCs.css +1 -0
- package/src/client/dist/pwa/assets/ComparisonPanel-CfmJHeH_.js +2 -0
- package/src/client/dist/pwa/assets/CreatePage-6_AP5zso.js +2 -0
- package/src/client/dist/pwa/assets/CreatePage-CMyl2j8Z.css +1 -0
- package/src/client/dist/pwa/assets/DashboardPage-BKCrV6u0.js +1 -0
- package/src/client/dist/pwa/assets/DashboardPage-CtaHaVdf.css +1 -0
- package/src/client/dist/pwa/assets/DiffViewer-BMzrb9fB.js +8 -0
- package/src/client/dist/pwa/assets/DocumentsPanel-CqGg38Oi.js +1 -0
- package/src/client/dist/pwa/assets/{DrawerToggleButton-Bh4FXdtJ.js → DrawerToggleButton-DKytIBlk.js} +1 -1
- package/src/client/dist/pwa/assets/{GitPanel-C4wOcZV2.css → GitPanel-C3blk_Pd.css} +1 -1
- package/src/client/dist/pwa/assets/GitPanel-DRzhrs_V.js +3 -0
- package/src/client/dist/pwa/assets/HealthPage-DT8FGrKA.css +1 -0
- package/src/client/dist/pwa/assets/HealthPage-DWotmfkh.js +1 -0
- package/src/client/dist/pwa/assets/MainLayout-DkSavoiV.css +1 -0
- package/src/client/dist/pwa/assets/MainLayout-fRVZ01LN.js +2 -0
- package/src/client/dist/pwa/assets/QBanner-mzrnn82R.js +1 -0
- package/src/client/dist/pwa/assets/{QBtnDropdown-DVbxGTNb.js → QBtnDropdown-BGGvQIvH.js} +1 -1
- package/src/client/dist/pwa/assets/QChip-Chu7r6RZ.js +1 -0
- package/src/client/dist/pwa/assets/QExpansionItem-C2XXHOHk.js +1 -0
- package/src/client/dist/pwa/assets/QItemSection-CR3Kfibl.js +1 -0
- package/src/client/dist/pwa/assets/QList-DnezW6gW.js +1 -0
- package/src/client/dist/pwa/assets/QMarkupTable-wuDf50Oh.js +1 -0
- package/src/client/dist/pwa/assets/QScrollArea-BnmQ9US7.js +1 -0
- package/src/client/dist/pwa/assets/QSelect-BskTzVCM.js +1 -0
- package/src/client/dist/pwa/assets/{QTooltip-BLhkNT4s.js → QTooltip-CO84H0cq.js} +1 -1
- package/src/client/dist/pwa/assets/SchedulePanel-CBM_GpS3.css +1 -0
- package/src/client/dist/pwa/assets/SchedulePanel-CPirTYw5.js +1 -0
- package/src/client/dist/pwa/assets/SearchPage-CnT0-Hpe.js +1 -0
- package/src/client/dist/pwa/assets/{SettingsPage-BYdLXJEJ.css → SettingsPage-CpykRKq2.css} +1 -1
- package/src/client/dist/pwa/assets/SettingsPage-DCMZL7GO.js +9 -0
- package/src/client/dist/pwa/assets/SplitWorkspacePage-DwWsD7f5.js +1 -0
- package/src/client/dist/pwa/assets/{TerminalPanel-D2j5AA8S.js → TerminalPanel-C4dYabx6.js} +2 -2
- package/src/client/dist/pwa/assets/{TerminalPanel-DbVZk0vD.css → TerminalPanel-Dz9EE-Ph.css} +1 -1
- package/src/client/dist/pwa/assets/ToolCallItem-CjXWiKJc.js +8 -0
- package/src/client/dist/pwa/assets/ToolCallItem-DhjdG8vp.css +1 -0
- package/src/client/dist/pwa/assets/{TouchPan-C0EqFcrr.js → TouchPan-6fI8xFPq.js} +1 -1
- package/src/client/dist/pwa/assets/{TourReplayButton-DlMvPblb.js → TourReplayButton-DhbNI6O5.js} +1 -1
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-CZxW7QSq.js +1 -0
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-DSw8Upcp.css +1 -0
- package/src/client/dist/pwa/assets/WorkspacePage-B0wCQdev.js +4 -0
- package/src/client/dist/pwa/assets/{WorkspacePage--5HsnMQ1.css → WorkspacePage-BnW-gp3u.css} +1 -1
- package/src/client/dist/pwa/assets/{browser-DNEbLF-4.js → browser-CTkGHrN5.js} +1 -1
- package/src/client/dist/pwa/assets/build-path-tree-Bd50EZMW.js +1 -0
- package/src/client/dist/pwa/assets/custom-sounds-DoPDmcY5.js +1 -0
- package/src/client/dist/pwa/assets/de-BPtzBQu0.js +17 -0
- package/src/client/dist/pwa/assets/defineProperty-BbfpZ9Tg.js +1 -0
- package/src/client/dist/pwa/assets/{editor.api-PENN0io6.js → editor.api-CKcTKXcM.js} +1 -1
- package/src/client/dist/pwa/assets/efforts-CDsBbk8o.js +9 -0
- package/src/client/dist/pwa/assets/es-BoNOfG0w.js +17 -0
- package/src/client/dist/pwa/assets/expand-template-D6zFPzzB.js +1 -0
- package/src/client/dist/pwa/assets/{format-CVI7mr7G.js → format-MtzcnR3z.js} +7 -7
- package/src/client/dist/pwa/assets/formatters-DCjBZ47F.js +1 -0
- package/src/client/dist/pwa/assets/fr-Bu92fK5U.js +17 -0
- package/src/client/dist/pwa/assets/{freemarker2-BxN_Aq6C.js → freemarker2-JGUTjYvR.js} +1 -1
- package/src/client/dist/pwa/assets/{handlebars-C9UHXtO1.js → handlebars-BWStl2Hc.js} +1 -1
- package/src/client/dist/pwa/assets/{html-CTH9wSJr.js → html-CjGCxUEe.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-DKj-umtn.js +19 -0
- package/src/client/dist/pwa/assets/i18n-DtmbeJu8.js +1 -0
- package/src/client/dist/pwa/assets/index-BPmUmc6D.js +2 -0
- package/src/client/dist/pwa/assets/it-CrA4Q7oc.js +17 -0
- package/src/client/dist/pwa/assets/{javascript-WT4BS8p4.js → javascript-C_jd0j2i.js} +1 -1
- package/src/client/dist/pwa/assets/latest-request-CBPb8UWQ.js +1 -0
- package/src/client/dist/pwa/assets/{liquid-D6dfe-gU.js → liquid-DMyQVGkp.js} +1 -1
- package/src/client/dist/pwa/assets/{mdx-B8pNXNeC.js → mdx-DeW5qzOU.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco-contributions-COLfslyi.js → monaco-contributions-Be_o6s3m.js} +1 -1
- package/src/client/dist/pwa/assets/monaco.contribution-VUGNbVME.js +2 -0
- package/src/client/dist/pwa/assets/network-auth-CSx0jS5X.js +1 -0
- package/src/client/dist/pwa/assets/{notify-theme-7MsG-MyN.js → notify-theme-uhSG2r09.js} +1 -1
- package/src/client/dist/pwa/assets/{permissionModes-B-ciuUbz.js → permissionModes-Cc5VaOdC.js} +1 -1
- package/src/client/dist/pwa/assets/preload-helper-DjzSQ1t3.js +1 -0
- package/src/client/dist/pwa/assets/{project-color-Bo6iEcqN.js → project-color-BQbOnYBN.js} +1 -1
- package/src/client/dist/pwa/assets/{python-D2gb6Jjz.js → python-C1v3VovI.js} +1 -1
- package/src/client/dist/pwa/assets/{razor-dksrCQF2.js → razor-DMEmKSdO.js} +1 -1
- package/src/client/dist/pwa/assets/{render-chat-markdown-px_6WDIX.js → render-chat-markdown-CafzCcmu.js} +1 -1
- package/src/client/dist/pwa/assets/{typescript-DTPyRTrW.js → typescript-rKyiSQID.js} +1 -1
- package/src/client/dist/pwa/assets/use-checkbox-BqDMQ6x7.js +1 -0
- package/src/client/dist/pwa/assets/use-search-index-status-BL3cb04x.js +1 -0
- package/src/client/dist/pwa/assets/{use-tours-b00RruX_.js → use-tours-Dum-bOyQ.js} +2 -2
- package/src/client/dist/pwa/assets/use-worktree-restore-BQyoz3Qa.js +1 -0
- package/src/client/dist/pwa/assets/workspace-templates-YRwFjkUh.js +1 -0
- package/src/client/dist/pwa/assets/{xml-mxUZCM4r.js → xml-U_pDcywM.js} +1 -1
- package/src/client/dist/pwa/assets/{yaml-BuN8sUWC.js → yaml-CRCd_ET9.js} +1 -1
- package/src/client/dist/pwa/index.html +6 -3
- package/src/client/dist/pwa/sw.js +1 -1
- package/src/client/dist/pwa/assets/ActivityFeed-DCmiJP-z.css +0 -1
- package/src/client/dist/pwa/assets/ActivityFeed-DdGARu8o.js +0 -8
- package/src/client/dist/pwa/assets/ChangelogPage-CGenjiI5.js +0 -1
- package/src/client/dist/pwa/assets/ChangelogPage-DKJkFiU_.css +0 -1
- package/src/client/dist/pwa/assets/ComparisonPanel-CRRaDs2D.css +0 -1
- package/src/client/dist/pwa/assets/ComparisonPanel-aXgjEulK.js +0 -2
- package/src/client/dist/pwa/assets/CreatePage-B9C9J9Pw.css +0 -1
- package/src/client/dist/pwa/assets/CreatePage-BaB_30xJ.js +0 -2
- package/src/client/dist/pwa/assets/DashboardPage-DsbwmO2W.js +0 -1
- package/src/client/dist/pwa/assets/DashboardPage-XiCj24i2.css +0 -1
- package/src/client/dist/pwa/assets/DiffViewer--dUMf7b3.js +0 -8
- package/src/client/dist/pwa/assets/DocumentsPanel-DTjEUYUd.js +0 -1
- package/src/client/dist/pwa/assets/GitPanel-PUPLj0M8.js +0 -3
- package/src/client/dist/pwa/assets/HealthPage-BHgucifz.js +0 -1
- package/src/client/dist/pwa/assets/HealthPage-CFZ-tdKK.css +0 -1
- package/src/client/dist/pwa/assets/MainLayout-CNmwCnDD.css +0 -1
- package/src/client/dist/pwa/assets/MainLayout-CQVLnTX3.js +0 -2
- package/src/client/dist/pwa/assets/QBanner-XE0EMe9L.js +0 -1
- package/src/client/dist/pwa/assets/QChip-Bt_JvnHb.js +0 -1
- package/src/client/dist/pwa/assets/QExpansionItem-CxEBY3jY.js +0 -1
- package/src/client/dist/pwa/assets/QItemSection-Cf6OZEH4.js +0 -1
- package/src/client/dist/pwa/assets/QList-Z90BspT4.js +0 -1
- package/src/client/dist/pwa/assets/QMarkupTable-Bwjknbmw.js +0 -1
- package/src/client/dist/pwa/assets/QSelect-CMzJDhi-.js +0 -1
- package/src/client/dist/pwa/assets/SchedulePanel-BYKSFoFj.css +0 -1
- package/src/client/dist/pwa/assets/SchedulePanel-DTVwe7sX.js +0 -1
- package/src/client/dist/pwa/assets/SearchPage-CtrR7dNr.js +0 -1
- package/src/client/dist/pwa/assets/SettingsPage-Bl_RNTGi.js +0 -9
- package/src/client/dist/pwa/assets/SplitWorkspacePage-Bac_Pzmm.js +0 -1
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-BAk5l4Ik.js +0 -1
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-CrLP_I5S.css +0 -1
- package/src/client/dist/pwa/assets/WorkspacePage-FScR5YAD.js +0 -4
- package/src/client/dist/pwa/assets/build-path-tree-C0b61nxI.js +0 -1
- package/src/client/dist/pwa/assets/custom-sounds-DE4JLdGG.js +0 -1
- package/src/client/dist/pwa/assets/de-DgZ9jKq_.js +0 -17
- package/src/client/dist/pwa/assets/documents-CALwmao-.js +0 -1
- package/src/client/dist/pwa/assets/efforts-Detdm-FH.js +0 -9
- package/src/client/dist/pwa/assets/es-zvlgzw9H.js +0 -17
- package/src/client/dist/pwa/assets/expand-template-ZIVLPdW2.js +0 -1
- package/src/client/dist/pwa/assets/formatters-C_zcfJBt.js +0 -1
- package/src/client/dist/pwa/assets/fr-BICzq59I.js +0 -17
- package/src/client/dist/pwa/assets/i18n-DG_vChZ3.js +0 -1
- package/src/client/dist/pwa/assets/index-fuLr-ZVa.js +0 -20
- package/src/client/dist/pwa/assets/it-BIGArQrS.js +0 -17
- package/src/client/dist/pwa/assets/monaco.contribution-CDhuQbzi.js +0 -2
- package/src/client/dist/pwa/assets/network-auth-QouPCx6A.js +0 -1
- package/src/client/dist/pwa/assets/pending-diff-open-CX24u9gW.js +0 -1
- package/src/client/dist/pwa/assets/pinia-BdFjQEqa.js +0 -1
- package/src/client/dist/pwa/assets/use-checkbox-Bh9EQhkV.js +0 -1
- package/src/client/dist/pwa/assets/use-search-index-status-_nCyACc7.js +0 -1
- package/src/client/dist/pwa/assets/use-worktree-restore-BDOZc9fk.js +0 -1
- package/src/client/dist/pwa/assets/workspace-templates-DV9Njz0T.js +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@ 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.6
|
|
8
|
+
|
|
9
|
+
- feat(subagents): add focused activity views
|
|
10
|
+
|
|
11
|
+
## 2.1.5
|
|
12
|
+
|
|
13
|
+
- fix(workspace): improve reliability and git controls
|
|
14
|
+
|
|
7
15
|
## 2.1.4
|
|
8
16
|
|
|
9
17
|
- chore(deps): update server client and pwa packages
|
package/CONFIGURATION.md
CHANGED
|
@@ -751,9 +751,17 @@ both global (the database is single):
|
|
|
751
751
|
| Events always kept per workspace | `0` | The most recent events of each workspace survive whatever their age. Set it (5000 is a good starting point) at the same time you enable retention, so an old but still-open workspace keeps a readable history. |
|
|
752
752
|
|
|
753
753
|
The prune runs **at server startup and every 24 hours** while the backend stays
|
|
754
|
-
running. Each pass reads the current settings
|
|
755
|
-
|
|
756
|
-
|
|
754
|
+
running. Each pass reads the current settings, calculates the protected workspace
|
|
755
|
+
tails once, and deletes in batches of 500 rows on a dedicated worker. Preview
|
|
756
|
+
counts also run on that worker, so maintenance does not block HTTP or agent
|
|
757
|
+
event handling. Shutdown waits for the worker to finish its current batch and
|
|
758
|
+
reconcile affected metrics before closing the database.
|
|
759
|
+
|
|
760
|
+
Freed database pages are reused for new events. Live maintenance performs a
|
|
761
|
+
non-blocking WAL checkpoint and deliberately does not run a full `VACUUM`, which
|
|
762
|
+
would hold SQLite's writer lock and interrupt live agents. To return database
|
|
763
|
+
file space to the filesystem immediately, stop Kōbō and perform an explicit
|
|
764
|
+
offline SQLite `VACUUM` after making a backup.
|
|
757
765
|
|
|
758
766
|
**What it deletes:** eligible rows of `ws_events` — the recorded agent output.
|
|
759
767
|
Affected session metrics and the derived search index are reconciled with the
|
package/dist/server/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { serve } from '@hono/node-server';
|
|
5
5
|
import { Hono } from 'hono';
|
|
6
|
-
import
|
|
6
|
+
import { WebSocketServer } from 'ws';
|
|
7
7
|
import { closeDb, getDb } from './db/index.js';
|
|
8
8
|
import { getPendingMigrations, runMigrations } from './db/migrations.js';
|
|
9
9
|
import { hostCheckMiddleware } from './middleware/host-check-middleware.js';
|
|
@@ -49,14 +49,16 @@ import { startSearchIndex, stopSearchIndex } from './services/search-service.js'
|
|
|
49
49
|
import { reconcileSessionHandoffs } from './services/session-handoff-service.js';
|
|
50
50
|
import { getGlobalSettings, updateNetworkAccessSettings } from './services/settings-service.js';
|
|
51
51
|
import { reloadDefaultTemplates } from './services/templates-service.js';
|
|
52
|
-
import {
|
|
52
|
+
import { destroyAllTerminals } from './services/terminal-service.js';
|
|
53
|
+
import { handleTerminalConnection } from './services/terminal-websocket-service.js';
|
|
53
54
|
import { startUpdateChecker, stopUpdateChecker } from './services/update-check-service.js';
|
|
54
55
|
import { startUsagePoller, stopUsagePoller } from './services/usage/index.js';
|
|
55
56
|
import * as wakeupService from './services/wakeup-service.js';
|
|
56
57
|
import { handleConnection, setMessageHandler } from './services/websocket-service.js';
|
|
57
58
|
import { deliverWorkspaceMessage } from './services/workspace-message-service.js';
|
|
58
59
|
import { getWorkspace } from './services/workspace-service.js';
|
|
59
|
-
import {
|
|
60
|
+
import { resolveRetentionConfig } from './services/ws-events-retention-service.js';
|
|
61
|
+
import { runWsEventsRetention, stopWsEventsRetention } from './services/ws-events-retention-worker-service.js';
|
|
60
62
|
import { getChangelogPath, getClientSpaPath, getDbPath, getKoboHome, getPackageVersion, resolveSpaFile, } from './utils/paths.js';
|
|
61
63
|
import { formatStartupBanner, readStartupNotes, startStartupSpinner, startupDebug } from './utils/startup-display.js';
|
|
62
64
|
startupDebug(`[kobo] Kōbō home: ${getKoboHome()}`);
|
|
@@ -87,13 +89,13 @@ startSearchIndex();
|
|
|
87
89
|
// conversation on the next upgrade, which is precisely why the UI shows a
|
|
88
90
|
// count and asks first.
|
|
89
91
|
// Best-effort — a failure must never block boot.
|
|
90
|
-
function runRetentionPass(context) {
|
|
92
|
+
async function runRetentionPass(context) {
|
|
91
93
|
try {
|
|
92
94
|
const retentionConfig = resolveRetentionConfig(getGlobalSettings());
|
|
93
95
|
if (retentionConfig.retentionDays <= 0)
|
|
94
96
|
return;
|
|
95
97
|
const retentionStartedAt = Date.now();
|
|
96
|
-
const retention =
|
|
98
|
+
const retention = await runWsEventsRetention(retentionConfig);
|
|
97
99
|
if (retention.deleted > 0 || retention.vacuumed) {
|
|
98
100
|
console.log(`[kobo] Event retention ${context} (${retentionConfig.retentionDays} d, keeping ${retentionConfig.keepPerWorkspace}/workspace): ` +
|
|
99
101
|
`${retention.deleted} agent event(s) permanently deleted, ` +
|
|
@@ -106,12 +108,12 @@ function runRetentionPass(context) {
|
|
|
106
108
|
console.error('[kobo] Event retention failed (continuing):', err);
|
|
107
109
|
}
|
|
108
110
|
}
|
|
109
|
-
runRetentionPass('at boot');
|
|
111
|
+
void runRetentionPass('at boot');
|
|
110
112
|
// Kōbō is a daemon people leave running for weeks, and `emit` writes a row per
|
|
111
113
|
// agent event. Running the pass only at boot meant a window the user enabled
|
|
112
114
|
// was not applied again until the next restart.
|
|
113
115
|
const RETENTION_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
114
|
-
const retentionTimer = setInterval(() => runRetentionPass('daily'), RETENTION_INTERVAL_MS);
|
|
116
|
+
const retentionTimer = setInterval(() => void runRetentionPass('daily'), RETENTION_INTERVAL_MS);
|
|
115
117
|
retentionTimer.unref?.();
|
|
116
118
|
// Check on boot and hourly so servers running longer than a day keep backing up.
|
|
117
119
|
const dailyBackupScheduler = startDailyDbBackupScheduler(async () => {
|
|
@@ -278,87 +280,7 @@ wss.on('connection', (ws) => {
|
|
|
278
280
|
});
|
|
279
281
|
// Wire terminal WebSocket connections
|
|
280
282
|
terminalWss.on('connection', (ws, workspaceId) => {
|
|
281
|
-
|
|
282
|
-
let dataDisposable = null;
|
|
283
|
-
let exitDisposable = null;
|
|
284
|
-
function attachListeners(ptyInstance) {
|
|
285
|
-
// Dispose previous listeners to avoid stacking on reconnect
|
|
286
|
-
dataDisposable?.dispose();
|
|
287
|
-
exitDisposable?.dispose();
|
|
288
|
-
dataDisposable = ptyInstance.onData((output) => {
|
|
289
|
-
if (ws.readyState === WebSocket.OPEN) {
|
|
290
|
-
ws.send(Buffer.from(output), { binary: true });
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
exitDisposable = ptyInstance.onExit(({ exitCode }) => {
|
|
294
|
-
if (ws.readyState === WebSocket.OPEN) {
|
|
295
|
-
ws.send(JSON.stringify({ type: 'exited', code: exitCode }));
|
|
296
|
-
ws.close();
|
|
297
|
-
}
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
ws.on('close', () => {
|
|
301
|
-
dataDisposable?.dispose();
|
|
302
|
-
exitDisposable?.dispose();
|
|
303
|
-
dataDisposable = null;
|
|
304
|
-
exitDisposable = null;
|
|
305
|
-
});
|
|
306
|
-
ws.on('error', (err) => {
|
|
307
|
-
console.error(`[terminal] WebSocket error for workspace ${workspaceId}:`, err);
|
|
308
|
-
});
|
|
309
|
-
ws.on('message', (data, isBinary) => {
|
|
310
|
-
if (isBinary) {
|
|
311
|
-
if (currentPty) {
|
|
312
|
-
currentPty.write(data.toString());
|
|
313
|
-
}
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
let msg;
|
|
317
|
-
try {
|
|
318
|
-
msg = JSON.parse(data.toString());
|
|
319
|
-
}
|
|
320
|
-
catch {
|
|
321
|
-
return; // Invalid JSON — ignore
|
|
322
|
-
}
|
|
323
|
-
if (msg.type === 'create') {
|
|
324
|
-
if (!currentPty) {
|
|
325
|
-
const workspace = getWorkspace(workspaceId);
|
|
326
|
-
if (!workspace) {
|
|
327
|
-
ws.send(JSON.stringify({ type: 'error', message: 'Workspace not found' }));
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
if (workspace.archivedAt) {
|
|
331
|
-
ws.send(JSON.stringify({ type: 'error', message: 'Workspace is archived' }));
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
const cwd = workspace.worktreePath;
|
|
335
|
-
try {
|
|
336
|
-
currentPty = createTerminal(workspaceId, cwd);
|
|
337
|
-
}
|
|
338
|
-
catch (err) {
|
|
339
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
340
|
-
ws.send(JSON.stringify({ type: 'error', message }));
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
attachListeners(currentPty);
|
|
345
|
-
ws.send(JSON.stringify({ type: 'ready' }));
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
if (msg.type === 'resize' && msg.cols && msg.rows) {
|
|
349
|
-
if (currentPty) {
|
|
350
|
-
const cols = Math.max(1, Math.floor(msg.cols));
|
|
351
|
-
const rows = Math.max(1, Math.floor(msg.rows));
|
|
352
|
-
try {
|
|
353
|
-
currentPty.resize(cols, rows);
|
|
354
|
-
}
|
|
355
|
-
catch (err) {
|
|
356
|
-
console.error(`[terminal] resize failed for workspace ${workspaceId}:`, err);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
361
|
-
});
|
|
283
|
+
handleTerminalConnection(ws, workspaceId, () => isShuttingDown);
|
|
362
284
|
});
|
|
363
285
|
// Wire websocket-service message handler to the agent orchestrator
|
|
364
286
|
setMessageHandler(async (type, payload) => {
|
|
@@ -411,6 +333,10 @@ setMessageHandler(async (type, payload) => {
|
|
|
411
333
|
});
|
|
412
334
|
// Handle WebSocket upgrade requests on /ws path
|
|
413
335
|
server.on('upgrade', (request, socket, head) => {
|
|
336
|
+
if (isShuttingDown) {
|
|
337
|
+
socket.destroy();
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
414
340
|
const { pathname } = new URL(request.url ?? '/', `http://localhost:${PORT}`);
|
|
415
341
|
const wsGlobal = getGlobalSettings();
|
|
416
342
|
// WebSockets are exempt from the same-origin policy and send no preflight,
|
|
@@ -495,7 +421,7 @@ async function gracefulShutdown(signal, exitCode = 0) {
|
|
|
495
421
|
for (const client of terminalWss.clients)
|
|
496
422
|
client.close(1001, 'Server shutting down');
|
|
497
423
|
try {
|
|
498
|
-
destroyAllTerminals();
|
|
424
|
+
await destroyAllTerminals();
|
|
499
425
|
console.log('[kobo] Terminals killed');
|
|
500
426
|
}
|
|
501
427
|
catch {
|
|
@@ -547,8 +473,15 @@ async function gracefulShutdown(signal, exitCode = 0) {
|
|
|
547
473
|
}
|
|
548
474
|
finally {
|
|
549
475
|
try {
|
|
550
|
-
await
|
|
551
|
-
|
|
476
|
+
const stopped = await Promise.allSettled([
|
|
477
|
+
stopWsEventsRetention(),
|
|
478
|
+
stopSearchIndex(),
|
|
479
|
+
dailyBackupScheduler.stop(),
|
|
480
|
+
]);
|
|
481
|
+
for (const result of stopped) {
|
|
482
|
+
if (result.status === 'rejected')
|
|
483
|
+
console.error('[kobo] Background service shutdown failed:', result.reason);
|
|
484
|
+
}
|
|
552
485
|
closeDb();
|
|
553
486
|
console.log('[kobo] Database closed');
|
|
554
487
|
}
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
2
|
import { getDb } from '../db/index.js';
|
|
3
3
|
import { listOrphanWorktrees } from '../services/worktree-service.js';
|
|
4
|
-
import {
|
|
4
|
+
import { listBranchesAsync, listRemoteBranchesAsync, listWorktreeFilesAsync } from '../utils/git-ops.js';
|
|
5
5
|
/** Hono sub-router for git-related endpoints (branch listing). */
|
|
6
6
|
const app = new Hono();
|
|
7
7
|
// GET /api/git/branches?path=<repoPath> — list branches for a repo
|
|
8
|
-
app.get('/branches', (c) => {
|
|
8
|
+
app.get('/branches', async (c) => {
|
|
9
9
|
try {
|
|
10
10
|
const repoPath = c.req.query('path');
|
|
11
11
|
if (!repoPath) {
|
|
12
12
|
return c.json({ error: 'Missing required query parameter: path' }, 400);
|
|
13
13
|
}
|
|
14
|
-
const local =
|
|
15
|
-
const remote = listRemoteBranches(repoPath);
|
|
14
|
+
const [local, remote] = await Promise.all([listBranchesAsync(repoPath), listRemoteBranchesAsync(repoPath)]);
|
|
16
15
|
return c.json({ local, remote });
|
|
17
16
|
}
|
|
18
17
|
catch (err) {
|
|
@@ -20,12 +19,12 @@ app.get('/branches', (c) => {
|
|
|
20
19
|
return c.json({ error: message }, 500);
|
|
21
20
|
}
|
|
22
21
|
});
|
|
23
|
-
/** Hard ceiling on the worktree listing. `
|
|
22
|
+
/** Hard ceiling on the worktree listing. `listWorktreeFilesAsync` already truncates
|
|
24
23
|
* at this value; exposing it lets a caller ask for LESS, never for more. */
|
|
25
24
|
const MAX_WORKTREE_FILES = 5000;
|
|
26
25
|
// GET /api/git/files?path=<worktreePath>&limit=<n> — list a worktree's files
|
|
27
26
|
// (tracked + untracked-but-not-ignored) for the chat's `@file` autocomplete.
|
|
28
|
-
app.get('/files', (c) => {
|
|
27
|
+
app.get('/files', async (c) => {
|
|
29
28
|
try {
|
|
30
29
|
const repoPath = c.req.query('path');
|
|
31
30
|
if (!repoPath) {
|
|
@@ -33,7 +32,7 @@ app.get('/files', (c) => {
|
|
|
33
32
|
}
|
|
34
33
|
const requested = Number.parseInt(c.req.query('limit') ?? '', 10);
|
|
35
34
|
const limit = Number.isFinite(requested) && requested > 0 ? Math.min(requested, MAX_WORKTREE_FILES) : MAX_WORKTREE_FILES;
|
|
36
|
-
return c.json({ files:
|
|
35
|
+
return c.json({ files: await listWorktreeFilesAsync(repoPath, limit) });
|
|
37
36
|
}
|
|
38
37
|
catch (err) {
|
|
39
38
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -11,8 +11,8 @@ import { DEFAULT_CHANGE_SOURCE_BRANCH_SCRIPT } from '../services/settings-defaul
|
|
|
11
11
|
import * as settingsService from '../services/settings-service.js';
|
|
12
12
|
import { DEFAULT_CI_FIX_PROMPT_TEMPLATE, DEFAULT_FINALIZATION_PROMPT, DEFAULT_GIT_CONVENTIONS, DEFAULT_PR_PROMPT_TEMPLATE, } from '../services/settings-service.js';
|
|
13
13
|
import { getSuitePrompts } from '../services/skill-suite-prompts.js';
|
|
14
|
-
import { listTemplates, replaceAllTemplates } from '../services/templates-service.js';
|
|
15
|
-
import {
|
|
14
|
+
import { listTemplates, replaceAllTemplates, validateTemplatesForReplacement } from '../services/templates-service.js';
|
|
15
|
+
import { previewWsEventsRetention } from '../services/ws-events-retention-worker-service.js';
|
|
16
16
|
/** Hono sub-router for global and per-project settings CRUD. */
|
|
17
17
|
const app = new Hono();
|
|
18
18
|
// GET /api/settings — return full settings, credentials masked
|
|
@@ -67,17 +67,15 @@ app.get('/defaults', (c) => {
|
|
|
67
67
|
// the confirmation dialog: enabling retention on a year-old database destroys
|
|
68
68
|
// months of conversation in one go, and the user is entitled to the number
|
|
69
69
|
// before agreeing to it.
|
|
70
|
-
app.get('/ws-events-retention-preview', (c) => {
|
|
70
|
+
app.get('/ws-events-retention-preview', async (c) => {
|
|
71
71
|
try {
|
|
72
72
|
const days = Number.parseInt(c.req.query('days') ?? '', 10);
|
|
73
73
|
const keep = Number.parseInt(c.req.query('keep') ?? '', 10);
|
|
74
74
|
if (!Number.isInteger(days) || days < 0 || !Number.isInteger(keep) || keep < 0) {
|
|
75
75
|
return c.json({ error: 'days and keep must be non-negative integers' }, 400);
|
|
76
76
|
}
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
const deletable = countPrunableWsEvents(db, { retentionDays: days, keepPerWorkspace: keep });
|
|
80
|
-
return c.json({ deletable, total });
|
|
77
|
+
const preview = await previewWsEventsRetention({ retentionDays: days, keepPerWorkspace: keep });
|
|
78
|
+
return c.json(preview);
|
|
81
79
|
}
|
|
82
80
|
catch (err) {
|
|
83
81
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -252,11 +250,23 @@ app.get('/export', (c) => {
|
|
|
252
250
|
app.post('/import', async (c) => {
|
|
253
251
|
try {
|
|
254
252
|
const body = (await c.req.json());
|
|
255
|
-
// Validate
|
|
253
|
+
// Validate templates before settings import writes anything. An invalid
|
|
254
|
+
// row must not leave an otherwise rejected bundle partially applied.
|
|
255
|
+
let validatedTemplates;
|
|
256
|
+
if (body?.templates !== undefined) {
|
|
257
|
+
try {
|
|
258
|
+
validatedTemplates = validateTemplatesForReplacement(body.templates);
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// Settings validates its section before writing. Each file is replaced
|
|
265
|
+
// atomically; this is not a transaction spanning the two files.
|
|
256
266
|
settingsService.importConfigBundle(body);
|
|
257
|
-
if (
|
|
258
|
-
// Accept missing templates
|
|
259
|
-
replaceAllTemplates(
|
|
267
|
+
if (validatedTemplates !== undefined) {
|
|
268
|
+
// Accept missing templates for backward-compatible bundles.
|
|
269
|
+
replaceAllTemplates(validatedTemplates);
|
|
260
270
|
}
|
|
261
271
|
return c.json({ ok: true });
|
|
262
272
|
}
|