@loicngr/kobo 1.11.13 → 1.11.14
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 +4 -0
- package/dist/mcp-server/kobo-tasks-handlers.js +26 -3
- package/dist/server/index.js +5 -7
- package/dist/server/routes/export.js +2 -1
- package/dist/server/routes/workspaces.js +15 -0
- package/dist/server/services/agent/engines/claude-code/engine.js +9 -7
- package/dist/server/services/agent/engines/claude-code/event-mapper.js +3 -1
- package/dist/server/services/agent/orchestrator.js +34 -8
- package/dist/server/services/cron-service.js +2 -1
- package/dist/server/services/dev-server-service.js +13 -1
- package/dist/server/services/wakeup-service.js +14 -12
- package/dist/server/utils/git-ops.js +3 -2
- package/dist/server/utils/safe-path.js +34 -0
- package/package.json +3 -3
- package/src/client/dist/pwa/assets/{ActivityFeed-Ds8cAMNc.js → ActivityFeed-DqDJDX2v.js} +1 -1
- package/src/client/dist/pwa/assets/{ChangelogPage-DO6DNO4Q.js → ChangelogPage-Dw2LbIK2.js} +1 -1
- package/src/client/dist/pwa/assets/{ClosePopup-DuAqn8Pb.js → ClosePopup-eKuUr7CY.js} +1 -1
- package/src/client/dist/pwa/assets/{CreatePage-BnMSo_79.js → CreatePage--4KDhiYt.js} +1 -1
- package/src/client/dist/pwa/assets/{DiffViewer-CMnFGReI.js → DiffViewer-Bl9D1N1M.js} +3 -3
- package/src/client/dist/pwa/assets/{HealthPage-BRAxQKSP.js → HealthPage-CaOdjuVx.js} +1 -1
- package/src/client/dist/pwa/assets/{MainLayout-BN1ug95K.js → MainLayout-DP9shoO1.js} +2 -2
- package/src/client/dist/pwa/assets/{QBtnToggle-DsCDNQzX.js → QBtnToggle-BwPZ0Smh.js} +1 -1
- package/src/client/dist/pwa/assets/{QChip-DWGDTZnX.js → QChip-CY0_8v_e.js} +1 -1
- package/src/client/dist/pwa/assets/{QSelect-8Ip46vhk.js → QSelect-xHphd7hW.js} +1 -1
- package/src/client/dist/pwa/assets/{QTooltip-Bz4g3bmr.js → QTooltip-DyS20jDT.js} +1 -1
- package/src/client/dist/pwa/assets/{SearchPage-BD53VJEr.js → SearchPage-CggXs3dH.js} +1 -1
- package/src/client/dist/pwa/assets/{SettingsPage-3rYdJZga.js → SettingsPage-8AWL_4DM.js} +1 -1
- package/src/client/dist/pwa/assets/{TouchPan-BKu8V4eN.js → TouchPan-Cd9KTb6-.js} +1 -1
- package/src/client/dist/pwa/assets/{WorkspacePage-CbC_t5Rx.js → WorkspacePage-CJKSrSEC.js} +3 -3
- package/src/client/dist/pwa/assets/{cssMode-vaMVESGy.js → cssMode-BsN8TWME.js} +1 -1
- package/src/client/dist/pwa/assets/{documents-CyKQ9lT6.js → documents-DJy1tgPd.js} +1 -1
- package/src/client/dist/pwa/assets/{editor.api-CH3dhpKq.js → editor.api-Dj6TIm_z.js} +1 -1
- package/src/client/dist/pwa/assets/{expand-template-DQQQftsk.js → expand-template-By-8Db7_.js} +1 -1
- package/src/client/dist/pwa/assets/{freemarker2-deLpERsj.js → freemarker2-DC0xjWx5.js} +1 -1
- package/src/client/dist/pwa/assets/{handlebars-DdjS05Tc.js → handlebars-0Uz9nw5d.js} +1 -1
- package/src/client/dist/pwa/assets/{html-pK1ZEFvP.js → html-BYPeU1Vb.js} +1 -1
- package/src/client/dist/pwa/assets/{htmlMode-DJFDI9Ru.js → htmlMode-B0UND_vz.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-DPDR4mQI.js +1 -0
- package/src/client/dist/pwa/assets/{index-B_OmZ65F.js → index-CtK8ORpZ.js} +3 -3
- package/src/client/dist/pwa/assets/{javascript-DbuFfxco.js → javascript-B8PZW8pe.js} +1 -1
- package/src/client/dist/pwa/assets/{jsonMode-B0Fu8Dcc.js → jsonMode-BixnmFse.js} +1 -1
- package/src/client/dist/pwa/assets/{kobo-commands-rb43ZeWG.js → kobo-commands-CX8pforo.js} +1 -1
- package/src/client/dist/pwa/assets/{layout-tAFrvQ5V.js → layout-BAE6NR2A.js} +1 -1
- package/src/client/dist/pwa/assets/{liquid-DqDIKOCy.js → liquid-BH16_Zn1.js} +1 -1
- package/src/client/dist/pwa/assets/{lspLanguageFeatures-CVcEibfJ.js → lspLanguageFeatures-YMzTIuK0.js} +1 -1
- package/src/client/dist/pwa/assets/{mdx-Bs4CovhI.js → mdx-CrrWJ7fg.js} +1 -1
- package/src/client/dist/pwa/assets/{network-auth-DYrrh7Zr.js → network-auth-NMnCVoaZ.js} +1 -1
- package/src/client/dist/pwa/assets/{pending-diff-open-D1IYH8Pd.js → pending-diff-open-DHlXLorX.js} +1 -1
- package/src/client/dist/pwa/assets/{project-color-DO6uj29M.js → project-color-BJrCEQ0A.js} +1 -1
- package/src/client/dist/pwa/assets/{python-BJoP0uN-.js → python-fk3K3s3o.js} +1 -1
- package/src/client/dist/pwa/assets/{razor-cwD-YBcr.js → razor-Dg1yHOal.js} +1 -1
- package/src/client/dist/pwa/assets/{render-chat-markdown-DZnIueUV.js → render-chat-markdown-C00A6CfY.js} +1 -1
- package/src/client/dist/pwa/assets/{tsMode-of3oS9sU.js → tsMode-qJ7nTArc.js} +1 -1
- package/src/client/dist/pwa/assets/{typescript-BTWMoUxn.js → typescript-DayfKox1.js} +1 -1
- package/src/client/dist/pwa/assets/{use-onboarding-BTFkRBjE.js → use-onboarding-CF_D2gbm.js} +1 -1
- package/src/client/dist/pwa/assets/{use-quasar-C1RCDSb6.js → use-quasar-C3oo6nVn.js} +1 -1
- package/src/client/dist/pwa/assets/{vs-DDeaNKsH.js → vs-qnWnWXG9.js} +3 -3
- package/src/client/dist/pwa/assets/{workers-DQnKuGcq.js → workers-CvvtZekC.js} +1 -1
- package/src/client/dist/pwa/assets/{xml-RglGWD_3.js → xml-CJ1QFxhD.js} +1 -1
- package/src/client/dist/pwa/assets/{yaml-CSqGQpai.js → yaml-C3N49N5E.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/kobo-tasks-handlers.ts +24 -4
- package/src/client/dist/pwa/assets/i18n-CK38o-IF.js +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,10 @@ All notable changes to Kōbō are documented here. The format is based on
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/). Each release is an `## <version>`
|
|
5
5
|
section — the in-app "What's new" dialog reads this file.
|
|
6
6
|
|
|
7
|
+
## 1.11.14
|
|
8
|
+
|
|
9
|
+
- ⬆️ develop - refactor(deps): Bump SDKs + refactor shutdown + hardening sécurité
|
|
10
|
+
|
|
7
11
|
## 1.11.13
|
|
8
12
|
|
|
9
13
|
- fix(engines): unstick agent sessions that never emit session:ended
|
|
@@ -4,6 +4,7 @@ import { nanoid } from 'nanoid';
|
|
|
4
4
|
import * as cronService from '../server/services/cron-service.js';
|
|
5
5
|
import * as settingsService from '../server/services/settings-service.js';
|
|
6
6
|
import { slugifyProjectName } from '../server/utils/project-slug.js';
|
|
7
|
+
import { resolveExistingPathInside } from '../server/utils/safe-path.js';
|
|
7
8
|
import { resolveWorkspaceWorktreePath } from '../server/utils/worktree-paths.js';
|
|
8
9
|
/** Allowed task status values. */
|
|
9
10
|
export const VALID_TASK_STATUSES = ['pending', 'in_progress', 'done'];
|
|
@@ -296,6 +297,7 @@ export function getTicketSourcesHandler(worktreePath) {
|
|
|
296
297
|
export const DOCUMENT_DIRS = ['docs/plans', 'docs/superpowers', '.ai/thoughts'];
|
|
297
298
|
/** Depth cap to keep recursion bounded even on pathological symlink loops. */
|
|
298
299
|
const DOC_MAX_DEPTH = 8;
|
|
300
|
+
const DOC_MAX_FILE_BYTES = 1024 * 1024;
|
|
299
301
|
function walkMarkdownFiles(rootAbs, rootRel, out, depth = 0) {
|
|
300
302
|
if (depth > DOC_MAX_DEPTH)
|
|
301
303
|
return;
|
|
@@ -313,11 +315,13 @@ function walkMarkdownFiles(rootAbs, rootRel, out, depth = 0) {
|
|
|
313
315
|
const relEntry = `${rootRel}/${entry}`;
|
|
314
316
|
let stat;
|
|
315
317
|
try {
|
|
316
|
-
stat = fs.
|
|
318
|
+
stat = fs.lstatSync(absEntry);
|
|
317
319
|
}
|
|
318
320
|
catch {
|
|
319
321
|
continue;
|
|
320
322
|
}
|
|
323
|
+
if (stat.isSymbolicLink())
|
|
324
|
+
continue;
|
|
321
325
|
if (stat.isDirectory()) {
|
|
322
326
|
walkMarkdownFiles(absEntry, relEntry, out, depth + 1);
|
|
323
327
|
}
|
|
@@ -336,7 +340,14 @@ export function listDocumentsHandler(worktreePath) {
|
|
|
336
340
|
const absDir = path.join(worktreePath, dir);
|
|
337
341
|
if (!fs.existsSync(absDir))
|
|
338
342
|
continue;
|
|
339
|
-
|
|
343
|
+
try {
|
|
344
|
+
if (fs.lstatSync(absDir).isSymbolicLink())
|
|
345
|
+
continue;
|
|
346
|
+
walkMarkdownFiles(resolveExistingPathInside(worktreePath, absDir), dir, documents);
|
|
347
|
+
}
|
|
348
|
+
catch {
|
|
349
|
+
// A document directory outside the worktree is never eligible.
|
|
350
|
+
}
|
|
340
351
|
}
|
|
341
352
|
documents.sort((a, b) => new Date(b.modifiedAt).getTime() - new Date(a.modifiedAt).getTime());
|
|
342
353
|
return documents;
|
|
@@ -361,7 +372,19 @@ export function readDocumentHandler(worktreePath, relPath) {
|
|
|
361
372
|
if (!fs.existsSync(abs)) {
|
|
362
373
|
throw new Error(`Document not found: ${normalized}`);
|
|
363
374
|
}
|
|
364
|
-
|
|
375
|
+
const lstat = fs.lstatSync(abs);
|
|
376
|
+
if (lstat.isSymbolicLink()) {
|
|
377
|
+
// Resolve for a consistent error if the link escapes; internal links are
|
|
378
|
+
// rejected too so list/read share the same no-symlink contract.
|
|
379
|
+
resolveExistingPathInside(worktreePath, abs);
|
|
380
|
+
throw new Error('Symbolic links are not allowed in documents');
|
|
381
|
+
}
|
|
382
|
+
const safeAbs = resolveExistingPathInside(worktreePath, abs);
|
|
383
|
+
const stat = fs.statSync(safeAbs);
|
|
384
|
+
if (stat.size > DOC_MAX_FILE_BYTES) {
|
|
385
|
+
throw new Error(`Document too large (max ${DOC_MAX_FILE_BYTES / 1024 / 1024} MB)`);
|
|
386
|
+
}
|
|
387
|
+
return { path: normalized, content: fs.readFileSync(safeAbs, 'utf-8') };
|
|
365
388
|
}
|
|
366
389
|
/**
|
|
367
390
|
* Append a thought / decision / note to
|
package/dist/server/index.js
CHANGED
|
@@ -25,12 +25,12 @@ import templatesRouter from './routes/templates.js';
|
|
|
25
25
|
import usageRoutes from './routes/usage.js';
|
|
26
26
|
import voiceRouter from './routes/voice.js';
|
|
27
27
|
import workspacesRouter from './routes/workspaces.js';
|
|
28
|
-
import { getAvailableSkills, reconcileOrphanSessions, restoreRetryCountsFromDb, sendMessage, setBackendPort, startAgent, startWatchdog, stopAgent, stopWatchdog, } from './services/agent/orchestrator.js';
|
|
28
|
+
import { getAvailableSkills, reconcileOrphanSessions, restoreRetryCountsFromDb, sendMessage, setBackendPort, startAgent, startWatchdog, stopAgent, stopAllAgents, stopWatchdog, } from './services/agent/orchestrator.js';
|
|
29
29
|
import * as autoLoopService from './services/auto-loop-service.js';
|
|
30
30
|
import { runContentMigrationIfNeeded } from './services/content-migration-service.js';
|
|
31
31
|
import * as cronService from './services/cron-service.js';
|
|
32
32
|
import { createDailyDbBackupIfNeeded, createPreMigrationBackup } from './services/db-backup-service.js';
|
|
33
|
-
import { startDevServer, stopDevServer } from './services/dev-server-service.js';
|
|
33
|
+
import { startDevServer, stopAllDevServers, stopDevServer } from './services/dev-server-service.js';
|
|
34
34
|
import { authorizeWsUpgrade, generateToken, getLanUrls, resolveBindHost, resolveNetworkAccessEnvOverrides, } from './services/network-access-service.js';
|
|
35
35
|
import { startPrWatcher, stopPrWatcher } from './services/pr-watcher-service.js';
|
|
36
36
|
import * as quotaBackoffService from './services/quota-backoff-service.js';
|
|
@@ -42,7 +42,6 @@ import * as wakeupService from './services/wakeup-service.js';
|
|
|
42
42
|
import { emit, emitEphemeral, handleConnection, setMessageHandler } from './services/websocket-service.js';
|
|
43
43
|
import { getActiveSession, getWorkspace, updateWorkspaceStatus } from './services/workspace-service.js';
|
|
44
44
|
import { getClientSpaPath, getDbPath, getKoboHome, getPackageVersion } from './utils/paths.js';
|
|
45
|
-
import { initProcessCleanup, killAll as killAllTrackedProcesses } from './utils/process-tracker.js';
|
|
46
45
|
console.log(`[kobo] Kōbō home: ${getKoboHome()}`);
|
|
47
46
|
// Initialize DB + run migrations
|
|
48
47
|
const db = getDb();
|
|
@@ -72,7 +71,6 @@ void createDailyDbBackupIfNeeded(db, getDbPath()).then((r) => {
|
|
|
72
71
|
}
|
|
73
72
|
});
|
|
74
73
|
// Initialize process cleanup, agent watchdog, PR watcher, and wakeup rehydration
|
|
75
|
-
initProcessCleanup();
|
|
76
74
|
reconcileOrphanSessions();
|
|
77
75
|
startWatchdog();
|
|
78
76
|
wakeupService.rehydrate();
|
|
@@ -490,10 +488,10 @@ async function gracefulShutdown(signal) {
|
|
|
490
488
|
catch {
|
|
491
489
|
// Best-effort
|
|
492
490
|
}
|
|
493
|
-
//
|
|
491
|
+
// Stop managed agents and both direct/Docker dev servers before closing DB.
|
|
494
492
|
try {
|
|
495
|
-
|
|
496
|
-
console.log('[kobo]
|
|
493
|
+
await Promise.all([stopAllAgents(), stopAllDevServers()]);
|
|
494
|
+
console.log('[kobo] Agents and dev servers stopped');
|
|
497
495
|
}
|
|
498
496
|
catch {
|
|
499
497
|
// Best-effort
|
|
@@ -5,7 +5,8 @@ import * as workspaceService from '../services/workspace-service.js';
|
|
|
5
5
|
const app = new Hono();
|
|
6
6
|
/** Quote a CSV cell when it contains a delimiter, quote, or newline (RFC 4180). */
|
|
7
7
|
function csvCell(value) {
|
|
8
|
-
|
|
8
|
+
const safeValue = /^[\s]*[=+\-@]/.test(value) ? `'${value}` : value;
|
|
9
|
+
return /[",\r\n]/.test(safeValue) ? `"${safeValue.replace(/"/g, '""')}"` : safeValue;
|
|
9
10
|
}
|
|
10
11
|
function csvRow(values) {
|
|
11
12
|
return values.map(csvCell).join(',');
|
|
@@ -42,6 +42,7 @@ import * as worktreeService from '../services/worktree-service.js';
|
|
|
42
42
|
import { resolveUniqueBranchAndPath } from '../utils/branch-resolver.js';
|
|
43
43
|
import * as gitOps from '../utils/git-ops.js';
|
|
44
44
|
import { slugifyProjectName } from '../utils/project-slug.js';
|
|
45
|
+
import * as safePath from '../utils/safe-path.js';
|
|
45
46
|
import { resolveSiblingWorkspaceWorktreePath } from '../utils/worktree-paths.js';
|
|
46
47
|
/** Hono sub-router for workspace CRUD, tasks, agent lifecycle, git operations, and PR creation. */
|
|
47
48
|
const app = new Hono();
|
|
@@ -2692,6 +2693,13 @@ app.get('/:id/diff-file', (c) => {
|
|
|
2692
2693
|
return c.json({ error: `Workspace '${id}' not found` }, 404);
|
|
2693
2694
|
}
|
|
2694
2695
|
const worktreePath = workspace.worktreePath;
|
|
2696
|
+
try {
|
|
2697
|
+
safePath.assertPathInside(worktreePath, filePath);
|
|
2698
|
+
}
|
|
2699
|
+
catch (err) {
|
|
2700
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2701
|
+
return c.json({ error: message }, 400);
|
|
2702
|
+
}
|
|
2695
2703
|
if (c.req.query('mode') === 'commits') {
|
|
2696
2704
|
const from = c.req.query('from');
|
|
2697
2705
|
const to = c.req.query('to');
|
|
@@ -2750,6 +2758,13 @@ app.post('/:id/rollback-file', async (c) => {
|
|
|
2750
2758
|
if (!filePath) {
|
|
2751
2759
|
return c.json({ error: 'Missing or invalid `path` field' }, 400);
|
|
2752
2760
|
}
|
|
2761
|
+
try {
|
|
2762
|
+
safePath.assertPathInside(workspace.worktreePath, filePath);
|
|
2763
|
+
}
|
|
2764
|
+
catch (err) {
|
|
2765
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2766
|
+
return c.json({ error: message }, 400);
|
|
2767
|
+
}
|
|
2753
2768
|
let target;
|
|
2754
2769
|
try {
|
|
2755
2770
|
target = gitOps.rollbackFile(workspace.worktreePath, workspace.workingBranch, filePath);
|
|
@@ -97,7 +97,9 @@ export function createClaudeCodeEngine() {
|
|
|
97
97
|
async start(options, onEvent) {
|
|
98
98
|
const abortController = new AbortController();
|
|
99
99
|
const mapperState = createMapperState();
|
|
100
|
-
|
|
100
|
+
// UI/tool ids can differ from the SDK task id. Key by the stable UI id
|
|
101
|
+
// so a terminal notification that omits task_id still clears the task.
|
|
102
|
+
const activeSubagentTaskIds = new Map();
|
|
101
103
|
// Pending canUseTool callbacks, keyed by SDK ctx.toolUseID.
|
|
102
104
|
const pendingResolvers = new Map();
|
|
103
105
|
const isInteractive = options.agentPermissionMode === 'interactive';
|
|
@@ -309,7 +311,7 @@ export function createClaudeCodeEngine() {
|
|
|
309
311
|
if (inputStream.hasUnansweredInput(completedResponses))
|
|
310
312
|
return;
|
|
311
313
|
console.warn(`[claude-engine] Subagent(s) still tracked active ${SUBAGENT_STALL_TIMEOUT_MS}ms after the last turn ended — forcing session drain.`);
|
|
312
|
-
|
|
314
|
+
activeSubagentTaskIds.clear();
|
|
313
315
|
// This is a forced, unclean termination (the subagent may still be
|
|
314
316
|
// alive server-side) — never report it as a normal completion, so
|
|
315
317
|
// auto-loop doesn't treat an orphaned run as forward progress.
|
|
@@ -328,16 +330,16 @@ export function createClaudeCodeEngine() {
|
|
|
328
330
|
if (ev.kind !== 'subagent:progress')
|
|
329
331
|
continue;
|
|
330
332
|
if (ev.status === 'running')
|
|
331
|
-
|
|
333
|
+
activeSubagentTaskIds.set(ev.toolCallId, ev.taskId ?? ev.toolCallId);
|
|
332
334
|
else
|
|
333
|
-
|
|
335
|
+
activeSubagentTaskIds.delete(ev.toolCallId);
|
|
334
336
|
}
|
|
335
337
|
// The stall watchdog is only armed while we're waiting purely on
|
|
336
338
|
// background subagents (no further SDK turn expected unless one
|
|
337
339
|
// reports done). React the moment the last one clears instead of
|
|
338
340
|
// waiting for a 'result' message that may never come.
|
|
339
341
|
if (subagentStallTimer &&
|
|
340
|
-
|
|
342
|
+
activeSubagentTaskIds.size === 0 &&
|
|
341
343
|
!inputStream.hasUnansweredInput(completedResponses)) {
|
|
342
344
|
clearSubagentStallWatchdog();
|
|
343
345
|
inputStream.close();
|
|
@@ -360,7 +362,7 @@ export function createClaudeCodeEngine() {
|
|
|
360
362
|
completedResponses++;
|
|
361
363
|
// A queued forced message starts the next response on this same SDK stream.
|
|
362
364
|
if (!inputStream.hasUnansweredInput(completedResponses)) {
|
|
363
|
-
if (
|
|
365
|
+
if (activeSubagentTaskIds.size === 0) {
|
|
364
366
|
clearSubagentStallWatchdog();
|
|
365
367
|
inputStream.close();
|
|
366
368
|
armResultDrainWatchdog();
|
|
@@ -454,7 +456,7 @@ export function createClaudeCodeEngine() {
|
|
|
454
456
|
// background subagent task keeps running unless told to stop via
|
|
455
457
|
// its own API, which would otherwise leave the Stop button
|
|
456
458
|
// appearing to do nothing for up to SUBAGENT_STALL_TIMEOUT_MS.
|
|
457
|
-
for (const taskId of
|
|
459
|
+
for (const taskId of activeSubagentTaskIds.values()) {
|
|
458
460
|
try {
|
|
459
461
|
void q.stopTask(taskId).catch(() => {
|
|
460
462
|
/* best-effort */
|
|
@@ -192,7 +192,8 @@ export function mapSdkMessage(msg, state) {
|
|
|
192
192
|
return events;
|
|
193
193
|
}
|
|
194
194
|
if (subtype === 'task_started' || subtype === 'task_progress' || subtype === 'task_notification') {
|
|
195
|
-
const
|
|
195
|
+
const taskId = typeof parsed.task_id === 'string' ? parsed.task_id : undefined;
|
|
196
|
+
const toolCallId = typeof parsed.tool_use_id === 'string' ? parsed.tool_use_id : taskId;
|
|
196
197
|
if (toolCallId) {
|
|
197
198
|
const usage = parsed.usage;
|
|
198
199
|
// Unlike task_started/task_progress (in-flight signals), task_notification
|
|
@@ -204,6 +205,7 @@ export function mapSdkMessage(msg, state) {
|
|
|
204
205
|
events.push({
|
|
205
206
|
kind: 'subagent:progress',
|
|
206
207
|
toolCallId,
|
|
208
|
+
...(taskId ? { taskId } : {}),
|
|
207
209
|
status: isDone ? 'done' : 'running',
|
|
208
210
|
description: typeof parsed.description === 'string' ? parsed.description : undefined,
|
|
209
211
|
taskType: typeof parsed.task_type === 'string' ? parsed.task_type : undefined,
|
|
@@ -987,12 +987,7 @@ export function interruptAgent(workspaceId, options = {}) {
|
|
|
987
987
|
autoLoopService.disable(workspaceId, 'user-action');
|
|
988
988
|
}
|
|
989
989
|
}
|
|
990
|
-
|
|
991
|
-
export function stopAgent(workspaceId) {
|
|
992
|
-
const ctrl = controllers.get(workspaceId);
|
|
993
|
-
if (!ctrl) {
|
|
994
|
-
throw new Error(`No agent running for workspace '${workspaceId}'`);
|
|
995
|
-
}
|
|
990
|
+
async function stopController(workspaceId, ctrl) {
|
|
996
991
|
wakeupService.cancel(workspaceId, 'stopped');
|
|
997
992
|
// If the session was waiting on a question/permission, normalize the state
|
|
998
993
|
// synchronously so callers (archive, delete, manual stop) see a clean
|
|
@@ -1020,11 +1015,42 @@ export function stopAgent(workspaceId) {
|
|
|
1020
1015
|
controllers.delete(workspaceId);
|
|
1021
1016
|
// Manual stop should also drop any pending quota auto-resume.
|
|
1022
1017
|
quotaBackoffService.cancel(workspaceId, 'user');
|
|
1023
|
-
|
|
1024
|
-
|
|
1018
|
+
await ctrl.stop();
|
|
1019
|
+
}
|
|
1020
|
+
/** Gracefully stop an agent (the engine handles SIGTERM + SIGKILL). */
|
|
1021
|
+
export function stopAgent(workspaceId) {
|
|
1022
|
+
const ctrl = controllers.get(workspaceId);
|
|
1023
|
+
if (!ctrl) {
|
|
1024
|
+
throw new Error(`No agent running for workspace '${workspaceId}'`);
|
|
1025
|
+
}
|
|
1026
|
+
// Fire-and-forget: callers such as archive/delete must remain synchronous.
|
|
1027
|
+
void stopController(workspaceId, ctrl).catch((err) => {
|
|
1025
1028
|
console.error('[orchestrator] controller.stop failed:', err);
|
|
1026
1029
|
});
|
|
1027
1030
|
}
|
|
1031
|
+
/** Stop every live engine before process shutdown, with a bounded wait per engine. */
|
|
1032
|
+
export async function stopAllAgents(timeoutMs = 3_000) {
|
|
1033
|
+
const stops = [...controllers.entries()].map(async ([workspaceId, ctrl]) => {
|
|
1034
|
+
let timeout;
|
|
1035
|
+
try {
|
|
1036
|
+
await Promise.race([
|
|
1037
|
+
stopController(workspaceId, ctrl),
|
|
1038
|
+
new Promise((resolve) => {
|
|
1039
|
+
timeout = setTimeout(resolve, timeoutMs);
|
|
1040
|
+
timeout.unref?.();
|
|
1041
|
+
}),
|
|
1042
|
+
]);
|
|
1043
|
+
}
|
|
1044
|
+
catch (err) {
|
|
1045
|
+
console.error(`[orchestrator] Failed to stop '${workspaceId}' during shutdown:`, err);
|
|
1046
|
+
}
|
|
1047
|
+
finally {
|
|
1048
|
+
if (timeout)
|
|
1049
|
+
clearTimeout(timeout);
|
|
1050
|
+
}
|
|
1051
|
+
});
|
|
1052
|
+
await Promise.all(stops);
|
|
1053
|
+
}
|
|
1028
1054
|
/** Write a user message to the running agent. */
|
|
1029
1055
|
export async function sendMessage(workspaceId, content, expectedSessionId) {
|
|
1030
1056
|
const ctrl = controllers.get(workspaceId);
|
|
@@ -170,7 +170,6 @@ function fireOrSkip(id) {
|
|
|
170
170
|
}
|
|
171
171
|
let status = 'skipped-active';
|
|
172
172
|
if (!orchestrator.hasController(row.workspace_id)) {
|
|
173
|
-
status = 'fired';
|
|
174
173
|
try {
|
|
175
174
|
const globalSettings = settingsService.getGlobalSettings();
|
|
176
175
|
const projectSettings = settingsService.getProjectSettings(wsRow.project_path);
|
|
@@ -186,9 +185,11 @@ function fireOrSkip(id) {
|
|
|
186
185
|
// every fire" (clean context, no conversation continuity).
|
|
187
186
|
const resumeMode = row.agent_session_id !== null;
|
|
188
187
|
orchestrator.startAgent(row.workspace_id, worktreePath, row.prompt, wsRow.model, resumeMode, agentPermissionMode, row.agent_session_id ?? undefined, wsRow.reasoning_effort);
|
|
188
|
+
status = 'fired';
|
|
189
189
|
}
|
|
190
190
|
catch (err) {
|
|
191
191
|
console.error(`[cron-service] startAgent at fire time failed for cron '${id}':`, err);
|
|
192
|
+
status = 'start-failed';
|
|
192
193
|
}
|
|
193
194
|
}
|
|
194
195
|
const now = new Date();
|
|
@@ -3,7 +3,7 @@ import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { getProjectSettings } from './settings-service.js';
|
|
5
5
|
import { emitEphemeral } from './websocket-service.js';
|
|
6
|
-
import { getWorkspace, updateDevServerStatus } from './workspace-service.js';
|
|
6
|
+
import { getWorkspace, listWorkspaces, updateDevServerStatus } from './workspace-service.js';
|
|
7
7
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
8
8
|
/** Build a clean env for child processes, stripping Kobo-specific variables. */
|
|
9
9
|
function cleanEnv() {
|
|
@@ -312,6 +312,18 @@ export async function stopDevServer(workspaceId) {
|
|
|
312
312
|
emitEphemeral(workspaceId, 'devserver:status', status);
|
|
313
313
|
return status;
|
|
314
314
|
}
|
|
315
|
+
/** Stop direct and Docker dev servers for every persisted workspace. */
|
|
316
|
+
export async function stopAllDevServers() {
|
|
317
|
+
const workspaceIds = new Set([...listWorkspaces(true).map((workspace) => workspace.id), ...trackedProcesses.keys()]);
|
|
318
|
+
await Promise.all([...workspaceIds].map(async (workspaceId) => {
|
|
319
|
+
try {
|
|
320
|
+
await stopDevServer(workspaceId);
|
|
321
|
+
}
|
|
322
|
+
catch (err) {
|
|
323
|
+
console.error(`[dev-server] Failed to stop '${workspaceId}' during shutdown:`, err);
|
|
324
|
+
}
|
|
325
|
+
}));
|
|
326
|
+
}
|
|
315
327
|
// ── Logs ───────────────────────────────────────────────────────────────────────
|
|
316
328
|
/**
|
|
317
329
|
* Get logs from running dev-server containers for a workspace.
|
|
@@ -6,6 +6,7 @@ import * as settingsService from './settings-service.js';
|
|
|
6
6
|
import { emitEphemeral } from './websocket-service.js';
|
|
7
7
|
const MIN_DELAY_SECONDS = 60;
|
|
8
8
|
const MAX_DELAY_SECONDS = 21600;
|
|
9
|
+
const ACTIVE_SESSION_RETRY_MS = 15_000;
|
|
9
10
|
const STALE_WAKEUP_GRACE_MS = 5 * 60 * 1000;
|
|
10
11
|
const AUTONOMOUS_LOOP_SENTINEL = '<<autonomous-loop-dynamic>>';
|
|
11
12
|
const AUTONOMOUS_LOOP_FALLBACK_PROMPT = 'Continue where you left off.';
|
|
@@ -19,6 +20,15 @@ function rowToPending(row) {
|
|
|
19
20
|
return null;
|
|
20
21
|
return { targetAt: row.target_at, reason: row.reason ?? undefined };
|
|
21
22
|
}
|
|
23
|
+
/** Keep a claimed wakeup durable while the session is still unavailable. */
|
|
24
|
+
function defer(workspaceId, row) {
|
|
25
|
+
const targetAt = new Date(Date.now() + ACTIVE_SESSION_RETRY_MS).toISOString();
|
|
26
|
+
getDb().prepare('UPDATE pending_wakeups SET target_at = ? WHERE workspace_id = ?').run(targetAt, workspaceId);
|
|
27
|
+
const timeout = setTimeout(() => fire(workspaceId), ACTIVE_SESSION_RETRY_MS);
|
|
28
|
+
timeout.unref?.();
|
|
29
|
+
timers.set(workspaceId, timeout);
|
|
30
|
+
emitEphemeral(workspaceId, 'wakeup:scheduled', { targetAt, reason: row.reason ?? undefined });
|
|
31
|
+
}
|
|
22
32
|
/** Schedule a wakeup for the given workspace. Replaces any existing pending wakeup. */
|
|
23
33
|
export function schedule(workspaceId, delaySeconds, prompt, reason, agentSessionId) {
|
|
24
34
|
try {
|
|
@@ -118,21 +128,12 @@ export function rehydrate() {
|
|
|
118
128
|
function fire(workspaceId) {
|
|
119
129
|
try {
|
|
120
130
|
const db = getDb();
|
|
121
|
-
|
|
122
|
-
// cancel() can't race us between the read and the act. If we come out
|
|
123
|
-
// with a row, it's ours exclusively; no other caller will ever see it.
|
|
124
|
-
const row = db.transaction(() => {
|
|
125
|
-
const r = db.prepare('SELECT * FROM pending_wakeups WHERE workspace_id = ?').get(workspaceId);
|
|
126
|
-
if (r) {
|
|
127
|
-
db.prepare('DELETE FROM pending_wakeups WHERE workspace_id = ?').run(workspaceId);
|
|
128
|
-
}
|
|
129
|
-
return r;
|
|
130
|
-
})();
|
|
131
|
+
const row = db.prepare('SELECT * FROM pending_wakeups WHERE workspace_id = ?').get(workspaceId);
|
|
131
132
|
timers.delete(workspaceId);
|
|
132
133
|
if (!row)
|
|
133
134
|
return;
|
|
134
135
|
if (orchestrator.hasController(workspaceId)) {
|
|
135
|
-
|
|
136
|
+
defer(workspaceId, row);
|
|
136
137
|
return;
|
|
137
138
|
}
|
|
138
139
|
const wsRow = db
|
|
@@ -155,12 +156,13 @@ function fire(workspaceId) {
|
|
|
155
156
|
const agentPermissionMode = stored === 'plan' || stored === 'strict' || stored === 'interactive' ? stored : 'bypass';
|
|
156
157
|
try {
|
|
157
158
|
orchestrator.startAgent(workspaceId, worktreePath, row.prompt, wsRow.model, true, agentPermissionMode, row.agent_session_id ?? undefined, wsRow.reasoning_effort);
|
|
159
|
+
db.prepare('DELETE FROM pending_wakeups WHERE workspace_id = ?').run(workspaceId);
|
|
158
160
|
emitEphemeral(workspaceId, 'wakeup:fired', {});
|
|
159
161
|
}
|
|
160
162
|
catch (err) {
|
|
161
163
|
const message = err instanceof Error ? err.message : String(err);
|
|
162
164
|
console.error(`[wakeup-service] startAgent at fire time failed for '${workspaceId}':`, message);
|
|
163
|
-
|
|
165
|
+
defer(workspaceId, row);
|
|
164
166
|
}
|
|
165
167
|
}
|
|
166
168
|
catch (err) {
|
|
@@ -2,6 +2,7 @@ import { execFile as execFileCb, execFileSync } from 'node:child_process';
|
|
|
2
2
|
import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { promisify } from 'node:util';
|
|
5
|
+
import { resolvePathInside } from './safe-path.js';
|
|
5
6
|
const execFileAsync = promisify(execFileCb);
|
|
6
7
|
const READ_ONLY_GIT_TIMEOUT_MS = 15_000;
|
|
7
8
|
function git(repoPath, args) {
|
|
@@ -854,6 +855,7 @@ export function getChangedFilesBetween(repoPath, fromRef, toRef) {
|
|
|
854
855
|
* feedback in the UI.
|
|
855
856
|
*/
|
|
856
857
|
export function rollbackFile(repoPath, branchName, filePath, remote = 'origin') {
|
|
858
|
+
const absPath = resolvePathInside(repoPath, filePath);
|
|
857
859
|
const remoteRef = `${remote}/${branchName}`;
|
|
858
860
|
let remoteRefExists = false;
|
|
859
861
|
try {
|
|
@@ -884,7 +886,6 @@ export function rollbackFile(repoPath, branchName, filePath, remote = 'origin')
|
|
|
884
886
|
// with a previous rollback, stale UI list, manual rm), we still return
|
|
885
887
|
// 'deleted' since the end state matches the user's intent. `rmSync`
|
|
886
888
|
// over `git clean -f` keeps the action narrow to one file.
|
|
887
|
-
const absPath = join(repoPath, filePath);
|
|
888
889
|
if (existsSync(absPath)) {
|
|
889
890
|
rmSync(absPath, { force: true });
|
|
890
891
|
}
|
|
@@ -896,7 +897,7 @@ export const rollbackFileToRemote = rollbackFile;
|
|
|
896
897
|
/** Get the current content of a file in the worktree. Returns null if the file doesn't exist. */
|
|
897
898
|
export function getFileContent(repoPath, filePath) {
|
|
898
899
|
try {
|
|
899
|
-
return readFileSync(
|
|
900
|
+
return readFileSync(resolvePathInside(repoPath, filePath), 'utf-8');
|
|
900
901
|
}
|
|
901
902
|
catch {
|
|
902
903
|
return null;
|
|
@@ -4,6 +4,13 @@ export function isPathInside(rootPath, candidatePath) {
|
|
|
4
4
|
const relative = path.relative(rootPath, candidatePath);
|
|
5
5
|
return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
|
|
6
6
|
}
|
|
7
|
+
/** Reject an absolute or traversal path without requiring it to exist on disk. */
|
|
8
|
+
export function assertPathInside(rootPath, relativePath) {
|
|
9
|
+
const root = path.resolve(rootPath);
|
|
10
|
+
const candidate = path.resolve(root, relativePath);
|
|
11
|
+
if (!isPathInside(root, candidate))
|
|
12
|
+
throw new Error(`Path '${relativePath}' escapes allowed root`);
|
|
13
|
+
}
|
|
7
14
|
/** Resolve two existing paths and reject candidates that escape through a symlinked parent. */
|
|
8
15
|
export function resolveExistingPathInside(rootPath, candidatePath) {
|
|
9
16
|
const realRoot = fs.realpathSync(rootPath);
|
|
@@ -12,3 +19,30 @@ export function resolveExistingPathInside(rootPath, candidatePath) {
|
|
|
12
19
|
throw new Error('Path escapes allowed root');
|
|
13
20
|
return realCandidate;
|
|
14
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Resolve a worktree path, including its parent symlinks, while allowing a
|
|
24
|
+
* missing leaf (needed when removing an untracked file).
|
|
25
|
+
*/
|
|
26
|
+
export function resolvePathInside(rootPath, relativePath) {
|
|
27
|
+
assertPathInside(rootPath, relativePath);
|
|
28
|
+
const root = fs.realpathSync(rootPath);
|
|
29
|
+
const candidate = path.resolve(rootPath, relativePath);
|
|
30
|
+
let parent;
|
|
31
|
+
try {
|
|
32
|
+
parent = fs.realpathSync(path.dirname(candidate));
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
throw new Error(`Path '${relativePath}' is invalid (parent directory does not exist)`);
|
|
36
|
+
}
|
|
37
|
+
if (!isPathInside(root, parent))
|
|
38
|
+
throw new Error('Path escapes allowed root');
|
|
39
|
+
const resolved = path.join(parent, path.basename(candidate));
|
|
40
|
+
try {
|
|
41
|
+
return resolveExistingPathInside(root, resolved);
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
if (err.code === 'ENOENT')
|
|
45
|
+
return resolved;
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loicngr/kobo",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.14",
|
|
4
4
|
"description": "Kōbō — multi-workspace agent manager for Claude Code. Orchestrates isolated git worktrees with dev servers, Notion integration, and MCP tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "GPL-3.0-or-later",
|
|
@@ -67,12 +67,12 @@
|
|
|
67
67
|
"prepublishOnly": "npm run build"
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
|
-
"@anthropic-ai/claude-agent-sdk": "^0.3.
|
|
70
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.246",
|
|
71
71
|
"@emnapi/core": "^1.11.3",
|
|
72
72
|
"@emnapi/runtime": "^1.11.3",
|
|
73
73
|
"@hono/node-server": "^2.1.0",
|
|
74
74
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
75
|
-
"@openai/codex": "^0.
|
|
75
|
+
"@openai/codex": "^0.149.1",
|
|
76
76
|
"better-sqlite3": "^13.0.3",
|
|
77
77
|
"cron-parser": "^5.7.0",
|
|
78
78
|
"hono": "^4.13.1",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{n as e}from"./vue-i18n-C3W5i7h6.js";import{B as t,C as n,St as r,wt as i,yt as a}from"./reactivity.esm-bundler-DtSZKRQB.js";import{$ as o,A as s,B as c,H as l,It as u,Mt as d,Rt as f,Tt as p,U as m,V as h,W as g,Wt as _,X as v,Z as y,_n as b,fn as x,t as S,w as C}from"./runtime-dom.esm-bundler-DgevF6KV.js";import{l as w,t as T}from"./QIcon--SvgE-nS.js";import{G as E,P as D,Q as O,d as k,f as A,v as j,y as M}from"./index-
|
|
1
|
+
import{n as e}from"./vue-i18n-C3W5i7h6.js";import{B as t,C as n,St as r,wt as i,yt as a}from"./reactivity.esm-bundler-DtSZKRQB.js";import{$ as o,A as s,B as c,H as l,It as u,Mt as d,Rt as f,Tt as p,U as m,V as h,W as g,Wt as _,X as v,Z as y,_n as b,fn as x,t as S,w as C}from"./runtime-dom.esm-bundler-DgevF6KV.js";import{l as w,t as T}from"./QIcon--SvgE-nS.js";import{G as E,P as D,Q as O,d as k,f as A,v as j,y as M}from"./index-CtK8ORpZ.js";import{t as N}from"./QSpinnerDots-B4RmFJUj.js";import{u as ee}from"./QSelect-xHphd7hW.js";import{t as P}from"./QTooltip-DyS20jDT.js";import{r as F,t as te}from"./pending-diff-open-DHlXLorX.js";import{t as I}from"./documents-DJy1tgPd.js";import{i as L,n as ne,t as R}from"./render-chat-markdown-C00A6CfY.js";import{t as z}from"./_plugin-vue_export-helper-BDNMzG2s.js";import{n as B}from"./WorkspacePage-CJKSrSEC.js";function re(e,t,n=!0,r){let i=[],a=new Map,o=new Map;for(let n=0;n<e.length;n++){let s=e[n],c=t?.[n],l=r?.[n]??void 0;switch(s.kind){case`message:text`:{let e=a.get(s.messageId);if(e)e.text+=s.text,e.streaming=s.streaming,l&&e.eventIds?.push(l);else{let e={type:`text`,messageId:s.messageId,text:s.text,streaming:s.streaming,ts:c,eventIds:l?[l]:void 0};a.set(s.messageId,e),i.push(e)}break}case`message:end`:{let e=a.get(s.messageId);e&&(e.streaming=!1);break}case`message:thinking`:i.push({type:`thinking`,messageId:s.messageId,text:s.text,ts:c,eventIds:l?[l]:void 0});break;case`tool:call`:{let e={type:`tool`,toolCallId:s.toolCallId,name:s.name,input:s.input,ts:c,eventIds:l?[l]:void 0};o.set(s.toolCallId,e),i.push(e);break}case`tool:result`:{let e=o.get(s.toolCallId);e&&(e.result={output:s.output,isError:s.isError},l&&e.eventIds?.push(l));break}case`session:started`:i.push({type:`session`,kind:`started`,detail:{engineSessionId:s.engineSessionId,model:s.model},ts:c,eventIds:l?[l]:void 0});break;case`session:ended`:i.push({type:`session`,kind:`ended`,detail:{reason:s.reason,exitCode:s.exitCode},ts:c,eventIds:l?[l]:void 0});break;case`session:compacted`:i.push({type:`session`,kind:`compacted`,ts:c,eventIds:l?[l]:void 0})}}let s=null;for(let e of i)e.type===`text`&&e.streaming&&(s&&(s.streaming=!1),s=e);return s&&!n&&(s.streaming=!1),i}function ie(e,t){if(t.length===0)return e;let n=t.map(e=>({type:`user`,content:e.content,sender:e.sender,ts:e.ts,eventIds:e.eventIds})),r=[...e,...n];r.sort((e,t)=>{let n=e.ts??``,r=t.ts??``;return n===r?0:n?r?n<r?-1:1:-1:1});let i;for(let e of r)e.type===`user`&&e.sender!==`system-prompt`&&e.ts&&(!i||e.ts>i)&&(i=e.ts);if(i)for(let e of r)e.type===`text`&&e.streaming&&(!e.ts||e.ts<i)&&(e.streaming=!1);return r}var V=new Set([`setup`,`cleanup`,`archive`]);function H(e){switch(e.type){case`user`:return e.sender===`system-prompt`?`system-prompt`:V.has(e.sender)?`script`:`user`;case`session`:return`session`;default:return`agent`}}function ae(e){let t=[],n=null,r=null;for(let i of e){let e=H(i),a=e===`session`||e===`system-prompt`,o=e===`script`&&i.type===`user`?`script:${i.sender}`: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}var U={class:`text-caption text-grey-6`},oe=o({__name:`SessionEventItem`,props:{item:{}},setup(e){let t=e,n=c(()=>{switch(t.item.kind){case`started`:return`session.started`;case`ended`:return`session.ended`;case`compacted`:return`session.compacted`;default:return`session.started`}});return(e,t)=>(f(),g(`span`,U,i(e.$t(n.value)),1))}});function se(e,t){if(t.length===0||e.length===0)return e;let n=[...t].sort((e,t)=>t.length-e.length),r=new DOMParser().parseFromString(`<div>${e}</div>`,`text/html`),i=r.body.firstChild;if(!i)return e;function a(e){if(e.nodeType===Node.TEXT_NODE){ce(e,n,r);return}if(e.nodeName===`A`)return;let t=Array.from(e.childNodes);for(let e of t)a(e)}return a(i),i.innerHTML}function ce(e,t,n){let r=e.textContent??``;if(!t.some(e=>r.includes(e)))return;let i=n.createDocumentFragment(),a=0;for(;a<r.length;){let e=le(r,a,t);if(!e){i.appendChild(n.createTextNode(r.slice(a)));break}e.index>a&&i.appendChild(n.createTextNode(r.slice(a,e.index)));let o=n.createElement(`a`);o.className=`document-link`,o.setAttribute(`data-document-path`,e.path),o.setAttribute(`href`,`#`),o.textContent=e.path,i.appendChild(o),a=e.index+e.path.length}e.parentNode?.replaceChild(i,e)}function le(e,t,n){let r=null;for(let i of n){let n=e.indexOf(i,t);n<0||(!r||n<r.index||n===r.index&&i.length>r.path.length)&&(r={index:n,path:i})}return r}var W=[`innerHTML`],ue=z(o({__name:`TextMessageItem`,props:{item:{}},setup(e){let t=e,n=I(),r=k(),i=c(()=>{let e=r.selectedWorkspaceId;return e?n.documentsFor(e).map(e=>e.path):[]}),a=c(()=>{let e=se(L.parse(t.item.text,{async:!1,breaks:!0,gfm:!0}),i.value);return ne(e,{addAttr:[`data-document-path`]})});function o(e){let t=e.target?.closest(`.document-link`);if(!t)return;e.preventDefault();let i=t.getAttribute(`data-document-path`),a=r.selectedWorkspaceId;!i||!a||n.openDocumentByPath(a,i)}return(t,n)=>(f(),g(`div`,{class:`markdown-message`,onClick:o},[h(`div`,{innerHTML:a.value},null,8,W),e.item.streaming?(f(),l(w,{key:0,size:`xs`,class:`q-ml-xs`})):m(``,!0)]))}}),[[`__scopeId`,`data-v-1b7bd8ca`]]);function de(e,t){let n=e.split(`
|
|
2
2
|
`),r=t.split(`
|
|
3
3
|
`),i=n.length,a=r.length,o=Array.from({length:i+1},()=>Array(a+1).fill(0));for(let e=i-1;e>=0;e--)for(let t=a-1;t>=0;t--)n[e]===r[t]?o[e][t]=o[e+1][t+1]+1:o[e][t]=Math.max(o[e+1][t],o[e][t+1]);let s=[],c=0,l=0;for(;c<i&&l<a;)n[c]===r[l]?(s.push({type:`context`,content:n[c]}),c++,l++):o[c+1][l]>=o[c][l+1]?(s.push({type:`del`,content:n[c]}),c++):(s.push({type:`add`,content:r[l]}),l++);for(;c<i;)s.push({type:`del`,content:n[c++]});for(;l<a;)s.push({type:`add`,content:r[l++]});return s}function G(e){let t=e.split(`
|
|
4
4
|
`),n=[];for(let e of t)e.startsWith(`@@`)||e.startsWith(`+++`)||e.startsWith(`---`)||(e.startsWith(`+`)?n.push({type:`add`,content:e.slice(1)}):e.startsWith(`-`)?n.push({type:`del`,content:e.slice(1)}):e.startsWith(` `)?n.push({type:`context`,content:e.slice(1)}):e.length>0&&n.push({type:`context`,content:e}));return n}function K(e,t){if(!t||typeof t!=`object`)return null;let n=t;if(e===`Edit`){let e=n.file_path;if(!e)return null;let t=n.old_string??``,r=n.new_string??``,i=typeof n.diff==`string`?n.diff:``;if(!t&&!r&&i.length>0){let t=G(i);return{toolName:`Edit`,filePath:e,additions:t.filter(e=>e.type===`add`).length,deletions:t.filter(e=>e.type===`del`).length,diffLines:t}}return{toolName:`Edit`,filePath:e,oldString:t,newString:r,replaceAll:n.replace_all??!1,additions:r?r.split(`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{B as e,C as t,wt as n}from"./reactivity.esm-bundler-DtSZKRQB.js";import{$ as r,A as i,H as a,Mt as o,Rt as s,U as c,V as l,W as u,Wt as d,Z as f,_n as p}from"./runtime-dom.esm-bundler-DgevF6KV.js";import{Dt as m,G as h,kt as g,s as _}from"./index-
|
|
1
|
+
import{B as e,C as t,wt as n}from"./reactivity.esm-bundler-DtSZKRQB.js";import{$ as r,A as i,H as a,Mt as o,Rt as s,U as c,V as l,W as u,Wt as d,Z as f,_n as p}from"./runtime-dom.esm-bundler-DgevF6KV.js";import{Dt as m,G as h,kt as g,s as _}from"./index-CtK8ORpZ.js";import{t as v}from"./render-chat-markdown-C00A6CfY.js";import{t as y}from"./_plugin-vue_export-helper-BDNMzG2s.js";import{t as b}from"./QSpace-D9UEvM99.js";import{t as x}from"./QChip-CY0_8v_e.js";import{t as S}from"./QPage-DuIsRmYu.js";var C={class:`row items-center q-mb-md`},w={class:`text-h6 q-ml-sm`},T={key:0,class:`text-grey-6 text-center q-pa-lg`},E={key:1,class:`text-negative text-center q-pa-lg`},D={key:2,class:`text-grey-6 text-center q-pa-lg`},O={key:3,class:`column q-gutter-md`},k={key:0,class:`text-caption text-grey-6`},A={class:`row items-center q-mb-sm`},j={class:`text-subtitle1 text-indigo-3`,style:{"font-family":`var(--kobo-font-mono, monospace)`}},M=[`innerHTML`],N=y(r({__name:`ChangelogPage`,setup(r){let y=_(),N=t([]),P=t(``),F=t(!1),I=t(null);function L(e){return v(e)}async function R(){F.value=!0,I.value=null;try{let e=await fetch(`/api/changelog`);if(!e.ok)throw Error(`HTTP ${e.status}`);let t=await e.json();P.value=t.currentVersion??``,N.value=t.versions??[]}catch(e){I.value=e instanceof Error?e.message:String(e)}finally{F.value=!1}}return o(R),(t,r)=>(s(),a(S,{class:`q-pa-md`,style:{"max-width":`900px`,margin:`0 auto`}},{default:p(()=>[l(`div`,C,[f(h,{flat:``,dense:``,round:``,icon:`arrow_back`,onClick:r[0]||=t=>e(y).back()}),l(`div`,w,n(t.$t(`changelog.title`)),1),f(b),f(h,{flat:``,dense:``,icon:`refresh`,loading:F.value,label:t.$t(`common.refresh`),onClick:R},null,8,[`loading`,`label`])]),F.value&&N.value.length===0?(s(),u(`div`,T,n(t.$t(`common.loading`)),1)):I.value?(s(),u(`div`,E,n(I.value),1)):N.value.length===0?(s(),u(`div`,D,n(t.$t(`changelog.empty`)),1)):(s(),u(`div`,O,[P.value?(s(),u(`div`,k,n(t.$t(`changelog.currentVersion`,{version:P.value})),1)):c(``,!0),(s(!0),u(i,null,d(N.value,e=>(s(),a(m,{key:e.version,dark:``,flat:``,bordered:``},{default:p(()=>[f(g,null,{default:p(()=>[l(`div`,A,[l(`div`,j,` v`+n(e.version),1),e.version===P.value?(s(),a(x,{key:0,dense:``,size:`sm`,color:`indigo-7`,"text-color":`grey-2`,label:t.$t(`changelog.current`),class:`q-ml-sm`},null,8,[`label`])):c(``,!0)]),l(`div`,{class:`changelog-notes`,innerHTML:L(e.notes)},null,8,M)]),_:2},1024)]),_:2},1024))),128))]))]),_:1}))}}),[[`__scopeId`,`data-v-ed73d661`]]);export{N as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{P as e,b as t}from"./QIcon--SvgE-nS.js";import{gt as n,ht as r}from"./index-
|
|
1
|
+
import{P as e,b as t}from"./QIcon--SvgE-nS.js";import{gt as n,ht as r}from"./index-CtK8ORpZ.js";var i={left:!0,right:!0,up:!0,down:!0,horizontal:!0,vertical:!0},a=Object.keys(i);i.all=!0;function o(e){let t={};for(let n of a)e[n]&&(t[n]=!0);return Object.keys(t).length===0?i:(t.horizontal?t.left=t.right=!0:t.left&&t.right&&(t.horizontal=!0),t.vertical?t.up=t.down=!0:t.up&&t.down&&(t.vertical=!0),t.horizontal&&t.vertical&&(t.all=!0),t)}var s=[`INPUT`,`TEXTAREA`];function c(e,t){return t.event===void 0&&e.target!==void 0&&!e.target.draggable&&typeof t.handler==`function`&&!s.includes(e.target.nodeName.toUpperCase())&&(e.qClonedBy===void 0||!e.qClonedBy.includes(t.uid))}function l(e){return e===!1?0:e===!0||e===void 0?1:Number.parseInt(e,10)||0}var u=e({name:`close-popup`,beforeMount(e,{value:i}){let a={depth:l(i),handler(t){a.depth!==0&&setTimeout(()=>{let i=n(e);i!==void 0&&r(i,t,a.depth)},0)},handlerKey(e){t(e,13)&&a.handler(e)}};e.__qclosepopup=a,e.addEventListener(`click`,a.handler),e.addEventListener(`keyup`,a.handlerKey)},updated(e,{value:t,oldValue:n}){t!==n&&(e.__qclosepopup.depth=l(t))},beforeUnmount(e){let t=e.__qclosepopup;e.removeEventListener(`click`,t.handler),e.removeEventListener(`keyup`,t.handlerKey),delete e.__qclosepopup}});export{o as n,c as r,u as t};
|