@loicngr/kobo 1.11.43 → 1.11.44
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 +36 -1
- package/CHANGELOG.md +6 -0
- package/dist/mcp-server/kobo-tasks-handlers.js +31 -74
- package/dist/mcp-server/kobo-tasks-server.js +51 -4
- package/dist/server/db/auto-loop-schema.js +44 -0
- package/dist/server/db/migrations.js +4 -0
- package/dist/server/db/review-return-schema.js +10 -0
- package/dist/server/db/schema.js +4 -0
- package/dist/server/index.js +10 -9
- package/dist/server/routes/auto-loop-messages.js +51 -0
- package/dist/server/routes/workspaces.js +46 -136
- package/dist/server/services/agent/engines/claude-code/engine.js +1 -0
- package/dist/server/services/agent/engines/codex/engine.js +91 -31
- package/dist/server/services/agent/orchestrator.js +237 -88
- package/dist/server/services/agent/session-controller.js +42 -2
- package/dist/server/services/auto-loop-message-service.js +79 -0
- package/dist/server/services/auto-loop-service.js +225 -86
- package/dist/server/services/auto-loop-state-service.js +30 -0
- package/dist/server/services/cron-service.js +30 -16
- package/dist/server/services/quota-backoff-service.js +42 -10
- package/dist/server/services/review-return-service.js +64 -0
- package/dist/server/services/review-service.js +187 -0
- package/dist/server/services/settings-service.js +3 -3
- package/dist/server/services/task-mutations.js +186 -0
- package/dist/server/services/wakeup-service.js +16 -1
- package/dist/server/services/workspace-message-service.js +16 -2
- package/dist/server/services/workspace-service.js +25 -36
- package/dist/shared/auto-loop-prompts.js +22 -11
- package/dist/shared/auto-loop-types.js +1 -0
- package/dist/shared/review.js +6 -0
- package/dist/shared/task-verification.js +11 -0
- package/package.json +12 -12
- package/src/client/dist/pwa/assets/{ActivityFeed-D1e-zV3O.js → ActivityFeed-D_y4aikp.js} +1 -1
- package/src/client/dist/pwa/assets/{ChangelogPage-DX1WGFea.js → ChangelogPage-DSKCvpr2.js} +1 -1
- package/src/client/dist/pwa/assets/{ClosePopup-BqW5dy-O.js → ClosePopup-CWjIRHxu.js} +1 -1
- package/src/client/dist/pwa/assets/{ComparisonPanel-B4ne55Xj.js → ComparisonPanel-DDxcsxyC.js} +1 -1
- package/src/client/dist/pwa/assets/{CreatePage-Bx_GpUni.js → CreatePage-DKVEknbL.js} +2 -2
- package/src/client/dist/pwa/assets/{DashboardPage-Cv6HJEcO.js → DashboardPage-Dg2s9nnv.js} +1 -1
- package/src/client/dist/pwa/assets/{DiffViewer-COwGxPt6.js → DiffViewer-BSzNScIr.js} +4 -4
- package/src/client/dist/pwa/assets/{DocumentsPanel-DCXpLLb7.js → DocumentsPanel-DfEoI_f7.js} +1 -1
- package/src/client/dist/pwa/assets/{DrawerToggleButton-DRL3RCkM.js → DrawerToggleButton-D85PwhKb.js} +1 -1
- package/src/client/dist/pwa/assets/GitPanel-CuG8MlgF.js +3 -0
- package/src/client/dist/pwa/assets/{HealthPage-XrOuMoDJ.js → HealthPage-CaLCpejR.js} +1 -1
- package/src/client/dist/pwa/assets/MainLayout-CDxYIJ_s.js +2 -0
- package/src/client/dist/pwa/assets/MainLayout-DTLNunOi.css +1 -0
- package/src/client/dist/pwa/assets/{QBadge-DbkxGtht.js → QBadge-FWC1zDCq.js} +1 -1
- package/src/client/dist/pwa/assets/{QBanner-DhqH95GW.js → QBanner-CcRI_QIy.js} +1 -1
- package/src/client/dist/pwa/assets/QBtn-DEdx_Y1V.js +1 -0
- package/src/client/dist/pwa/assets/{QBtnDropdown-163oM6T_.js → QBtnDropdown-qJWGcZpC.js} +1 -1
- package/src/client/dist/pwa/assets/{QBtnGroup-DELKYo3u.js → QBtnGroup-CAoD6G0Q.js} +1 -1
- package/src/client/dist/pwa/assets/{QBtnToggle-BI3k3Ch2.js → QBtnToggle-C_hQ2UCQ.js} +1 -1
- package/src/client/dist/pwa/assets/{QChip-owciRuZu.js → QChip-DCPUhqXX.js} +1 -1
- package/src/client/dist/pwa/assets/QDrawer-DX3PhMqC.js +1 -0
- package/src/client/dist/pwa/assets/{QItemSection-D_5_0UrX.js → QItemSection-BhqphaH6.js} +1 -1
- package/src/client/dist/pwa/assets/{QList-qupzuEqO.js → QList-CHm4JNP0.js} +1 -1
- package/src/client/dist/pwa/assets/{QMarkupTable-B9QuKtK_.js → QMarkupTable-CNpiA9oq.js} +1 -1
- package/src/client/dist/pwa/assets/{QPage-DaX9sMQC.js → QPage-BXBw2Alx.js} +1 -1
- package/src/client/dist/pwa/assets/QSelect-CY4TWW_j.js +1 -0
- package/src/client/dist/pwa/assets/{QSpinnerDots-DnCV3VzI.js → QSpinnerDots-BkFiCsH3.js} +1 -1
- package/src/client/dist/pwa/assets/{QTooltip-BQWY7aeM.js → QTooltip-cDK5AOQ5.js} +1 -1
- package/src/client/dist/pwa/assets/{SchedulePanel-Bos94OyR.js → SchedulePanel-LvHiZ6ZH.js} +1 -1
- package/src/client/dist/pwa/assets/{SearchPage-DOHkP5lj.js → SearchPage-BO-J4aNc.js} +1 -1
- package/src/client/dist/pwa/assets/{SettingsPage-DtGsd0XH.js → SettingsPage-Aw89fRSq.js} +2 -2
- package/src/client/dist/pwa/assets/{SplitWorkspacePage-DL4kp9Ah.js → SplitWorkspacePage-BIEceNwH.js} +1 -1
- package/src/client/dist/pwa/assets/{TerminalPanel-DHDnyiYh.js → TerminalPanel-DitOCad1.js} +1 -1
- package/src/client/dist/pwa/assets/{TouchPan-BnHXDR6F.js → TouchPan-DvPAhZsa.js} +1 -1
- package/src/client/dist/pwa/assets/{TourReplayButton-ClVcB6mO.js → TourReplayButton-CJYdH3Jf.js} +1 -1
- package/src/client/dist/pwa/assets/{WorkspaceAttentionLabels-Bxe6oIJI.js → WorkspaceAttentionLabels-CY-9z9nw.js} +1 -1
- package/src/client/dist/pwa/assets/WorkspacePage-DAVo-E4p.css +1 -0
- package/src/client/dist/pwa/assets/WorkspacePage-xH_OyA3r.js +4 -0
- package/src/client/dist/pwa/assets/{browser-Bkx4fbxl.js → browser-BsB6cFv4.js} +1 -1
- package/src/client/dist/pwa/assets/build-path-tree-CW-ER1Mt.js +1 -0
- package/src/client/dist/pwa/assets/{de-BU0dILBs.js → de-Mu7biceO.js} +2 -2
- package/src/client/dist/pwa/assets/documents-SCd3w8xV.js +1 -0
- package/src/client/dist/pwa/assets/{editor.api-DreEG0If.js → editor.api-BUdM7I6y.js} +1 -1
- package/src/client/dist/pwa/assets/editor.worker-iO5iN0DB.js +33 -0
- package/src/client/dist/pwa/assets/efforts-DTJQP9or.js +9 -0
- package/src/client/dist/pwa/assets/engineFeatures-B-9jHocY.css +1 -0
- package/src/client/dist/pwa/assets/engineFeatures-Bj6Ob41d.js +1 -0
- package/src/client/dist/pwa/assets/{es-CNp0s9na.js → es-BY5IfNI2.js} +2 -2
- package/src/client/dist/pwa/assets/expand-template-SM8vvYyO.js +1 -0
- package/src/client/dist/pwa/assets/{format-B7z3F-Mw.js → format-BjQxf4pe.js} +3 -3
- package/src/client/dist/pwa/assets/{formatters-BTBiU0v3.js → formatters-BejhmNTV.js} +1 -1
- package/src/client/dist/pwa/assets/{fr-RqJyWSdm.js → fr-BMXCzFCM.js} +2 -2
- package/src/client/dist/pwa/assets/{freemarker2-BZNTVQ3K.js → freemarker2-5d5GhyRp.js} +1 -1
- package/src/client/dist/pwa/assets/{handlebars-k8tN0dI3.js → handlebars-DaafEsDK.js} +1 -1
- package/src/client/dist/pwa/assets/{html-iZL3bbXq.js → html-CwHBDd2A.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-CgWW5vfb.js +1 -0
- package/src/client/dist/pwa/assets/index-0wtBh1Xt.css +1 -0
- package/src/client/dist/pwa/assets/index-Di9Sz5E8.js +20 -0
- package/src/client/dist/pwa/assets/{it-DSxNuCwL.js → it-ByPZQK_K.js} +2 -2
- package/src/client/dist/pwa/assets/{javascript-TV6gg0MO.js → javascript-DSNIEEH5.js} +1 -1
- package/src/client/dist/pwa/assets/{liquid-Cifa5ujF.js → liquid-Dma1UpA1.js} +1 -1
- package/src/client/dist/pwa/assets/{mdx-Bnj_8yGE.js → mdx-D8rqEAws.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco-contributions-pEh0n9Wk.js → monaco-contributions-2E7O2zoi.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco.contribution-D7NmJUJH.js → monaco.contribution-DqOgo72G.js} +2 -2
- package/src/client/dist/pwa/assets/{network-auth-3_5p4fdc.js → network-auth-DNmhJTZn.js} +1 -1
- package/src/client/dist/pwa/assets/{notify-theme-D_nf9AeF.js → notify-theme-B6Hiwvwb.js} +1 -1
- package/src/client/dist/pwa/assets/{pending-diff-open-CUnxC1xw.js → pending-diff-open-CeiVIFZn.js} +1 -1
- package/src/client/dist/pwa/assets/permissionModes-DWdtf81n.js +36 -0
- package/src/client/dist/pwa/assets/{pinia-BKqmoVru.js → pinia-CTJ3OfP3.js} +1 -1
- package/src/client/dist/pwa/assets/{private.use-form-C7umbGO-.js → private.use-form-Bk8ti264.js} +1 -1
- package/src/client/dist/pwa/assets/{project-color-CihwpDAA.js → project-color-BCp9SWoI.js} +1 -1
- package/src/client/dist/pwa/assets/{python-DOoNBSNL.js → python-f-9NqRlI.js} +1 -1
- package/src/client/dist/pwa/assets/{razor-Bvq5vVpJ.js → razor-DsCmoX0h.js} +1 -1
- package/src/client/dist/pwa/assets/reactivity.esm-bundler-Bo9anJjg.js +1 -0
- package/src/client/dist/pwa/assets/render-chat-markdown-DK5BD9mU.js +67 -0
- package/src/client/dist/pwa/assets/{runtime-core.esm-bundler-CDnVUfeZ.js → runtime-core.esm-bundler-CNFKm4ij.js} +1 -1
- package/src/client/dist/pwa/assets/{typescript-VVzxoeyM.js → typescript-cEHg4L10.js} +1 -1
- package/src/client/dist/pwa/assets/{use-checkbox-FnauAgTp.js → use-checkbox-CnvHBhSK.js} +1 -1
- package/src/client/dist/pwa/assets/{use-id-BIuaWKkB.js → use-id-wXd4c8FA.js} +1 -1
- package/src/client/dist/pwa/assets/use-is-mobile-DpKI3XMy.js +1 -0
- package/src/client/dist/pwa/assets/{use-search-index-status-DtLzygcy.js → use-search-index-status-Bluf9L99.js} +1 -1
- package/src/client/dist/pwa/assets/use-tours-BmTbosmB.js +2 -0
- package/src/client/dist/pwa/assets/{workspace-templates-1ui80Kqo.js → workspace-templates-DUWDLMIX.js} +1 -1
- package/src/client/dist/pwa/assets/{xml-CzGFIxQa.js → xml-Plt-Y7we.js} +1 -1
- package/src/client/dist/pwa/assets/{yaml-Bl7nDJeC.js → yaml-ScEmaAeN.js} +1 -1
- package/src/client/dist/pwa/index.html +10 -10
- package/src/client/dist/pwa/sw.js +1 -1
- package/src/mcp-server/kobo-tasks-handlers.ts +68 -104
- package/src/mcp-server/kobo-tasks-server.ts +56 -4
- package/src/client/dist/pwa/assets/GitPanel-C9mz4GXF.js +0 -3
- package/src/client/dist/pwa/assets/MainLayout-BwV89R-l.css +0 -1
- package/src/client/dist/pwa/assets/MainLayout-CwWavQjV.js +0 -2
- package/src/client/dist/pwa/assets/QBtn-CZaBclNw.js +0 -1
- package/src/client/dist/pwa/assets/QDrawer-DNEj8wLZ.js +0 -1
- package/src/client/dist/pwa/assets/QSelect-BUlOQe5k.js +0 -1
- package/src/client/dist/pwa/assets/WorkspacePage-Clc4Fl3B.js +0 -4
- package/src/client/dist/pwa/assets/WorkspacePage-DCg9zSxz.css +0 -1
- package/src/client/dist/pwa/assets/build-path-tree-BpSY7N5O.js +0 -1
- package/src/client/dist/pwa/assets/documents-BIlWh3o-.js +0 -1
- package/src/client/dist/pwa/assets/editor.worker-CwQAd_xr.js +0 -30
- package/src/client/dist/pwa/assets/efforts-D_yTzymv.js +0 -9
- package/src/client/dist/pwa/assets/engineFeatures-C33LTVkK.js +0 -1
- package/src/client/dist/pwa/assets/expand-template-wRhAXT-1.js +0 -1
- package/src/client/dist/pwa/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNa-Dr0goTwe.woff +0 -0
- package/src/client/dist/pwa/assets/i18n--PCet00a.js +0 -1
- package/src/client/dist/pwa/assets/index-CPfrY38K.js +0 -20
- package/src/client/dist/pwa/assets/index-CUoUy6tH.css +0 -1
- package/src/client/dist/pwa/assets/permissionModes-DC51Pz84.js +0 -36
- package/src/client/dist/pwa/assets/reactivity.esm-bundler-DEJj9cFv.js +0 -1
- package/src/client/dist/pwa/assets/render-chat-markdown-BQ_saiy5.js +0 -67
- package/src/client/dist/pwa/assets/use-is-mobile-CWjqe8gC.js +0 -1
- package/src/client/dist/pwa/assets/use-tours-BKeYrJaQ.js +0 -2
package/AGENTS.md
CHANGED
|
@@ -116,6 +116,7 @@ src/
|
|
|
116
116
|
| `tasks` | workspace sub-items: title, status, `is_acceptance_criterion`, sort_order; CASCADE DELETE on workspace |
|
|
117
117
|
| `agent_sessions` | agent-engine sessions: pid where applicable, engine session id, status, timestamps, model, and name |
|
|
118
118
|
| `ws_events` | persisted WebSocket events for replay on reconnect: type, payload, session_id, created_at |
|
|
119
|
+
| `pending_review_returns` | one-shot return from a review to its original session and LLM configuration (migration v44) |
|
|
119
120
|
| `pending_wakeups` | one-row-per-workspace scheduler for the `schedule_wakeup` MCP tool: target_at (ISO UTC), prompt, reason; CASCADE DELETE on workspace |
|
|
120
121
|
| `pending_quota_backoffs` | persisted quota retries and their attempt counts |
|
|
121
122
|
| `pending_crons` | recurring workspace schedules and their next run |
|
|
@@ -135,7 +136,11 @@ src/
|
|
|
135
136
|
|
|
136
137
|
`worktree_purged_at` + `worktree_purge_restore_data` drive the disk-space purge feature (see [Worktree purge](#worktree-purge) below): when set, the workspace's worktree folder has been removed from disk but the chat history is preserved. `worktree_purge_restore_data` is a JSON blob (`{ prNumber, prUrl, forge, mergeCommitSha, originalWorktreePath, originalSourceBranch, originalWorkingBranch }`) captured at purge time for future "Restore" UX. Both fields are cleared automatically by the pr-watcher when the worktree folder reappears on disk.
|
|
137
138
|
|
|
138
|
-
`auto_loop`
|
|
139
|
+
`auto_loop` preserves the user's execution intent. `auto_loop_runs` (migration v45) stores phase (`grooming | execution | finalization`), operational state (`active | waiting | blocked | completed | stopped`), iteration count and diagnostic recovery. `auto_loop_progress` records monotone task milestones; metadata changes are not progress. After three stagnant iterations, run one diagnostic and two further attempts, then block explicitly without clearing intent. Quotas wait for their actual reset, including multi-day windows; only transient failures use the retry cap. Archived/deleted workspaces disable the loop.
|
|
140
|
+
|
|
141
|
+
Tasks have `role` (`work | finalization`) and structured `verification`. MCP and HTTP share transactional task mutations. An auto-loop task cannot become done without passing reported checks. Finalization runs after all other tasks/criteria, is invalidated by scope changes, and is mandatory before completion. The proof is agent-reported evidence, not server-side execution of arbitrary commands.
|
|
142
|
+
|
|
143
|
+
`auto_loop_messages` durably queues next-iteration instructions, independently of engine session ids. A dispatch interrupted before confirmed completion becomes `unknown`, requiring explicit acknowledgement or retry. Unknown or in-flight instructions prevent completion. Actual engine closure (`EngineProcess.closed`), not a logical end event alone, releases controller ownership and permits the next writer.
|
|
139
144
|
|
|
140
145
|
`brainstorm_model` (nullable, set only at creation) lets a workspace created with `auto_loop=1` run its initial brainstorming session on a different model than the auto-loop iterations that follow, while staying on the same engine. `POST /api/workspaces` uses it for the very first `startAgent` call only; every subsequent auto-loop iteration (`auto-loop-service.ts`'s `spawnNextIteration`) always reads `model`, unchanged. The switch to `model` takes effect once that first live session ends naturally — not at the `[BRAINSTORM_COMPLETE]` marker, since the same session keeps running under `brainstorm_model` until it stops on its own.
|
|
141
146
|
|
|
@@ -259,6 +264,36 @@ Background: the engine was migrated from `@openai/codex-sdk` (one-shot `codex ex
|
|
|
259
264
|
|
|
260
265
|
## Workspace operations
|
|
261
266
|
|
|
267
|
+
### Review LLM and return to the original session
|
|
268
|
+
|
|
269
|
+
The current conversation is the running session, or the last used session by
|
|
270
|
+
`max(startedAt, endedAt)` after it stops. Keep the session list in creation order,
|
|
271
|
+
but do not use its first entry to identify the current conversation: a temporary
|
|
272
|
+
review can return to an older session. The stale-session banner and the next
|
|
273
|
+
review's return target must recognize that original session, including on reload.
|
|
274
|
+
|
|
275
|
+
`StartReviewDialog.vue` uses the shared model/effort/permission catalogues. Changed
|
|
276
|
+
settings force a new session on both client and server. `review-service.ts`
|
|
277
|
+
validates before side effects, waits for confirmed shutdown, then applies the
|
|
278
|
+
selected configuration and launches the review. Without a return request, these
|
|
279
|
+
settings remain the workspace defaults. Legacy requests retain their behavior.
|
|
280
|
+
|
|
281
|
+
The optional `returnToSession` flag captures the exact resumable original session.
|
|
282
|
+
`pending_review_returns` persists its configuration and the review session id
|
|
283
|
+
before switching engines. The orchestrator intercepts a successful review end
|
|
284
|
+
(including watchdog closure without a nonzero exit), restores configuration and
|
|
285
|
+
resumes that exact native conversation with the reviewer's final text report.
|
|
286
|
+
Resume selection is scoped to the target engine; never pass a reviewer's native
|
|
287
|
+
conversation id to the original engine, even after cancellation or restart.
|
|
288
|
+
The report reassembles text deltas/final snapshots, is bounded to 24,000 characters,
|
|
289
|
+
and links back to the complete review session through the conversation tool.
|
|
290
|
+
The receiving agent is asked to summarize and await instructions before fixes.
|
|
291
|
+
The intent is consumed before delivery, preventing repeated ends from sending twice.
|
|
292
|
+
Errors restore settings without automatically resuming; explicit stop cancels the
|
|
293
|
+
return. Startup restores interrupted-review settings without replaying a possibly
|
|
294
|
+
already-delivered handoff. Preserve these guards and the auto-loop interception.
|
|
295
|
+
`workspace:configuration` is ephemeral and updates all four LLM settings across tabs.
|
|
296
|
+
|
|
262
297
|
### Delete confirmation
|
|
263
298
|
|
|
264
299
|
The workspace deletion dialog requires the exact `workingBranch`, not the editable
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ 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.44
|
|
8
|
+
|
|
9
|
+
- chore(deps): update packages and node to 24.21.0
|
|
10
|
+
- fix(auto-loop): preserve mission intent until verified completion
|
|
11
|
+
- feat(review): configure review LLM and return to original session
|
|
12
|
+
|
|
7
13
|
## 1.11.43
|
|
8
14
|
|
|
9
15
|
- fix(workspaces): refine watchdog stops and deletion confirmation
|
|
@@ -3,10 +3,12 @@ import path from 'node:path';
|
|
|
3
3
|
import { nanoid } from 'nanoid';
|
|
4
4
|
import * as cronService from '../server/services/cron-service.js';
|
|
5
5
|
import * as settingsService from '../server/services/settings-service.js';
|
|
6
|
+
import { createTaskRecord, deleteTaskRecord, updateTaskRecord, } from '../server/services/task-mutations.js';
|
|
6
7
|
import { slugifyProjectName } from '../server/utils/project-slug.js';
|
|
7
8
|
import { ensureDirectoryInside, resolveExistingPathInside } from '../server/utils/safe-path.js';
|
|
8
9
|
import { resolveWorkspaceWorktreePath } from '../server/utils/worktree-paths.js';
|
|
9
10
|
import { MASKED_SECRET, SECRET_GLOBAL_KEYS } from '../shared/consts.js';
|
|
11
|
+
import { parseTaskVerification } from '../shared/task-verification.js';
|
|
10
12
|
/** Allowed task status values. */
|
|
11
13
|
export const VALID_TASK_STATUSES = ['pending', 'in_progress', 'done'];
|
|
12
14
|
function rowToDto(row) {
|
|
@@ -15,12 +17,15 @@ function rowToDto(row) {
|
|
|
15
17
|
title: row.title,
|
|
16
18
|
status: row.status,
|
|
17
19
|
is_acceptance_criterion: row.is_acceptance_criterion === 1,
|
|
20
|
+
sort_order: row.sort_order,
|
|
21
|
+
role: row.role,
|
|
22
|
+
verification: parseTaskVerification(row.verification),
|
|
18
23
|
};
|
|
19
24
|
}
|
|
20
25
|
/** Return all tasks for a workspace, ordered by sort_order. */
|
|
21
26
|
export function listTasksHandler(db, workspaceId) {
|
|
22
27
|
const rows = db
|
|
23
|
-
.prepare('SELECT
|
|
28
|
+
.prepare('SELECT * FROM tasks WHERE workspace_id = ? ORDER BY sort_order ASC, rowid ASC')
|
|
24
29
|
.all(workspaceId);
|
|
25
30
|
return rows.map(rowToDto);
|
|
26
31
|
}
|
|
@@ -81,86 +86,38 @@ export function setWorkspaceAgentDescriptionHandler(db, workspaceId, args) {
|
|
|
81
86
|
}
|
|
82
87
|
return { ok: true, description: stored };
|
|
83
88
|
}
|
|
84
|
-
/** Set a task's status to
|
|
85
|
-
export function markTaskDoneHandler(db, workspaceId, taskId) {
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (result.changes === 0) {
|
|
91
|
-
throw new Error(`Task '${taskId}' not found in workspace '${workspaceId}'`);
|
|
92
|
-
}
|
|
93
|
-
const row = db
|
|
94
|
-
.prepare('SELECT id, title, status, is_acceptance_criterion FROM tasks WHERE id = ?')
|
|
95
|
-
.get(taskId);
|
|
89
|
+
/** Set a task's status to done; auto-loop tasks require successful evidence. */
|
|
90
|
+
export function markTaskDoneHandler(db, workspaceId, taskId, verification) {
|
|
91
|
+
const row = updateTaskRecord(db, workspaceId, taskId, {
|
|
92
|
+
status: 'done',
|
|
93
|
+
...(verification !== undefined ? { verification } : {}),
|
|
94
|
+
});
|
|
96
95
|
return { success: true, task: rowToDto(row) };
|
|
97
96
|
}
|
|
98
|
-
/** Create a
|
|
97
|
+
/** Create a task at an explicit position or append it to the list. */
|
|
99
98
|
export function createTaskHandler(db, workspaceId, data) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
const id = nanoid();
|
|
109
|
-
const now = new Date().toISOString();
|
|
110
|
-
const isAC = data.is_acceptance_criterion ? 1 : 0;
|
|
111
|
-
// Append at the end: max(sort_order) + 1
|
|
112
|
-
const maxRow = db
|
|
113
|
-
.prepare('SELECT COALESCE(MAX(sort_order), -1) AS max FROM tasks WHERE workspace_id = ?')
|
|
114
|
-
.get(workspaceId);
|
|
115
|
-
const sortOrder = maxRow.max + 1;
|
|
116
|
-
db.prepare('INSERT INTO tasks (id, workspace_id, title, status, is_acceptance_criterion, sort_order, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)').run(id, workspaceId, data.title.trim(), 'pending', isAC, sortOrder, now, now);
|
|
117
|
-
const row = db.prepare('SELECT id, title, status, is_acceptance_criterion FROM tasks WHERE id = ?').get(id);
|
|
118
|
-
return rowToDto(row);
|
|
99
|
+
return rowToDto(createTaskRecord(db, workspaceId, {
|
|
100
|
+
title: data.title,
|
|
101
|
+
isAcceptanceCriterion: data.is_acceptance_criterion,
|
|
102
|
+
sortOrder: data.sort_order,
|
|
103
|
+
afterTaskId: data.after_task_id,
|
|
104
|
+
role: data.role,
|
|
105
|
+
}));
|
|
119
106
|
}
|
|
120
|
-
/** Update
|
|
107
|
+
/** Update task fields atomically, including insertion order and verification. */
|
|
121
108
|
export function updateTaskHandler(db, workspaceId, taskId, data) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (!data.title.trim())
|
|
131
|
-
throw new Error('title cannot be empty');
|
|
132
|
-
sets.push('title = ?');
|
|
133
|
-
values.push(data.title.trim());
|
|
134
|
-
}
|
|
135
|
-
if (data.status !== undefined) {
|
|
136
|
-
if (!VALID_TASK_STATUSES.includes(data.status)) {
|
|
137
|
-
throw new Error(`Invalid status '${data.status}'. Must be one of: ${VALID_TASK_STATUSES.join(', ')}`);
|
|
138
|
-
}
|
|
139
|
-
sets.push('status = ?');
|
|
140
|
-
values.push(data.status);
|
|
141
|
-
}
|
|
142
|
-
if (data.is_acceptance_criterion !== undefined) {
|
|
143
|
-
sets.push('is_acceptance_criterion = ?');
|
|
144
|
-
values.push(data.is_acceptance_criterion ? 1 : 0);
|
|
145
|
-
}
|
|
146
|
-
if (sets.length === 0) {
|
|
147
|
-
throw new Error('No fields to update (provide title, status, or is_acceptance_criterion)');
|
|
148
|
-
}
|
|
149
|
-
sets.push('updated_at = ?');
|
|
150
|
-
values.push(new Date().toISOString());
|
|
151
|
-
values.push(taskId);
|
|
152
|
-
db.prepare(`UPDATE tasks SET ${sets.join(', ')} WHERE id = ?`).run(...values);
|
|
153
|
-
const row = db
|
|
154
|
-
.prepare('SELECT id, title, status, is_acceptance_criterion FROM tasks WHERE id = ?')
|
|
155
|
-
.get(taskId);
|
|
156
|
-
return rowToDto(row);
|
|
109
|
+
return rowToDto(updateTaskRecord(db, workspaceId, taskId, {
|
|
110
|
+
title: data.title,
|
|
111
|
+
status: data.status,
|
|
112
|
+
isAcceptanceCriterion: data.is_acceptance_criterion,
|
|
113
|
+
sortOrder: data.sort_order,
|
|
114
|
+
afterTaskId: data.after_task_id,
|
|
115
|
+
verification: data.verification,
|
|
116
|
+
}));
|
|
157
117
|
}
|
|
158
|
-
/** Permanently delete a task from a workspace. */
|
|
118
|
+
/** Permanently delete a task from a workspace and invalidate its final review. */
|
|
159
119
|
export function deleteTaskHandler(db, workspaceId, taskId) {
|
|
160
|
-
|
|
161
|
-
if (result.changes === 0) {
|
|
162
|
-
throw new Error(`Task '${taskId}' not found in workspace '${workspaceId}'`);
|
|
163
|
-
}
|
|
120
|
+
deleteTaskRecord(db, workspaceId, taskId);
|
|
164
121
|
return { success: true, task_id: taskId };
|
|
165
122
|
}
|
|
166
123
|
/** Read the dev-server status for a workspace directly from the database. */
|
|
@@ -113,20 +113,53 @@ async function backendRequest(method, pathname, body) {
|
|
|
113
113
|
return text ? JSON.parse(text) : null;
|
|
114
114
|
}
|
|
115
115
|
const server = new Server({ name: 'kobo-tasks', version: '1.0.0' }, { capabilities: { tools: {} } });
|
|
116
|
+
const TASK_VERIFICATION_SCHEMA = {
|
|
117
|
+
type: 'object',
|
|
118
|
+
description: 'Required to complete an auto-loop task. Describe checks actually performed; every check must be passed for done. Record failed/not_run checks only on an unfinished task.',
|
|
119
|
+
properties: {
|
|
120
|
+
method: { type: 'string', minLength: 1 },
|
|
121
|
+
summary: { type: 'string', minLength: 1 },
|
|
122
|
+
checks: {
|
|
123
|
+
type: 'array',
|
|
124
|
+
minItems: 1,
|
|
125
|
+
items: {
|
|
126
|
+
type: 'object',
|
|
127
|
+
properties: {
|
|
128
|
+
name: { type: 'string', minLength: 1 },
|
|
129
|
+
status: { type: 'string', enum: ['passed', 'failed', 'not_run'] },
|
|
130
|
+
},
|
|
131
|
+
required: ['name', 'status'],
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
required: ['method', 'summary', 'checks'],
|
|
136
|
+
};
|
|
137
|
+
const TASK_ORDER_PROPERTIES = {
|
|
138
|
+
sort_order: {
|
|
139
|
+
type: 'integer',
|
|
140
|
+
minimum: 0,
|
|
141
|
+
description: 'Insert/move at this order, shifting siblings. Omit to append on creation. Mutually exclusive with after_task_id.',
|
|
142
|
+
},
|
|
143
|
+
after_task_id: {
|
|
144
|
+
type: 'string',
|
|
145
|
+
description: 'Insert/move immediately after this task in the same workspace, preserving the remaining order.',
|
|
146
|
+
},
|
|
147
|
+
};
|
|
116
148
|
const WORKSPACE_SCOPED_TOOLS = [
|
|
117
149
|
{
|
|
118
150
|
name: 'list_tasks',
|
|
119
|
-
description: 'CALL FIRST on any non-trivial turn to know what the user wants done and what is already completed. Returns every task and acceptance criterion for the current workspace with its id and
|
|
151
|
+
description: 'CALL FIRST on any non-trivial turn to know what the user wants done and what is already completed. Returns every task and acceptance criterion for the current workspace with its id, status, sort_order, role, and verification evidence. Re-call periodically (before marking something done, or after the user asks for a status) to stay in sync with user-added or external updates.',
|
|
120
152
|
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
121
153
|
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
122
154
|
},
|
|
123
155
|
{
|
|
124
156
|
name: 'mark_task_done',
|
|
125
|
-
description: 'CALL AS SOON AS a task or acceptance criterion is finished AND verified
|
|
157
|
+
description: 'CALL AS SOON AS a task or acceptance criterion is finished AND verified by checks actually performed. Auto-loop requires structured verification evidence with all checks passed. Never mark done if required checks failed or were not run. Finalization also requires all work tasks and criteria done. Do not wait for the end of the turn — the user watches progress live and marking each item as it completes is the primary signal Kōbō uses to track you.',
|
|
126
158
|
inputSchema: {
|
|
127
159
|
type: 'object',
|
|
128
160
|
properties: {
|
|
129
161
|
task_id: { type: 'string', description: 'Task id from list_tasks.' },
|
|
162
|
+
verification: TASK_VERIFICATION_SCHEMA,
|
|
130
163
|
},
|
|
131
164
|
required: ['task_id'],
|
|
132
165
|
},
|
|
@@ -140,10 +173,16 @@ const WORKSPACE_SCOPED_TOOLS = [
|
|
|
140
173
|
},
|
|
141
174
|
{
|
|
142
175
|
name: 'create_task',
|
|
143
|
-
description: 'CALL WHEN you discover follow-up work that was not in the original list and needs to stick around (e.g. "refactor this helper later", "add a test for edge case"). Appends
|
|
176
|
+
description: 'CALL WHEN you discover follow-up work that was not in the original list and needs to stick around (e.g. "refactor this helper later", "add a test for edge case"). Appends by default; use after_task_id for a dependent check immediately after its parent. Use role finalization for the final verification gate. Do not use it for ephemeral internal notes — prefer log_thought for those.',
|
|
144
177
|
inputSchema: {
|
|
145
178
|
type: 'object',
|
|
146
179
|
properties: {
|
|
180
|
+
...TASK_ORDER_PROPERTIES,
|
|
181
|
+
role: {
|
|
182
|
+
type: 'string',
|
|
183
|
+
enum: ['work', 'finalization'],
|
|
184
|
+
description: 'Default work. Finalization is the final verification gate and cannot finish while work/criteria remain open.',
|
|
185
|
+
},
|
|
147
186
|
title: { type: 'string', description: 'Short, imperative title (e.g. "Add retry to fetchUser").' },
|
|
148
187
|
is_acceptance_criterion: {
|
|
149
188
|
type: 'boolean',
|
|
@@ -161,6 +200,8 @@ const WORKSPACE_SCOPED_TOOLS = [
|
|
|
161
200
|
type: 'object',
|
|
162
201
|
properties: {
|
|
163
202
|
task_id: { type: 'string', description: 'Task id from list_tasks.' },
|
|
203
|
+
...TASK_ORDER_PROPERTIES,
|
|
204
|
+
verification: TASK_VERIFICATION_SCHEMA,
|
|
164
205
|
title: { type: 'string', description: 'New title (optional).' },
|
|
165
206
|
status: {
|
|
166
207
|
type: 'string',
|
|
@@ -612,7 +653,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
612
653
|
const taskId = a.task_id;
|
|
613
654
|
if (!taskId)
|
|
614
655
|
return fail('task_id parameter is required');
|
|
615
|
-
const result = markTaskDoneHandler(db, workspaceId, taskId);
|
|
656
|
+
const result = markTaskDoneHandler(db, workspaceId, taskId, a.verification);
|
|
616
657
|
void notifyBackend(taskId);
|
|
617
658
|
return ok(result);
|
|
618
659
|
}
|
|
@@ -627,7 +668,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
627
668
|
return fail('title parameter is required');
|
|
628
669
|
const task = createTaskHandler(db, workspaceId, {
|
|
629
670
|
title,
|
|
671
|
+
role: a.role,
|
|
630
672
|
is_acceptance_criterion: a.is_acceptance_criterion,
|
|
673
|
+
sort_order: a.sort_order,
|
|
674
|
+
after_task_id: a.after_task_id,
|
|
631
675
|
});
|
|
632
676
|
void notifyTasksUpdated();
|
|
633
677
|
return ok(task);
|
|
@@ -639,7 +683,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
639
683
|
const task = updateTaskHandler(db, workspaceId, taskId, {
|
|
640
684
|
title: a.title,
|
|
641
685
|
status: a.status,
|
|
686
|
+
verification: a.verification,
|
|
642
687
|
is_acceptance_criterion: a.is_acceptance_criterion,
|
|
688
|
+
sort_order: a.sort_order,
|
|
689
|
+
after_task_id: a.after_task_id,
|
|
643
690
|
});
|
|
644
691
|
void notifyTasksUpdated();
|
|
645
692
|
return ok(task);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Shared by fresh installs and the append-only upgrade. */
|
|
2
|
+
export function initAutoLoopSchema(db) {
|
|
3
|
+
const columns = db.prepare('PRAGMA table_info(tasks)').all();
|
|
4
|
+
if (columns.length) {
|
|
5
|
+
if (!columns.some((c) => c.name === 'role'))
|
|
6
|
+
db.exec("ALTER TABLE tasks ADD COLUMN role TEXT NOT NULL DEFAULT 'work'");
|
|
7
|
+
if (!columns.some((c) => c.name === 'verification'))
|
|
8
|
+
db.exec('ALTER TABLE tasks ADD COLUMN verification TEXT');
|
|
9
|
+
if (columns.some((c) => c.name === 'title')) {
|
|
10
|
+
db.exec("UPDATE tasks SET role = 'finalization' WHERE title LIKE '[FINAL] %' AND role = 'work'");
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
db.exec(`
|
|
14
|
+
CREATE TABLE IF NOT EXISTS auto_loop_runs (
|
|
15
|
+
workspace_id TEXT PRIMARY KEY REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
16
|
+
phase TEXT NOT NULL DEFAULT 'grooming',
|
|
17
|
+
state TEXT NOT NULL DEFAULT 'waiting',
|
|
18
|
+
reason TEXT,
|
|
19
|
+
iteration INTEGER NOT NULL DEFAULT 0,
|
|
20
|
+
diagnostic_attempts INTEGER NOT NULL DEFAULT 0,
|
|
21
|
+
current_task_id TEXT,
|
|
22
|
+
current_session_id TEXT,
|
|
23
|
+
updated_at TEXT NOT NULL
|
|
24
|
+
);
|
|
25
|
+
CREATE TABLE IF NOT EXISTS auto_loop_progress (
|
|
26
|
+
workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
27
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
28
|
+
milestone INTEGER NOT NULL DEFAULT 0,
|
|
29
|
+
PRIMARY KEY (workspace_id, task_id)
|
|
30
|
+
);
|
|
31
|
+
CREATE TABLE IF NOT EXISTS auto_loop_messages (
|
|
32
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
33
|
+
workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
34
|
+
client_message_id TEXT NOT NULL,
|
|
35
|
+
content TEXT NOT NULL,
|
|
36
|
+
source TEXT,
|
|
37
|
+
state TEXT NOT NULL DEFAULT 'pending',
|
|
38
|
+
session_id TEXT,
|
|
39
|
+
created_at TEXT NOT NULL,
|
|
40
|
+
UNIQUE (workspace_id, client_message_id)
|
|
41
|
+
);
|
|
42
|
+
CREATE INDEX IF NOT EXISTS idx_auto_loop_messages_pending ON auto_loop_messages(workspace_id, state, id);
|
|
43
|
+
`);
|
|
44
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { initAutoLoopSchema } from './auto-loop-schema.js';
|
|
1
2
|
import { initMcpMessageSchema } from './mcp-message-schema.js';
|
|
3
|
+
import { initReviewReturnSchema } from './review-return-schema.js';
|
|
2
4
|
import { initSchema } from './schema.js';
|
|
3
5
|
import { initSearchSchema } from './search-schema.js';
|
|
4
6
|
/** Ordered registry of all schema migrations. Append new entries at the end. */
|
|
@@ -744,6 +746,8 @@ export const migrations = [
|
|
|
744
746
|
},
|
|
745
747
|
{ version: 42, name: 'index-logical-search-messages', migrate: initSearchSchema },
|
|
746
748
|
{ version: 43, name: 'durable-mcp-message-requests', migrate: initMcpMessageSchema },
|
|
749
|
+
{ version: 44, name: 'review-session-returns', migrate: initReviewReturnSchema },
|
|
750
|
+
{ version: 45, name: 'durable-auto-loop', migrate: initAutoLoopSchema },
|
|
747
751
|
];
|
|
748
752
|
/** Current schema version — always equals the highest migration version. */
|
|
749
753
|
export const SCHEMA_VERSION = migrations.length > 0 ? migrations[migrations.length - 1].version : 1;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function initReviewReturnSchema(db) {
|
|
2
|
+
db.exec(`CREATE TABLE IF NOT EXISTS pending_review_returns (
|
|
3
|
+
workspace_id TEXT PRIMARY KEY REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
4
|
+
review_session_id TEXT NOT NULL REFERENCES agent_sessions(id) ON DELETE CASCADE,
|
|
5
|
+
original_session_id TEXT NOT NULL REFERENCES agent_sessions(id) ON DELETE CASCADE,
|
|
6
|
+
original_configuration TEXT NOT NULL,
|
|
7
|
+
review_configuration TEXT NOT NULL,
|
|
8
|
+
created_at TEXT NOT NULL
|
|
9
|
+
)`);
|
|
10
|
+
}
|
package/dist/server/db/schema.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { initAutoLoopSchema } from './auto-loop-schema.js';
|
|
1
2
|
import { initMcpMessageSchema } from './mcp-message-schema.js';
|
|
3
|
+
import { initReviewReturnSchema } from './review-return-schema.js';
|
|
2
4
|
import { initSearchSchema } from './search-schema.js';
|
|
3
5
|
/** Create all tables and indexes for a fresh install. Not used for upgrades -- see migrations.ts. */
|
|
4
6
|
export function initSchema(db) {
|
|
@@ -225,4 +227,6 @@ export function initSchema(db) {
|
|
|
225
227
|
`);
|
|
226
228
|
initSearchSchema(db);
|
|
227
229
|
initMcpMessageSchema(db);
|
|
230
|
+
initReviewReturnSchema(db);
|
|
231
|
+
initAutoLoopSchema(db);
|
|
228
232
|
}
|
package/dist/server/index.js
CHANGED
|
@@ -41,6 +41,7 @@ import { reconcileMessageRequests } from './services/mcp-message-request-service
|
|
|
41
41
|
import { authorizeWsUpgrade, generateToken, getLanHostnames, getLanUrls, isAllowedOrigin, isAllowedRequestHost, resolveBindHost, resolveDevClientOrigin, resolveNetworkAccessEnvOverrides, resolveProxyHostname, } from './services/network-access-service.js';
|
|
42
42
|
import { startPrWatcher, stopPrWatcher } from './services/pr-watcher-service.js';
|
|
43
43
|
import * as quotaBackoffService from './services/quota-backoff-service.js';
|
|
44
|
+
import { reconcileReviewReturns } from './services/review-return-service.js';
|
|
44
45
|
import { startSearchIndex, stopSearchIndex } from './services/search-service.js';
|
|
45
46
|
import { getGlobalSettings, updateNetworkAccessSettings } from './services/settings-service.js';
|
|
46
47
|
import { reloadDefaultTemplates } from './services/templates-service.js';
|
|
@@ -116,15 +117,7 @@ const dailyBackupScheduler = startDailyDbBackupScheduler(async () => {
|
|
|
116
117
|
});
|
|
117
118
|
// Initialize process cleanup, agent watchdog, PR watcher, and wakeup rehydration
|
|
118
119
|
reconcileOrphanSessions();
|
|
119
|
-
|
|
120
|
-
wakeupService.rehydrate();
|
|
121
|
-
autoLoopService.rehydrate();
|
|
122
|
-
// Restore in-memory retry counts BEFORE re-arming the persisted backoff timers,
|
|
123
|
-
// otherwise the next arm() after restart would compute the next ladder rung
|
|
124
|
-
// from retryCount=0 and undo the progression.
|
|
125
|
-
restoreRetryCountsFromDb();
|
|
126
|
-
quotaBackoffService.restoreOnBoot((workspaceId, pending) => autoLoopService.onQuotaBackoffExpired(workspaceId, pending));
|
|
127
|
-
cronService.restoreOnBoot();
|
|
120
|
+
reconcileReviewReturns();
|
|
128
121
|
// Deliver any new default prompt templates to existing installs (seed-once via the
|
|
129
122
|
// seededDefaultSlugs watermark; never overwrites or re-adds deleted defaults).
|
|
130
123
|
try {
|
|
@@ -229,6 +222,14 @@ const server = serve({
|
|
|
229
222
|
hostname: bindHost,
|
|
230
223
|
}, (info) => {
|
|
231
224
|
setBackendPort(info.port);
|
|
225
|
+
// Restored agents receive the actual listening endpoint, including an
|
|
226
|
+
// ephemeral or SERVER_PORT-selected port. Quota ownership comes first.
|
|
227
|
+
restoreRetryCountsFromDb();
|
|
228
|
+
quotaBackoffService.restoreOnBoot((workspaceId, pending) => autoLoopService.onQuotaBackoffExpired(workspaceId, pending));
|
|
229
|
+
autoLoopService.rehydrate();
|
|
230
|
+
wakeupService.rehydrate();
|
|
231
|
+
cronService.restoreOnBoot();
|
|
232
|
+
startWatchdog();
|
|
232
233
|
startUpdateChecker();
|
|
233
234
|
stopStartupSpinner();
|
|
234
235
|
const settings = getGlobalSettings();
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { listLoopMessages, resolveLoopMessage } from '../services/auto-loop-message-service.js';
|
|
3
|
+
import { deliverWorkspaceMessage } from '../services/workspace-message-service.js';
|
|
4
|
+
import { getWorkspace } from '../services/workspace-service.js';
|
|
5
|
+
import { assertWorkspaceLifecycleAvailable } from '../utils/workspace-lifecycle-guard.js';
|
|
6
|
+
const app = new Hono();
|
|
7
|
+
app.get('/:id/auto-loop/messages', (c) => {
|
|
8
|
+
try {
|
|
9
|
+
const id = c.req.param('id');
|
|
10
|
+
if (!getWorkspace(id))
|
|
11
|
+
return c.json({ error: 'Workspace not found' }, 404);
|
|
12
|
+
return c.json(listLoopMessages(id));
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
app.post('/:id/auto-loop/messages', async (c) => {
|
|
19
|
+
try {
|
|
20
|
+
const body = await c.req.json();
|
|
21
|
+
if (typeof body.content !== 'string' || typeof body.clientMessageId !== 'string')
|
|
22
|
+
return c.json({ error: 'content and clientMessageId are required' }, 400);
|
|
23
|
+
await deliverWorkspaceMessage(c.req.param('id'), {
|
|
24
|
+
content: body.content,
|
|
25
|
+
clientMessageId: body.clientMessageId,
|
|
26
|
+
delivery: 'next_iteration',
|
|
27
|
+
});
|
|
28
|
+
return c.json({ accepted: true });
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
app.patch('/:id/auto-loop/messages/:messageId', async (c) => {
|
|
35
|
+
try {
|
|
36
|
+
const id = c.req.param('id');
|
|
37
|
+
assertWorkspaceLifecycleAvailable(id);
|
|
38
|
+
const workspace = getWorkspace(id);
|
|
39
|
+
if (!workspace || workspace.archivedAt || workspace.worktreePurgedAt)
|
|
40
|
+
return c.json({ error: 'Workspace unavailable' }, 409);
|
|
41
|
+
const { action } = await c.req.json();
|
|
42
|
+
if (!['cancel', 'acknowledge', 'retry'].includes(action))
|
|
43
|
+
return c.json({ error: 'Invalid resolution' }, 400);
|
|
44
|
+
resolveLoopMessage(id, Number(c.req.param('messageId')), action);
|
|
45
|
+
return c.json({ ok: true });
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
export default app;
|