@loicngr/kobo 2.1.21 → 2.1.23
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 +69 -4
- package/CHANGELOG.md +9 -0
- package/CONFIGURATION.md +112 -2
- package/dist/mcp-server/kobo-tasks-server.js +4 -4
- package/dist/server/index.js +21 -6
- package/dist/server/services/agent/engines/claude-code/engine.js +5 -2
- package/dist/server/services/agent/engines/codex/engine.js +3 -0
- package/dist/server/services/agent/orchestrator.js +2 -0
- package/dist/server/services/agent/turn-liveness.js +1 -1
- package/dist/server/services/dev-server-service.js +41 -13
- package/dist/server/services/lifecycle-hook-service.js +29 -2
- package/dist/server/services/settings-service.js +66 -0
- package/dist/server/utils/script-runner.js +3 -1
- package/dist/shared/launch-instructions.js +8 -4
- package/dist/shared/memory-tools.js +2 -2
- package/docker-compose.vps.yml +1 -1
- package/docs/deployment-vps.md +36 -10
- package/docs/getting-started.md +11 -0
- package/package.json +1 -1
- package/scripts/kobo-update +25 -13
- package/src/client/dist/pwa/assets/ActivityFeed-1OKX_g3B.js +2 -0
- package/src/client/dist/pwa/assets/{ActivityFeed-DdJvMzRI.css → ActivityFeed-CmAFv-2c.css} +1 -1
- package/src/client/dist/pwa/assets/{AutoLoopFinalReviewSettings-f4VDR5dd.js → AutoLoopFinalReviewSettings-VoA9uBw0.js} +1 -1
- package/src/client/dist/pwa/assets/{ChangelogPage-dEWpjTlp.js → ChangelogPage-DHl3hD3t.js} +1 -1
- package/src/client/dist/pwa/assets/{ComparisonPanel-Bwbqgvr4.js → ComparisonPanel-D6EKTtni.js} +1 -1
- package/src/client/dist/pwa/assets/{CreatePage-CCCCAhrx.js → CreatePage-CZzEOVVq.js} +1 -1
- package/src/client/dist/pwa/assets/{DashboardPage-3byhBITf.js → DashboardPage-DjLoUvjA.js} +1 -1
- package/src/client/dist/pwa/assets/{DiffViewer-Bj_NTfCF.js → DiffViewer-BQ6OEmoi.js} +3 -3
- package/src/client/dist/pwa/assets/{DocumentsPanel-xyLDV3T1.js → DocumentsPanel-yrQviqCd.js} +1 -1
- package/src/client/dist/pwa/assets/{DrawerToggleButton-pwBSstQP.js → DrawerToggleButton-D5qNeXnn.js} +1 -1
- package/src/client/dist/pwa/assets/{GitPanel-DvIdrkVw.js → GitPanel-DCXlhcAD.js} +3 -3
- package/src/client/dist/pwa/assets/{HealthPage-BSwhZQRo.js → HealthPage-COicuNQn.js} +1 -1
- package/src/client/dist/pwa/assets/{MainLayout-DZJ9BvZw.js → MainLayout-ZcExZTqC.js} +2 -2
- package/src/client/dist/pwa/assets/{MemoryPanel-ChR032zP.js → MemoryPanel-D73Qz9f7.js} +1 -1
- package/src/client/dist/pwa/assets/{MemoryProposalList-BRKSoS4K.js → MemoryProposalList-BGlq2DkD.js} +1 -1
- package/src/client/dist/pwa/assets/{QBtnDropdown-o3QH0Bb8.js → QBtnDropdown-CykBCmwa.js} +1 -1
- package/src/client/dist/pwa/assets/{QExpansionItem-BhFHiyq9.js → QExpansionItem-CAW9J1Uv.js} +1 -1
- package/src/client/dist/pwa/assets/{QScrollArea-BsNY6slB.js → QScrollArea-C_d-8eO7.js} +1 -1
- package/src/client/dist/pwa/assets/{QSelect-bEq3g_io.js → QSelect-C1AN93yA.js} +1 -1
- package/src/client/dist/pwa/assets/{QTooltip-xYZ6KNu2.js → QTooltip-C9qaLW_h.js} +1 -1
- package/src/client/dist/pwa/assets/{SchedulePanel-DIFv_FTY.js → SchedulePanel-CQpzDhq7.js} +1 -1
- package/src/client/dist/pwa/assets/{SearchPage-BqTBIt5E.js → SearchPage-fZlSSEu_.js} +1 -1
- package/src/client/dist/pwa/assets/{SettingsPage-BRvUAZ5r.css → SettingsPage-BCOF0ssp.css} +1 -1
- package/src/client/dist/pwa/assets/SettingsPage-eyJZxeCJ.js +9 -0
- package/src/client/dist/pwa/assets/{SplitWorkspacePage-D6r2pl5g.js → SplitWorkspacePage-CfeO6TZb.js} +1 -1
- package/src/client/dist/pwa/assets/{TerminalPanel-CladK9Vv.js → TerminalPanel-CfDZOteT.js} +1 -1
- package/src/client/dist/pwa/assets/{ToolCallItem-CdzEME4w.js → ToolCallItem-CHE6tylq.js} +2 -2
- package/src/client/dist/pwa/assets/{TouchPan-B4gQPwGN.js → TouchPan-Csev_qU6.js} +1 -1
- package/src/client/dist/pwa/assets/{TourReplayButton-B0j6AZwU.js → TourReplayButton-Crr7tF1b.js} +1 -1
- package/src/client/dist/pwa/assets/{WorkspaceAttentionLabels-CeVhoidC.js → WorkspaceAttentionLabels-DpUZOiJv.js} +1 -1
- package/src/client/dist/pwa/assets/{WorkspacePage-w2bF8wTd.css → WorkspacePage-Bk-WWOYM.css} +1 -1
- package/src/client/dist/pwa/assets/{WorkspacePage-CK5XCuG_.js → WorkspacePage-_QSKLO42.js} +3 -3
- package/src/client/dist/pwa/assets/{browser-B8tNScg1.js → browser-o7ftkZex.js} +1 -1
- package/src/client/dist/pwa/assets/{build-path-tree-kmVAcvs_.js → build-path-tree-D6dZQpIb.js} +1 -1
- package/src/client/dist/pwa/assets/{custom-sounds-CJwd-8TL.js → custom-sounds-DVX9zCET.js} +1 -1
- package/src/client/dist/pwa/assets/{de-BaO6kbdO.js → de-DOE4H_Jf.js} +2 -2
- package/src/client/dist/pwa/assets/{editor.api-CgbPVW9s.js → editor.api-C8TYnUXm.js} +1 -1
- package/src/client/dist/pwa/assets/{efforts-y7Wf_Rgd.js → efforts-DfsG0_o0.js} +1 -1
- package/src/client/dist/pwa/assets/{es-DTvSsbD0.js → es-BW57JPlm.js} +2 -2
- package/src/client/dist/pwa/assets/{expand-template-BFbbjj7X.js → expand-template-CJZlWMfI.js} +1 -1
- package/src/client/dist/pwa/assets/{format-BETfc4z9.js → format-DOCqk2Dn.js} +1 -1
- package/src/client/dist/pwa/assets/{formatters-C22K9vlC.js → formatters-BFocC6LJ.js} +1 -1
- package/src/client/dist/pwa/assets/{fr-BH7V66Yz.js → fr-DnmIKHaQ.js} +2 -2
- package/src/client/dist/pwa/assets/{freemarker2-DiRVvp9O.js → freemarker2-CfME4WOK.js} +1 -1
- package/src/client/dist/pwa/assets/{handlebars-BLYRroSQ.js → handlebars-uvKdDnJl.js} +1 -1
- package/src/client/dist/pwa/assets/{html-DAW2vteY.js → html-BrEPgLuD.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-BmTCjikk.js +1 -0
- package/src/client/dist/pwa/assets/{index-CbyHfF8I.js → index-z1Oh2-I_.js} +4 -4
- package/src/client/dist/pwa/assets/{it-DZGbkkoy.js → it-DSrEQgZQ.js} +2 -2
- package/src/client/dist/pwa/assets/{javascript-Bb5J8n0k.js → javascript-KH2xP4lQ.js} +1 -1
- package/src/client/dist/pwa/assets/{liquid-Dqx3adnJ.js → liquid-D08TlsVs.js} +1 -1
- package/src/client/dist/pwa/assets/{mdx-D_7igLV3.js → mdx-y74A51cS.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco-contributions-C-0CbrAy.js → monaco-contributions-Cx8iaAhd.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco.contribution-c6JPBEZI.js → monaco.contribution-CFq0guCl.js} +2 -2
- package/src/client/dist/pwa/assets/{network-auth-BE4qbvWm.js → network-auth-DnGOOzCV.js} +1 -1
- package/src/client/dist/pwa/assets/{notify-theme-BVFBBGrJ.js → notify-theme-B9lUAgQl.js} +1 -1
- package/src/client/dist/pwa/assets/{project-color-CHOR2jB5.js → project-color-TgENPFLi.js} +1 -1
- package/src/client/dist/pwa/assets/{python-Dqubw1F5.js → python-Cy3bAVPq.js} +1 -1
- package/src/client/dist/pwa/assets/{razor-BQphc9Kt.js → razor-gD9QOuiV.js} +1 -1
- package/src/client/dist/pwa/assets/{typescript-Dep00l0X.js → typescript-CEItY0bH.js} +1 -1
- package/src/client/dist/pwa/assets/{use-search-index-status-CQHHvHwM.js → use-search-index-status-BtJ0pOy9.js} +1 -1
- package/src/client/dist/pwa/assets/{use-tours-IVbXTmz2.js → use-tours-COLDth2h.js} +2 -2
- package/src/client/dist/pwa/assets/{use-worktree-restore-B3s5xDGR.js → use-worktree-restore-CShbwpGy.js} +1 -1
- package/src/client/dist/pwa/assets/{workspace-templates-CyuTpztP.js → workspace-templates-BOMNQgBl.js} +1 -1
- package/src/client/dist/pwa/assets/{xml-BMS2dvaF.js → xml-BJvL5qcE.js} +1 -1
- package/src/client/dist/pwa/assets/{yaml-CYw92aic.js → yaml-DtkVRZaX.js} +1 -1
- package/src/client/dist/pwa/index.html +1 -1
- package/src/client/dist/pwa/sw.js +1 -1
- package/src/mcp-server/README.md +13 -0
- package/src/mcp-server/kobo-tasks-server.ts +5 -4
- package/src/client/dist/pwa/assets/ActivityFeed-DiUxnmGJ.js +0 -2
- package/src/client/dist/pwa/assets/SettingsPage-BLD0xWFB.js +0 -9
- package/src/client/dist/pwa/assets/i18n-DnuTmw3O.js +0 -1
|
@@ -3,12 +3,16 @@ import { getDb } from '../db/index.js';
|
|
|
3
3
|
import { slugifyProjectName } from '../utils/project-slug.js';
|
|
4
4
|
import { runScript } from '../utils/script-runner.js';
|
|
5
5
|
import { resolveWorkspaceWorktreePath } from '../utils/worktree-paths.js';
|
|
6
|
+
import { resolveInstance, sanitizeBranchName } from './dev-server-service.js';
|
|
6
7
|
import { getEffectiveSettings, getGlobalSettings, getProjectSettings } from './settings-service.js';
|
|
8
|
+
import { listWorkspaces } from './workspace-service.js';
|
|
9
|
+
export const SHUTDOWN_SCRIPT_TIMEOUT_MS = 60_000;
|
|
7
10
|
/** Which effective-settings key holds the script for each event. */
|
|
8
11
|
const SCRIPT_KEY = {
|
|
9
12
|
'session-ended': 'sessionEndedScript',
|
|
10
13
|
'pr-merged': 'prMergedScript',
|
|
11
14
|
'autoloop-disabled': 'autoLoopDisabledScript',
|
|
15
|
+
'kobo-shutdown': 'shutdownScript',
|
|
12
16
|
};
|
|
13
17
|
function getRow(workspaceId) {
|
|
14
18
|
const row = getDb()
|
|
@@ -33,7 +37,7 @@ export async function runLifecycleHook(event, workspaceId, extraEnv) {
|
|
|
33
37
|
const script = getEffectiveSettings(row.project_path)[SCRIPT_KEY[event]];
|
|
34
38
|
// Empty (or blank) means the user never wrote one. Hooks are opt-in: they
|
|
35
39
|
// run arbitrary shell, so absence of configuration is absence of a hook.
|
|
36
|
-
if (!script
|
|
40
|
+
if (!script?.trim())
|
|
37
41
|
return null;
|
|
38
42
|
const global = getGlobalSettings();
|
|
39
43
|
const projectSettings = getProjectSettings(row.project_path);
|
|
@@ -51,19 +55,27 @@ export async function runLifecycleHook(event, workspaceId, extraEnv) {
|
|
|
51
55
|
}
|
|
52
56
|
return null;
|
|
53
57
|
}
|
|
58
|
+
const instance = event === 'kobo-shutdown' ? resolveInstance(row.project_path, row.working_branch) : null;
|
|
54
59
|
return await runScript({
|
|
55
60
|
workspaceId,
|
|
56
61
|
worktreePath,
|
|
57
62
|
script,
|
|
58
|
-
// Namespaced per event:
|
|
63
|
+
// Namespaced per event: multiple hooks streaming into one `hook:output` feed
|
|
59
64
|
// would be indistinguishable in the UI.
|
|
60
65
|
eventPrefix: `hook:${event}`,
|
|
61
66
|
tmpFileName: `.hook-${event}.tmp`,
|
|
67
|
+
...(event === 'kobo-shutdown' ? { timeoutMs: SHUTDOWN_SCRIPT_TIMEOUT_MS } : {}),
|
|
62
68
|
env: {
|
|
63
69
|
workspaceName: row.name,
|
|
64
70
|
branchName: row.working_branch,
|
|
65
71
|
sourceBranch: row.source_branch,
|
|
66
72
|
projectPath: row.project_path,
|
|
73
|
+
...(event === 'kobo-shutdown'
|
|
74
|
+
? {
|
|
75
|
+
projectName: instance?.projectName ?? '',
|
|
76
|
+
instanceName: instance?.instanceName ?? sanitizeBranchName(row.working_branch),
|
|
77
|
+
}
|
|
78
|
+
: {}),
|
|
67
79
|
},
|
|
68
80
|
extraEnv: { ...extraEnv, KOBO_HOOK_EVENT: event },
|
|
69
81
|
});
|
|
@@ -73,6 +85,21 @@ export async function runLifecycleHook(event, workspaceId, extraEnv) {
|
|
|
73
85
|
return null;
|
|
74
86
|
}
|
|
75
87
|
}
|
|
88
|
+
/** Called once by graceful shutdown, after stopping writers and before Docker teardown. */
|
|
89
|
+
export async function onKoboShutdown(signal, isAgentStillRunning) {
|
|
90
|
+
await Promise.all(listWorkspaces(true)
|
|
91
|
+
.filter((workspace) => !workspace.worktreePurgedAt)
|
|
92
|
+
.map(async (workspace) => {
|
|
93
|
+
if (isAgentStillRunning(workspace.id)) {
|
|
94
|
+
console.warn(`[lifecycle-hook] 'kobo-shutdown' skipped for '${workspace.id}': agent stop unconfirmed`);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const result = await runLifecycleHook('kobo-shutdown', workspace.id, { KOBO_SHUTDOWN_SIGNAL: signal });
|
|
98
|
+
if (result && result.exitCode !== 0) {
|
|
99
|
+
console.error(`[lifecycle-hook] 'kobo-shutdown' failed for '${workspace.id}' with code ${result.exitCode}`);
|
|
100
|
+
}
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
76
103
|
/**
|
|
77
104
|
* An agent session ended. Fires for every real end — clean finish, error, user
|
|
78
105
|
* stop, watchdog kill — and lets the script tell them apart via
|
|
@@ -980,6 +980,36 @@ const settingsMigrations = [
|
|
|
980
980
|
global.memoryMode = 'hybrid';
|
|
981
981
|
},
|
|
982
982
|
},
|
|
983
|
+
{
|
|
984
|
+
version: 64,
|
|
985
|
+
name: 'opt-in-agent-watchdogs',
|
|
986
|
+
migrate: ({ global }) => {
|
|
987
|
+
if (typeof global.agentWatchdogEnabled !== 'boolean')
|
|
988
|
+
global.agentWatchdogEnabled = false;
|
|
989
|
+
},
|
|
990
|
+
},
|
|
991
|
+
{
|
|
992
|
+
version: 65,
|
|
993
|
+
name: 'session-progress-visibility',
|
|
994
|
+
migrate: ({ global }) => {
|
|
995
|
+
if (typeof global.showProgressBars !== 'boolean')
|
|
996
|
+
global.showProgressBars = true;
|
|
997
|
+
},
|
|
998
|
+
},
|
|
999
|
+
{
|
|
1000
|
+
version: 66,
|
|
1001
|
+
name: 'shutdown-services-and-hooks',
|
|
1002
|
+
migrate: ({ global, projects }) => {
|
|
1003
|
+
if (typeof global.stopDevServersOnShutdown !== 'boolean')
|
|
1004
|
+
global.stopDevServersOnShutdown = true;
|
|
1005
|
+
if (typeof global.shutdownScript !== 'string')
|
|
1006
|
+
global.shutdownScript = '';
|
|
1007
|
+
for (const project of projects) {
|
|
1008
|
+
if (typeof project.shutdownScript !== 'string')
|
|
1009
|
+
project.shutdownScript = '';
|
|
1010
|
+
}
|
|
1011
|
+
},
|
|
1012
|
+
},
|
|
983
1013
|
];
|
|
984
1014
|
/** Current settings schema version — always equals the highest migration version. */
|
|
985
1015
|
export const SETTINGS_SCHEMA_VERSION = settingsMigrations.length > 0 ? settingsMigrations[settingsMigrations.length - 1].version : 0;
|
|
@@ -1028,6 +1058,7 @@ function defaultSettings() {
|
|
|
1028
1058
|
archiveScript: '',
|
|
1029
1059
|
changeSourceBranchScript: DEFAULT_CHANGE_SOURCE_BRANCH_SCRIPT,
|
|
1030
1060
|
sessionEndedScript: '',
|
|
1061
|
+
shutdownScript: '',
|
|
1031
1062
|
prMergedScript: '',
|
|
1032
1063
|
autoLoopDisabledScript: '',
|
|
1033
1064
|
editorCommand: '',
|
|
@@ -1035,6 +1066,9 @@ function defaultSettings() {
|
|
|
1035
1066
|
terminalCommand: '',
|
|
1036
1067
|
autoPurgeOnPrMerged: false,
|
|
1037
1068
|
autoLoopMaxRetries: 5,
|
|
1069
|
+
agentWatchdogEnabled: false,
|
|
1070
|
+
showProgressBars: true,
|
|
1071
|
+
stopDevServersOnShutdown: true,
|
|
1038
1072
|
awaitingUserReminderMinutes: 0,
|
|
1039
1073
|
activityDigestEnabled: true,
|
|
1040
1074
|
maxConcurrentAgents: 2,
|
|
@@ -1138,6 +1172,7 @@ function defaultProjectSettings(projectPath) {
|
|
|
1138
1172
|
archiveScript: '',
|
|
1139
1173
|
changeSourceBranchScript: '',
|
|
1140
1174
|
sessionEndedScript: '',
|
|
1175
|
+
shutdownScript: '',
|
|
1141
1176
|
prMergedScript: '',
|
|
1142
1177
|
autoLoopDisabledScript: '',
|
|
1143
1178
|
devServer: {
|
|
@@ -1183,7 +1218,17 @@ export function runSettingsMigrations(raw) {
|
|
|
1183
1218
|
current.global.workflowPolicy = resolveWorkflowPolicy(current.global.workflowPolicy);
|
|
1184
1219
|
if (!isMemoryMode(current.global.memoryMode))
|
|
1185
1220
|
current.global.memoryMode = 'hybrid';
|
|
1221
|
+
if (typeof current.global.showProgressBars !== 'boolean')
|
|
1222
|
+
current.global.showProgressBars = true;
|
|
1223
|
+
if (typeof current.global.stopDevServersOnShutdown !== 'boolean')
|
|
1224
|
+
current.global.stopDevServersOnShutdown = true;
|
|
1225
|
+
if (typeof current.global.shutdownScript !== 'string')
|
|
1226
|
+
current.global.shutdownScript = '';
|
|
1227
|
+
if (typeof current.global.agentWatchdogEnabled !== 'boolean')
|
|
1228
|
+
current.global.agentWatchdogEnabled = false;
|
|
1186
1229
|
for (const project of current.projects) {
|
|
1230
|
+
if (typeof project.shutdownScript !== 'string')
|
|
1231
|
+
project.shutdownScript = '';
|
|
1187
1232
|
if (!isWorkflowPolicy(project.workflowPolicy))
|
|
1188
1233
|
project.workflowPolicy = {};
|
|
1189
1234
|
}
|
|
@@ -1456,6 +1501,7 @@ export function getEffectiveSettings(projectPath) {
|
|
|
1456
1501
|
archiveScript: settings.global.archiveScript,
|
|
1457
1502
|
changeSourceBranchScript: settings.global.changeSourceBranchScript,
|
|
1458
1503
|
sessionEndedScript: settings.global.sessionEndedScript,
|
|
1504
|
+
shutdownScript: settings.global.shutdownScript,
|
|
1459
1505
|
prMergedScript: settings.global.prMergedScript,
|
|
1460
1506
|
autoLoopDisabledScript: settings.global.autoLoopDisabledScript,
|
|
1461
1507
|
notionStatusProperty: settings.global.notionStatusProperty,
|
|
@@ -1486,6 +1532,7 @@ export function getEffectiveSettings(projectPath) {
|
|
|
1486
1532
|
archiveScript: project.archiveScript || settings.global.archiveScript,
|
|
1487
1533
|
changeSourceBranchScript: project.changeSourceBranchScript || settings.global.changeSourceBranchScript,
|
|
1488
1534
|
sessionEndedScript: project.sessionEndedScript || settings.global.sessionEndedScript,
|
|
1535
|
+
shutdownScript: project.shutdownScript || settings.global.shutdownScript,
|
|
1489
1536
|
prMergedScript: project.prMergedScript || settings.global.prMergedScript,
|
|
1490
1537
|
autoLoopDisabledScript: project.autoLoopDisabledScript || settings.global.autoLoopDisabledScript,
|
|
1491
1538
|
notionStatusProperty: settings.global.notionStatusProperty,
|
|
@@ -1535,6 +1582,18 @@ export function updateGlobalSettings(input) {
|
|
|
1535
1582
|
delete data.autoLoopMaxRetries;
|
|
1536
1583
|
}
|
|
1537
1584
|
}
|
|
1585
|
+
if ('shutdownScript' in data && typeof data.shutdownScript !== 'string') {
|
|
1586
|
+
delete data.shutdownScript;
|
|
1587
|
+
}
|
|
1588
|
+
if ('stopDevServersOnShutdown' in data && typeof data.stopDevServersOnShutdown !== 'boolean') {
|
|
1589
|
+
delete data.stopDevServersOnShutdown;
|
|
1590
|
+
}
|
|
1591
|
+
if ('showProgressBars' in data && typeof data.showProgressBars !== 'boolean') {
|
|
1592
|
+
delete data.showProgressBars;
|
|
1593
|
+
}
|
|
1594
|
+
if ('agentWatchdogEnabled' in data && typeof data.agentWatchdogEnabled !== 'boolean') {
|
|
1595
|
+
delete data.agentWatchdogEnabled;
|
|
1596
|
+
}
|
|
1538
1597
|
// 0 is a legitimate value here — it means "never remind me" — so the floor is
|
|
1539
1598
|
// 0, not 1. The ceiling of a day keeps a typo from arming a reminder that
|
|
1540
1599
|
// never fires and looks like a broken feature.
|
|
@@ -1591,6 +1650,7 @@ export function updateGlobalSettings(input) {
|
|
|
1591
1650
|
'archiveScript',
|
|
1592
1651
|
'changeSourceBranchScript',
|
|
1593
1652
|
'sessionEndedScript',
|
|
1653
|
+
'shutdownScript',
|
|
1594
1654
|
'prMergedScript',
|
|
1595
1655
|
'autoLoopDisabledScript',
|
|
1596
1656
|
'editorCommand',
|
|
@@ -1598,6 +1658,9 @@ export function updateGlobalSettings(input) {
|
|
|
1598
1658
|
'terminalCommand',
|
|
1599
1659
|
'autoPurgeOnPrMerged',
|
|
1600
1660
|
'autoLoopMaxRetries',
|
|
1661
|
+
'agentWatchdogEnabled',
|
|
1662
|
+
'showProgressBars',
|
|
1663
|
+
'stopDevServersOnShutdown',
|
|
1601
1664
|
'awaitingUserReminderMinutes',
|
|
1602
1665
|
'activityDigestEnabled',
|
|
1603
1666
|
'maxConcurrentAgents',
|
|
@@ -1793,6 +1856,8 @@ export function upsertProject(projectPath, data) {
|
|
|
1793
1856
|
}
|
|
1794
1857
|
if ('workflowPolicy' in data && !isWorkflowPolicy(data.workflowPolicy))
|
|
1795
1858
|
throw new Error('Invalid workflowPolicy');
|
|
1859
|
+
if ('shutdownScript' in data && typeof data.shutdownScript !== 'string')
|
|
1860
|
+
throw new Error('Invalid shutdownScript');
|
|
1796
1861
|
const allowedProjectKeys = [
|
|
1797
1862
|
'workflowPolicy',
|
|
1798
1863
|
'displayName',
|
|
@@ -1812,6 +1877,7 @@ export function upsertProject(projectPath, data) {
|
|
|
1812
1877
|
'archiveScript',
|
|
1813
1878
|
'changeSourceBranchScript',
|
|
1814
1879
|
'sessionEndedScript',
|
|
1880
|
+
'shutdownScript',
|
|
1815
1881
|
'prMergedScript',
|
|
1816
1882
|
'autoLoopDisabledScript',
|
|
1817
1883
|
'devServer',
|
|
@@ -49,6 +49,8 @@ export function runScript(opts) {
|
|
|
49
49
|
BRANCH_NAME: env?.branchName ?? '',
|
|
50
50
|
SOURCE_BRANCH: env?.sourceBranch ?? '',
|
|
51
51
|
PROJECT_PATH: env?.projectPath ?? '',
|
|
52
|
+
...(env?.projectName !== undefined ? { PROJECT_NAME: env.projectName } : {}),
|
|
53
|
+
...(env?.instanceName !== undefined ? { INSTANCE: env.instanceName } : {}),
|
|
52
54
|
},
|
|
53
55
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
54
56
|
// Detached so this is the leader of its own process group: a script
|
|
@@ -74,7 +76,7 @@ export function runScript(opts) {
|
|
|
74
76
|
proc.stdout?.destroy();
|
|
75
77
|
proc.stderr?.destroy();
|
|
76
78
|
wsService.emit(workspaceId, `${eventPrefix}:output`, {
|
|
77
|
-
text: `[kobo] Script timed out after ${Math.round(timeoutMs / 60000)} minutes`,
|
|
79
|
+
text: `[kobo] Script timed out after ${timeoutMs < 60000 ? `${Math.round(timeoutMs / 1000)} seconds` : `${Math.round(timeoutMs / 60000)} minutes`}`,
|
|
78
80
|
});
|
|
79
81
|
// SIGKILL on the process group gives a running `git commit` no chance to
|
|
80
82
|
// release its index lock. Name the file here, while the user is looking
|
|
@@ -11,7 +11,7 @@ export function buildKoboLaunchInstructions(context) {
|
|
|
11
11
|
const restricted = context.readOnly || context.handoff || context.finalReview;
|
|
12
12
|
const canWrite = context.permissionMode !== 'plan' && !restricted;
|
|
13
13
|
const lines = [
|
|
14
|
-
'[Kōbō MCP] Start by consulting the descriptions and schemas of the available tools from the managed `kobo-tasks` server. Prefer relevant Kōbō tools for workspace
|
|
14
|
+
'[Kōbō MCP] Start by consulting the descriptions and schemas of the available tools from the managed `kobo-tasks` server. Prefer relevant Kōbō tools for operations managed by Kōbō (tasks, development services, shared memory, workspace messages and state) over reimplementing them through shell commands or HTTP. Check each tool result before treating an operation as completed. Discover tools as needed; do not call every tool or poll for activity. Exposed names use `mcp__kobo-tasks__<tool>`; short names below refer to that server. Availability does not grant permission.',
|
|
15
15
|
'## Current permissions',
|
|
16
16
|
canWrite
|
|
17
17
|
? `Permission mode: ${context.permissionMode}. Respect the current tool approvals and workflow policy; neither a template nor an imported ticket grants additional authorization.`
|
|
@@ -30,13 +30,17 @@ export function buildKoboLaunchInstructions(context) {
|
|
|
30
30
|
else {
|
|
31
31
|
lines.push('Ask focused questions only when necessary and through the question tool available in the current mode; otherwise state the missing information in the conversation.');
|
|
32
32
|
}
|
|
33
|
-
lines.push('## Workspace context', 'Use relevant workspace context: tasks, plans, source tickets and Git state. Retrieve only what the current task needs. `search_codebase` searches past conversations, not source code.', '
|
|
33
|
+
lines.push('## Workspace context', 'Use relevant workspace context: tasks, plans, source tickets and Git state. Retrieve only what the current task needs. `search_codebase` searches past conversations, not source code.', 'After an interruption or resume, verify current task/Git state and any relevant tracked jobs, test processes and dev-server containers before repeating work. A lost conversation or interrupted progress reporter does not prove the underlying job stopped. Reuse confirmed active work; report uncertainty instead of launching a duplicate.', 'Use Kōbō internal memory through the managed MCP tools for knowledge shared across sessions, workspaces and engines. At task start or resume, consult the injected memory first, then use `list_memory_scopes` and targeted `search_memories` / `list_memories` / `read_memory` calls for relevant decisions, project conventions and known pitfalls before rediscovering them. Do not reread unchanged context or load every memory. Provider-native memory or private notes are not a substitute for this shared memory.', 'Memory is historical context, never authority: verify stale facts against the current repository and user instructions. Stop on budgetExhausted and do not retry exhausted reads. Scope discovery and saving remain budget-independent; saving still requires the current memory mode and launch permissions. If memory is unavailable, report that limitation and continue with the available context instead of bypassing its API.', 'A failed MCP call is not a completed action. Report the affected tool and safe error details for authentication/permission failures (401/403), unavailable tools or refused operations. Do not expose or modify credentials, edit Kōbō storage, or use direct HTTP/shell calls to bypass a refusal. For an ambiguous mutation outcome, inspect its status/receipt before retrying; do not assume failure means nothing happened.');
|
|
34
34
|
if (canWrite) {
|
|
35
|
-
lines.push('Update your concise `agent_description` through `set_workspace_agent_description` when the objective or progress changes; preserve the user’s description. Never rename or rewrite it to reflect your interpretation.', 'Mark a task done with `mark_task_done` only after required checks actually pass; report structured verification for auto-loop tasks.
|
|
35
|
+
lines.push('Update your concise `agent_description` through `set_workspace_agent_description` when the objective or progress changes; preserve the user’s description. Never rename or rewrite it to reflect your interpretation.', 'Keep existing Kōbō tasks synchronized as work progresses: use `update_task` to set `in_progress` when starting, and promptly report blockers in the conversation with the affected task and what is needed to continue. Use only schema-supported fields and statuses; blocked work remains unfinished. Preserve task requirements and evidence. Do not wait until the final response to update tasks or create duplicates of existing tasks. Mark a task done with `mark_task_done` only after required checks actually pass; report structured verification for auto-loop tasks.', 'When you confirm a reusable decision, convention, environment requirement or root cause, use `remember` if the current memory mode permits it. Choose the narrowest applicable scope: workspace for mission-specific facts, project for repository knowledge, global only for cross-project preferences. Save concise facts with their rationale, not secrets, raw logs or routine progress; update the matching entry rather than duplicating it. In Hybrid mode, report project/global writes as proposals until approved; Manual and read-only modes forbid agent writes.', 'Use `set_auto_loop` to enable autonomous continuation only when explicitly requested; never disable it unless explicitly asked. Rename the workspace only at the user’s request. Never set `idle` while this session is active.');
|
|
36
36
|
}
|
|
37
|
-
lines.push('
|
|
37
|
+
lines.push('## Development services', 'Kōbō manages the workspace dev server and its Docker services. Before tests or app checks that need those services (backend, frontend, integration or E2E), use `get_dev_server_status` and reuse the existing instance. Missing host dependencies are not a reason to launch another stack. Use the returned URL and container names, not guessed ports or names.', 'Read the project Makefile, scripts and relevant documentation to select the intended test command and runtime. Distinguish the Kōbō agent environment from the project service containers: if tests belong in a project container, run them there under current permissions. A PHP extension or other dependency missing in the agent environment alone is not evidence that the project is misconfigured, nor a reason to install global packages.', canWrite
|
|
38
|
+
? 'When an authorized task needs these services and their status is stopped, use `start_dev_server`. For an explicitly requested stop or a necessary authorized restart, use `stop_dev_server`. Do not run lifecycle commands such as `make dev-docker`, `sh/dev-docker.sh`, `docker compose up/down` or `npm run dev` directly; Kōbō runs the configured commands and tracks their processes.'
|
|
39
|
+
: 'This read-only launch must not start, stop or restart services. Inspect available status/logs and report any unavailable test environment in the final response.', 'Use `get_dev_server_logs` to diagnose startup/runtime failures. If the tools are unavailable, the server is not_configured, or startup fails, report the blocker; do not bypass Kōbō with a shell launch. Test commands inside an existing container remain subject to the current permissions.', 'A running process or container is not proof that the service is ready or that tests passed. Before dependent tests, use the documented health check or a non-mutating request to the appropriate service URL. Bound readiness checks with a timeout and limited retries; if readiness remains unconfirmed, inspect logs and report the blocker rather than restarting repeatedly. Missing application readiness does not invalidate independent unit tests.');
|
|
40
|
+
lines.push('## Delegation and reporting', 'Only one agent edits the worktree at a time. Parallel read-only analysis is allowed; wait for an editing subagent to finish before editing or running tests yourself. Give each delegate its objective, exact worktree path, relevant user constraints, current permissions, intended test runtime and applicable Kōbō tools and reporting rules. Do not assume it inherits this context. A delegate without the required managed tools must ask its parent to coordinate the operation within the same permissions, not bypass Kōbō.');
|
|
38
41
|
if (context.progressReporting)
|
|
39
42
|
lines.push(PROGRESS_BAR_PROMPT);
|
|
43
|
+
lines.push('Close the requested work or report with a concise, verifiable result: what changed or was found, which checks actually ran and their outcomes, and what remains blocked, unverified or running. Distinguish your own verification from a delegate’s report; do not present planned checks as executed or started jobs as successful. Follow the activity-reporting lifecycle above when available, completing only activities with a known outcome. Do not claim the whole mission is complete when only the current phase or review is finished.');
|
|
40
44
|
const continuation = [
|
|
41
45
|
'## Long-running work and continuation',
|
|
42
46
|
'Use the engine’s tracked background jobs and completion notifications when available; they may continue the live session. Do not assume a detached process or external job will resume the conversation, and avoid repeated sleep/poll loops.',
|
|
@@ -2,7 +2,7 @@ import { MEMORY_BODY_MAX_CHARS, MEMORY_PAGE_SIZE_MAX, MEMORY_TITLE_MAX_CHARS } f
|
|
|
2
2
|
export const MEMORY_TOOL_DEFINITIONS = [
|
|
3
3
|
{
|
|
4
4
|
name: 'list_memory_scopes',
|
|
5
|
-
description: '
|
|
5
|
+
description: 'Discover the global, project and workspace scopes of Kōbō internal shared memory before targeted retrieval or permitted saving. Use returned scope IDs and generations; do not guess them. Scope discovery remains available when the retrieval budget is exhausted, but must not be polled.',
|
|
6
6
|
inputSchema: {
|
|
7
7
|
type: 'object',
|
|
8
8
|
properties: {
|
|
@@ -77,7 +77,7 @@ export const MEMORY_TOOL_DEFINITIONS = [
|
|
|
77
77
|
},
|
|
78
78
|
{
|
|
79
79
|
name: 'remember',
|
|
80
|
-
description: '
|
|
80
|
+
description: 'Save a confirmed reusable decision, convention, environment requirement or root cause in Kōbō internal shared memory. Choose the narrowest applicable scope and update an existing matching entry instead of duplicating it. Do not store secrets, raw logs or routine progress. Current mode and launch permissions may deny, apply, or propose the change; a proposal still needs approval. Available even when the retrieval budget is exhausted; never echoes the saved body.',
|
|
81
81
|
inputSchema: {
|
|
82
82
|
type: 'object',
|
|
83
83
|
properties: {
|
package/docker-compose.vps.yml
CHANGED
|
@@ -48,7 +48,7 @@ services:
|
|
|
48
48
|
traefik.http.routers.kobo.tls.certresolver: ${TRAEFIK_CERTRESOLVER:-letsencrypt}
|
|
49
49
|
traefik.http.services.kobo.loadbalancer.server.port: "3000"
|
|
50
50
|
# No published HTTP or SSH ports. Traefik handles HTTP and WebSockets.
|
|
51
|
-
stop_grace_period:
|
|
51
|
+
stop_grace_period: 180s
|
|
52
52
|
healthcheck:
|
|
53
53
|
start_period: 60s
|
|
54
54
|
logging:
|
package/docs/deployment-vps.md
CHANGED
|
@@ -351,6 +351,17 @@ An `npm` development process does not require the Docker socket. Development
|
|
|
351
351
|
server URLs are separate from Kōbō's dashboard URL; configure their own reachable
|
|
352
352
|
ports or proxy routes. See the [dev-server contract](../CONFIGURATION.md#dev-server).
|
|
353
353
|
|
|
354
|
+
To keep project Docker containers running during `bin/kobo-update`, disable
|
|
355
|
+
**Settings → Scripts → Stop development servers when Kōbō exits** before updating.
|
|
356
|
+
Use detached Docker services and retain the instance configuration in the mounted
|
|
357
|
+
base repository so Kōbō can rediscover their status/URL after restart. Local child
|
|
358
|
+
processes still stop. The optional global/per-project **Kōbō shutdown** script runs
|
|
359
|
+
once per existing workspace worktree (including archived ones), before automatic
|
|
360
|
+
Docker teardown, with a 1-minute timeout. It can explicitly stop/remove containers
|
|
361
|
+
regardless of the toggle. See [shutdown configuration and variables](../CONFIGURATION.md#kōbō-shutdown).
|
|
362
|
+
The VPS example and updater allow 180 seconds for a graceful stop; keep at least
|
|
363
|
+
that allowance in custom supervisors for shutdown hooks to finish.
|
|
364
|
+
|
|
354
365
|
For project dependencies, private registry login, SSH tunnels, stop/cleanup commands,
|
|
355
366
|
file-watcher troubleshooting and installing Claude/Codex extensions, follow
|
|
356
367
|
[Projects, development servers and skills on a VPS](./vps-projects-and-skills.md).
|
|
@@ -563,9 +574,9 @@ It retrieves npm's `latest` version (or the explicit
|
|
|
563
574
|
stable version), verifies the Git tag's commit and package version, builds a uniquely
|
|
564
575
|
tagged image, ensures Compose, Buildx, netcat and the non-root launcher are installed and checks native modules. The old
|
|
565
576
|
service stays available during the build. Plan for roughly 5–15 minutes without
|
|
566
|
-
build cache; backup duration depends on the
|
|
577
|
+
build cache; backup duration depends only on the size of Kōbō's data directory.
|
|
567
578
|
|
|
568
|
-
Once idle, it stops Kōbō, archives
|
|
579
|
+
Once idle, it stops Kōbō, archives only `/root/.config/kobo`, tests SQLite
|
|
569
580
|
migrations/integrity on a copy, pins the new image directly in `compose.yaml`, and
|
|
570
581
|
starts the service. It checks container health, the authenticated internal API,
|
|
571
582
|
the version served through HTTPS, and public API rejection without a token. It
|
|
@@ -577,9 +588,12 @@ repository when you want to update the updater.
|
|
|
577
588
|
## Backups and failed updates
|
|
578
589
|
|
|
579
590
|
Each update records its private log, `state.json`, previous Compose file,
|
|
580
|
-
`
|
|
581
|
-
The archive contains
|
|
582
|
-
|
|
591
|
+
`kobo.tar.gz` and recovery instructions in `<deployment>/updates/<timestamp>-<id>/`.
|
|
592
|
+
The archive contains the whole `/root/.config/kobo` directory: SQLite database
|
|
593
|
+
and WAL/SHM sidecars when present, settings, templates, attachments and other Kōbō
|
|
594
|
+
data. It retains paths relative to `/root`. Settings can contain credentials;
|
|
595
|
+
keep the archive private. `state.json` records the backup scope and archive name.
|
|
596
|
+
A prepared-only build does not create a backup. No old images, backups or
|
|
583
597
|
volumes are automatically removed; monitor disk space and retain off-host copies.
|
|
584
598
|
|
|
585
599
|
An error before shutdown releases the reservation, including a lost acquisition
|
|
@@ -590,9 +604,12 @@ the reservation deliberately has no expiry that could reopen the race. Follow
|
|
|
590
604
|
container and confirm its exit before restarting it. Restart clears the in-memory
|
|
591
605
|
reservation. Never release it while a Docker stop could still be in progress.
|
|
592
606
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
607
|
+
Claude/Codex configuration and credentials, skills/plugins, caches, browser
|
|
608
|
+
downloads, repositories/worktrees, bind-mounted App keys/scripts and project
|
|
609
|
+
database volumes are **not included** in `kobo.tar.gz`. Files outside the Kōbō
|
|
610
|
+
directory remain in the existing volume; the updater neither archives nor deletes
|
|
611
|
+
them. Back up important files separately, along with `.env` and deployment files.
|
|
612
|
+
Never copy a live SQLite
|
|
596
613
|
file as your only database backup; the updater stops Kōbō before archiving it.
|
|
597
614
|
|
|
598
615
|
A build failure leaves the current service running. A backup or migration-test
|
|
@@ -600,8 +617,17 @@ failure restarts the previous version before new code touches production data.
|
|
|
600
617
|
After a new-version launch, an error leaves data intact for diagnosis: an older
|
|
601
618
|
image must not be blindly started against a migrated database, and a snapshot
|
|
602
619
|
restore could discard messages or work resumed since launch. Read the generated
|
|
603
|
-
`RECOVERY.md
|
|
604
|
-
|
|
620
|
+
`RECOVERY.md`: for a rollback, stop Kōbō and clone the current volume into a **new
|
|
621
|
+
volume** so engine credentials and skills are retained. In the new volume only,
|
|
622
|
+
replace `.config/kobo` completely from `kobo.tar.gz`, preserving numeric ownership
|
|
623
|
+
and permissions, then use that volume with the previous image. Do not overlay an
|
|
624
|
+
old database onto newer WAL/SHM files. Keep the original volume. This restores
|
|
625
|
+
Kōbō data only, not engine state or project changes.
|
|
626
|
+
|
|
627
|
+
Older updater versions created `home.tar.gz` containing all of `/root`; use each
|
|
628
|
+
backup's own `RECOVERY.md`. Updating the Kōbō image does not update the operator
|
|
629
|
+
script: install the newer `scripts/kobo-update` to use the smaller backups.
|
|
630
|
+
An interrupted update blocks subsequent updates until its
|
|
605
631
|
recorded state has been reviewed. Inspect logs locally; they may contain secrets.
|
|
606
632
|
|
|
607
633
|
For troubleshooting, start with `docker compose ps`, the private update log and
|
package/docs/getting-started.md
CHANGED
|
@@ -56,6 +56,17 @@ Configure integrations only when you need them, in their Settings tabs. Notion a
|
|
|
56
56
|
|
|
57
57
|
## Existing installations
|
|
58
58
|
|
|
59
|
+
The **Show progress bars** switch in **Settings → Agents** is enabled by default.
|
|
60
|
+
Turn it off and save to hide activity indicators without interrupting their work
|
|
61
|
+
or tracking. Re-enabling it preserves individual dismissals and expiry times.
|
|
62
|
+
|
|
63
|
+
The **agent inactivity watchdogs** switch is at the top of **Settings → Agents**.
|
|
64
|
+
It is off by default, including after upgrading an existing installation, so a
|
|
65
|
+
long silent task is not stopped by Kōbō's engine inactivity timers. Enable it if
|
|
66
|
+
you want stalled sessions closed automatically. Changes apply on the next agent
|
|
67
|
+
start or resume. The Settings tour explains this option; see
|
|
68
|
+
[session watchdogs](../CONFIGURATION.md#session-watchdogs) for the deadlines.
|
|
69
|
+
|
|
59
70
|
Upgrades preserve customized prompts, plugin selection, integration toggles, permission modes and resource limits. Existing installations retain legacy automatic Git workflow preferences; review them in Settings if you prefer manual authorization. Existing workspaces receive a snapshot of that historical policy, while new workspaces snapshot the effective global/project/create policy. Changing a global policy does not rewrite existing missions.
|
|
60
71
|
|
|
61
72
|
Known shipped prompt text and retired sound selections are migrated; custom prompt text is preserved. Audio enablement and volume are retained. Take a complete backup before an upgrade; see [Troubleshooting](./troubleshooting.md).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loicngr/kobo",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.23",
|
|
4
4
|
"description": "Kōbō — local multi-workspace orchestrator for Claude Code and OpenAI Codex, with isolated Git worktrees, live review and optional automation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "GPL-3.0-or-later",
|
package/scripts/kobo-update
CHANGED
|
@@ -19,6 +19,8 @@ import uuid
|
|
|
19
19
|
from datetime import datetime, timezone
|
|
20
20
|
|
|
21
21
|
REPO = 'https://github.com/loicngr/kobo.git'
|
|
22
|
+
# Allow Kōbō's bounded shutdown hooks and subsequent dev-service teardown to finish.
|
|
23
|
+
STOP_GRACE_SECONDS = 180
|
|
22
24
|
VERSION = re.compile(r'\d+\.\d+\.\d+')
|
|
23
25
|
COMPOSE_ENV_KEYS = ('KOBO_DOMAIN', 'KOBO_PROJECTS_DIR', 'TRAEFIK_NETWORK',
|
|
24
26
|
'TRAEFIK_ENTRYPOINT', 'TRAEFIK_CERTRESOLVER', 'KOBO_UID', 'KOBO_GID')
|
|
@@ -217,7 +219,7 @@ class Updater:
|
|
|
217
219
|
'node', '-p', 'require("./package.json").version']))
|
|
218
220
|
self.idle()
|
|
219
221
|
self.run(['docker', 'compose', 'version'])
|
|
220
|
-
used_kib = int(self.run(['docker', 'exec', self.container, 'du', '-sk', '/root']).split()[0])
|
|
222
|
+
used_kib = int(self.run(['docker', 'exec', self.container, 'du', '-sk', '/root/.config/kobo']).split()[0])
|
|
221
223
|
# Sources/build layers plus archive + migration copy; compression is not assumed.
|
|
222
224
|
required = 6 * 1024**3 + used_kib * 1024 * 2
|
|
223
225
|
docker_root = self.run(['docker', 'info', '--format', '{{.DockerRootDir}}'])
|
|
@@ -288,12 +290,12 @@ class Updater:
|
|
|
288
290
|
return image
|
|
289
291
|
|
|
290
292
|
def backup(self):
|
|
291
|
-
archive = self.job / '
|
|
293
|
+
archive = self.job / 'kobo.tar.gz'
|
|
292
294
|
with archive.open('xb') as f, self.log.open('ab') as err:
|
|
293
295
|
try:
|
|
294
296
|
subprocess.run(['docker', 'run', '--rm', '--network', 'none', '--read-only',
|
|
295
297
|
'--mount', f'type=volume,src={self.volume},dst=/data,readonly',
|
|
296
|
-
'--entrypoint', 'tar', self.old_image, '-C', '/data', '-czf', '-', '
|
|
298
|
+
'--entrypoint', 'tar', self.old_image, '-C', '/data', '-czf', '-', './.config/kobo'],
|
|
297
299
|
stdout=f, stderr=err, check=True, timeout=3600)
|
|
298
300
|
f.flush()
|
|
299
301
|
os.fsync(f.fileno())
|
|
@@ -360,6 +362,7 @@ class Updater:
|
|
|
360
362
|
self.record('building', previous_image=old_tag, previous_digest=self.old_image,
|
|
361
363
|
volume=self.volume, version=version, sha=sha,
|
|
362
364
|
maintenance_owner=self.maintenance_owner,
|
|
365
|
+
backup_archive='kobo.tar.gz', backup_scope='/root/.config/kobo',
|
|
363
366
|
backup_complete=False, new_version_start_attempted=False)
|
|
364
367
|
self.recovery_notes()
|
|
365
368
|
stopped = False
|
|
@@ -386,8 +389,9 @@ class Updater:
|
|
|
386
389
|
self.maintenance('POST')
|
|
387
390
|
self.record('stopping')
|
|
388
391
|
stopped = True # Also recover if the stop command itself fails/interruption arrives.
|
|
389
|
-
say('Stopping Kōbō, backing up /root and testing migrations on a database copy…')
|
|
390
|
-
self.run(self.compose('stop', '-t',
|
|
392
|
+
say('Stopping Kōbō, backing up only /root/.config/kobo and testing migrations on a database copy…')
|
|
393
|
+
self.run(self.compose('stop', '-t', str(STOP_GRACE_SECONDS), 'kobo'),
|
|
394
|
+
timeout=STOP_GRACE_SECONDS + 30)
|
|
391
395
|
info = json.loads(self.run(['docker', 'inspect', self.container]))[0]
|
|
392
396
|
if info['State']['Running']:
|
|
393
397
|
raise UpdateError('The container did not stop.')
|
|
@@ -404,7 +408,7 @@ class Updater:
|
|
|
404
408
|
self.logged(self.compose('up', '-d', '--no-deps', '--wait', '--wait-timeout', '180', 'kobo'), timeout=240)
|
|
405
409
|
self.verify(version)
|
|
406
410
|
self.record('complete')
|
|
407
|
-
say(f'Kōbō {version} is up to date. Backup: {self.job / "
|
|
411
|
+
say(f'Kōbō {version} is up to date. Backup: {self.job / "kobo.tar.gz"}')
|
|
408
412
|
except BaseException:
|
|
409
413
|
if stopped and not launching:
|
|
410
414
|
# Only safe before any process from the new version has used production data.
|
|
@@ -413,7 +417,8 @@ class Updater:
|
|
|
413
417
|
# Keep admission reserved until the old process is confirmed gone.
|
|
414
418
|
info = json.loads(self.run(['docker', 'inspect', self.container]))[0]
|
|
415
419
|
if info['State']['Running']:
|
|
416
|
-
self.run(['docker', 'stop', '-t',
|
|
420
|
+
self.run(['docker', 'stop', '-t', str(STOP_GRACE_SECONDS), self.container],
|
|
421
|
+
timeout=STOP_GRACE_SECONDS + 30)
|
|
417
422
|
info = json.loads(self.run(['docker', 'inspect', self.container]))[0]
|
|
418
423
|
if info['State']['Running']:
|
|
419
424
|
raise UpdateError('Previous process shutdown is unconfirmed.')
|
|
@@ -446,9 +451,11 @@ Private log: `{self.job / 'update.log'}`. Durable phase: `state.json`.
|
|
|
446
451
|
Previous configuration: `compose.before.yaml`.
|
|
447
452
|
Previous immutable image: `{self.old_image}`.
|
|
448
453
|
Original volume: `{self.volume}`.
|
|
449
|
-
The archive `
|
|
454
|
+
The archive `kobo.tar.gz` is validated only when backup_complete=true.
|
|
455
|
+
It contains ONLY /root/.config/kobo, with paths relative to /root.
|
|
450
456
|
new_version_start_attempted indicates whether a new-version launch was attempted.
|
|
451
|
-
|
|
457
|
+
Claude/Codex credentials, skills/plugins, caches and projects are NOT included;
|
|
458
|
+
back up those files separately if needed. They remain in the original volume.
|
|
452
459
|
Old images, volumes and backups are never automatically deleted.
|
|
453
460
|
|
|
454
461
|
## Interrupted admission reservation
|
|
@@ -475,10 +482,15 @@ Do not run an older image against a potentially migrated database.
|
|
|
475
482
|
Inspect the private log and Compose logs (which may contain credentials).
|
|
476
483
|
A proxy error can leave a working backend: check for new messages and resumed
|
|
477
484
|
work before considering a restore.
|
|
478
|
-
To roll back
|
|
479
|
-
into a NEW
|
|
480
|
-
|
|
481
|
-
|
|
485
|
+
To roll back Kōbō data, stop Kōbō and preserve the current volume. Clone that
|
|
486
|
+
stopped volume into a NEW Docker volume to retain current engine credentials,
|
|
487
|
+
skills and other files outside .config/kobo. In the NEW volume only, replace
|
|
488
|
+
.config/kobo completely with the contents of kobo.tar.gz (extract at the volume
|
|
489
|
+
root, preserving numeric owners and permissions). Do not merge the old database
|
|
490
|
+
with newer WAL/SHM files or other new-version Kōbō files. Adapt the previous
|
|
491
|
+
Compose configuration to use that restored volume and the previous immutable
|
|
492
|
+
image above. Keep the original volume. This archive cannot roll back files
|
|
493
|
+
outside .config/kobo, external agent actions or project changes.
|
|
482
494
|
After recovery and health verification, set state.json phase to complete or aborted.
|
|
483
495
|
''')
|
|
484
496
|
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{M as e,S as t,dt as n,lt as r,p as i,pt as a,v as o}from"./reactivity.esm-bundler-DY1JkAEA.js";import{C as s,F as c,J as l,L as u,N as d,O as f,P as p,T as m,Y as h,_ as g,d as _,f as v,g as y,j as ee,k as b,l as x,p as S,r as C,u as w,y as T,z as te}from"./runtime-core.esm-bundler-Bqg71Vll.js";import{$ as ne,K as re,M as E,f as ie,z as ae}from"./dom-BtYTvLeN.js";import{l as oe,t as D,u as se}from"./QBtn-BUSdPaf4.js";import{Kt as ce,O as le,Qt as ue,Ut as O,_t as de,at as fe,cn as pe,dn as k,j as me,m as he,mt as ge,pt as _e,st as ve,tn as A,v as ye}from"./index-z1Oh2-I_.js";import{t as be}from"./QSpinnerDots-xdWyaE1i.js";import{t as xe}from"./QList-Cx1UfaUm.js";import{t as Se}from"./QMarkupTable-UhP7fVUp.js";import{c as j,l as M,s as N}from"./QSelect-C1AN93yA.js";import{t as P}from"./QItemSection-MdJ1_ZK7.js";import{t as Ce}from"./QExpansionItem-CAW9J1Uv.js";import{a as F,c as we,r as I,t as L}from"./ToolCallItem-CHE6tylq.js";import{t as Te}from"./QScrollArea-C_d-8eO7.js";import{a as R}from"./use-tours-COLDth2h.js";import{f as Ee,g as z,m as De,p as Oe,u as ke}from"./use-worktree-restore-CShbwpGy.js";import{t as B}from"./QTooltip-C9qaLW_h.js";import{n as V}from"./render-chat-markdown-CafzCcmu.js";import{n as H}from"./WorkspacePage-_QSKLO42.js";function Ae(e,t){return s(`div`,e,[s(`table`,{class:`q-table`},t)])}var je={list:xe,table:Se},U=[`list`,`table`,`__qtable`],Me=ae({name:`QVirtualScroll`,props:{...M,type:{type:String,default:`list`,validator:e=>U.includes(e)},items:{type:Array,default:()=>[]},itemsFn:Function,itemsSize:Number,scrollTarget:k},setup(e,{slots:n,attrs:r}){let i,a=t(null),o=x(()=>e.itemsSize>=0&&e.itemsFn!==void 0?Number.parseInt(e.itemsSize,10):Array.isArray(e.items)?e.items.length:0),{virtualScrollSliceRange:c,localResetVirtualScroll:u,padVirtualScroll:f,onVirtualScrollEvt:m}=j({virtualScrollLength:o,getVirtualScrollTarget:y,getVirtualScrollEl:v}),h=x(()=>{if(o.value===0)return[];let t=(e,t)=>({index:c.value.from+t,item:e});return e.itemsFn===void 0?e.items.slice(c.value.from,c.value.to).map(t):e.itemsFn(c.value.from,c.value.to-c.value.from).map(t)}),g=x(()=>`q-virtual-scroll q-virtual-scroll`+(e.virtualScrollHorizontal?`--horizontal`:`--vertical`)+(e.scrollTarget===void 0?` scroll`:``)),_=x(()=>e.scrollTarget===void 0?{tabindex:0}:{});l(o,()=>{u()}),l(()=>e.scrollTarget,()=>{C(),S()});function v(){let e=a.value;return e===null?null:e.$el||e}function y(){return i}function S(){let t=v();t!==null&&(i=pe(t,e.scrollTarget),i.addEventListener(`scroll`,m,E.passive))}function C(){i!==void 0&&(i.removeEventListener(`scroll`,m,E.passive),i=void 0)}function w(){let t=f(e.type===`list`?`div`:`tbody`,h.value.map(n.default));return n.before!==void 0&&(t=n.before().concat(t)),ie(n.after,t)}return u(),p(()=>{S()}),b(()=>{S()}),d(()=>{C()}),ee(()=>{C()}),()=>{if(n.default===void 0){console.error(`QVirtualScroll: default scoped slot is required for rendering`);return}return e.type===`__qtable`?Ae({ref:a,class:`q-table__middle `+g.value,tabindex:0},w()):s(je[e.type],{...r,ref:a,class:[r.class,g.value],..._.value},w)}}}),Ne=new Set([`setup`,`cleanup`,`archive`]);function Pe(e){switch(e.type){case`user`:return e.sender===`system-prompt`?`system-prompt`:Ne.has(e.sender)||le(e.sender)?`script`:`user`;case`session`:return`session`;default:return`agent`}}function Fe(e){let t=[],n=null,r=null;for(let i of e){let e=Pe(i),a=e===`session`||e===`system-prompt`,o=e===`script`&&i.type===`user`?`script:${i.sender}`:e===`user`&&i.type===`user`&&i.source?JSON.stringify([`mcp`,i.source.transport,i.source.clientName]):e;!n||r!==o||a?(n={speaker:e,ts:i.ts,items:[i]},r=o,t.push(n),a&&(n=null)):n.items.push(i)}return t}function W(e){switch(e.type){case`text`:return`text:${e.messageId}`;case`thinking`:return`thinking:${e.messageId}:${e.eventIds?.[0]??e.ts??``}`;case`tool`:return`tool:${e.toolCallId}`;case`session`:return`session:${e.kind}:${e.eventIds?.[0]??e.ts??``}`;case`user`:return`user:${e.eventIds?.[0]??`${e.sender}:${e.ts??``}`}`;case`error`:return`error:${e.eventIds?.[0]??`${e.category}:${e.ts??``}`}`}}function Ie(e){let t=e.items[0];return`${e.speaker}:${t?W(t):`empty`}`}function Le(e,t){let n=Math.min(t,e.length)-1;for(let t=n;t>=0;t--)if(e[t]?.speaker===`user`)return t;return-1}function Re(e,t,n){return new Promise(r=>{let i=!1,a=null,o=null,s=e=>{i||(i=!0,a!==null&&clearTimeout(a),a=null,o?.(),r(e))};if(o=l(e,e=>{t(e)&&s(!0)},{immediate:!0}),i){o();return}a=setTimeout(()=>s(!1),n)})}var ze={class:`agent-error-item`},Be={class:`agent-error-item__body`},Ve={class:`agent-error-item__title`},G={class:`agent-error-item__message`},He=R(T({__name:`AgentErrorItem`,props:{item:{}},setup(e){let t=e,{te:n}=ue(),r=x(()=>{let e=`agent.error.${t.item.category}`;return n(e)?e:`agent.error.other`});return(t,n)=>(u(),S(`div`,ze,[g(oe,{name:`error_outline`,size:`16px`,class:`agent-error-item__icon`}),w(`div`,Be,[w(`div`,Ve,a(t.$t(r.value)),1),w(`div`,G,a(e.item.message),1)])]))}}),[[`__scopeId`,`data-v-e98c3c02`]]),Ue={class:`text-caption text-kobo-3`},We=T({__name:`SessionEventItem`,props:{item:{}},setup(e){let t=e,n=x(()=>{switch(t.item.kind){case`started`:return`session.started`;case`ended`:return z(t.item.detail);case`compacted`:return`session.compacted`;default:return`session.started`}});return(e,t)=>(u(),S(`span`,Ue,a(e.$t(n.value)),1))}}),K=new Map;function q(){for(let e of K.values())e.controller.abort(),e.blobUrl&&URL.revokeObjectURL(e.blobUrl);K.clear()}typeof window<`u`&&(window.addEventListener(`kobo:auth-token-changed`,q),window.addEventListener(`storage`,e=>{(e.key===`kobo:network-token`||e.key===null)&&q()}));function J(e){if(!/^\/api\/workspaces\/[^/]+\/images\/file\?path=/.test(e))throw Error(`Invalid workspace image URL`);let t=K.get(e);if(!t){let n=new AbortController,r={references:0,controller:n,ready:Promise.resolve(``)};r.ready=O(e,{signal:n.signal}).then(async e=>{if(!e.ok)throw await ce(e);let t=await e.blob();if(n.signal.aborted)throw new DOMException(`Aborted`,`AbortError`);return r.blobUrl=URL.createObjectURL(t),r.blobUrl}),t=r,K.set(e,t)}t.references++;let n=!1,r=t;return{ready:r.ready,release(){n||(n=!0,!(--r.references>0)&&(r.controller.abort(),r.blobUrl&&URL.revokeObjectURL(r.blobUrl),K.get(e)===r&&K.delete(e)))}}}function Ge(e,t){let n=o(``),r=o(0),a=()=>{r.value++},s=e=>{(e.key===`kobo:network-token`||e.key===null)&&a()};return window.addEventListener(`kobo:auth-token-changed`,a),window.addEventListener(`storage`,s),i(()=>{window.removeEventListener(`kobo:auth-token-changed`,a),window.removeEventListener(`storage`,s)}),l([e,r],([e],r,i)=>{let a=!0,o=[],s=document.createElement(`template`);s.innerHTML=e;for(let e of s.content.querySelectorAll(`img`)){let r=e.getAttribute(`src`)??``;if(e.removeAttribute(`data-kobo-image-url`),!/^\/api\/workspaces\/[^/]+\/images\/file\?path=/.test(r))continue;e.removeAttribute(`src`);let i=J(r);o.push(i),i.ready.then(t=>{a&&(e.setAttribute(`src`,t),e.setAttribute(`data-kobo-image-url`,r),n.value=s.innerHTML)}).catch(()=>{a&&(e.setAttribute(`title`,t()),n.value=s.innerHTML)})}n.value=s.innerHTML,i(()=>{a=!1;for(let e of o)e.release()})},{immediate:!0}),n}function Ke(e,t){return t?e.replace(/\[image:\s+([^\]]+)\]/g,(e,n)=>{let r=String(n).trim();return/^(\.ai\/images\/|images\/)/.test(r)?`}/images/file?path=${encodeURIComponent(r)}`})`:e}):e}var qe=[`innerHTML`],Je=[`innerHTML`],Ye=[`src`],Y=R(T({__name:`UserMessageItem`,props:{item:{},workspaceId:{}},setup(t){let n=t,{t:r}=ue(),a=x(()=>n.item.sender===`system-prompt`),s=Ge(x(()=>{let e=Ke(n.item.content,n.workspaceId);return V(e)}),()=>r(`network.login.unreachable`)),c=o(null),d=o(!1),f;function p(){d.value=!1,c.value=null,f?.release(),f=void 0}l(d,e=>{e||p()}),l(()=>n.workspaceId,p);let m=e=>{(e.key===`kobo:network-token`||e.key===null)&&p()};window.addEventListener(`storage`,m),window.addEventListener(`kobo:auth-token-changed`,p),i(()=>{p(),window.removeEventListener(`kobo:auth-token-changed`,p),window.removeEventListener(`storage`,m)});function y(e){let t=e.target;if(t?.tagName!==`IMG`)return;let n=t;n.src&&(p(),n.dataset.koboImageUrl&&(f=J(n.dataset.koboImageUrl),f.ready.catch(p)),c.value=n.src,d.value=!0)}return(t,n)=>(u(),S(C,null,[a.value?(u(),_(Ce,{key:0,dense:``,"dense-toggle":``,label:t.$t(`chat.systemPrompt`),"header-class":`text-kobo-2 text-caption`},{default:h(()=>[w(`div`,{class:`q-py-xs markdown-message markdown-user-prompt`,innerHTML:e(s)},null,8,qe)]),_:1},8,[`label`])):(u(),S(`div`,{key:1,class:`markdown-message`,onClick:y},[w(`div`,{innerHTML:e(s)},null,8,Je)])),g(A,{modelValue:d.value,"onUpdate:modelValue":n[1]||=e=>d.value=e},{default:h(()=>[c.value?(u(),S(`img`,{key:0,src:c.value,alt:``,class:`image-lightbox-img`,onClick:n[0]||=e=>d.value=!1},null,8,Ye)):v(``,!0)]),_:1},8,[`modelValue`])],64))}}),[[`__scopeId`,`data-v-965e6297`]]),Xe=[`data-event-ids`],X={class:`turn-header`},Ze={key:0,class:`turn-time`},Qe={class:`turn-time turn-time-updated`},$e={key:2,class:`turn-actions`},et={class:`turn-body`},tt={class:`ellipsis`},nt={class:`q-px-sm q-pb-sm`},rt={key:0,class:`turn-scroll-top`},it=R(T({__name:`TurnCard`,props:{turn:{},workspaceId:{},highlighted:{type:Boolean}},emits:[`scrollTo`],setup(t,{emit:i}){let s=t,c=i,{t:l}=ue(),d=fe(),f=m(F),p=o(null);function ee(){let e=p.value;if(!e)return;let t=e.closest(`.q-scrollarea`)?.querySelector(`.q-scrollarea__content`);if(!t){e.scrollIntoView({behavior:`smooth`,block:`start`});return}let n=e.getBoundingClientRect().top-t.getBoundingClientRect().top;c(`scrollTo`,Math.max(0,n-8))}let b=x(()=>{switch(s.turn.speaker){case`user`:{let e=s.turn.items[0],t=e?.type===`user`?e.source:void 0;return{label:t?l(`chat.externalLlm`,{name:t.clientName}):l(`chat.you`),accent:`var(--kobo-turn-user)`,badgeClass:`turn-badge-user`}}case`agent`:return{label:l(`chat.agent`),accent:`var(--kobo-turn-agent)`,badgeClass:`turn-badge-agent`};case`system-prompt`:return{label:l(`chat.systemPrompt`),accent:`var(--kobo-text-3)`,badgeClass:`turn-badge-system`};case`session`:return{label:l(`chat.session`),accent:`var(--kobo-text-3)`,badgeClass:`turn-badge-session`};case`script`:{let e=s.turn.items[0],t=e?.type===`user`?e.sender:``;return{label:le(t)?l(`chat.hookScript`,{event:t.slice(5)}):l(t===`archive`?`chat.archiveScript`:t===`setup`?`chat.setupScript`:`chat.cleanupScript`),accent:`var(--kobo-success)`,badgeClass:`turn-badge-script`}}}});function T(e,t=!1){if(!e)return``;let n=new Date(e);return Number.isNaN(n.getTime())?``:n.toLocaleTimeString(void 0,t?{hour:`2-digit`,minute:`2-digit`,second:`2-digit`}:{hour:`2-digit`,minute:`2-digit`})}let re=x(()=>T(s.turn.ts)),E=x(()=>s.turn.items.flatMap(e=>e.eventIds??[]).join(` `)),ie=x(()=>{let e=s.turn.items;if(e.length===0)return null;for(let t=e.length-1;t>=0;t--){let n=e[t].ts;if(n)return n}return null}),ae=x(()=>{let e=s.turn.ts,t=ie.value;if(!t||!e||t===e)return``;let n=new Date(e).getTime(),r=new Date(t).getTime();return Number.isNaN(n)||Number.isNaN(r)||r<=n?``:T(t,r-n<6e4)}),se=x(()=>ae.value!==``);function ce(e){return we(e.origin,Object.values(d.subagents[s.workspaceId]??{}),e.type===`tool`?e.toolCallId:void 0)}function O(e){return e||l(`chat.subagentActivity`)}function de(e){return(e.type===`text`||e.type===`thinking`||e.type===`tool`)&&e.origin?.kind===`subagent`}let pe=x(()=>{let e=[],t=new Map;for(let n of s.turn.items){if(!de(n)){e.push({type:`item`,key:W(n),item:n});continue}let r=ce(n),i=r?.toolUseId??n.origin?.toolCallId??n.origin?.threadId??W(n),a=t.get(i);if(a){a.items.push(n);continue}let o={type:`subagent`,key:`subagent:${W(n)}`,items:[n],subagentKey:i,subagentName:r?.description||null};t.set(i,o),e.push(o)}return e});function k(e){e.origin&&f?.({origin:e.origin})}let me=x(()=>s.turn.items.filter(e=>e.type===`tool`).length);return(i,o)=>(u(),S(`div`,{ref_key:`cardEl`,ref:p,class:r([`turn-card`,{"turn-card--user":t.turn.speaker===`user`,"turn-card--highlighted":t.highlighted}]),"data-event-ids":E.value,style:n({"--turn-accent":b.value.accent})},[w(`div`,X,[w(`span`,{class:r([`turn-badge`,b.value.badgeClass])},a(b.value.label),3),re.value?(u(),S(`span`,Ze,a(re.value),1)):v(``,!0),se.value?(u(),S(C,{key:1},[g(oe,{name:`arrow_forward`,size:`10px`,color:`kobo-3`,class:`turn-time-arrow`}),w(`span`,Qe,[y(a(ae.value)+` `,1),g(B,null,{default:h(()=>[y(a(e(l)(`chat.lastUpdatedAt`,{time:ae.value})),1)]),_:1})])],64)):v(``,!0),me.value>0?(u(),S(`span`,$e,` · `+a(e(l)(`chat.nActions`,{n:me.value})),1)):v(``,!0)]),w(`div`,et,[(u(!0),S(C,null,te(pe.value,n=>(u(),S(C,{key:n.key},[n.type===`subagent`?(u(),_(Ce,{key:0,class:`turn-subagent-activity`,dense:``,label:O(n.subagentName),caption:e(l)(`chat.nActions`,{n:n.items.length}),"default-opened":t.highlighted&&n.items.some(e=>e.eventIds?.some(e=>E.value.includes(e)))},{header:h(()=>[g(P,null,{default:h(()=>[g(N,{class:`row items-center no-wrap`,title:n.subagentName??void 0},{default:h(()=>[g(oe,{name:`hub`,size:`16px`,class:`q-mr-xs`},{default:h(()=>[g(B,null,{default:h(()=>[y(a(e(l)(`chat.subagentActivity`)),1)]),_:1})]),_:1}),w(`span`,tt,a(O(n.subagentName)),1)]),_:2},1032,[`title`]),g(N,{caption:``},{default:h(()=>[y(a(e(l)(`chat.nActions`,{n:n.items.length})),1)]),_:2},1024)]),_:2},1024),g(P,{side:``},{default:h(()=>[g(D,{flat:``,round:``,dense:``,size:`sm`,icon:`open_in_new`,"aria-label":e(l)(`chat.openSubagentActivity`),onClick:ne(e=>k(n.items[0]),[`stop`])},{default:h(()=>[g(B,null,{default:h(()=>[y(a(e(l)(`chat.openSubagentActivity`)),1)]),_:1})]),_:1},8,[`aria-label`,`onClick`])]),_:2},1024)]),default:h(()=>[w(`div`,nt,[(u(!0),S(C,null,te(n.items,t=>(u(),S(C,{key:e(W)(t)},[t.type===`text`?(u(),_(I,{key:0,item:t},null,8,[`item`])):t.type===`thinking`?(u(),_(H,{key:1,item:t},null,8,[`item`])):t.type===`tool`?(u(),_(L,{key:2,item:t},null,8,[`item`])):v(``,!0)],64))),128))])]),_:2},1032,[`label`,`caption`,`default-opened`])):(u(),S(C,{key:1},[n.item.type===`text`?(u(),_(I,{key:0,item:n.item},null,8,[`item`])):n.item.type===`thinking`?(u(),_(H,{key:1,item:n.item},null,8,[`item`])):n.item.type===`tool`?(u(),_(L,{key:2,item:n.item},null,8,[`item`])):n.item.type===`user`?(u(),_(Y,{key:3,item:n.item,"workspace-id":t.workspaceId},null,8,[`item`,`workspace-id`])):n.item.type===`session`?(u(),_(We,{key:4,item:n.item},null,8,[`item`])):n.item.type===`error`?(u(),_(He,{key:5,item:n.item},null,8,[`item`])):v(``,!0)],64))],64))),128))]),t.turn.items.length>4?(u(),S(`div`,rt,[g(D,{flat:``,round:``,dense:``,size:`xs`,icon:`arrow_upward`,color:`kobo-3`,class:`turn-scroll-top-btn`,onClick:ee},{default:h(()=>[g(B,null,{default:h(()=>[y(a(e(l)(`chat.scrollToTurnTop`)),1)]),_:1})]),_:1})])):v(``,!0)],14,Xe))}}),[[`__scopeId`,`data-v-13bebb3d`]]),at={key:0,class:`activity-feed-switching`},ot={key:1,class:`activity-feed-wrap`,"data-tour":`ws-chat`},st={key:0,role:`alert`,class:`q-pa-sm text-negative`},ct={key:1,"data-testid":`history-load-error`,role:`alert`,class:`q-pa-sm text-negative`},lt=[`aria-label`],ut={key:0,class:`activity-feed-compacting`},dt={class:`text-caption text-kobo-2`},ft={key:0,class:`text-center q-py-sm text-caption text-kobo-3`},pt={key:1,class:`activity-feed-empty q-pa-md`},mt={class:`activity-feed-empty__title`},ht={class:`activity-feed-empty__hint`},gt={key:2,class:`q-px-md q-pb-md`},_t=[`title`],vt={class:`ellipsis`},yt={class:`activity-feed-nav-cluster`},bt=300,xt=60,St=200,Ct=180,wt=255,Tt=3,Et=60,Dt=R(T({__name:`ActivityFeed`,props:{workspaceId:{}},setup(t){let{t:n}=ue(),r=t,i=de(),s=me(),d=fe(),m=he(),ee=x(()=>m.isCompacting(r.workspaceId)),b=x(()=>d.selectedSessionId),T=x(()=>d.sessions.find(e=>e.id===b.value)?.engineSessionId??null),ne=x(()=>{let e=d.sessions;return e.length!==0&&b.value===e[e.length-1].id});function E(e){return b.value?e?e===b.value||e===T.value:ne.value:!0}let ie=x(()=>(d.activityFeeds[r.workspaceId]??[]).filter(e=>e.type===`text`&&typeof e.content==`string`&&E(e.sessionId)).map(e=>({content:e.content,sender:e.meta?.sender??`user`,source:ye(e.meta?.source),ts:e.timestamp,sessionId:e.sessionId,eventIds:[e.id]}))),ae=x(()=>{let e=d.workspaces.find(e=>e.id===r.workspaceId);return ve(e?.status)}),le=ke(),pe=``,k=x(()=>{let e=i.eventsFor(r.workspaceId),t=i.timestampsFor(r.workspaceId),n=i.sessionIdsFor(r.workspaceId),a=i.eventIdsFor(r.workspaceId),o=[],c=[],l=[];for(let r=0;r<e.length;r++)E(n[r])&&(o.push(e[r]),c.push(t[r]),l.push(a[r]??null));let u=`${r.workspaceId}::${b.value??`*`}`;u!==pe&&(le=ke(),pe=u);let d=Ee(le,o,c,ae.value,l);return Fe(De(d,ie.value).filter(e=>e.type===`thinking`?!1:e.type===`session`?e.kind===`ended`?s.showVerboseSystemMessages||!Oe(e.detail):s.showVerboseSystemMessages:!0))}),A=x(()=>s.showVerboseSystemMessages?i.eventsFor(r.workspaceId).filter(e=>e.kind===`message:raw`).map(e=>e.content):[]),xe=x(()=>{for(let e=k.value.length-1;e>=0;e--){let t=k.value[e]?.items??[];for(let e=t.length-1;e>=0;e--){let r=t[e];if(r){if(r.type===`text`&&!r.streaming)return{key:`text:${r.messageId}`,text:r.text};if(r.type===`tool`)return r.result?{key:`tool:${r.toolCallId}:end`,text:r.result.isError?n(`activity.a11y.toolFailed`,{name:r.name}):n(`activity.a11y.toolFinished`,{name:r.name})}:{key:`tool:${r.toolCallId}:start`,text:n(`activity.a11y.toolStarted`,{name:r.name})}}}}return null}),Se=o(``);l(xe,(e,t)=>{if(!e||e.key===t?.key)return;let r=e.key.startsWith(`text:`)?n(`activity.a11y.agentSaid`,{text:e.text.slice(0,bt)}):e.text;Se.value=r});let j=o(null),M=o(!0),N=o(!1),P=o(``),F=_e(),we=_e(),I=_e(),L=o(null),R=!1,z=!0,B=null,V=o(new Set),H=new Map,Ae=x(()=>{if(k.value.length>0||A.value.length>0)return!1;let e=r.workspaceId,t=b.value;return!!d.loadingSessions[e]||m.isSyncPending(e)||t!==null&&V.value.has(W(e,t))}),je=o(new Map),U=x(()=>k.value.length>0||A.value.length>0),Ne=x(()=>!!d.loadingSessions[r.workspaceId]||m.isSyncPending(r.workspaceId)||b.value!==null&&V.value.has(W(r.workspaceId,b.value)));function Pe(e){tt();let t=B;if(B=e.verticalSize,t!==null&&e.verticalSize>t&&M.value&&!N.value){q(0);return}let n=e.verticalSize-e.verticalPosition-e.verticalContainerSize;M.value=n<=xt,R&&e.verticalPosition<=St&&!N.value&&!P.value&&ze()&&He()}function W(e,t){return`${e}:${t}`}function ze(){let e=b.value;return e?je.value.get(W(r.workspaceId,e))??!0:i.hasMoreOlderFor(r.workspaceId)}function Be(e,t,n){je.value.set(W(e,t),n)}function Ve(e){if(!b.value)return i.oldestIdFor(e);let t=i.eventIdsFor(e),n=i.sessionIdsFor(e);for(let e=0;e<t.length;e++){if(!E(n[e]))continue;let r=t[e];if(r)return r}}let G=null;function He(){if(G)return G;let e=K().finally(()=>{G===e&&(G=null)});return G=e,G}function Ue(){F.abort(),we.abort(),I.abort(),N.value=!1,G=null;for(let e of V.value)Ft.delete(e);H.clear(),V.value.clear(),P.value=``}function We(){P.value=``,Nt.value===0&&b.value?It():He()}l(()=>[r.workspaceId,b.value],Ue,{flush:`sync`});async function K(){let e=r.workspaceId,t=b.value,n=Ve(e);if(!n)return;let a=F.begin();P.value=``,N.value=!0;try{let r=j.value;if(await f(),!F.isCurrent(a))return;let o=r?.getScroll().verticalSize??0,s=r?.getScroll().verticalPosition??0,c=new URLSearchParams({before:n,limit:`200`});t&&c.set(`session`,t);let l=await O(`/api/workspaces/${e}/events?${c.toString()}`,{signal:a});if(!l.ok)throw await ce(l);let u=await l.json();if(!F.isCurrent(a))return;let p=u.events??[],m=p.filter(t=>t.type===`agent:event`&&t.workspaceId===e),h=p.filter(t=>t.type===`user:message`&&t.workspaceId===e),g=m.map(e=>e.payload),_=m.map(e=>e.createdAt),v=m.map(e=>e.sessionId??null),y=m.map(e=>e.id),ee=p.length>0?p[0].id:n;t&&Be(e,t,u.hasMore),i.prepend(e,g,_,{oldestId:ee,hasMoreOlder:t?i.hasMoreOlderFor(e):u.hasMore,sessionIds:v,eventIds:y});for(let t of h){let n=t.payload;typeof n.content==`string`&&d.addActivityItem(e,{id:t.id,type:`text`,content:n.content,timestamp:t.createdAt,sessionId:t.sessionId??void 0,meta:{sender:n.sender??`user`,source:ye(n.source)}})}if(await f(),r&&F.isCurrent(a)){let e=r.getScroll().verticalSize-o;if(e>0){let t=Math.max(s+e,250);r.setScrollPosition(`vertical`,t,0)}}}catch(e){if(ge(e)||!F.isCurrent(a))return;P.value=e instanceof Error?e.message:String(e),console.error(`[ActivityFeed] failed to load older events:`,e)}finally{F.isCurrent(a)&&(N.value=!1)}}async function q(e=0){let t=$();if(await f(),t!==$())return;let n=j.value;if(!n)return;let r=n.getScroll();n.setScrollPosition(`vertical`,r.verticalSize,e)}let J=null,Ge=0;function Ke(){if(J!=null)return;let e=performance.now()-Ge<Ct,t=$();J=requestAnimationFrame(()=>{J=null,t===$()&&(Ge=performance.now(),q(e?0:180))})}function qe(e){Q(),M.value=!1;let t=j.value;t&&t.setScrollPosition(`vertical`,Math.max(0,e),250)}function Je(){let e=window.location.hash,t=e.indexOf(`?`);if(t===-1||!new URLSearchParams(e.slice(t+1)).has(`eventId`))return;let n=e.slice(0,t);window.history.replaceState(window.history.state,``,`${window.location.pathname}${window.location.search}${n}`)}async function Ye(e){let t=e.detail;if(t?.workspaceId!==r.workspaceId||!t.eventId)return;Q(),M.value=!1;let n=I.begin(),a=r.workspaceId;try{let e=new URLSearchParams({around:t.eventId,limit:`200`});t.sessionId&&e.set(`session`,t.sessionId);let o=await O(`/api/workspaces/${a}/events?${e}`,{signal:n});if(!o.ok)throw await ce(o);let s=await o.json();if(!I.isCurrent(n))return;let c=s.events??[],l=c.filter(e=>e.type===`agent:event`&&e.workspaceId===r.workspaceId),u=c.filter(e=>e.type===`user:message`&&e.workspaceId===r.workspaceId);i.reset(r.workspaceId,l.map(e=>e.payload),l.map(e=>e.createdAt),{oldestId:c[0]?.id,hasMoreOlder:s.hasMore??!1,sessionIds:l.map(e=>e.sessionId),eventIds:l.map(e=>e.id)});for(let e of u){let t=e.payload;typeof t.content==`string`&&d.addActivityItem(r.workspaceId,{id:e.id,type:`text`,content:t.content,timestamp:e.createdAt,sessionId:e.sessionId??void 0,meta:{sender:t.sender??`user`,source:ye(t.source)}})}if(await Re(Ae,e=>!e,5e3),await f(),await new Promise(e=>requestAnimationFrame(()=>requestAnimationFrame(()=>e()))),!I.isCurrent(n))return;let p=k.value.findIndex(e=>e.items.some(e=>e.eventIds?.includes(t.eventId)??!1));if(p<0)return;L.value=t.eventId,Y.value?.scrollTo(p,`center`),X.value=p,window.setTimeout(()=>{L.value===t.eventId&&(L.value=null)},1800)}catch(e){if(ge(e)||!I.isCurrent(n))return;P.value=e instanceof Error?e.message:String(e),console.error(`[ActivityFeed] failed to focus history event:`,e)}}let Y=o(null),Xe=o(null),X=o(0),Ze=o(!1),Qe=x(()=>{for(let e=k.value.length-1;e>=0;e--)if(k.value[e]?.speaker===`user`)return e;return-1}),$e=null;function et(){let e=Qe.value,t=j.value?.getScrollTarget(),n=t?.querySelector(`[data-turn-index="${e}"]`);if(!n||!t){Ze.value=!1;return}let r=t.getBoundingClientRect(),i=n.getBoundingClientRect();Ze.value=i.bottom>r.top&&i.top<r.bottom}function tt(){$e===null&&($e=requestAnimationFrame(()=>{$e=null,et()}))}function nt(e){return e.length<=wt?e:`${e.slice(0,252)}...`}let rt=x(()=>{let e=Qe.value,t=k.value[e];if(!t||X.value<=e||Ze.value)return null;let n=t.items.filter(e=>e.type===`user`).map(e=>e.content).join(`
|
|
2
|
+
`).trim();return n?{index:e,content:nt(n)}:null});function Dt(e){X.value=e.index,tt()}function Ot(){let e=rt.value;e&&(Q(),M.value=!1,Y.value?.scrollTo(e.index,`start`),X.value=e.index)}let kt=o(!1);async function At(){if(kt.value)return;Q(),M.value=!1,kt.value=!0;let e=r.workspaceId;try{let t=Le(k.value,X.value);if(t<0)for(let n=0;n<15;n++){if(e!==r.workspaceId)return;if(!ze())break;let n=k.value.length;if(await He(),await f(),e!==r.workspaceId)return;if(X.value+=k.value.length-n,t=Le(k.value,X.value),t>=0)break}t>=0&&e===r.workspaceId&&(Y.value?.scrollTo(t,`start`),X.value=t)}finally{kt.value=!1}}let Z=0;function jt(){return new Promise(e=>requestAnimationFrame(()=>e()))}async function Mt(){if(!z||!U.value||Ae.value)return;R=!1;let e=++Z;if(await f(),e!==Z||!z||(Xe.value=j.value?.getScrollTarget()??null,await f(),e!==Z||!z))return;k.value.length>0&&Y.value?.scrollTo(k.value.length-1,`end-force`),await jt();let t=-1,n=0;for(let r=0;r<Et&&n<Tt;r++){if(e!==Z||!z)return;await q(0),await jt();let r=j.value?.getScroll().verticalSize??0;B=r,n=r===t?n+1:0,t=r}e===Z&&!Ne.value&&(R=!0,z=!1)}function Q(){z=!1,!R&&(Z++,R=!0)}let Nt=x(()=>{let e=i.sessionIdsFor(r.workspaceId);if(!b.value)return e.length;let t=0;for(let n of e)E(n)&&t++;return t});l(Ae,async e=>{!e&&U.value&&await Mt(),e||f(()=>{Xe.value=j.value?.getScrollTarget()??null,tt()})}),l(Ne,(e,t)=>{!e&&t&&z&&U.value&&Mt()}),p(()=>{window.addEventListener(`kobo:focus-history-event`,Ye),f(()=>{Xe.value=j.value?.getScrollTarget()??null,tt()}),U.value&&Mt(),b.value&&It();let e=new URLSearchParams(window.location.hash.split(`?`)[1]??``).get(`eventId`);e&&(Ye(new CustomEvent(`kobo:focus-history-event`,{detail:{workspaceId:r.workspaceId,sessionId:b.value,eventId:e}})),Je())}),l([Nt,()=>ie.value.length,()=>A.value.length],async([e,t,n])=>{N.value||z&&e+t+n>0&&await Mt()}),l([Qe,()=>k.value.length],()=>{f(tt)});let Pt=x(()=>i.liveAppendCountFor(r.workspaceId,E)),$=()=>`${r.workspaceId}\u0000${d.selectedSessionId??``}`;l([Pt,$],([e,t],[n,r])=>{t===r&&e>n&&M.value&&!N.value&&Ke()}),c(()=>{Z++,z=!1,Ue(),window.removeEventListener(`kobo:focus-history-event`,Ye),J!=null&&(cancelAnimationFrame(J),J=null),$e!==null&&cancelAnimationFrame($e)}),l([()=>r.workspaceId,()=>d.selectedSessionId],()=>{Z++,z=!0,B=null,M.value=!0,R=!1,It(),U.value&&Mt()});let Ft=new Set;async function It(){let e=r.workspaceId,t=b.value;if(!t||Nt.value>0)return;let n=W(e,t);if(Ft.has(n))return;Ft.add(n),V.value.add(n);let a=we.begin();H.set(n,a),P.value=``;try{let n=await O(`/api/workspaces/${e}/events?session=${encodeURIComponent(t)}&limit=500`,{signal:a});if(!n.ok)throw await ce(n);let r=await n.json();if(!we.isCurrent(a))return;let o=r.events??[];if(o.length===0)return;let s=o.filter(t=>t.type===`agent:event`&&t.workspaceId===e),c=o.filter(t=>t.type===`user:message`&&t.workspaceId===e),l=s.map(e=>e.payload),u=s.map(e=>e.createdAt),f=s.map(e=>e.sessionId??null),p=s.map(e=>e.id);Be(e,t,r.hasMore),l.length>0&&i.prepend(e,l,u,{oldestId:o[0].id,hasMoreOlder:i.hasMoreOlderFor(e),sessionIds:f,eventIds:p});for(let t of c){let n=t.payload;typeof n.content==`string`&&d.addActivityItem(e,{id:t.id,type:`text`,content:n.content,timestamp:t.createdAt,sessionId:t.sessionId??void 0,meta:{sender:n.sender??`user`,source:ye(n.source)}})}}catch(e){if(ge(e)||!we.isCurrent(a)){(!H.has(n)||H.get(n)===a)&&Ft.delete(n);return}P.value=e instanceof Error?e.message:String(e),console.error(`[ActivityFeed] fetchSessionIfMissing failed:`,e),Ft.delete(n)}finally{H.get(n)===a&&(H.delete(n),V.value.delete(n))}}let Lt=x(()=>ie.value.filter(e=>e.sender===`user`).length),Rt=x(()=>(d.activityFeeds[r.workspaceId]??[]).filter(e=>e.meta?.sender===`user`&&e.meta.pending&&E(e.sessionId)).length);l([Lt,Rt,$],async([e,t,n],[r,i,a])=>{let o=t>i;n===a&&(o||e>r&&M.value&&!N.value&&!Ne.value)&&(M.value=!0,await q(o?180:0))});async function zt(){M.value=!0,await q(250)}return(t,n)=>Ae.value?(u(),S(`div`,at,[g(be,{size:`40px`,color:`primary`})])):(u(),S(`div`,ot,[e(d).sessionsErrors[r.workspaceId]?(u(),S(`div`,st,[y(a(t.$t(`activity.sessionsError`,{message:e(d).sessionsErrors[r.workspaceId]}))+` `,1),g(D,{flat:``,dense:``,label:t.$t(`common.retry`),onClick:n[0]||=t=>e(d).fetchSessions(r.workspaceId)},null,8,[`label`])])):v(``,!0),P.value?(u(),S(`div`,ct,[y(a(t.$t(`activity.historyError`,{message:P.value}))+` `,1),g(D,{flat:``,dense:``,label:t.$t(`common.retry`),onClick:We},null,8,[`label`])])):v(``,!0),w(`div`,{"data-testid":`activity-live-region`,class:`sr-only`,role:`log`,"aria-live":`polite`,"aria-atomic":`true`,"aria-label":t.$t(`activity.a11y.region`)},a(Se.value),9,lt),g(re,{name:`fade`},{default:h(()=>[ee.value?(u(),S(`div`,ut,[g(be,{size:`18px`,color:`primary`}),w(`span`,dt,a(t.$t(`activity.compacting`)),1)])):v(``,!0)]),_:1}),g(Te,{ref_key:`scrollRef`,ref:j,class:`activity-feed-scroll`,onScroll:Pe,onWheelPassive:Q,onTouchstartPassive:Q,onKeydown:Q},{default:h(()=>[N.value?(u(),S(`div`,ft,[g(se,{size:`sm`}),y(` `+a(t.$t(`activity.loading_older`)),1)])):v(``,!0),g(Me,{ref_key:`virtualScrollRef`,ref:Y,class:`q-pa-md`,items:k.value,"scroll-target":Xe.value??void 0,"virtual-scroll-item-size":160,"virtual-scroll-slice-size":20,onVirtualScroll:Dt},{default:h(({item:t,index:n})=>[(u(),_(it,{key:e(Ie)(t),turn:t,"workspace-id":r.workspaceId,"data-turn-index":n,highlighted:t.items.some(e=>e.eventIds?.includes(L.value??``)??!1),onScrollTo:qe},null,8,[`turn`,`workspace-id`,`data-turn-index`,`highlighted`]))]),_:1},8,[`items`,`scroll-target`]),k.value.length===0&&A.value.length===0&&!N.value?(u(),S(`div`,pt,[g(oe,{name:`forum`,size:`28px`}),w(`div`,mt,a(t.$t(`activity.empty`)),1),w(`div`,ht,a(t.$t(`activity.emptyHint`)),1)])):v(``,!0),A.value.length?(u(),S(`div`,gt,[g(Ce,{label:t.$t(`activity.raw_lines`,{n:A.value.length}),dense:``},{default:h(()=>[(u(!0),S(C,null,te(A.value,(e,t)=>(u(),S(`div`,{key:t,class:`text-caption text-kobo-3 q-pa-xs`},a(e),1))),128))]),_:1},8,[`label`])])):v(``,!0)]),_:1},512),rt.value?(u(),S(`button`,{key:2,type:`button`,class:`activity-feed-last-user`,"data-testid":`latest-user-message`,title:rt.value.content,onClick:Ot},[g(oe,{name:`person`,size:`16px`}),w(`span`,vt,a(rt.value.content),1)],8,_t)):v(``,!0),w(`div`,yt,[M.value?v(``,!0):(u(),_(D,{key:0,round:``,dense:``,unelevated:``,color:`kobo-surface-2`,"text-color":`kobo-1`,icon:`arrow_downward`,size:`sm`,class:`activity-feed-nav-btn`,title:t.$t(`activity.scroll_to_bottom`),"aria-label":t.$t(`activity.scroll_to_bottom`),onClick:zt},null,8,[`title`,`aria-label`])),g(D,{round:``,dense:``,unelevated:``,color:`kobo-surface-2`,"text-color":`kobo-1`,icon:`arrow_upward`,size:`sm`,class:`activity-feed-nav-btn`,title:t.$t(`activity.prev_user_message`),"aria-label":t.$t(`activity.prev_user_message`),loading:kt.value,disable:kt.value,onClick:At},null,8,[`title`,`aria-label`,`loading`,`disable`])])]))}}),[[`__scopeId`,`data-v-58324159`]]);export{Dt as default};
|