@loicngr/kobo 1.11.36 → 1.11.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +18 -1
- package/CHANGELOG.md +8 -0
- package/dist/server/index.js +20 -24
- package/dist/server/routes/dev-server.js +2 -1
- package/dist/server/routes/pull-requests.js +5 -4
- package/dist/server/routes/workspaces.js +161 -109
- package/dist/server/services/agent/engines/claude-code/engine.js +11 -0
- package/dist/server/services/agent/engines/codex/client.js +1 -0
- package/dist/server/services/agent/engines/codex/engine.js +47 -10
- package/dist/server/services/agent/engines/codex/event-mapper.js +8 -2
- package/dist/server/services/agent/engines/codex/jsonrpc/peer.js +21 -8
- package/dist/server/services/agent/engines/codex/jsonrpc/transport.js +30 -3
- package/dist/server/services/agent/event-router.js +1 -1
- package/dist/server/services/agent/orchestrator.js +219 -78
- package/dist/server/services/agent/session-controller.js +12 -2
- package/dist/server/services/auto-loop-service.js +60 -6
- package/dist/server/services/change-source-branch-service.js +36 -4
- package/dist/server/services/cron-service.js +19 -0
- package/dist/server/services/db-backup-service.js +30 -0
- package/dist/server/services/dev-server-service.js +167 -38
- package/dist/server/services/image-service.js +25 -28
- package/dist/server/services/pr-checkout-service.js +34 -5
- package/dist/server/services/pr-watcher-service.js +5 -3
- package/dist/server/services/quota-backoff-service.js +25 -8
- package/dist/server/services/wakeup-service.js +23 -3
- package/dist/server/services/websocket-service.js +10 -2
- package/dist/server/services/workspace-service.js +8 -4
- package/dist/server/services/worktree-purge-service.js +3 -7
- package/dist/server/utils/agent-stop-result.js +12 -0
- package/dist/server/utils/git-ops.js +8 -1
- package/dist/server/utils/git-worktree-fingerprint.js +86 -0
- package/dist/server/utils/safe-path.js +21 -0
- package/dist/server/utils/script-runner.js +32 -9
- package/dist/server/utils/workspace-lifecycle-guard.js +27 -0
- package/package.json +1 -1
- package/src/client/dist/pwa/assets/ActivityFeed-BzzvcOmq.js +8 -0
- package/src/client/dist/pwa/assets/{ActivityFeed-Bakgvay6.css → ActivityFeed-Chsw1Mh7.css} +1 -1
- package/src/client/dist/pwa/assets/{ChangelogPage-BFGYQ6eY.js → ChangelogPage-D8563008.js} +1 -1
- package/src/client/dist/pwa/assets/ComparisonPanel-Bd0X08sg.js +2 -0
- package/src/client/dist/pwa/assets/ComparisonPanel-CRRaDs2D.css +1 -0
- package/src/client/dist/pwa/assets/CreatePage-A3pVradx.css +1 -0
- package/src/client/dist/pwa/assets/CreatePage-Bon8qIka.js +2 -0
- package/src/client/dist/pwa/assets/DashboardPage-D8g7_r4S.css +1 -0
- package/src/client/dist/pwa/assets/DashboardPage-DYxdxEul.js +1 -0
- package/src/client/dist/pwa/assets/{DiffViewer-DSlSAIiW.js → DiffViewer-BpsLNxnj.js} +3 -3
- package/src/client/dist/pwa/assets/{DocumentsPanel-C1eXTw_Q.js → DocumentsPanel-BJsJ0fNs.js} +1 -1
- package/src/client/dist/pwa/assets/{DrawerToggleButton-BxYpUb4y.js → DrawerToggleButton-BVLO5K5d.js} +1 -1
- package/src/client/dist/pwa/assets/{GitPanel-DnBWSdVp.js → GitPanel-Cal_wzC0.js} +3 -3
- package/src/client/dist/pwa/assets/{HealthPage-rX1TiaP9.js → HealthPage-RFZS7Lao.js} +1 -1
- package/src/client/dist/pwa/assets/{MainLayout-Ceuh5uJj.js → MainLayout-vvyiRcql.js} +2 -2
- package/src/client/dist/pwa/assets/{QBtnDropdown-K7syiyyH.js → QBtnDropdown-X8oNU5Mb.js} +1 -1
- package/src/client/dist/pwa/assets/{QDrawer-DapzE8DH.js → QDrawer-Cij17skX.js} +1 -1
- package/src/client/dist/pwa/assets/{QSelect-Bodml89k.js → QSelect-xd-AlGo_.js} +1 -1
- package/src/client/dist/pwa/assets/{QTooltip-K-9OdDfq.js → QTooltip-CwcCGSWq.js} +1 -1
- package/src/client/dist/pwa/assets/{SchedulePanel-CfumTzAa.js → SchedulePanel-CXRUsz6G.js} +1 -1
- package/src/client/dist/pwa/assets/{SearchPage-AhZYS-dS.js → SearchPage-DopbgLxh.js} +1 -1
- package/src/client/dist/pwa/assets/{SettingsPage-BoHXWvKn.js → SettingsPage-Jx6TnOzR.js} +1 -1
- package/src/client/dist/pwa/assets/{SplitWorkspacePage-3wK11mcu.js → SplitWorkspacePage-D7lGFRKs.js} +1 -1
- package/src/client/dist/pwa/assets/{TerminalPanel-CKg0BeNK.js → TerminalPanel-DI8DHb9e.js} +1 -1
- package/src/client/dist/pwa/assets/{TouchPan-D6ctyPYf.js → TouchPan-B3nrkCY8.js} +1 -1
- package/src/client/dist/pwa/assets/{TourReplayButton-Dn4Dy_kH.js → TourReplayButton-DU1UShMY.js} +1 -1
- package/src/client/dist/pwa/assets/{WorkspaceAttentionLabels-DnoabsfK.js → WorkspaceAttentionLabels-C-yN5IX8.js} +1 -1
- package/src/client/dist/pwa/assets/{WorkspacePage-DYQrfeYw.css → WorkspacePage-BAC6u9OP.css} +1 -1
- package/src/client/dist/pwa/assets/WorkspacePage-DKRpdpPg.js +4 -0
- package/src/client/dist/pwa/assets/{browser-DHpSUK8-.js → browser-f9MHQjfi.js} +1 -1
- package/src/client/dist/pwa/assets/{build-path-tree-DtEjsZlh.js → build-path-tree-BWBzP8os.js} +1 -1
- package/src/client/dist/pwa/assets/{de-Dt_m-H83.js → de-QkDLrcGq.js} +1 -1
- package/src/client/dist/pwa/assets/{documents-B2dg1wRf.js → documents-C2cLnEBQ.js} +1 -1
- package/src/client/dist/pwa/assets/{editor.api-Cd1D35oj.js → editor.api-sw146MWN.js} +1 -1
- package/src/client/dist/pwa/assets/{efforts-IB1vPjJQ.js → efforts-C5ZZgCH4.js} +1 -1
- package/src/client/dist/pwa/assets/{engineFeatures-DYSyDEJe.js → engineFeatures-CAVMDawq.js} +1 -1
- package/src/client/dist/pwa/assets/{es-DFg25DCS.js → es-BgQFKlo8.js} +1 -1
- package/src/client/dist/pwa/assets/{expand-template-CNAHiesL.js → expand-template-B6xAlAaM.js} +1 -1
- package/src/client/dist/pwa/assets/{format-DidwgbbX.js → format-BwWP6Aay.js} +1 -1
- package/src/client/dist/pwa/assets/{formatters-BjzyUHlu.js → formatters-DyWtO-nR.js} +1 -1
- package/src/client/dist/pwa/assets/{fr-CQ1slpol.js → fr-DAdLWjNM.js} +1 -1
- package/src/client/dist/pwa/assets/{freemarker2-jNGWawVw.js → freemarker2-DZ5Hm8UH.js} +1 -1
- package/src/client/dist/pwa/assets/{handlebars-DfSqClrW.js → handlebars-BRUGpTA7.js} +1 -1
- package/src/client/dist/pwa/assets/{html-ByLX-n8T.js → html-DNOayNNs.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-CBez_vip.js +1 -0
- package/src/client/dist/pwa/assets/index-VL3fI9a3.js +20 -0
- package/src/client/dist/pwa/assets/{it-w09rfaP7.js → it-Bumaewp1.js} +1 -1
- package/src/client/dist/pwa/assets/{javascript-Dja1fHfv.js → javascript-C0tDQU3z.js} +1 -1
- package/src/client/dist/pwa/assets/{liquid-D315hW6C.js → liquid-CN46O6OF.js} +1 -1
- package/src/client/dist/pwa/assets/{mdx-B5yUwpzv.js → mdx-DM-lrr8Q.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco-contributions-NfxrU0r3.js → monaco-contributions-CkN7y-8c.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco.contribution-D1hXPkDZ.js → monaco.contribution-2BcAOLjc.js} +2 -2
- package/src/client/dist/pwa/assets/{network-auth-CARVtbNe.js → network-auth-KYS4snQH.js} +1 -1
- package/src/client/dist/pwa/assets/{notify-theme-CKC3-EAZ.js → notify-theme-a91Tcr6o.js} +1 -1
- package/src/client/dist/pwa/assets/{pending-diff-open-BlLrQaW7.js → pending-diff-open-DOtELZfB.js} +1 -1
- package/src/client/dist/pwa/assets/{permissionModes-DMzFEQhr.js → permissionModes-C-9Crj-c.js} +1 -1
- package/src/client/dist/pwa/assets/{project-color-CFjdIj6W.js → project-color-BRe4oyNx.js} +1 -1
- package/src/client/dist/pwa/assets/{python-Dc6mnEip.js → python-BRpJG6sZ.js} +1 -1
- package/src/client/dist/pwa/assets/{razor-DArVFBGO.js → razor-CehCmvHl.js} +1 -1
- package/src/client/dist/pwa/assets/{render-chat-markdown-BTPAjU2g.js → render-chat-markdown-DlbQLZ02.js} +1 -1
- package/src/client/dist/pwa/assets/{typescript-COE1vIyB.js → typescript-Dt_8mVuj.js} +1 -1
- package/src/client/dist/pwa/assets/{use-tours-C8x2V1w5.js → use-tours-CSVM_54K.js} +1 -1
- package/src/client/dist/pwa/assets/{workspace-templates-DHyA_GEW.js → workspace-templates-GCObcjO6.js} +1 -1
- package/src/client/dist/pwa/assets/{xml-eYjr4cGR.js → xml-_gOwdzB4.js} +1 -1
- package/src/client/dist/pwa/assets/{yaml-UooOcssF.js → yaml-BxeBqpGf.js} +1 -1
- package/src/client/dist/pwa/index.html +1 -1
- package/src/client/dist/pwa/sw.js +1 -1
- package/src/client/dist/pwa/assets/ActivityFeed-BPx-xhFY.js +0 -8
- package/src/client/dist/pwa/assets/ComparisonPanel-C6DfjpZZ.js +0 -2
- package/src/client/dist/pwa/assets/ComparisonPanel-x1iuICgd.css +0 -1
- package/src/client/dist/pwa/assets/CreatePage-DxOyY65u.css +0 -1
- package/src/client/dist/pwa/assets/CreatePage-ypEzkAKv.js +0 -2
- package/src/client/dist/pwa/assets/DashboardPage-DsVRjzzy.js +0 -1
- package/src/client/dist/pwa/assets/DashboardPage-DsWn5cWl.css +0 -1
- package/src/client/dist/pwa/assets/WorkspacePage-BI7CW2yT.js +0 -4
- package/src/client/dist/pwa/assets/i18n-CnjXQjxy.js +0 -1
- package/src/client/dist/pwa/assets/index-DcwcFDrZ.js +0 -20
package/AGENTS.md
CHANGED
|
@@ -114,7 +114,9 @@ src/
|
|
|
114
114
|
| `workspace_chat_history` | chat-input history per workspace: message text + `created_at`, ordered by autoincrement id, capped at 200 entries by the service; CASCADE DELETE on workspace |
|
|
115
115
|
| `workspace_permission_rules` | remembered per-workspace tool approvals, scoped to an exact operation or every invocation of a tool; CASCADE DELETE on workspace |
|
|
116
116
|
|
|
117
|
-
`status` enum: `created | extracting | brainstorming | executing | completed | idle | error | quota`. Transitions are validated in `updateWorkspaceStatus` against `VALID_TRANSITIONS`.
|
|
117
|
+
`status` enum: `created | extracting | brainstorming | executing | compacting | awaiting-user | completed | idle | error | quota`. Transitions are validated in `updateWorkspaceStatus` against `VALID_TRANSITIONS`.
|
|
118
|
+
|
|
119
|
+
`compacting` is a server-owned busy state while the agent compacts context. The orchestrator associates the previous status with the current controller and restores it when compaction ends; stop, errors and startup reconciliation must not leave the workspace stuck in compaction. Normal workspace status updates make this state available after reload and across clients. Chat sends are refused during compaction before delivery or fallback starts, and the client keeps the user's draft while disabling send. The existing status column is unrestricted TEXT, so adding this state does not change the database schema.
|
|
118
120
|
|
|
119
121
|
`archived_at` is **orthogonal** to `status`. Archiving is a visibility flag, not a lifecycle state. Unarchive restores the exact pre-archive `status`.
|
|
120
122
|
|
|
@@ -239,6 +241,8 @@ Each event maps to one effective-settings key (`sessionEndedScript` / `prMergedS
|
|
|
239
241
|
|
|
240
242
|
### Engine comparison
|
|
241
243
|
|
|
244
|
+
Successful paired creation opens the existing `split` route with `left` and `right` workspace ids in creation order. A single successful creation keeps the normal workspace route, including when the second creation fails.
|
|
245
|
+
|
|
242
246
|
One task, two engines, two sibling worktrees. `POST /api/workspaces` accepts a `comparisonId`; the client posts twice with the same one (sequentially — parallel creations contend on the git index lock), suffixing the name with the engine's display name and the branch with the engine id and giving the non-configured engine the model picked for it on the form and its own effort / permission defaults. `workspaces.comparison_id` (migration v40) groups them; `listComparisonMembers` reads them back, treating an empty id as no group rather than as a match against the NULL majority. `GET /api/workspaces/:id/comparison` returns each member with its cached git stats (`null`, never zeros, when nothing has been measured). `ComparisonPanel.vue` renders the table at the top of the Git tab, only for workspaces that belong to a comparison. See [CONFIGURATION.md → Comparing two engines on one task](CONFIGURATION.md#comparing-two-engines-on-one-task).
|
|
243
247
|
|
|
244
248
|
### Unanswered question reminder
|
|
@@ -384,3 +388,16 @@ deviate from the documented system without explicit user approval.
|
|
|
384
388
|
- Don't skip `try/catch` swallowing on best-effort cleanup (agent stop, dev-server stop, worktree removal). These must never break the primary operation.
|
|
385
389
|
- Don't hardcode user-visible text in the frontend. Every string must go through `$t()` / `t()` with keys in all 5 locale files. See [Internationalization (i18n)](#internationalization-i18n).
|
|
386
390
|
- Don't hardcode hex colors, spacing literals, or font names in components. Use the CSS variables from `src/client/src/css/design-tokens.scss` and the patterns in `DESIGN.md`. See [Design System](#design-system).
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
## Audit remediation contracts (September 2026)
|
|
394
|
+
|
|
395
|
+
- `stopAgentAndWait` returns `stopped | not-running | timeout | failed`. Destructive callers must pass its result through `assertAgentStopped`. A timeout or failure retains the same controller in `stopping` until actual stop completion; it never authorizes a replacement or worktree removal. Pending replacements remain cancellable and are finalized as `killed` when stopped before starting. Dev-server shutdown retains ownership of the entire process group until no live member remains, including when the shell exits first; process inspection failures propagate to destructive callers. A successful custom stop can override a failed generic Docker shutdown only after an independent successful query confirms no running containers with the exact project label.
|
|
396
|
+
- Acquire `withWorkspaceLifecycleGuard` before `withGitRepoLock`. Agent/dev-server starts check lifecycle ownership centrally. Wakeups and cron defer while busy; auto-loop waiters are reconsidered on guard release and confirmed capacity release. A user stop persistently disables auto-loop, including while waiting for capacity without a controller. Internal stops explicitly pass `replacement`, `setup`, `archive` or `shutdown` to preserve that intent; delete and purge retain their dedicated causes.
|
|
397
|
+
- Quota-backoff `arm` receives the authoritative next `retryCount` from the orchestrator. Persist that value directly; consuming the preceding timer row must not reset the retry sequence.
|
|
398
|
+
- A quota retry with a residual controller awaits a confirmed technical stop before starting another iteration. Persist recovery before that await and retain its attempt count if the stop remains unconfirmed. Shutdown suspends quota, wakeup and cron timers without consuming their rows; late arms stay persisted but do not deliver until boot restores the schedulers. Automatic continuations also check shutdown when deferred lifecycle callbacks run, preserving auto-loop intent instead of treating a refused start as an error.
|
|
399
|
+
- Codex uses a 120-second stream-idle deadline and a bounded 30-minute deadline while normalized foreground tool calls remain unresolved. Synthetic `TodoWrite` calls emitted for completed plans must have matching results so they do not keep the tool deadline active. Human-input pauses remain independent of the tool timeout.
|
|
400
|
+
- Codex explicit stop confirms `exit` after each escalation, including `SIGKILL`; an unconfirmed exit rejects the stop instead of authorizing destructive follow-up. Exit listeners and deadlines are removed when that wait settles.
|
|
401
|
+
- Conversation loading indicators follow outstanding session/history/sync requests only while no content is available. Background refreshes must preserve the displayed conversation, component state and reading position, including feeds containing only user messages.
|
|
402
|
+
- PR diagnosis fingerprints are asynchronous and include binary staged/unstaged diffs and untracked file contents (symlink targets, not their destinations). Untracked nested Git repositories are fingerprinted recursively, including HEAD and index changes; ignored files and Git internals are excluded. Await them at every callsite. Workspace matching always includes normalized project identity.
|
|
403
|
+
- Daily DB backups are checked at startup and hourly, with the existing 24-hour minimum age and retention. Stop the scheduler and await its active backup before closing SQLite.
|
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
|
+
## 1.11.38
|
|
8
|
+
|
|
9
|
+
- feat(workspaces): show compaction status and block chat sends
|
|
10
|
+
|
|
11
|
+
## 1.11.37
|
|
12
|
+
|
|
13
|
+
- fix(kobo): harden workspace lifecycle and comparison navigation
|
|
14
|
+
|
|
7
15
|
## 1.11.36
|
|
8
16
|
|
|
9
17
|
- fix(agent): resume quota waits and scheduled background checks
|
package/dist/server/index.js
CHANGED
|
@@ -29,12 +29,12 @@ import usageRoutes from './routes/usage.js';
|
|
|
29
29
|
import voiceRouter from './routes/voice.js';
|
|
30
30
|
import workspaceTemplatesRouter from './routes/workspace-templates.js';
|
|
31
31
|
import workspacesRouter from './routes/workspaces.js';
|
|
32
|
-
import { getAvailableSkills, isAgentUnavailableError, reconcileOrphanSessions, restoreRetryCountsFromDb, sendMessage, setBackendPort, startAgent, startWatchdog,
|
|
32
|
+
import { getAvailableSkills, isAgentUnavailableError, reconcileOrphanSessions, restoreRetryCountsFromDb, sendMessage, setBackendPort, startAgent, startWatchdog, stopAgentAndWait, stopAllAgents, stopWatchdog, } from './services/agent/orchestrator.js';
|
|
33
33
|
import * as autoLoopService from './services/auto-loop-service.js';
|
|
34
34
|
import { startAwaitingUserReminder, stopAwaitingUserReminder } from './services/awaiting-user-reminder-service.js';
|
|
35
35
|
import { runContentMigrationIfNeeded } from './services/content-migration-service.js';
|
|
36
36
|
import * as cronService from './services/cron-service.js';
|
|
37
|
-
import { createDailyDbBackupIfNeeded, createPreMigrationBackup } from './services/db-backup-service.js';
|
|
37
|
+
import { createDailyDbBackupIfNeeded, createPreMigrationBackup, startDailyDbBackupScheduler, } from './services/db-backup-service.js';
|
|
38
38
|
import { startDevServer, stopAllDevServers, stopDevServer } from './services/dev-server-service.js';
|
|
39
39
|
import { authorizeWsUpgrade, generateToken, getLanHostnames, getLanUrls, isAllowedOrigin, isAllowedRequestHost, resolveBindHost, resolveDevClientOrigin, resolveNetworkAccessEnvOverrides, resolveProxyHostname, } from './services/network-access-service.js';
|
|
40
40
|
import { startPrWatcher, stopPrWatcher } from './services/pr-watcher-service.js';
|
|
@@ -101,23 +101,11 @@ runRetentionPass('at boot');
|
|
|
101
101
|
const RETENTION_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
102
102
|
const retentionTimer = setInterval(() => runRetentionPass('daily'), RETENTION_INTERVAL_MS);
|
|
103
103
|
retentionTimer.unref?.();
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (r.created) {
|
|
110
|
-
startupDebug(`[kobo] Daily DB backup: ${r.created}`);
|
|
111
|
-
if (r.deleted.length > 0) {
|
|
112
|
-
startupDebug(`[kobo] Rotated ${r.deleted.length} old DB backup(s)`);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
})
|
|
116
|
-
.catch((err) => {
|
|
117
|
-
// A full disk or an unwritable backup directory must not stop the server
|
|
118
|
-
// from booting: without this catch the rejection reaches the last-resort
|
|
119
|
-
// handler below and Kōbō exits before it ever listens.
|
|
120
|
-
console.error('[kobo] Daily DB backup failed (continuing):', err);
|
|
104
|
+
// Check on boot and hourly so servers running longer than a day keep backing up.
|
|
105
|
+
const dailyBackupScheduler = startDailyDbBackupScheduler(async () => {
|
|
106
|
+
const result = await createDailyDbBackupIfNeeded(db, getDbPath());
|
|
107
|
+
if (result.created)
|
|
108
|
+
startupDebug(`[kobo] Daily DB backup: ${result.created}`);
|
|
121
109
|
});
|
|
122
110
|
// Initialize process cleanup, agent watchdog, PR watcher, and wakeup rehydration
|
|
123
111
|
reconcileOrphanSessions();
|
|
@@ -128,7 +116,7 @@ autoLoopService.rehydrate();
|
|
|
128
116
|
// otherwise the next arm() after restart would compute the next ladder rung
|
|
129
117
|
// from retryCount=0 and undo the progression.
|
|
130
118
|
restoreRetryCountsFromDb();
|
|
131
|
-
quotaBackoffService.restoreOnBoot((workspaceId) => autoLoopService.onQuotaBackoffExpired(workspaceId));
|
|
119
|
+
quotaBackoffService.restoreOnBoot((workspaceId, pending) => autoLoopService.onQuotaBackoffExpired(workspaceId, pending));
|
|
132
120
|
cronService.restoreOnBoot();
|
|
133
121
|
// Deliver any new default prompt templates to existing installs (seed-once via the
|
|
134
122
|
// seededDefaultSlugs watermark; never overwrites or re-adds deleted defaults).
|
|
@@ -358,6 +346,15 @@ terminalWss.on('connection', (ws, workspaceId) => {
|
|
|
358
346
|
setMessageHandler(async (type, payload) => {
|
|
359
347
|
const p = payload;
|
|
360
348
|
if (type === 'chat:message' && p?.workspaceId && p?.content) {
|
|
349
|
+
if (getWorkspace(p.workspaceId)?.status === 'compacting') {
|
|
350
|
+
emitEphemeral(p.workspaceId, 'chat:rejected', {
|
|
351
|
+
reason: 'compacting',
|
|
352
|
+
sessionId: p.sessionId,
|
|
353
|
+
content: p.content,
|
|
354
|
+
message: 'Workspace is compacting its context; wait until compaction finishes before sending a message',
|
|
355
|
+
});
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
361
358
|
// Auto-loop owns the agent's turns. A user message means the user wants
|
|
362
359
|
// to redirect the conversation, so disable the loop (idempotent — the
|
|
363
360
|
// `autoloop:disabled` event is emitted with reason='user-action' so the
|
|
@@ -453,10 +450,8 @@ setMessageHandler(async (type, payload) => {
|
|
|
453
450
|
}
|
|
454
451
|
if (type === 'workspace:stop' && p?.workspaceId) {
|
|
455
452
|
try {
|
|
456
|
-
//
|
|
457
|
-
|
|
458
|
-
autoLoopService.disable(p.workspaceId, 'user-action');
|
|
459
|
-
stopAgent(p.workspaceId);
|
|
453
|
+
// Share the HTTP stop contract, including loops waiting without an agent.
|
|
454
|
+
await stopAgentAndWait(p.workspaceId);
|
|
460
455
|
}
|
|
461
456
|
catch (err) {
|
|
462
457
|
console.error('[ws] Failed to stop agent:', err);
|
|
@@ -613,6 +608,7 @@ async function gracefulShutdown(signal, exitCode = 0) {
|
|
|
613
608
|
}
|
|
614
609
|
finally {
|
|
615
610
|
try {
|
|
611
|
+
await dailyBackupScheduler.stop();
|
|
616
612
|
closeDb();
|
|
617
613
|
console.log('[kobo] Database closed');
|
|
618
614
|
}
|
|
@@ -3,6 +3,7 @@ import { migrationGuard } from '../middleware/migration-guard.js';
|
|
|
3
3
|
import { getDevServerLogs, getStatus, startDevServer, stopDevServer } from '../services/dev-server-service.js';
|
|
4
4
|
import { getProjectSettings } from '../services/settings-service.js';
|
|
5
5
|
import { getWorkspace } from '../services/workspace-service.js';
|
|
6
|
+
import { WorkspaceLifecycleBusyError } from '../utils/workspace-lifecycle-guard.js';
|
|
6
7
|
/** Hono sub-router for per-workspace dev server lifecycle (start, stop, status, logs). */
|
|
7
8
|
const app = new Hono();
|
|
8
9
|
// GET /api/dev-server/:workspaceId/status
|
|
@@ -51,7 +52,7 @@ app.post('/:workspaceId/start', migrationGuard, (c) => {
|
|
|
51
52
|
}
|
|
52
53
|
catch (err) {
|
|
53
54
|
const message = err instanceof Error ? err.message : String(err);
|
|
54
|
-
const status =
|
|
55
|
+
const status = err instanceof WorkspaceLifecycleBusyError || /already starting|is stopping/.test(message) ? 409 : 500;
|
|
55
56
|
return c.json({ error: message }, status);
|
|
56
57
|
}
|
|
57
58
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
1
2
|
import { Hono } from 'hono';
|
|
2
3
|
import { getForgeProvider } from '../services/forge/registry.js';
|
|
3
4
|
import { resolveForge } from '../services/forge/resolve.js';
|
|
@@ -61,7 +62,7 @@ app.post('/diagnose', async (c) => {
|
|
|
61
62
|
ongoingOperation: null,
|
|
62
63
|
branch: { state: 'absent' },
|
|
63
64
|
};
|
|
64
|
-
return c.json({ report, pr: null, fingerprint: prCheckout.computeFingerprint(report) });
|
|
65
|
+
return c.json({ report, pr: null, fingerprint: await prCheckout.computeFingerprint(report) });
|
|
65
66
|
}
|
|
66
67
|
// Page through the full list — a PR the user explicitly diagnosed by
|
|
67
68
|
// number must never come back "not found" just for sitting past the
|
|
@@ -75,11 +76,11 @@ app.post('/diagnose', async (c) => {
|
|
|
75
76
|
} while (!pr && cursor !== null);
|
|
76
77
|
if (!pr)
|
|
77
78
|
return c.json({ error: `Pull request #${prNumber} not found` }, 404);
|
|
78
|
-
const workspaces = listWorkspaces(true);
|
|
79
|
+
const workspaces = listWorkspaces(true).filter((workspace) => path.resolve(workspace.projectPath) === path.resolve(projectPath));
|
|
79
80
|
const report = prCheckout.diagnoseLocalState(projectPath, pr.headBranch, worktreesPath ?? null, attachedWorktreePaths(workspaces), workspaces);
|
|
80
81
|
if (pr.isFork)
|
|
81
82
|
report.blockers.push({ kind: 'fork-pr' });
|
|
82
|
-
return c.json({ report, pr, fingerprint: prCheckout.computeFingerprint(report) });
|
|
83
|
+
return c.json({ report, pr, fingerprint: await prCheckout.computeFingerprint(report) });
|
|
83
84
|
}
|
|
84
85
|
catch (err) {
|
|
85
86
|
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
@@ -93,7 +94,7 @@ app.post('/resolve', async (c) => {
|
|
|
93
94
|
if (!body.projectPath || !body.fingerprint) {
|
|
94
95
|
return c.json({ error: 'Missing required fields: projectPath, fingerprint' }, 400);
|
|
95
96
|
}
|
|
96
|
-
const workspaces = listWorkspaces(true);
|
|
97
|
+
const workspaces = listWorkspaces(true).filter((workspace) => path.resolve(workspace.projectPath) === path.resolve(body.projectPath));
|
|
97
98
|
const result = await prCheckout.resolvePrCheckout({
|
|
98
99
|
projectPath: body.projectPath,
|
|
99
100
|
headBranch: body.headBranch,
|