@loicngr/kobo 2.1.2 → 2.1.5
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 +12 -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 +82 -41
- package/dist/server/services/agent/engines/claude-code/engine.js +87 -20
- package/dist/server/services/agent/engines/claude-code/event-mapper.js +88 -22
- package/dist/server/services/agent/engines/claude-code/options-builder.js +1 -0
- package/dist/server/services/agent/engines/codex/engine.js +27 -5
- package/dist/server/services/agent/engines/codex/options-builder.js +1 -0
- package/dist/server/services/agent/integration-mcp.js +4 -3
- package/dist/server/services/agent/orchestrator.js +36 -1
- package/dist/server/services/agent/session-controller.js +12 -0
- 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 +8 -8
- package/src/client/dist/pwa/assets/{ActivityFeed-DCmiJP-z.css → ActivityFeed-B4lTKMVE.css} +1 -1
- package/src/client/dist/pwa/assets/ActivityFeed-DXWXioW9.js +8 -0
- package/src/client/dist/pwa/assets/ChangelogPage-BL5Jh8-v.js +1 -0
- package/src/client/dist/pwa/assets/ChangelogPage-DVyx9ZjV.css +1 -0
- package/src/client/dist/pwa/assets/{ClosePopup-BikuzYle.js → ClosePopup-C1a82pL9.js} +1 -1
- package/src/client/dist/pwa/assets/ComparisonPanel-Bv2SZrVZ.js +2 -0
- package/src/client/dist/pwa/assets/ComparisonPanel-ByVbqYCs.css +1 -0
- package/src/client/dist/pwa/assets/CreatePage-CMyl2j8Z.css +1 -0
- package/src/client/dist/pwa/assets/CreatePage-d43ITOC1.js +2 -0
- package/src/client/dist/pwa/assets/DashboardPage-BEXXRguE.js +1 -0
- package/src/client/dist/pwa/assets/DashboardPage-CtaHaVdf.css +1 -0
- package/src/client/dist/pwa/assets/DiffViewer-B92w_jd7.js +8 -0
- package/src/client/dist/pwa/assets/DocumentsPanel-C9JXyqHg.js +1 -0
- package/src/client/dist/pwa/assets/{DrawerToggleButton-Dfvkaro0.js → DrawerToggleButton-Btx8QwHo.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-Cgz9crLN.js +3 -0
- package/src/client/dist/pwa/assets/HealthPage-CDhgJQkL.js +1 -0
- package/src/client/dist/pwa/assets/HealthPage-DT8FGrKA.css +1 -0
- package/src/client/dist/pwa/assets/MainLayout-DTTtj9sq.js +2 -0
- package/src/client/dist/pwa/assets/{MainLayout-gHENJEFQ.css → MainLayout-knNNtkre.css} +1 -1
- package/src/client/dist/pwa/assets/QBadge-rGILsUYl.js +1 -0
- package/src/client/dist/pwa/assets/QBanner-7EWRdc4U.js +1 -0
- package/src/client/dist/pwa/assets/QBtn-b_baQtAt.js +1 -0
- package/src/client/dist/pwa/assets/QBtnDropdown-vjat4GZl.js +1 -0
- package/src/client/dist/pwa/assets/QBtnGroup-CUmAgBV8.js +1 -0
- package/src/client/dist/pwa/assets/QBtnToggle-COL46j6W.js +1 -0
- package/src/client/dist/pwa/assets/QChip-CG1kWhFm.js +1 -0
- package/src/client/dist/pwa/assets/QExpansionItem-BQuy89vv.js +1 -0
- package/src/client/dist/pwa/assets/QItemSection-BrVklYfM.js +1 -0
- package/src/client/dist/pwa/assets/QList-BVX-b8nc.js +1 -0
- package/src/client/dist/pwa/assets/QMarkupTable-B8_FKLvo.js +1 -0
- package/src/client/dist/pwa/assets/QPage-BCDXjfVj.js +1 -0
- package/src/client/dist/pwa/assets/QSelect-DBW_TZri.js +1 -0
- package/src/client/dist/pwa/assets/QSpinnerDots-BsKmccpX.js +1 -0
- package/src/client/dist/pwa/assets/QTooltip-DzYbguTR.js +1 -0
- package/src/client/dist/pwa/assets/SchedulePanel-BX6iKh6S.js +1 -0
- package/src/client/dist/pwa/assets/SchedulePanel-CBM_GpS3.css +1 -0
- package/src/client/dist/pwa/assets/SearchPage-_In2gH2P.js +1 -0
- package/src/client/dist/pwa/assets/SettingsPage-BqkNYzC5.js +9 -0
- package/src/client/dist/pwa/assets/{SettingsPage-BYdLXJEJ.css → SettingsPage-CpykRKq2.css} +1 -1
- package/src/client/dist/pwa/assets/SplitWorkspacePage-DRoXbSRP.js +1 -0
- package/src/client/dist/pwa/assets/{TerminalPanel-B9LDe7uG.js → TerminalPanel-CfqiuSuk.js} +2 -2
- package/src/client/dist/pwa/assets/{TerminalPanel-DbVZk0vD.css → TerminalPanel-Dz9EE-Ph.css} +1 -1
- package/src/client/dist/pwa/assets/TouchPan-DcOCRf7x.js +1 -0
- package/src/client/dist/pwa/assets/{TourReplayButton-Dtv_UDaY.js → TourReplayButton-gp7m6tSV.js} +1 -1
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-DSw8Upcp.css +1 -0
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-IcoMGkJh.js +1 -0
- package/src/client/dist/pwa/assets/{WorkspacePage--5HsnMQ1.css → WorkspacePage-BnW-gp3u.css} +1 -1
- package/src/client/dist/pwa/assets/WorkspacePage-CR2xAzW_.js +4 -0
- package/src/client/dist/pwa/assets/{browser-B15GGwtW.js → browser-BveHkbe-.js} +1 -1
- package/src/client/dist/pwa/assets/build-path-tree-Dtp7rlxS.js +1 -0
- package/src/client/dist/pwa/assets/custom-sounds-CBoWzJ85.js +1 -0
- package/src/client/dist/pwa/assets/de-BJsWkZro.js +17 -0
- package/src/client/dist/pwa/assets/defineProperty-BbfpZ9Tg.js +1 -0
- package/src/client/dist/pwa/assets/documents-sb2sQ4JC.js +1 -0
- package/src/client/dist/pwa/assets/{editor.api-DhaLhNl-.js → editor.api-CbkPxoJf.js} +1 -1
- package/src/client/dist/pwa/assets/efforts-BCli1Unj.js +9 -0
- package/src/client/dist/pwa/assets/es-tNYw7itB.js +17 -0
- package/src/client/dist/pwa/assets/expand-template-CfaEhb2f.js +1 -0
- package/src/client/dist/pwa/assets/{format-2JisC3s1.js → format-BRl2DUlm.js} +7 -7
- package/src/client/dist/pwa/assets/formatters-C5TjPSR_.js +1 -0
- package/src/client/dist/pwa/assets/fr-B5sFski4.js +17 -0
- package/src/client/dist/pwa/assets/{freemarker2-PYrDqU04.js → freemarker2-CDmvOQyn.js} +1 -1
- package/src/client/dist/pwa/assets/{handlebars-BuiY0fFf.js → handlebars-Bx_w8mx1.js} +1 -1
- package/src/client/dist/pwa/assets/{html-Du3cjdug.js → html-DVRfiRkS.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-Bm-L4nW3.js +1 -0
- package/src/client/dist/pwa/assets/i18n-D21JKsVt.js +19 -0
- package/src/client/dist/pwa/assets/index-C7vFf6p2.js +2 -0
- package/src/client/dist/pwa/assets/index-C8zAZ-k-.css +1 -0
- package/src/client/dist/pwa/assets/it-Bc2FncM9.js +17 -0
- package/src/client/dist/pwa/assets/{javascript-Bb48hPli.js → javascript-CPvttU5J.js} +1 -1
- package/src/client/dist/pwa/assets/latest-request-CN4-B22z.js +1 -0
- package/src/client/dist/pwa/assets/{liquid-Be77iQe3.js → liquid-CI0gQddb.js} +1 -1
- package/src/client/dist/pwa/assets/{mdx-CfohNJja.js → mdx-BveBwYJH.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco-contributions-CyM4wsIg.js → monaco-contributions-CHXFh4P2.js} +1 -1
- package/src/client/dist/pwa/assets/monaco.contribution-DOK8t_9B.js +2 -0
- package/src/client/dist/pwa/assets/network-auth-CItnNZDh.js +1 -0
- package/src/client/dist/pwa/assets/{notify-theme-DvvaSyg1.js → notify-theme-BO6r1Rd7.js} +1 -1
- package/src/client/dist/pwa/assets/pending-diff-open-DpS4iyDM.js +1 -0
- package/src/client/dist/pwa/assets/{permissionModes-B-ciuUbz.js → permissionModes-Cc5VaOdC.js} +1 -1
- package/src/client/dist/pwa/assets/preload-helper-BZ1Pz5am.js +1 -0
- package/src/client/dist/pwa/assets/{private.use-form-Bk8ti264.js → private.use-form-vxvbh_VN.js} +1 -1
- package/src/client/dist/pwa/assets/{project-color-Be0t12-n.js → project-color-Bnv02P9D.js} +1 -1
- package/src/client/dist/pwa/assets/{python-BNemmtx8.js → python-BesLCxpi.js} +1 -1
- package/src/client/dist/pwa/assets/{razor-3NM8iFVf.js → razor-Dv1PMxuY.js} +1 -1
- package/src/client/dist/pwa/assets/{reactivity.esm-bundler-Bo9anJjg.js → reactivity.esm-bundler-tuEvQ47f.js} +1 -1
- package/src/client/dist/pwa/assets/{render-chat-markdown-BWJNIDv3.js → render-chat-markdown-CafzCcmu.js} +1 -1
- package/src/client/dist/pwa/assets/runtime-core.esm-bundler-DOiZcasU.js +1 -0
- package/src/client/dist/pwa/assets/{typescript-DwyZ4CF7.js → typescript-26L2Xsu9.js} +1 -1
- package/src/client/dist/pwa/assets/use-checkbox-BLeif5-t.js +1 -0
- package/src/client/dist/pwa/assets/use-id-b4A2I8LB.js +1 -0
- package/src/client/dist/pwa/assets/use-is-mobile-DLIZxEo9.js +1 -0
- package/src/client/dist/pwa/assets/use-search-index-status-Mv3pIO6p.js +1 -0
- package/src/client/dist/pwa/assets/{use-slide-transition-CiaDm-tR.js → use-slide-transition-C5eDGOmu.js} +1 -1
- package/src/client/dist/pwa/assets/{use-tours-CjASQcG5.js → use-tours-B5uIqxRS.js} +2 -2
- package/src/client/dist/pwa/assets/use-worktree-restore-BBChALUM.js +1 -0
- package/src/client/dist/pwa/assets/workspace-templates-CDY-MiH2.js +1 -0
- package/src/client/dist/pwa/assets/{xml-B1UujXdS.js → xml-CkRojTcc.js} +1 -1
- package/src/client/dist/pwa/assets/{yaml-mHN8kNgU.js → yaml-DKkQHwew.js} +1 -1
- package/src/client/dist/pwa/index.html +12 -9
- package/src/client/dist/pwa/sw.js +1 -1
- package/src/client/dist/pwa/assets/ActivityFeed-CO5rOaa5.js +0 -8
- package/src/client/dist/pwa/assets/ChangelogPage-DKJkFiU_.css +0 -1
- package/src/client/dist/pwa/assets/ChangelogPage-DNRcPRtW.js +0 -1
- package/src/client/dist/pwa/assets/ComparisonPanel-CRRaDs2D.css +0 -1
- package/src/client/dist/pwa/assets/ComparisonPanel-DZoDqgJI.js +0 -2
- package/src/client/dist/pwa/assets/CreatePage-B9C9J9Pw.css +0 -1
- package/src/client/dist/pwa/assets/CreatePage-wNH35uTG.js +0 -2
- package/src/client/dist/pwa/assets/DashboardPage-DUyy4SzS.js +0 -1
- package/src/client/dist/pwa/assets/DashboardPage-XiCj24i2.css +0 -1
- package/src/client/dist/pwa/assets/DiffViewer-CZ5QwXqi.js +0 -8
- package/src/client/dist/pwa/assets/DocumentsPanel-sBatgKA-.js +0 -1
- package/src/client/dist/pwa/assets/GitPanel-B8e1olfm.js +0 -3
- package/src/client/dist/pwa/assets/HealthPage-BwXYpczh.js +0 -1
- package/src/client/dist/pwa/assets/HealthPage-CFZ-tdKK.css +0 -1
- package/src/client/dist/pwa/assets/MainLayout-DFXIS_wv.js +0 -2
- package/src/client/dist/pwa/assets/QBadge-CQF1smL-.js +0 -1
- package/src/client/dist/pwa/assets/QBanner-bdDA-A5g.js +0 -1
- package/src/client/dist/pwa/assets/QBtn-CcQQrYA_.js +0 -1
- package/src/client/dist/pwa/assets/QBtnDropdown-C-1EhO9J.js +0 -1
- package/src/client/dist/pwa/assets/QBtnGroup-wd5lKRXu.js +0 -1
- package/src/client/dist/pwa/assets/QBtnToggle-BnC8rTql.js +0 -1
- package/src/client/dist/pwa/assets/QChip-D75nKi-L.js +0 -1
- package/src/client/dist/pwa/assets/QExpansionItem-RpMPvfxX.js +0 -1
- package/src/client/dist/pwa/assets/QItemSection-Bt9H35JJ.js +0 -1
- package/src/client/dist/pwa/assets/QList-D5msNbSZ.js +0 -1
- package/src/client/dist/pwa/assets/QMarkupTable-D3REW6RC.js +0 -1
- package/src/client/dist/pwa/assets/QPage-HAaeQ-cv.js +0 -1
- package/src/client/dist/pwa/assets/QSelect-Dpru_T81.js +0 -1
- package/src/client/dist/pwa/assets/QSpinnerDots-DDsPDUH9.js +0 -1
- package/src/client/dist/pwa/assets/QTooltip-C31vXtse.js +0 -1
- package/src/client/dist/pwa/assets/SchedulePanel-BYKSFoFj.css +0 -1
- package/src/client/dist/pwa/assets/SchedulePanel-D6LTiRFg.js +0 -1
- package/src/client/dist/pwa/assets/SearchPage-Dj487BdP.js +0 -1
- package/src/client/dist/pwa/assets/SettingsPage-CJmgFiAa.js +0 -9
- package/src/client/dist/pwa/assets/SplitWorkspacePage-BGSDgGIO.js +0 -1
- package/src/client/dist/pwa/assets/TouchPan-DJqqxDBF.js +0 -1
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-CrLP_I5S.css +0 -1
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-VlJCIkwU.js +0 -1
- package/src/client/dist/pwa/assets/WorkspacePage-B_SlX8YB.js +0 -4
- package/src/client/dist/pwa/assets/build-path-tree-CU6uXunR.js +0 -1
- package/src/client/dist/pwa/assets/custom-sounds-CDJsgChY.js +0 -1
- package/src/client/dist/pwa/assets/de-B_Y4EArT.js +0 -17
- package/src/client/dist/pwa/assets/documents-Cf6nmu8Z.js +0 -1
- package/src/client/dist/pwa/assets/efforts-DoYZi0AZ.js +0 -9
- package/src/client/dist/pwa/assets/engineFeatures-C9XiCfvv.js +0 -1
- package/src/client/dist/pwa/assets/es-DtsTTShP.js +0 -17
- package/src/client/dist/pwa/assets/expand-template-LDLM-6iN.js +0 -1
- package/src/client/dist/pwa/assets/formatters-ZVNs2VOl.js +0 -1
- package/src/client/dist/pwa/assets/fr-C82MIVrA.js +0 -17
- package/src/client/dist/pwa/assets/i18n-BumeGGx_.js +0 -1
- package/src/client/dist/pwa/assets/index-DolbyIM4.js +0 -20
- package/src/client/dist/pwa/assets/index-VMFLfvp2.css +0 -1
- package/src/client/dist/pwa/assets/it-BJNuTcj2.js +0 -17
- package/src/client/dist/pwa/assets/monaco.contribution-BcIfN6Mi.js +0 -2
- package/src/client/dist/pwa/assets/network-auth-B1xAaHmx.js +0 -1
- package/src/client/dist/pwa/assets/pending-diff-open-B6N0vI5n.js +0 -1
- package/src/client/dist/pwa/assets/pinia-CTJ3OfP3.js +0 -1
- package/src/client/dist/pwa/assets/runtime-core.esm-bundler-CNFKm4ij.js +0 -1
- package/src/client/dist/pwa/assets/use-checkbox-2xzX4yYj.js +0 -1
- package/src/client/dist/pwa/assets/use-id-vOr8PRTG.js +0 -1
- package/src/client/dist/pwa/assets/use-is-mobile-CJfSpTHx.js +0 -1
- package/src/client/dist/pwa/assets/use-search-index-status-Bluf9L99.js +0 -1
- package/src/client/dist/pwa/assets/workspace-templates-BxYxJt3S.js +0 -1
- /package/src/client/dist/pwa/assets/{engineFeatures-B-9jHocY.css → use-worktree-restore-B-9jHocY.css} +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
5
6
|
import * as gitOps from '../utils/git-ops.js';
|
|
6
7
|
import { GitConflictError } from '../utils/git-ops.js';
|
|
7
8
|
import { withGitRepoLock } from '../utils/git-repo-lock.js';
|
|
@@ -41,23 +42,41 @@ const EMPTY_CHANGES = { present: false, modified: 0, staged: 0, untracked: 0 };
|
|
|
41
42
|
// interactively over a pipe nobody can answer; the timeout is a backstop for
|
|
42
43
|
// anything else that could still stall (a slow filesystem, a stuck lock).
|
|
43
44
|
const GIT_TIMEOUT_MS = 60_000;
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
const execFileAsync = promisify(execFile);
|
|
46
|
+
async function runGitAsync(repoPath, args, timeout = GIT_TIMEOUT_MS) {
|
|
47
|
+
const { stdout } = await execFileAsync('git', args, {
|
|
46
48
|
cwd: repoPath,
|
|
47
49
|
encoding: 'utf-8',
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
env:
|
|
51
|
-
})
|
|
50
|
+
timeout,
|
|
51
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
52
|
+
env: gitOps.buildNonInteractiveGitEnv(),
|
|
53
|
+
});
|
|
54
|
+
return stdout.trim();
|
|
55
|
+
}
|
|
56
|
+
function readGitAsync(repoPath, args) {
|
|
57
|
+
return runGitAsync(repoPath, args, 15_000);
|
|
58
|
+
}
|
|
59
|
+
async function conflictedFilesAsync(repoPath) {
|
|
60
|
+
try {
|
|
61
|
+
return (await readGitAsync(repoPath, ['diff', '--name-only', '--diff-filter=U']))
|
|
62
|
+
.split('\n')
|
|
63
|
+
.map((file) => file.trim())
|
|
64
|
+
.filter(Boolean);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
52
69
|
}
|
|
53
70
|
/** Compare the local branch against `origin/<branch>`. */
|
|
54
|
-
function diagnoseBranch(projectPath, branch) {
|
|
55
|
-
if (!
|
|
71
|
+
async function diagnoseBranch(projectPath, branch, localExists, remoteExists) {
|
|
72
|
+
if (!localExists)
|
|
56
73
|
return { state: 'absent' };
|
|
57
|
-
if (!
|
|
74
|
+
if (!remoteExists)
|
|
58
75
|
return { state: 'in-sync' };
|
|
59
|
-
const ahead =
|
|
60
|
-
|
|
76
|
+
const [ahead, behind] = await Promise.all([
|
|
77
|
+
gitOps.getCommitCountAsync(projectPath, branch, branch),
|
|
78
|
+
gitOps.getCommitsBehindAsync(projectPath, branch, branch),
|
|
79
|
+
]);
|
|
61
80
|
if (ahead > 0 && behind > 0)
|
|
62
81
|
return { state: 'diverged', ahead, behind };
|
|
63
82
|
// Note: `hasCommonAncestor` below is what produces the `no-common-ancestor`
|
|
@@ -71,9 +90,9 @@ function diagnoseBranch(projectPath, branch) {
|
|
|
71
90
|
}
|
|
72
91
|
/** False when the local branch and its remote share no history at all — a name
|
|
73
92
|
* collision, not a divergence. Every realignment strategy is wrong for it. */
|
|
74
|
-
function hasCommonAncestor(repoPath, branch) {
|
|
93
|
+
async function hasCommonAncestor(repoPath, branch) {
|
|
75
94
|
try {
|
|
76
|
-
|
|
95
|
+
await readGitAsync(repoPath, ['merge-base', branch, `origin/${branch}`]);
|
|
77
96
|
return true;
|
|
78
97
|
}
|
|
79
98
|
catch {
|
|
@@ -107,8 +126,8 @@ async function diagnoseWorktree(projectPath, branch, targetPath, attachedPaths)
|
|
|
107
126
|
return { worktree: { state: 'none' }, blockers };
|
|
108
127
|
}
|
|
109
128
|
/** Count working-tree changes by category. */
|
|
110
|
-
function diagnoseChanges(worktreePath) {
|
|
111
|
-
const status = gitOps.
|
|
129
|
+
async function diagnoseChanges(worktreePath) {
|
|
130
|
+
const status = await gitOps.getWorkingTreeStatusAsync(worktreePath);
|
|
112
131
|
const modified = status.modified ?? 0;
|
|
113
132
|
const staged = status.staged ?? 0;
|
|
114
133
|
const untracked = status.untracked ?? 0;
|
|
@@ -130,9 +149,9 @@ export function resolveWorkspaceState(workspaces, branch, projectPath) {
|
|
|
130
149
|
return { state: 'active', id: match.id, name: match.name };
|
|
131
150
|
}
|
|
132
151
|
/** `git rev-parse <ref>`, or null when the ref does not resolve. */
|
|
133
|
-
function safeRevParse(repoPath, ref) {
|
|
152
|
+
async function safeRevParse(repoPath, ref) {
|
|
134
153
|
try {
|
|
135
|
-
return
|
|
154
|
+
return await readGitAsync(repoPath, ['rev-parse', ref]);
|
|
136
155
|
}
|
|
137
156
|
catch {
|
|
138
157
|
return null;
|
|
@@ -140,8 +159,10 @@ function safeRevParse(repoPath, ref) {
|
|
|
140
159
|
}
|
|
141
160
|
/** Hash the observed state so `/resolve` can refuse a plan built on stale facts. */
|
|
142
161
|
export async function computeFingerprint(report) {
|
|
143
|
-
const localHead =
|
|
144
|
-
|
|
162
|
+
const [localHead, remoteHead] = await Promise.all([
|
|
163
|
+
safeRevParse(report.projectPath, report.headBranch),
|
|
164
|
+
safeRevParse(report.projectPath, `origin/${report.headBranch}`),
|
|
165
|
+
]);
|
|
145
166
|
const payload = JSON.stringify({
|
|
146
167
|
contents: report.worktree.state === 'orphan' || report.worktree.state === 'attached'
|
|
147
168
|
? await fingerprintWorktree(report.worktree.path)
|
|
@@ -166,12 +187,15 @@ export async function computeFingerprint(report) {
|
|
|
166
187
|
export async function diagnoseLocalState(projectPath, headBranch, worktreesPath, attachedPaths = new Map(), workspaces = []) {
|
|
167
188
|
const targetWorktreePath = resolveWorkspaceWorktreePath(projectPath, headBranch, worktreesPath);
|
|
168
189
|
const blockers = [];
|
|
169
|
-
const lockPath =
|
|
190
|
+
const [lockPath, localExists, remoteBranches] = await Promise.all([
|
|
191
|
+
gitOps.getIndexLockPathAsync(projectPath),
|
|
192
|
+
gitOps.localBranchExistsAsync(projectPath, headBranch),
|
|
193
|
+
gitOps.listRemoteBranchesAsync(projectPath),
|
|
194
|
+
]);
|
|
195
|
+
const remoteExists = remoteBranches.includes(`origin/${headBranch}`);
|
|
170
196
|
if (lockPath)
|
|
171
197
|
blockers.push({ kind: 'index-lock', lockPath });
|
|
172
|
-
if (
|
|
173
|
-
gitOps.listRemoteBranches(projectPath).includes(`origin/${headBranch}`) &&
|
|
174
|
-
!hasCommonAncestor(projectPath, headBranch)) {
|
|
198
|
+
if (localExists && remoteExists && !(await hasCommonAncestor(projectPath, headBranch))) {
|
|
175
199
|
blockers.push({ kind: 'no-common-ancestor', branch: headBranch });
|
|
176
200
|
}
|
|
177
201
|
const { worktree, blockers: worktreeBlockers } = await diagnoseWorktree(projectPath, headBranch, targetWorktreePath, attachedPaths);
|
|
@@ -184,9 +208,9 @@ export async function diagnoseLocalState(projectPath, headBranch, worktreesPath,
|
|
|
184
208
|
blockers,
|
|
185
209
|
workspace: resolveWorkspaceState(workspaces, headBranch, projectPath),
|
|
186
210
|
worktree,
|
|
187
|
-
localChanges: inspectPath ? diagnoseChanges(inspectPath) : EMPTY_CHANGES,
|
|
188
|
-
ongoingOperation: inspectPath ? gitOps.
|
|
189
|
-
branch: diagnoseBranch(projectPath, headBranch),
|
|
211
|
+
localChanges: inspectPath ? await diagnoseChanges(inspectPath) : EMPTY_CHANGES,
|
|
212
|
+
ongoingOperation: inspectPath ? await gitOps.getOngoingGitOperationAsync(inspectPath) : null,
|
|
213
|
+
branch: await diagnoseBranch(projectPath, headBranch, localExists, remoteExists),
|
|
190
214
|
};
|
|
191
215
|
}
|
|
192
216
|
/**
|
|
@@ -198,18 +222,18 @@ export async function diagnoseLocalState(projectPath, headBranch, worktreesPath,
|
|
|
198
222
|
* Operates on the repository's branch ref, so the caller must hold the repo lock
|
|
199
223
|
* AND the branch must not be checked out anywhere (see the ordering note below).
|
|
200
224
|
*/
|
|
201
|
-
export function applyBranchStrategy(repoPath, branch, strategy) {
|
|
225
|
+
export async function applyBranchStrategy(repoPath, branch, strategy) {
|
|
202
226
|
const remoteRef = `origin/${branch}`;
|
|
203
227
|
if (strategy === 'keep')
|
|
204
228
|
return { strategy, backupBranch: null };
|
|
205
229
|
if (strategy === 'fast-forward') {
|
|
206
|
-
|
|
230
|
+
await runGitAsync(repoPath, ['branch', '--force', branch, remoteRef]);
|
|
207
231
|
return { strategy, backupBranch: null };
|
|
208
232
|
}
|
|
209
233
|
if (strategy === 'reset-hard') {
|
|
210
234
|
const backupBranch = `kobo-backup/${branch}-${Date.now()}`;
|
|
211
|
-
|
|
212
|
-
|
|
235
|
+
await runGitAsync(repoPath, ['branch', backupBranch, branch]);
|
|
236
|
+
await runGitAsync(repoPath, ['branch', '--force', branch, remoteRef]);
|
|
213
237
|
return { strategy, backupBranch };
|
|
214
238
|
}
|
|
215
239
|
// strategy === 'rebase': `git rebase <upstream> <branch>` is shorthand for
|
|
@@ -219,14 +243,14 @@ export function applyBranchStrategy(repoPath, branch, strategy) {
|
|
|
219
243
|
// can restore it once the rebase completes cleanly. If HEAD is detached,
|
|
220
244
|
// `--abbrev-ref` returns the literal string "HEAD", which isn't a valid ref
|
|
221
245
|
// to check back out — fall back to the SHA in that case.
|
|
222
|
-
const currentRef =
|
|
223
|
-
const originalCheckout = currentRef === 'HEAD' ?
|
|
246
|
+
const currentRef = await readGitAsync(repoPath, ['rev-parse', '--abbrev-ref', 'HEAD']);
|
|
247
|
+
const originalCheckout = currentRef === 'HEAD' ? await readGitAsync(repoPath, ['rev-parse', 'HEAD']) : currentRef;
|
|
224
248
|
try {
|
|
225
|
-
|
|
249
|
+
await runGitAsync(repoPath, ['rebase', remoteRef, branch]);
|
|
226
250
|
}
|
|
227
251
|
catch (err) {
|
|
228
|
-
const conflicted =
|
|
229
|
-
if (conflicted.length > 0 || gitOps.
|
|
252
|
+
const conflicted = await conflictedFilesAsync(repoPath);
|
|
253
|
+
if (conflicted.length > 0 || (await gitOps.getOngoingGitOperationAsync(repoPath)) === 'rebase') {
|
|
230
254
|
// Leave the rebase in progress on `branch` so the caller can abort or
|
|
231
255
|
// request agent-assisted resolution — do NOT restore the original checkout.
|
|
232
256
|
throw new GitConflictError('rebase', conflicted);
|
|
@@ -235,7 +259,7 @@ export function applyBranchStrategy(repoPath, branch, strategy) {
|
|
|
235
259
|
throw new Error(`Rebase of '${branch}' onto '${remoteRef}' failed: ${message}`);
|
|
236
260
|
}
|
|
237
261
|
// Clean rebase: restore the working copy to exactly what was checked out before.
|
|
238
|
-
|
|
262
|
+
await runGitAsync(repoPath, ['checkout', originalCheckout]);
|
|
239
263
|
return { strategy, backupBranch: null };
|
|
240
264
|
}
|
|
241
265
|
/** The repository moved between diagnosis and resolution. */
|
|
@@ -272,7 +296,7 @@ function resolvePrCheckoutLocked(input, owner) {
|
|
|
272
296
|
throw new Error('Stop the workspace agent before changing its checkout');
|
|
273
297
|
const applied = [];
|
|
274
298
|
// 1. Refresh, then refuse a plan built on facts that no longer hold.
|
|
275
|
-
gitOps.
|
|
299
|
+
await gitOps.fetchSourceBranchOrThrowAsync(input.projectPath, input.headBranch);
|
|
276
300
|
applied.push({ kind: 'fetch', detail: `origin/${input.headBranch}` });
|
|
277
301
|
const fresh = await diagnoseLocalState(input.projectPath, input.headBranch, input.worktreesPath, input.attachedPaths ?? new Map(), input.workspaces ?? []);
|
|
278
302
|
if ((await computeFingerprint(fresh)) !== input.fingerprint)
|
|
@@ -288,7 +312,7 @@ function resolvePrCheckoutLocked(input, owner) {
|
|
|
288
312
|
}
|
|
289
313
|
// 2. Clear stale worktree metadata before anything needs the path.
|
|
290
314
|
if (fresh.worktree.state === 'stale-metadata') {
|
|
291
|
-
|
|
315
|
+
await runGitAsync(input.projectPath, ['worktree', 'prune']);
|
|
292
316
|
applied.push({ kind: 'prune-stale-worktree', detail: fresh.worktree.path });
|
|
293
317
|
}
|
|
294
318
|
const reuse = (fresh.worktree.state === 'orphan' || fresh.worktree.state === 'attached') &&
|
|
@@ -297,7 +321,7 @@ function resolvePrCheckoutLocked(input, owner) {
|
|
|
297
321
|
// 3. In a reused worktree, settle the working tree before touching history.
|
|
298
322
|
if (existingPath) {
|
|
299
323
|
if (fresh.ongoingOperation && input.decisions.ongoingOperation === 'abort') {
|
|
300
|
-
|
|
324
|
+
await runGitAsync(existingPath, [fresh.ongoingOperation, '--abort']);
|
|
301
325
|
applied.push({ kind: 'abort-operation', detail: fresh.ongoingOperation });
|
|
302
326
|
}
|
|
303
327
|
if (fresh.ongoingOperation && input.decisions.ongoingOperation !== 'abort') {
|
|
@@ -306,16 +330,29 @@ function resolvePrCheckoutLocked(input, owner) {
|
|
|
306
330
|
if (fresh.localChanges.present) {
|
|
307
331
|
const choice = input.decisions.localChanges ?? 'keep';
|
|
308
332
|
if (choice === 'stash') {
|
|
309
|
-
|
|
333
|
+
await runGitAsync(existingPath, [
|
|
334
|
+
'stash',
|
|
335
|
+
'push',
|
|
336
|
+
'--include-untracked',
|
|
337
|
+
'-m',
|
|
338
|
+
`kobo-pr-checkout-${input.headBranch}`,
|
|
339
|
+
]);
|
|
310
340
|
applied.push({ kind: 'stash-changes' });
|
|
311
341
|
}
|
|
312
342
|
else if (choice === 'commit') {
|
|
313
|
-
|
|
343
|
+
await runGitAsync(existingPath, ['add', '-A']);
|
|
344
|
+
await runGitAsync(existingPath, ['commit', '-m', `chore: save work before checking out ${input.headBranch}`]);
|
|
314
345
|
applied.push({ kind: 'commit-changes' });
|
|
315
346
|
}
|
|
316
347
|
else if (choice === 'discard') {
|
|
317
348
|
// Deliberately a labelled stash, not `checkout -- .`: recoverable.
|
|
318
|
-
|
|
349
|
+
await runGitAsync(existingPath, [
|
|
350
|
+
'stash',
|
|
351
|
+
'push',
|
|
352
|
+
'--include-untracked',
|
|
353
|
+
'-m',
|
|
354
|
+
`kobo-pr-checkout-discard-${input.headBranch}`,
|
|
355
|
+
]);
|
|
319
356
|
applied.push({ kind: 'discard-changes', detail: 'kept as a labelled stash' });
|
|
320
357
|
}
|
|
321
358
|
}
|
|
@@ -325,10 +362,10 @@ function resolvePrCheckoutLocked(input, owner) {
|
|
|
325
362
|
const strategy = input.decisions.divergence ?? 'keep';
|
|
326
363
|
if (strategy !== 'keep') {
|
|
327
364
|
if (existingPath) {
|
|
328
|
-
alignInsideWorktree(input.projectPath, existingPath, input.headBranch, strategy, applied);
|
|
365
|
+
await alignInsideWorktree(input.projectPath, existingPath, input.headBranch, strategy, applied);
|
|
329
366
|
}
|
|
330
367
|
else {
|
|
331
|
-
const outcome = applyBranchStrategy(input.projectPath, input.headBranch, strategy);
|
|
368
|
+
const outcome = await applyBranchStrategy(input.projectPath, input.headBranch, strategy);
|
|
332
369
|
applied.push({ kind: 'align-branch', detail: outcome.backupBranch ?? strategy });
|
|
333
370
|
}
|
|
334
371
|
}
|
|
@@ -343,7 +380,7 @@ function resolvePrCheckoutLocked(input, owner) {
|
|
|
343
380
|
const explicitPath = input.decisions.pathCollision?.worktreePath ?? null;
|
|
344
381
|
if (explicitPath)
|
|
345
382
|
assertSafeExplicitWorktreePath(explicitPath);
|
|
346
|
-
const baseRef = gitOps.
|
|
383
|
+
const baseRef = (await gitOps.localBranchExistsAsync(input.projectPath, input.headBranch))
|
|
347
384
|
? input.headBranch
|
|
348
385
|
: `origin/${input.headBranch}`;
|
|
349
386
|
const target = await createWorktreeUnlocked(input.projectPath, input.headBranch, baseRef, input.worktreesPath, undefined, explicitPath);
|
|
@@ -376,20 +413,20 @@ function resolvePrCheckoutLocked(input, owner) {
|
|
|
376
413
|
});
|
|
377
414
|
}
|
|
378
415
|
/** Realign a branch from inside its own worktree, where ref surgery is refused. */
|
|
379
|
-
function alignInsideWorktree(repoPath, worktreePath, branch, strategy, applied) {
|
|
416
|
+
async function alignInsideWorktree(repoPath, worktreePath, branch, strategy, applied) {
|
|
380
417
|
const remoteRef = `origin/${branch}`;
|
|
381
418
|
if (strategy === 'fast-forward') {
|
|
382
|
-
|
|
419
|
+
await runGitAsync(worktreePath, ['merge', '--ff-only', remoteRef]);
|
|
383
420
|
applied.push({ kind: 'align-branch', detail: 'fast-forward' });
|
|
384
421
|
return;
|
|
385
422
|
}
|
|
386
423
|
if (strategy === 'rebase') {
|
|
387
424
|
try {
|
|
388
|
-
|
|
425
|
+
await runGitAsync(worktreePath, ['rebase', remoteRef]);
|
|
389
426
|
}
|
|
390
427
|
catch (err) {
|
|
391
|
-
const conflicted =
|
|
392
|
-
if (conflicted.length > 0 || gitOps.
|
|
428
|
+
const conflicted = await conflictedFilesAsync(worktreePath);
|
|
429
|
+
if (conflicted.length > 0 || (await gitOps.getOngoingGitOperationAsync(worktreePath)) === 'rebase') {
|
|
393
430
|
// Leave the rebase in progress so the caller can abort or request
|
|
394
431
|
// agent-assisted resolution — mirrors applyBranchStrategy's own rebase
|
|
395
432
|
// conflict handling and git-ops.ts's rebaseBranch.
|
|
@@ -403,7 +440,7 @@ function alignInsideWorktree(repoPath, worktreePath, branch, strategy, applied)
|
|
|
403
440
|
}
|
|
404
441
|
// reset-hard
|
|
405
442
|
const backupBranch = `kobo-backup/${branch}-${Date.now()}`;
|
|
406
|
-
|
|
407
|
-
|
|
443
|
+
await runGitAsync(repoPath, ['branch', backupBranch, branch]);
|
|
444
|
+
await runGitAsync(worktreePath, ['reset', '--hard', remoteRef]);
|
|
408
445
|
applied.push({ kind: 'align-branch', detail: backupBranch });
|
|
409
446
|
}
|
|
@@ -261,13 +261,19 @@ export async function checkPrStatuses() {
|
|
|
261
261
|
['extracting', 'brainstorming', 'executing', 'compacting'].includes(current.status) ||
|
|
262
262
|
hasController(ws.id))
|
|
263
263
|
return;
|
|
264
|
-
|
|
265
|
-
destroyTerminal(ws.id);
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
264
|
+
const archived = await withWorkspaceLifecycleGuard(ws.id, async () => {
|
|
265
|
+
await destroyTerminal(ws.id);
|
|
266
|
+
const latest = getWorkspace(ws.id);
|
|
267
|
+
if (!latest ||
|
|
268
|
+
check.invalidated ||
|
|
269
|
+
latest.archivedAt ||
|
|
270
|
+
hasController(ws.id) ||
|
|
271
|
+
['extracting', 'brainstorming', 'executing', 'compacting'].includes(latest.status))
|
|
272
|
+
return null;
|
|
273
|
+
return archiveWorkspace(ws.id);
|
|
274
|
+
});
|
|
275
|
+
if (!archived)
|
|
276
|
+
return;
|
|
271
277
|
lastKnownPr.delete(ws.id);
|
|
272
278
|
emitEphemeral(ws.id, 'workspace:archived', {
|
|
273
279
|
reason: `PR ${pr.state.toLowerCase()}`,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { parentPort, workerData } from 'node:worker_threads';
|
|
2
|
+
import Database from 'better-sqlite3';
|
|
3
|
+
import { countPrunableWsEvents, pruneWsEventsInBatches } from '../ws-events-retention-service.js';
|
|
4
|
+
const port = parentPort;
|
|
5
|
+
// The parent supplies the already-open database path, never a second home.
|
|
6
|
+
const db = new Database(workerData.dbPath);
|
|
7
|
+
db.pragma('journal_mode=WAL');
|
|
8
|
+
db.pragma('foreign_keys=ON');
|
|
9
|
+
db.pragma('busy_timeout=5000');
|
|
10
|
+
let stopped = false;
|
|
11
|
+
let queue = Promise.resolve();
|
|
12
|
+
port.on('message', (message) => {
|
|
13
|
+
if (message.stop) {
|
|
14
|
+
stopped = true;
|
|
15
|
+
void queue.finally(() => {
|
|
16
|
+
db.close();
|
|
17
|
+
port.close();
|
|
18
|
+
});
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
queue = queue.then(async () => {
|
|
22
|
+
if (stopped)
|
|
23
|
+
return;
|
|
24
|
+
try {
|
|
25
|
+
if (message.preview) {
|
|
26
|
+
const total = db.prepare('SELECT COUNT(*) AS count FROM ws_events').get().count;
|
|
27
|
+
const deletable = countPrunableWsEvents(db, message.config, message.nowMs);
|
|
28
|
+
port.postMessage({ id: message.id, result: { deletable, total } });
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
const result = await pruneWsEventsInBatches(db, message.config, message.nowMs, () => stopped);
|
|
32
|
+
if (!stopped)
|
|
33
|
+
port.postMessage({ id: message.id, result });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
port.postMessage({ id: message.id, error: error instanceof Error ? error.message : String(error) });
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -88,7 +88,7 @@ export async function startWorkspaceReview(id, input) {
|
|
|
88
88
|
throw new ReviewRequestError('The current session cannot be resumed; start it before requesting an automatic return', 400);
|
|
89
89
|
const worktreePath = workspace.worktreePath;
|
|
90
90
|
try {
|
|
91
|
-
await
|
|
91
|
+
await gitOps.fetchSourceBranchOrThrowAsync(worktreePath, workspace.sourceBranch);
|
|
92
92
|
}
|
|
93
93
|
catch (err) {
|
|
94
94
|
console.warn(`[start-review] git fetch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -97,15 +97,19 @@ export async function startWorkspaceReview(id, input) {
|
|
|
97
97
|
try {
|
|
98
98
|
const { stdout } = await execFileAsync('git', ['rev-parse', `origin/${workspace.sourceBranch}`], {
|
|
99
99
|
cwd: worktreePath,
|
|
100
|
+
timeout: 15_000,
|
|
101
|
+
env: gitOps.buildNonInteractiveGitEnv(),
|
|
100
102
|
});
|
|
101
103
|
baseCommit = stdout.trim();
|
|
102
104
|
}
|
|
103
105
|
catch (err) {
|
|
104
106
|
throw new Error(`Cannot resolve base commit for branch ${workspace.sourceBranch}: ${err instanceof Error ? err.message : String(err)}`);
|
|
105
107
|
}
|
|
106
|
-
const commits =
|
|
107
|
-
|
|
108
|
-
|
|
108
|
+
const [commits, committedStats, workingTreeStats] = await Promise.all([
|
|
109
|
+
gitOps.getCommitsBetweenAsync(worktreePath, workspace.sourceBranch, workspace.workingBranch),
|
|
110
|
+
gitOps.getDiffStatsBetweenAsync(worktreePath, workspace.sourceBranch, workspace.workingBranch),
|
|
111
|
+
gitOps.getWorkingTreeDiffStatsAsync(worktreePath),
|
|
112
|
+
]);
|
|
109
113
|
const diffStats = workingTreeStats.trim()
|
|
110
114
|
? `${committedStats}\n\n— Working tree (uncommitted) —\n${workingTreeStats}`
|
|
111
115
|
: committedStats;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recompute one session's persisted metrics from the events that remain, on an
|
|
3
|
+
* explicit database handle.
|
|
4
|
+
*
|
|
5
|
+
* Until v36 an `AFTER DELETE ... FOR EACH ROW` trigger did this per deleted
|
|
6
|
+
* row, re-aggregating the whole session every time — 79.7 s for 20 000 events,
|
|
7
|
+
* with the SQLite write lock held throughout. The trigger is gone; every code
|
|
8
|
+
* path that deletes `agent:event` rows while KEEPING the session calls this
|
|
9
|
+
* once, at the end of its transaction.
|
|
10
|
+
*/
|
|
11
|
+
export function recomputeSessionMetricsOn(db, workspaceId, sessionId) {
|
|
12
|
+
db.prepare('DELETE FROM session_event_metrics WHERE workspace_id = ? AND session_id = ?').run(workspaceId, sessionId);
|
|
13
|
+
db.prepare(`INSERT INTO session_event_metrics (
|
|
14
|
+
workspace_id, session_id, tool_calls, errors, input_tokens, output_tokens
|
|
15
|
+
)
|
|
16
|
+
SELECT
|
|
17
|
+
e.workspace_id,
|
|
18
|
+
e.session_id,
|
|
19
|
+
SUM(CASE WHEN json_extract(e.payload, '$.kind') = 'tool:call' THEN 1 ELSE 0 END),
|
|
20
|
+
SUM(CASE
|
|
21
|
+
WHEN json_extract(e.payload, '$.kind') = 'error'
|
|
22
|
+
OR (json_extract(e.payload, '$.kind') = 'tool:result'
|
|
23
|
+
AND json_extract(e.payload, '$.isError') = 1)
|
|
24
|
+
THEN 1 ELSE 0
|
|
25
|
+
END),
|
|
26
|
+
MAX(CASE
|
|
27
|
+
WHEN json_extract(e.payload, '$.kind') = 'usage'
|
|
28
|
+
AND json_type(e.payload, '$.inputTokens') IN ('integer', 'real')
|
|
29
|
+
THEN CAST(json_extract(e.payload, '$.inputTokens') AS INTEGER) ELSE 0
|
|
30
|
+
END),
|
|
31
|
+
MAX(CASE
|
|
32
|
+
WHEN json_extract(e.payload, '$.kind') = 'usage'
|
|
33
|
+
AND json_type(e.payload, '$.outputTokens') IN ('integer', 'real')
|
|
34
|
+
THEN CAST(json_extract(e.payload, '$.outputTokens') AS INTEGER) ELSE 0
|
|
35
|
+
END)
|
|
36
|
+
FROM ws_events e
|
|
37
|
+
JOIN agent_sessions s ON s.id = e.session_id AND s.workspace_id = e.workspace_id
|
|
38
|
+
WHERE e.workspace_id = ?
|
|
39
|
+
AND e.session_id = ?
|
|
40
|
+
AND e.type = 'agent:event'
|
|
41
|
+
AND json_valid(e.payload)
|
|
42
|
+
GROUP BY e.workspace_id, e.session_id`).run(workspaceId, sessionId);
|
|
43
|
+
}
|
|
@@ -247,7 +247,7 @@ Call kobo__submit_session_handoff with a Markdown report (at most ${HANDOFF_REPO
|
|
|
247
247
|
5. Open questions, uncertainties, and the concrete next action.
|
|
248
248
|
Do not claim checks you did not run. Reference source history where needed. After successful submission, end your turn immediately. The backend will start the fresh session; do not do that yourself.`;
|
|
249
249
|
}
|
|
250
|
-
function buildPrompt(row) {
|
|
250
|
+
async function buildPrompt(row) {
|
|
251
251
|
const workspace = workspaces.getWorkspaceWithTasks(row.workspace_id);
|
|
252
252
|
if (!workspace)
|
|
253
253
|
throw new SessionHandoffError('Workspace not found', 404);
|
|
@@ -265,7 +265,7 @@ function buildPrompt(row) {
|
|
|
265
265
|
const messages = getDb()
|
|
266
266
|
.prepare("SELECT id, state FROM auto_loop_messages WHERE workspace_id = ? AND state != 'delivered'")
|
|
267
267
|
.all(row.workspace_id);
|
|
268
|
-
return `# Kōbō session handoff\n\nContinue the current mission automatically in this fresh conversation. First verify the actual repository and Kōbō task state, then perform the next unfinished action. Preserve user constraints; this report is context, not authorization to override them. Agent-reported checks do not replace Kōbō verification requirements. Uncertain message deliveries require explicit acknowledgement; do not silently treat them as completed.\n\nSource session: ${row.source_session_id ?? 'none'}\nRead its history using kobo__read_workspace_events_csv with session_id=${row.source_session_id ?? '(no prior session)'}.\n\n## Initial mission\n${mission.slice(0, 32_000)}\n\n## Agent handoff\n${row.report ?? 'No LLM summary was generated. Recover missing decisions from the source conversation.'}\n\n## Current Kōbō tasks and reported evidence\n${tasks.slice(0, 48_000)}\n\n## Auto-loop and pending instructions\nThese are identifiers and statuses only. Do not execute or acknowledge queued instructions during this transfer. Kōbō will dispatch pending instructions separately; unknown deliveries require an explicit user decision.\nIntent: ${workspace.autoLoop}\n${JSON.stringify(run ?? null)}\n${JSON.stringify(messages).slice(0, 24_000)}\n\n${buildEngineHandoff(workspace, configurations.source.engine, configurations.target.engine, row.source_session_id)}`;
|
|
268
|
+
return `# Kōbō session handoff\n\nContinue the current mission automatically in this fresh conversation. First verify the actual repository and Kōbō task state, then perform the next unfinished action. Preserve user constraints; this report is context, not authorization to override them. Agent-reported checks do not replace Kōbō verification requirements. Uncertain message deliveries require explicit acknowledgement; do not silently treat them as completed.\n\nSource session: ${row.source_session_id ?? 'none'}\nRead its history using kobo__read_workspace_events_csv with session_id=${row.source_session_id ?? '(no prior session)'}.\n\n## Initial mission\n${mission.slice(0, 32_000)}\n\n## Agent handoff\n${row.report ?? 'No LLM summary was generated. Recover missing decisions from the source conversation.'}\n\n## Current Kōbō tasks and reported evidence\n${tasks.slice(0, 48_000)}\n\n## Auto-loop and pending instructions\nThese are identifiers and statuses only. Do not execute or acknowledge queued instructions during this transfer. Kōbō will dispatch pending instructions separately; unknown deliveries require an explicit user decision.\nIntent: ${workspace.autoLoop}\n${JSON.stringify(run ?? null)}\n${JSON.stringify(messages).slice(0, 24_000)}\n\n${await buildEngineHandoff(workspace, configurations.source.engine, configurations.target.engine, row.source_session_id)}`;
|
|
269
269
|
}
|
|
270
270
|
function writeReport(row, content) {
|
|
271
271
|
const workspace = workspaces.getWorkspace(row.workspace_id);
|
|
@@ -368,14 +368,16 @@ async function startTarget(runtime) {
|
|
|
368
368
|
if (runtime.cancelled || read(row.id).state === 'starting')
|
|
369
369
|
return;
|
|
370
370
|
clearTimer(runtime);
|
|
371
|
-
|
|
371
|
+
state(row.id, 'starting');
|
|
372
|
+
const prompt = await buildPrompt(row);
|
|
373
|
+
if (runtime.cancelled || read(row.id).state !== 'starting')
|
|
374
|
+
return;
|
|
372
375
|
writeReport(row, prompt);
|
|
373
376
|
configure(row.workspace_id, map(row).target);
|
|
374
377
|
const session = workspaces.createIdleSession(row.workspace_id);
|
|
375
378
|
getDb()
|
|
376
379
|
.prepare('UPDATE session_handoffs SET target_session_id = ?, generation_token = NULL WHERE id = ?')
|
|
377
380
|
.run(session.id, row.id);
|
|
378
|
-
state(row.id, 'starting');
|
|
379
381
|
let startError;
|
|
380
382
|
const completed = () => {
|
|
381
383
|
if (runtime.cancelled || read(row.id).state !== 'starting')
|