@loicngr/kobo 1.7.4 → 1.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +20 -8
  2. package/dist/mcp-server/kobo-tasks-handlers.js +26 -1
  3. package/dist/mcp-server/kobo-tasks-server.js +41 -1
  4. package/dist/server/db/migrations.js +49 -0
  5. package/dist/server/db/schema.js +11 -0
  6. package/dist/server/index.js +7 -1
  7. package/dist/server/routes/workspaces.js +82 -4
  8. package/dist/server/services/agent/engines/claude-code/engine.js +4 -1
  9. package/dist/server/services/agent/engines/claude-code/event-mapper.js +6 -2
  10. package/dist/server/services/agent/orchestrator.js +72 -71
  11. package/dist/server/services/auto-loop-service.js +8 -0
  12. package/dist/server/services/quota-backoff-service.js +127 -0
  13. package/dist/server/services/workspace-service.js +80 -0
  14. package/package.json +1 -1
  15. package/src/client/dist/spa/assets/{ActivityFeed-ClJLeAXJ.js → ActivityFeed-oW9PgZ8E.js} +1 -1
  16. package/src/client/dist/spa/assets/AutoLoopChip-Y53cnGfZ.js +1 -0
  17. package/src/client/dist/spa/assets/{CreatePage-BOkt0Psl.js → CreatePage-CuD7sMR7.js} +1 -1
  18. package/src/client/dist/spa/assets/{DiffViewer-Dls1jFCN.js → DiffViewer-rc3tE9fq.js} +3 -3
  19. package/src/client/dist/spa/assets/{HealthPage-CMxH3SBS.js → HealthPage-Dz0yGGMB.js} +1 -1
  20. package/src/client/dist/spa/assets/{MainLayout-DHNIerYJ.js → MainLayout-B9i06p7n.js} +17 -17
  21. package/src/client/dist/spa/assets/{MainLayout-DKurmqtk.css → MainLayout-DDa3rGKA.css} +1 -1
  22. package/src/client/dist/spa/assets/{SearchPage-BEnZ-CLq.js → SearchPage-DdX7JZCD.js} +1 -1
  23. package/src/client/dist/spa/assets/{SettingsPage-DeCbWvPb.js → SettingsPage-Dnj1CWc3.js} +1 -1
  24. package/src/client/dist/spa/assets/{WorkspacePage-eymEd4kx.css → WorkspacePage-CCtIrBiR.css} +1 -1
  25. package/src/client/dist/spa/assets/WorkspacePage-DHp20nl-.js +4 -0
  26. package/src/client/dist/spa/assets/{cssMode-AlflsawW.js → cssMode-DSB5jkRt.js} +1 -1
  27. package/src/client/dist/spa/assets/{editor.api-DtvjQlUm.js → editor.api-Bcw50eFD.js} +1 -1
  28. package/src/client/dist/spa/assets/{editor.main-Ccy_gjVD.js → editor.main-D9piVGaH.js} +3 -3
  29. package/src/client/dist/spa/assets/{expand-template-AQsvbQ8_.js → expand-template-BIPuNAYV.js} +1 -1
  30. package/src/client/dist/spa/assets/{freemarker2-DdQktlXK.js → freemarker2-CVh_Zh8H.js} +1 -1
  31. package/src/client/dist/spa/assets/{handlebars-CE3ee2NH.js → handlebars-CpCgELpu.js} +1 -1
  32. package/src/client/dist/spa/assets/{html-CCKX8Xv9.js → html-ikWDpvWk.js} +1 -1
  33. package/src/client/dist/spa/assets/{htmlMode-Dh8jDJum.js → htmlMode-C9TTCKih.js} +1 -1
  34. package/src/client/dist/spa/assets/i18n-DZCb8dnb.js +1 -0
  35. package/src/client/dist/spa/assets/index-DuK38XN5.js +2 -0
  36. package/src/client/dist/spa/assets/{javascript-DhmZNdUp.js → javascript-C4OlkNeA.js} +1 -1
  37. package/src/client/dist/spa/assets/{jsonMode-B0xAtnNK.js → jsonMode-BiD34_86.js} +1 -1
  38. package/src/client/dist/spa/assets/{liquid-ByL0HpZ0.js → liquid-Dty0Ui2c.js} +1 -1
  39. package/src/client/dist/spa/assets/{mdx-DX4pehAZ.js → mdx-yiUjOVv6.js} +1 -1
  40. package/src/client/dist/spa/assets/{models-ClWoqWeC.js → models-BDkLiht9.js} +1 -1
  41. package/src/client/dist/spa/assets/{monaco.contribution-Fegh8Y1Y.js → monaco.contribution-Bz9yFPWR.js} +2 -2
  42. package/src/client/dist/spa/assets/{purify.es-BWZjBa9F.js → purify.es-BIY760fF.js} +1 -1
  43. package/src/client/dist/spa/assets/{python-COS2MM8n.js → python-7SPSWQoD.js} +1 -1
  44. package/src/client/dist/spa/assets/{razor-Cc3xCJU7.js → razor-eagZawXK.js} +1 -1
  45. package/src/client/dist/spa/assets/{render-chat-markdown-DcGIpMoe.js → render-chat-markdown-TvAqpDih.js} +1 -1
  46. package/src/client/dist/spa/assets/{tsMode-eQIJjERk.js → tsMode-CLYG2xeJ.js} +1 -1
  47. package/src/client/dist/spa/assets/{typescript-DwIlacVU.js → typescript-CzOXM8yS.js} +1 -1
  48. package/src/client/dist/spa/assets/{xml-DP-09Aih.js → xml-2_0_6RAX.js} +1 -1
  49. package/src/client/dist/spa/assets/{yaml-BhrtimeA.js → yaml-CtpgNyXs.js} +1 -1
  50. package/src/client/dist/spa/index.html +1 -1
  51. package/src/mcp-server/kobo-tasks-handlers.ts +35 -1
  52. package/src/mcp-server/kobo-tasks-server.ts +42 -0
  53. package/src/client/dist/spa/assets/WorkspacePage-DFAFT5OW.js +0 -4
  54. package/src/client/dist/spa/assets/i18n-BOsrrRj4.js +0 -1
  55. package/src/client/dist/spa/assets/index-_ZaIBxd6.js +0 -2
  56. /package/src/client/dist/spa/assets/{QPage-ChUKoaKe.js → QPage-DFi3K093.js} +0 -0
  57. /package/src/client/dist/spa/assets/{formatters-BD0_hovB.js → formatters-DCAQ6ANJ.js} +0 -0
@@ -4,7 +4,9 @@ import { getDb } from '../../db/index.js';
4
4
  import { ensureKoboHome, getCompiledMcpServerPath, getDbPath, getKoboHome, getMcpServerSourcePath, getSettingsPath, getSkillsPath, } from '../../utils/paths.js';
5
5
  import { unregisterProcess } from '../../utils/process-tracker.js';
6
6
  import * as autoLoopService from '../auto-loop-service.js';
7
+ import * as quotaBackoffService from '../quota-backoff-service.js';
7
8
  import { getEffectiveSettings } from '../settings-service.js';
9
+ import { refreshNow } from '../usage/poller.js';
8
10
  import * as wakeupService from '../wakeup-service.js';
9
11
  import { emit, emitEphemeral } from '../websocket-service.js';
10
12
  import { getWorkspace as getWs, markWorkspaceUnread, updateWorkspaceStatus, } from '../workspace-service.js';
@@ -137,8 +139,6 @@ let availableSkills = (() => {
137
139
  const retryCounts = new Map();
138
140
  /** Tracks workspaces where the current session failed due to a stale --resume session ID. */
139
141
  const resumeFailedSet = new Set();
140
- /** workspaceId -> backoff timer */
141
- const backoffTimers = new Map();
142
142
  // ── Watchdog ──────────────────────────────────────────────────────────────────
143
143
  const WATCHDOG_INTERVAL_MS = 30_000;
144
144
  let watchdogTimer = null;
@@ -469,7 +469,7 @@ function handleEvent(workspaceId, agentSessionId, ev) {
469
469
  }
470
470
  }
471
471
  if (ev.kind === 'error' && ev.category === 'quota') {
472
- handleQuota(workspaceId, agentSessionId);
472
+ void handleQuota(workspaceId, agentSessionId);
473
473
  }
474
474
  if (ev.kind === 'error' && ev.category === 'resume_failed') {
475
475
  resumeFailedSet.add(workspaceId);
@@ -569,14 +569,11 @@ function onSessionEnded(workspaceId, agentSessionId, exitCode, reason, resumeFai
569
569
  if (wasStopping)
570
570
  return;
571
571
  // When the session hit quota, handleQuota() already transitioned the
572
- // workspace to `quota` and armed the retry timer. Keep that timer alive
573
- // and preserve the `quota` status so auto-loop can resume after reset.
572
+ // workspace to `quota` and armed the retry via quotaBackoffService.
573
+ // Preserve that pending backoff in the quota path; otherwise clear any
574
+ // stale entry (defensive — shouldn't normally exist on a non-quota end).
574
575
  if (!preserveQuotaBackoff) {
575
- const pendingBackoff = backoffTimers.get(workspaceId);
576
- if (pendingBackoff) {
577
- clearTimeout(pendingBackoff);
578
- backoffTimers.delete(workspaceId);
579
- }
576
+ quotaBackoffService.cancel(workspaceId, 'completed');
580
577
  }
581
578
  if (preserveQuotaBackoff) {
582
579
  try {
@@ -774,11 +771,8 @@ export function stopAgent(workspaceId) {
774
771
  // session:ended handler checks identity before removing, so a new controller
775
772
  // started in the meantime is preserved.
776
773
  controllers.delete(workspaceId);
777
- const timer = backoffTimers.get(workspaceId);
778
- if (timer) {
779
- clearTimeout(timer);
780
- backoffTimers.delete(workspaceId);
781
- }
774
+ // Manual stop should also drop any pending quota auto-resume.
775
+ quotaBackoffService.cancel(workspaceId, 'user');
782
776
  // Fire-and-forget: controller.stop is async but we don't block callers.
783
777
  void ctrl.stop().catch((err) => {
784
778
  console.error('[orchestrator] controller.stop failed:', err);
@@ -1098,12 +1092,14 @@ const QUOTA_FALLBACK_LADDER_MINUTES = [15, 30, 60, 180, 300];
1098
1092
  /**
1099
1093
  * Compute the delay before retrying a workspace hit by quota.
1100
1094
  *
1101
- * Prefers the `resetsAt` of the saturated bucket with the furthest-future
1102
- * reset (a tighter bucket will unlock by then anyway). Falls back to a
1103
- * fixed ladder (15 30 60 180 → 300 min) whenever the rate_limit
1104
- * info is missing, malformed, or implausible.
1095
+ * Prefers (1) the `resetsAt` of the saturated bucket with the furthest-future
1096
+ * reset reported by the agent's `rate_limit` event, then (1.5) the official
1097
+ * Anthropic usage API (`five_hour` bucket) when it reports saturation, and
1098
+ * finally (2) a fixed ladder (15 → 30 → 60 → 180 → 300 min) whenever neither
1099
+ * source is usable.
1105
1100
  */
1106
- export function computeQuotaBackoffMs(workspaceId, retryCount) {
1101
+ export async function computeQuotaBackoffMs(workspaceId, retryCount) {
1102
+ // 1. Prefer the rate_limit event from the agent stream — most recent + most precise.
1107
1103
  const info = latestRateLimitInfo.get(workspaceId);
1108
1104
  if (info?.buckets?.length) {
1109
1105
  const candidates = info.buckets
@@ -1119,15 +1115,38 @@ export function computeQuotaBackoffMs(workspaceId, retryCount) {
1119
1115
  }
1120
1116
  }
1121
1117
  }
1118
+ // 1.5. Try the official usage API (Claude subscription). Best-effort; never throws.
1119
+ try {
1120
+ const snap = await refreshNow('claude-code');
1121
+ if (snap) {
1122
+ const fiveHour = snap.buckets.find((b) => b.id === 'five_hour');
1123
+ if (fiveHour &&
1124
+ typeof fiveHour.usedPct === 'number' &&
1125
+ fiveHour.usedPct >= QUOTA_SATURATION_THRESHOLD_PCT &&
1126
+ typeof fiveHour.resetsAt === 'string') {
1127
+ const resetTs = Date.parse(fiveHour.resetsAt);
1128
+ const delta = resetTs - Date.now() + QUOTA_SAFETY_MARGIN_MS;
1129
+ if (delta > 0 && delta <= QUOTA_MAX_BACKOFF_MS) {
1130
+ return { delayMs: delta, resetsAt: fiveHour.resetsAt, source: 'usage_api' };
1131
+ }
1132
+ }
1133
+ }
1134
+ }
1135
+ catch (err) {
1136
+ console.warn('[orchestrator] computeQuotaBackoffMs — usage API call failed:', err);
1137
+ }
1138
+ // 2. Hard-coded ladder.
1122
1139
  const idx = Math.min(Math.max(0, retryCount), QUOTA_FALLBACK_LADDER_MINUTES.length - 1);
1123
1140
  const backoffMinutes = QUOTA_FALLBACK_LADDER_MINUTES[idx];
1124
- return { delayMs: backoffMinutes * 60 * 1000, source: 'exponential_fallback' };
1141
+ return { delayMs: backoffMinutes * 60 * 1000, source: 'fallback_ladder' };
1125
1142
  }
1143
+ /** @internal test-only — re-export of `computeQuotaBackoffMs` to anchor a stable seam. */
1144
+ export const _computeQuotaBackoffMs = computeQuotaBackoffMs;
1126
1145
  /** @internal Test-only. */
1127
1146
  export function _test_setRateLimitInfo(workspaceId, info) {
1128
1147
  latestRateLimitInfo.set(workspaceId, info);
1129
1148
  }
1130
- function handleQuota(workspaceId, _agentSessionId) {
1149
+ async function handleQuota(workspaceId, _agentSessionId) {
1131
1150
  try {
1132
1151
  updateWorkspaceStatus(workspaceId, 'quota');
1133
1152
  }
@@ -1135,53 +1154,39 @@ function handleQuota(workspaceId, _agentSessionId) {
1135
1154
  // May fail if transition is not valid
1136
1155
  }
1137
1156
  const retryCount = retryCounts.get(workspaceId) ?? 0;
1138
- const { delayMs, resetsAt, source } = computeQuotaBackoffMs(workspaceId, retryCount);
1139
- const backoffMs = delayMs;
1157
+ const { delayMs, resetsAt, source } = await computeQuotaBackoffMs(workspaceId, retryCount);
1140
1158
  retryCounts.set(workspaceId, retryCount + 1);
1141
- // Surface the backoff schedule as an ephemeral event so the UI can display
1142
- // retry count / wait time without polluting the persistent event log.
1143
- emitEphemeral(workspaceId, 'agent:quota-backoff', {
1144
- retryCount: retryCount + 1,
1145
- backoffMinutes: Math.round(delayMs / 60_000),
1146
- resetsAt,
1147
- source,
1148
- });
1149
- const timer = setTimeout(() => {
1150
- backoffTimers.delete(workspaceId);
1151
- if (!controllers.has(workspaceId)) {
1152
- const freshWs = getWs(workspaceId);
1153
- if (!freshWs || freshWs.archivedAt !== null || freshWs.status !== 'quota') {
1154
- return;
1155
- }
1156
- try {
1157
- if (freshWs.autoLoop) {
1158
- autoLoopService.onQuotaBackoffExpired(workspaceId);
1159
- }
1160
- else {
1161
- const freshWorkingDir = freshWs.worktreePath;
1162
- startAgent(workspaceId, freshWorkingDir, 'Continue the previous task where you left off.', undefined, true);
1163
- }
1164
- }
1165
- catch (err) {
1166
- console.error(`[orchestrator] Quota retry for workspace '${workspaceId}' failed:`, err);
1167
- const msg = err instanceof Error ? err.message : String(err);
1168
- try {
1169
- updateWorkspaceStatus(workspaceId, 'error');
1170
- }
1171
- catch {
1172
- // transition may not be valid
1173
- }
1174
- routeEvent(workspaceId, '', {
1175
- kind: 'error',
1176
- category: 'other',
1177
- message: `Quota retry failed: ${msg}`,
1178
- });
1179
- }
1180
- }
1181
- }, backoffMs);
1182
- timer.unref?.();
1183
- backoffTimers.set(workspaceId, timer);
1159
+ // The quotaBackoffService owns the timer + the persistent row + the
1160
+ // 'agent:quota-backoff' WS emit. Hand off everything to it.
1161
+ quotaBackoffService.arm(workspaceId, delayMs, { resetsAt: resetsAt ?? null, source });
1184
1162
  }
1163
+ /** @internal test-only — re-export of `handleQuota` for direct testing. */
1164
+ export const _handleQuota = handleQuota;
1165
+ /**
1166
+ * Rebuild the in-memory `retryCounts` map from the persisted `pending_quota_backoffs`
1167
+ * rows. Called from `index.ts` at boot, before `quotaBackoffService.restoreOnBoot`.
1168
+ * Without this, an arm() after restart would compute the next backoff from
1169
+ * `retryCount=0`, undoing the ladder progression.
1170
+ */
1171
+ export function restoreRetryCountsFromDb() {
1172
+ for (const pending of quotaBackoffService.listPending()) {
1173
+ retryCounts.set(pending.workspaceId, pending.retryCount);
1174
+ }
1175
+ }
1176
+ // One-time wire: when the persisted backoff timer fires (or a row is
1177
+ // restored at boot), hand the workspace off to auto-loop. The auto-loop
1178
+ // service decides whether to spawn the next iteration or fall back to a
1179
+ // manual resume.
1180
+ //
1181
+ // IMPORTANT — behavioural contract: only auto-loop workspaces auto-resume
1182
+ // after a quota backoff. `onQuotaBackoffExpired` no-ops if `auto_loop !== 1`
1183
+ // (see auto-loop-service). Workspaces hit by quota WITHOUT auto-loop stay
1184
+ // in `quota` status and require manual user action (resume / new message)
1185
+ // to leave that state. This is intentional: without an auto-loop intent,
1186
+ // firing a fresh agent run in the user's absence would surprise them.
1187
+ quotaBackoffService.setOnFireCallback((workspaceId) => {
1188
+ autoLoopService.onQuotaBackoffExpired(workspaceId);
1189
+ });
1185
1190
  // ── Testing utilities ─────────────────────────────────────────────────────────
1186
1191
  /** @internal test-only */
1187
1192
  export function _getControllers() {
@@ -1192,10 +1197,6 @@ export function _getRetryCounts() {
1192
1197
  return retryCounts;
1193
1198
  }
1194
1199
  /** @internal test-only */
1195
- export function _getBackoffTimers() {
1196
- return backoffTimers;
1197
- }
1198
- /** @internal test-only */
1199
1200
  export function _getSessionIds() {
1200
1201
  return sessionIds;
1201
1202
  }
@@ -309,6 +309,14 @@ function spawnNextIteration(workspaceId, opts = {}) {
309
309
  * Called by orchestrator.handleQuota's backoff timer when auto-loop is enabled.
310
310
  * Spawns the next auto-loop iteration if the workspace is still in quota status
311
311
  * with auto_loop active; no-ops otherwise (race-safe).
312
+ *
313
+ * No-op cases — these all leave the workspace in `quota` status awaiting
314
+ * manual user action:
315
+ * - workspace was deleted between arm and fire
316
+ * - `auto_loop !== 1` (workspace was never an auto-loop target, OR the user
317
+ * toggled the loop off during the backoff window)
318
+ * - `status !== 'quota'` (user already manually resumed, or another path
319
+ * transitioned the workspace)
312
320
  */
313
321
  export function onQuotaBackoffExpired(workspaceId) {
314
322
  const row = getRow(workspaceId);
@@ -0,0 +1,127 @@
1
+ import { getDb } from '../db/index.js';
2
+ import { emitEphemeral } from './websocket-service.js';
3
+ import { getWorkspace } from './workspace-service.js';
4
+ const timers = new Map();
5
+ let onFireCallback = null;
6
+ function rowToPending(row) {
7
+ return {
8
+ workspaceId: row.workspace_id,
9
+ targetAt: row.target_at,
10
+ resetsAt: row.resets_at,
11
+ source: row.source,
12
+ retryCount: row.retry_count,
13
+ createdAt: row.created_at,
14
+ };
15
+ }
16
+ /**
17
+ * Schedule (or reschedule) the auto-resume timer for a workspace that just
18
+ * hit a Claude quota. Persists the target time so it survives restarts and
19
+ * keeps the in-RAM `setTimeout` alive for the current process.
20
+ *
21
+ * `delayMs` is the "fire-now-plus-delta" offset; it MUST already include
22
+ * any safety margin the caller wants. orchestrator.handleQuota owns that math.
23
+ */
24
+ export function arm(workspaceId, delayMs, meta) {
25
+ const db = getDb();
26
+ const now = new Date();
27
+ const targetAt = new Date(now.getTime() + delayMs).toISOString();
28
+ const existing = db
29
+ .prepare('SELECT retry_count FROM pending_quota_backoffs WHERE workspace_id = ?')
30
+ .get(workspaceId);
31
+ const retryCount = (existing?.retry_count ?? 0) + 1;
32
+ db.prepare(`INSERT INTO pending_quota_backoffs (workspace_id, target_at, resets_at, source, retry_count, created_at)
33
+ VALUES (?, ?, ?, ?, ?, ?)
34
+ ON CONFLICT(workspace_id) DO UPDATE SET
35
+ target_at = excluded.target_at,
36
+ resets_at = excluded.resets_at,
37
+ source = excluded.source,
38
+ retry_count = excluded.retry_count,
39
+ created_at = excluded.created_at`).run(workspaceId, targetAt, meta.resetsAt, meta.source, retryCount, now.toISOString());
40
+ const previous = timers.get(workspaceId);
41
+ if (previous)
42
+ clearTimeout(previous);
43
+ const timer = setTimeout(() => fireOrSkip(workspaceId), Math.max(0, delayMs));
44
+ timer.unref?.();
45
+ timers.set(workspaceId, timer);
46
+ emitEphemeral(workspaceId, 'agent:quota-backoff', {
47
+ targetAt,
48
+ resetsAt: meta.resetsAt,
49
+ source: meta.source,
50
+ retryCount,
51
+ });
52
+ }
53
+ /**
54
+ * Cancel the pending backoff for a workspace. Returns true if a row existed
55
+ * (and was deleted), false if there was nothing to cancel. Idempotent.
56
+ */
57
+ export function cancel(workspaceId, reason) {
58
+ const db = getDb();
59
+ const result = db.prepare('DELETE FROM pending_quota_backoffs WHERE workspace_id = ?').run(workspaceId);
60
+ const existed = result.changes > 0;
61
+ const previous = timers.get(workspaceId);
62
+ if (previous) {
63
+ clearTimeout(previous);
64
+ timers.delete(workspaceId);
65
+ }
66
+ if (existed) {
67
+ emitEphemeral(workspaceId, 'agent:quota-backoff-cancelled', { reason });
68
+ }
69
+ return existed;
70
+ }
71
+ export function getPending(workspaceId) {
72
+ const db = getDb();
73
+ const row = db.prepare('SELECT * FROM pending_quota_backoffs WHERE workspace_id = ?').get(workspaceId);
74
+ return row ? rowToPending(row) : null;
75
+ }
76
+ export function listPending() {
77
+ const db = getDb();
78
+ const rows = db.prepare('SELECT * FROM pending_quota_backoffs').all();
79
+ return rows.map(rowToPending);
80
+ }
81
+ export function setOnFireCallback(fn) {
82
+ onFireCallback = fn;
83
+ }
84
+ /**
85
+ * Re-arm timers for rows persisted across restart. Future rows get a fresh
86
+ * `setTimeout`; past rows fire immediately (delay = 0). Rows pointing at
87
+ * archived or missing workspaces are deleted without firing.
88
+ */
89
+ export function restoreOnBoot(onFire) {
90
+ setOnFireCallback(onFire);
91
+ const db = getDb();
92
+ const rows = db.prepare('SELECT * FROM pending_quota_backoffs').all();
93
+ for (const row of rows) {
94
+ const ws = getWorkspace(row.workspace_id);
95
+ if (!ws || ws.archivedAt !== null) {
96
+ db.prepare('DELETE FROM pending_quota_backoffs WHERE workspace_id = ?').run(row.workspace_id);
97
+ continue;
98
+ }
99
+ const delta = new Date(row.target_at).getTime() - Date.now();
100
+ const timer = setTimeout(() => fireOrSkip(row.workspace_id), Math.max(0, delta));
101
+ timer.unref?.();
102
+ timers.set(row.workspace_id, timer);
103
+ }
104
+ }
105
+ /** Internal — invoked when a timer fires. */
106
+ function fireOrSkip(workspaceId) {
107
+ timers.delete(workspaceId);
108
+ // Final archive check before firing — workspace might have been archived
109
+ // between the timer being armed and now.
110
+ const ws = getWorkspace(workspaceId);
111
+ if (!ws || ws.archivedAt !== null) {
112
+ cancel(workspaceId, 'archive');
113
+ return;
114
+ }
115
+ // Consume the persisted row BEFORE invoking the callback. If the server
116
+ // crashes during the spawn the callback triggers, restoreOnBoot won't see
117
+ // a stale row with target_at in the past and re-fire on the next start
118
+ // (which would cause a double spawn). The cb's downstream effects (next
119
+ // iteration, status transitions) are tracked by their own state.
120
+ getDb().prepare('DELETE FROM pending_quota_backoffs WHERE workspace_id = ?').run(workspaceId);
121
+ const cb = onFireCallback;
122
+ if (!cb)
123
+ return;
124
+ cb(workspaceId);
125
+ }
126
+ /** @internal test-only */
127
+ export const _timers = timers;
@@ -3,7 +3,9 @@ import { getDb } from '../db/index.js';
3
3
  import { resolveWorkspaceWorktreePath } from '../utils/worktree-paths.js';
4
4
  import * as orchestrator from './agent/orchestrator.js';
5
5
  import * as autoLoopService from './auto-loop-service.js';
6
+ import * as quotaBackoffService from './quota-backoff-service.js';
6
7
  import * as wakeupService from './wakeup-service.js';
8
+ import { emitEphemeral } from './websocket-service.js';
7
9
  /** Allowed status transitions per current status. Enforced by updateWorkspaceStatus. */
8
10
  const VALID_TRANSITIONS = {
9
11
  created: ['extracting', 'brainstorming', 'idle', 'error'],
@@ -51,6 +53,8 @@ function mapWorkspace(row) {
51
53
  archivedAt: row.archived_at,
52
54
  favoritedAt: row.favorited_at,
53
55
  tags: parseTags(row.tags),
56
+ description: row.description,
57
+ agentDescription: row.agent_description,
54
58
  engine: row.engine ?? 'claude-code',
55
59
  autoLoop: row.auto_loop === 1,
56
60
  autoLoopReady: row.auto_loop_ready === 1,
@@ -270,6 +274,66 @@ export function updateAgentPermissionMode(id, mode) {
270
274
  }
271
275
  return getWorkspace(id);
272
276
  }
277
+ /**
278
+ * Update a workspace's short description (≤ 200 chars after trim).
279
+ * Empty string (after trim) or `null` clears the column.
280
+ *
281
+ * Emits an ephemeral `workspace:description-updated` WebSocket event so every
282
+ * subscribed client (sidebar + the workspace header) refreshes in real-time
283
+ * without a manual reload. The truth lives in the DB; sync replay on
284
+ * reconnect re-fetches via GET /api/workspaces.
285
+ *
286
+ * @throws when the description exceeds 200 chars after trim or the workspace
287
+ * does not exist.
288
+ */
289
+ export function updateWorkspaceDescription(id, description) {
290
+ const trimmed = description == null ? null : description.trim();
291
+ if (trimmed !== null && trimmed.length > 200) {
292
+ throw new Error(`Description must be 200 characters or fewer (got ${trimmed.length})`);
293
+ }
294
+ const stored = trimmed && trimmed.length > 0 ? trimmed : null;
295
+ const db = getDb();
296
+ const result = db
297
+ .prepare('UPDATE workspaces SET description = ?, updated_at = ? WHERE id = ?')
298
+ .run(stored, new Date().toISOString(), id);
299
+ if (result.changes === 0) {
300
+ throw new Error(`Workspace '${id}' not found`);
301
+ }
302
+ emitEphemeral(id, 'workspace:description-updated', { description: stored });
303
+ return getWorkspace(id);
304
+ }
305
+ /**
306
+ * Update a workspace's agent-side description (≤ 200 chars after trim).
307
+ * Empty string (after trim) or `null` clears the column.
308
+ *
309
+ * Mirror of `updateWorkspaceDescription` but writes the `agent_description`
310
+ * column, which is exclusively the agent's to set via the
311
+ * `set_workspace_agent_description` MCP tool. The user's `description`
312
+ * column is untouched.
313
+ *
314
+ * Emits an ephemeral `workspace:agent-description-updated` event so every
315
+ * subscribed client (sidebar fallback display + workspace header read-only
316
+ * line) refreshes in real-time.
317
+ *
318
+ * @throws when the description exceeds 200 chars after trim or the workspace
319
+ * does not exist.
320
+ */
321
+ export function updateWorkspaceAgentDescription(id, description) {
322
+ const trimmed = description == null ? null : description.trim();
323
+ if (trimmed !== null && trimmed.length > 200) {
324
+ throw new Error(`Description must be 200 characters or fewer (got ${trimmed.length})`);
325
+ }
326
+ const stored = trimmed && trimmed.length > 0 ? trimmed : null;
327
+ const db = getDb();
328
+ const result = db
329
+ .prepare('UPDATE workspaces SET agent_description = ?, updated_at = ? WHERE id = ?')
330
+ .run(stored, new Date().toISOString(), id);
331
+ if (result.changes === 0) {
332
+ throw new Error(`Workspace '${id}' not found`);
333
+ }
334
+ emitEphemeral(id, 'workspace:agent-description-updated', { agentDescription: stored });
335
+ return getWorkspace(id);
336
+ }
273
337
  /** Update the dev-server status column for a workspace. */
274
338
  export function updateDevServerStatus(id, status) {
275
339
  const db = getDb();
@@ -291,6 +355,14 @@ export function deleteWorkspace(id) {
291
355
  // The DB row is removed via ON DELETE CASCADE, but the timer would
292
356
  // otherwise fire and hit an empty workspace.
293
357
  wakeupService.cancel(id, 'deleted');
358
+ // Same for any pending quota backoff. Best-effort: failure must not
359
+ // block delete. The DB row is also removed via ON DELETE CASCADE.
360
+ try {
361
+ quotaBackoffService.cancel(id, 'deleted');
362
+ }
363
+ catch (err) {
364
+ console.error('[workspace-service] cancel quota backoff on delete failed:', err);
365
+ }
294
366
  // Drop the cached rate_limit.info so memory doesn't leak on workspace
295
367
  // churn. The Map has no FK to clean up for it automatically.
296
368
  orchestrator.forgetRateLimitInfo(id);
@@ -383,6 +455,14 @@ export function archiveWorkspace(id) {
383
455
  }
384
456
  // Cancel any pending wakeup — archived workspaces should not wake up.
385
457
  wakeupService.cancel(id, 'archived');
458
+ // Cancel any pending quota backoff — archived workspaces should not auto-resume.
459
+ // Best-effort: failure here must not block archive.
460
+ try {
461
+ quotaBackoffService.cancel(id, 'archive');
462
+ }
463
+ catch (err) {
464
+ console.error('[workspace-service] cancel quota backoff on archive failed:', err);
465
+ }
386
466
  // Disable auto-loop — archived workspaces should not keep looping.
387
467
  // Idempotent: no-op if auto_loop was already 0.
388
468
  autoLoopService.disable(id, 'user-action');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loicngr/kobo",
3
- "version": "1.7.4",
3
+ "version": "1.7.5",
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",
@@ -1,4 +1,4 @@
1
- import{E as e,F as t,H as n,L as r,M as i,Q as a,U as o,_t as s,bt as c,d as l,f as u,g as d,h as f,l as p,p as m,r as h,rt as g,u as _,v,yt as y}from"./runtime-core.esm-bundler-C3IgBgY5.js";import{U as b,l as x,t as S}from"./QIcon-BJuyqdsT.js";import{c as C,s as w}from"./notifications-OnPq4FrH.js";import{t as T}from"./QBtn-a6jxWjmW.js";import{n as E}from"./vue-i18n-BcfTCFFS.js";import{d as D,g as O,l as k,p as A}from"./index-_ZaIBxd6.js";import{t as j}from"./QSpinnerDots-CszPQQ9J.js";import{t as M}from"./QTooltip-fDNzBEfN.js";import{t as N}from"./QExpansionItem-CH1ipL9n.js";import{n as ee,r as te,t as P}from"./render-chat-markdown-DcGIpMoe.js";import{n as F}from"./purify.es-BWZjBa9F.js";import{t as I}from"./documents-kx0vLfSG.js";import{t as L}from"./_plugin-vue_export-helper-Cj6tcsj6.js";function ne(e,t,n=!0){let r=[],i=new Map,a=new Map;for(let n=0;n<e.length;n++){let o=e[n],s=t?.[n];switch(o.kind){case`message:text`:{let e=i.get(o.messageId);if(e)e.text+=o.text,e.streaming=o.streaming;else{let e={type:`text`,messageId:o.messageId,text:o.text,streaming:o.streaming,ts:s};i.set(o.messageId,e),r.push(e)}break}case`message:end`:{let e=i.get(o.messageId);e&&(e.streaming=!1);break}case`message:thinking`:r.push({type:`thinking`,messageId:o.messageId,text:o.text,ts:s});break;case`tool:call`:{let e={type:`tool`,toolCallId:o.toolCallId,name:o.name,input:o.input,ts:s};a.set(o.toolCallId,e),r.push(e);break}case`tool:result`:{let e=a.get(o.toolCallId);e&&(e.result={output:o.output,isError:o.isError});break}case`session:started`:r.push({type:`session`,kind:`started`,detail:{engineSessionId:o.engineSessionId,model:o.model},ts:s});break;case`session:ended`:r.push({type:`session`,kind:`ended`,detail:{reason:o.reason,exitCode:o.exitCode},ts:s});break;case`session:compacted`:r.push({type:`session`,kind:`compacted`,ts:s});break;case`session:brainstorm-complete`:case`session:user-input-requested`:case`message:raw`:case`skills:discovered`:case`usage`:case`rate_limit`:case`subagent:progress`:case`error`:break;default:}}let o=null;for(let e of r)e.type===`text`&&e.streaming&&(o&&(o.streaming=!1),o=e);return o&&!n&&(o.streaming=!1),r}function re(e,t){if(t.length===0)return e;let n=t.map(e=>({type:`user`,content:e.content,sender:e.sender,ts:e.ts})),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}function R(e){switch(e.type){case`user`:return e.sender===`system-prompt`?`system-prompt`:`user`;case`session`:return`session`;default:return`agent`}}function ie(e){let t=[],n=null;for(let r of e){let e=R(r),i=e===`session`||e===`system-prompt`;!n||n.speaker!==e||i?(n={speaker:e,ts:r.ts,items:[r]},t.push(n),i&&(n=null)):n.items.push(r)}return t}var z={class:`text-caption text-grey-6`},B=v({__name:`SessionEventItem`,props:{item:{}},setup(e){let n=e,r=p(()=>{switch(n.item.kind){case`started`:return`session.started`;case`ended`:return`session.ended`;case`compacted`:return`session.compacted`;default:return`session.started`}});return(e,n)=>(t(),m(`span`,z,c(e.$t(r.value)),1))}});function V(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){ae(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 ae(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=H(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 H(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 U=[`innerHTML`],W=L(v({__name:`TextMessageItem`,props:{item:{}},setup(e){let n=e,r=I(),i=k(),a=p(()=>{let e=i.selectedWorkspaceId;return e?r.documentsFor(e).map(e=>e.path):[]}),o=p(()=>ee(V(F.parse(n.item.text,{async:!1,breaks:!0,gfm:!0}),a.value),{addAttr:[`data-document-path`]}));function s(e){let t=e.target?.closest(`.document-link`);if(!t)return;e.preventDefault();let n=t.getAttribute(`data-document-path`),a=i.selectedWorkspaceId;!n||!a||r.openDocumentByPath(a,n)}return(n,r)=>(t(),m(`div`,{class:`markdown-message`,onClick:s},[_(`div`,{innerHTML:o.value},null,8,U),e.item.streaming?(t(),l(x,{key:0,size:`xs`,class:`q-ml-xs`})):u(``,!0)]))}}),[[`__scopeId`,`data-v-1b7bd8ca`]]),oe={key:0,class:`text-caption text-grey-5`,style:{"font-style":`italic`}},G=[`innerHTML`],K={key:1,style:{"white-space":`pre-wrap`}},se=L(v({__name:`ThinkingItem`,props:{item:{}},setup(e){let n=e,r=p(()=>n.item.text.trim().slice(0,100)),i=p(()=>n.item.text.trim().length>0),a=p(()=>n.item.text.trim().length>100),s=p(()=>P(n.item.text));return(n,d)=>i.value?(t(),m(`div`,oe,[a.value?(t(),l(N,{key:0,dense:``,"dense-toggle":``,label:r.value,"header-class":`text-grey-5 text-caption`,style:{"font-style":`italic`}},{default:o(()=>[_(`div`,{class:`q-py-xs markdown-thinking`,innerHTML:s.value},null,8,G)]),_:1},8,[`label`])):(t(),m(`span`,K,c(e.item.text),1))])):u(``,!0)}}),[[`__scopeId`,`data-v-7f45ed94`]]);function ce(e,t){let n=e.split(`
1
+ import{E as e,F as t,H as n,L as r,M as i,Q as a,U as o,_t as s,bt as c,d as l,f as u,g as d,h as f,l as p,p as m,r as h,rt as g,u as _,v,yt as y}from"./runtime-core.esm-bundler-C3IgBgY5.js";import{U as b,l as x,t as S}from"./QIcon-BJuyqdsT.js";import{c as C,s as w}from"./notifications-OnPq4FrH.js";import{t as T}from"./QBtn-a6jxWjmW.js";import{n as E}from"./vue-i18n-BcfTCFFS.js";import{d as D,g as O,l as k,p as A}from"./index-DuK38XN5.js";import{t as j}from"./QSpinnerDots-CszPQQ9J.js";import{t as M}from"./QTooltip-fDNzBEfN.js";import{t as N}from"./QExpansionItem-CH1ipL9n.js";import{n as ee,r as te,t as P}from"./render-chat-markdown-TvAqpDih.js";import{n as F}from"./purify.es-BIY760fF.js";import{t as I}from"./documents-kx0vLfSG.js";import{t as L}from"./_plugin-vue_export-helper-Cj6tcsj6.js";function ne(e,t,n=!0){let r=[],i=new Map,a=new Map;for(let n=0;n<e.length;n++){let o=e[n],s=t?.[n];switch(o.kind){case`message:text`:{let e=i.get(o.messageId);if(e)e.text+=o.text,e.streaming=o.streaming;else{let e={type:`text`,messageId:o.messageId,text:o.text,streaming:o.streaming,ts:s};i.set(o.messageId,e),r.push(e)}break}case`message:end`:{let e=i.get(o.messageId);e&&(e.streaming=!1);break}case`message:thinking`:r.push({type:`thinking`,messageId:o.messageId,text:o.text,ts:s});break;case`tool:call`:{let e={type:`tool`,toolCallId:o.toolCallId,name:o.name,input:o.input,ts:s};a.set(o.toolCallId,e),r.push(e);break}case`tool:result`:{let e=a.get(o.toolCallId);e&&(e.result={output:o.output,isError:o.isError});break}case`session:started`:r.push({type:`session`,kind:`started`,detail:{engineSessionId:o.engineSessionId,model:o.model},ts:s});break;case`session:ended`:r.push({type:`session`,kind:`ended`,detail:{reason:o.reason,exitCode:o.exitCode},ts:s});break;case`session:compacted`:r.push({type:`session`,kind:`compacted`,ts:s});break;case`session:brainstorm-complete`:case`session:user-input-requested`:case`message:raw`:case`skills:discovered`:case`usage`:case`rate_limit`:case`subagent:progress`:case`error`:break;default:}}let o=null;for(let e of r)e.type===`text`&&e.streaming&&(o&&(o.streaming=!1),o=e);return o&&!n&&(o.streaming=!1),r}function re(e,t){if(t.length===0)return e;let n=t.map(e=>({type:`user`,content:e.content,sender:e.sender,ts:e.ts})),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}function R(e){switch(e.type){case`user`:return e.sender===`system-prompt`?`system-prompt`:`user`;case`session`:return`session`;default:return`agent`}}function ie(e){let t=[],n=null;for(let r of e){let e=R(r),i=e===`session`||e===`system-prompt`;!n||n.speaker!==e||i?(n={speaker:e,ts:r.ts,items:[r]},t.push(n),i&&(n=null)):n.items.push(r)}return t}var z={class:`text-caption text-grey-6`},B=v({__name:`SessionEventItem`,props:{item:{}},setup(e){let n=e,r=p(()=>{switch(n.item.kind){case`started`:return`session.started`;case`ended`:return`session.ended`;case`compacted`:return`session.compacted`;default:return`session.started`}});return(e,n)=>(t(),m(`span`,z,c(e.$t(r.value)),1))}});function V(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){ae(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 ae(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=H(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 H(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 U=[`innerHTML`],W=L(v({__name:`TextMessageItem`,props:{item:{}},setup(e){let n=e,r=I(),i=k(),a=p(()=>{let e=i.selectedWorkspaceId;return e?r.documentsFor(e).map(e=>e.path):[]}),o=p(()=>ee(V(F.parse(n.item.text,{async:!1,breaks:!0,gfm:!0}),a.value),{addAttr:[`data-document-path`]}));function s(e){let t=e.target?.closest(`.document-link`);if(!t)return;e.preventDefault();let n=t.getAttribute(`data-document-path`),a=i.selectedWorkspaceId;!n||!a||r.openDocumentByPath(a,n)}return(n,r)=>(t(),m(`div`,{class:`markdown-message`,onClick:s},[_(`div`,{innerHTML:o.value},null,8,U),e.item.streaming?(t(),l(x,{key:0,size:`xs`,class:`q-ml-xs`})):u(``,!0)]))}}),[[`__scopeId`,`data-v-1b7bd8ca`]]),oe={key:0,class:`text-caption text-grey-5`,style:{"font-style":`italic`}},G=[`innerHTML`],K={key:1,style:{"white-space":`pre-wrap`}},se=L(v({__name:`ThinkingItem`,props:{item:{}},setup(e){let n=e,r=p(()=>n.item.text.trim().slice(0,100)),i=p(()=>n.item.text.trim().length>0),a=p(()=>n.item.text.trim().length>100),s=p(()=>P(n.item.text));return(n,d)=>i.value?(t(),m(`div`,oe,[a.value?(t(),l(N,{key:0,dense:``,"dense-toggle":``,label:r.value,"header-class":`text-grey-5 text-caption`,style:{"font-style":`italic`}},{default:o(()=>[_(`div`,{class:`q-py-xs markdown-thinking`,innerHTML:s.value},null,8,G)]),_:1},8,[`label`])):(t(),m(`span`,K,c(e.item.text),1))])):u(``,!0)}}),[[`__scopeId`,`data-v-7f45ed94`]]);function ce(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 q(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??``;return{toolName:`Edit`,filePath:e,oldString:t,newString:r,replaceAll:n.replace_all??!1,additions:r?r.split(`
4
4
  `).length:0,deletions:t?t.split(`
@@ -0,0 +1 @@
1
+ import{F as e,U as t,bt as n,d as r,f as i,g as a,h as o,l as s,rt as c,v as l}from"./runtime-core.esm-bundler-C3IgBgY5.js";import{n as u}from"./vue-i18n-BcfTCFFS.js";import{l as d}from"./index-DuK38XN5.js";import{t as f}from"./QTooltip-fDNzBEfN.js";import{t as p}from"./QChip-ByxK0Tuf.js";var m=l({__name:`AutoLoopChip`,props:{workspace:{default:null}},setup(l){let m=l,{t:h}=u(),g=d(),_=s(()=>m.workspace??g.selectedWorkspace),v=s(()=>_.value?.id??null),y=s(()=>v.value?g.autoLoopStates[v.value]??null:null),b=s(()=>!!y.value?.auto_loop),x=s(()=>!!y.value?.auto_loop_ready),S=s(()=>v.value!==null&&g.selectedWorkspaceId===v.value),C=s(()=>S.value?g.tasks.filter(e=>e.status===`done`).length:y.value?.tasks_done??0),w=s(()=>S.value?g.tasks.length:y.value?.tasks_total??0);return(s,l)=>b.value&&!x.value?(e(),r(p,{key:0,dense:``,square:``,size:`sm`,color:`indigo-4`,"text-color":`white`,icon:`hourglass_top`,class:`q-ml-sm`},{default:t(()=>[o(n(c(h)(`autoLoop.preparing`))+` `,1),a(f,null,{default:t(()=>[o(n(c(h)(`autoLoop.preparingTooltip`)),1)]),_:1})]),_:1})):b.value?(e(),r(p,{key:1,dense:``,square:``,size:`sm`,color:`amber-9`,"text-color":`white`,icon:`autorenew`,class:`q-ml-sm`},{default:t(()=>[o(n(c(h)(`autoLoop.progress`,{done:C.value,total:w.value})),1)]),_:1})):i(``,!0)}});export{m as t};
@@ -1,2 +1,2 @@
1
- import{E as e,F as t,H as n,L as r,M as ee,N as te,Q as i,T as ne,U as a,_t as re,bt as o,d as s,f as c,g as l,h as u,l as d,p as f,r as ie,rt as p,u as m,v as ae,vt as oe,x as se}from"./runtime-core.esm-bundler-C3IgBgY5.js";import{H as ce,R as le,U as ue,t as h}from"./QIcon-BJuyqdsT.js";import{r as g}from"./use-id-CuaR1RiE.js";import{s as de}from"./notifications-OnPq4FrH.js";import{t as _}from"./QBtn-a6jxWjmW.js";import{n as fe}from"./vue-i18n-BcfTCFFS.js";import{t as v}from"./QInput-Cm5-AGQ4.js";import{a as pe,h as me,l as he,s as ge}from"./index-_ZaIBxd6.js";import{n as y,t as b}from"./QItemSection-5YpFpPDm.js";import{t as x}from"./QItemLabel-DrTxqTqV.js";import{t as S}from"./QTooltip-fDNzBEfN.js";import{t as _e}from"./QExpansionItem-CH1ipL9n.js";import{t as ve}from"./_plugin-vue_export-helper-Cj6tcsj6.js";import{t as ye}from"./QSpace-CLtL3aPy.js";import{t as be}from"./use-quasar-Sdcq6zzV.js";import{n as C,t as xe}from"./models-ClWoqWeC.js";import{t as Se}from"./QPage-ChUKoaKe.js";import{i as Ce,n as we,r as Te,t as Ee}from"./expand-template-AQsvbQ8_.js";var w=`kobo:create-page-prefs`;function De(){try{let e=localStorage.getItem(w);return e===null?void 0:JSON.parse(e)}catch{return}}function T(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Oe(){let e=De();if(!T(e))return{};let t={};return typeof e.projectPath==`string`&&e.projectPath.length>0&&(t.projectPath=e.projectPath),typeof e.autoLoop==`boolean`&&(t.autoLoop=e.autoLoop),t}function ke(e){try{localStorage.setItem(w,JSON.stringify(e))}catch{}}var Ae={class:`create-inner`},je={class:`create-title text-center text-weight-bold q-mb-lg text-grey-3`},Me={class:`create-card rounded-borders`},Ne={class:`card-top-bar row items-center q-px-md q-py-xs`},Pe={class:`model-badge cursor-default row items-center q-gutter-xs`},Fe={class:`text-indigo-3 text-weight-medium text-caption`},Ie={key:0,class:`notion-url-wrap`},Le={key:0,class:`notion-error text-caption q-px-md q-pb-xs text-red-5`},Re={key:1,class:`notion-valid text-caption q-px-md q-pb-xs text-green-4`},ze={key:2,class:`notion-peek-choice q-px-md q-pb-sm`},Be={class:`text-caption text-grey-4 q-mb-sm`},Ve={class:`row q-gutter-sm`},He={class:`peek-card-text`},Ue={class:`peek-card-title`},We={class:`peek-card-desc`},Ge={class:`peek-card-text`},Ke={class:`peek-card-title`},qe={class:`peek-card-desc`},Je={key:0,class:`sentry-url-wrap`},Ye={key:0,class:`sentry-error text-caption q-px-md q-pb-xs text-red-5`},Xe={key:1,class:`sentry-valid text-caption q-px-md q-pb-xs text-red-4`},Ze={class:`card-name-wrap`},Qe={class:`card-textarea-wrap`},$e={class:`manual-hint q-px-md q-py-sm text-caption text-grey-6`},et={class:`q-pa-sm manual-section-body`},tt={class:`row items-center q-gutter-sm q-mb-sm`},nt={class:`col text-caption text-grey-4`},rt={class:`q-pa-sm manual-section-body`},it={class:`row items-center q-gutter-sm q-mb-sm`},at={class:`col text-caption text-grey-4`},ot={class:`card-bottom-bar`},st={class:`row q-col-gutter-xs q-px-xs`},ct={key:0,class:`col-12 col-sm-6 col-md-3`},lt={class:`bottom-select-label row items-center no-wrap`},ut={class:`col-12 col-sm-6 col-md-3`},dt={class:`bottom-select-label row items-center no-wrap`},ft={class:`col-12 col-sm-6 col-md-3`},pt={class:`bottom-select-label row items-center no-wrap`},mt={class:`col-12 col-md-3`},ht={class:`bottom-select-label row items-center no-wrap`},gt={class:`row q-col-gutter-xs q-pa-xs col-12 items-center justify-center`},_t={class:`col-12 col-md-auto`},vt={class:`col-12 col-md-auto`},yt={class:`col-12 col-md-auto`},bt={class:`row q-col-gutter-xs q-px-xs bottom-row-git`},xt={class:`col-12 col-md-4`},St={key:0,class:`col-12 col-md-4`},Ct={class:`bottom-select-label row items-center no-wrap`},wt={key:1,class:`col-12 col-md-4`},Tt={class:`bottom-select-label row items-center no-wrap`},Et={class:`col-12 col-md-4`},Dt={class:`bottom-select-label row items-center no-wrap`},Ot={class:`row justify-center q-px-sm q-py-sm`},kt={class:`create-hint text-center text-body2 q-mt-md text-grey-8`},E=ve(ae({__name:`CreatePage`,setup(ae){let ve=pe(),w=be(),De=he(),T=de(),{t:E}=fe(),At=i([]),jt=i(``),D=i(``),Mt=i(null),O=i(``),k=i(!1),A=i(`claude-opus-4-7`),j=i(`auto`),M=i(``),N=i(null),Nt=i(`feature`),P=i(!1),Pt=i([]),F=i(`claude-code`),Ft=d(()=>Pt.value.find(e=>e.id===F.value)),It=d(()=>Pt.value.map(e=>({value:e.id,label:e.displayName}))),Lt=[{label:`feature/`,value:`feature`},{label:`fix/`,value:`fix`},{label:`hotfix/`,value:`hotfix`},{label:`chore/`,value:`chore`},{label:`refactor/`,value:`refactor`},{label:`docs/`,value:`docs`},{label:`test/`,value:`test`}],Rt=[`plan`,`bypass`,`strict`,`interactive`];function zt(e){let t=e?T.getProjectByPath(e):void 0,n=t?.agentPermissionMode;if(typeof n==`string`&&Rt.includes(n))return n;let r=T.global.defaultPermissionMode;return Rt.includes(r)?r:r===`plan`?`plan`:t?.dangerouslySkipPermissions??T.global.dangerouslySkipPermissions??!0?`bypass`:`interactive`}let I=i(zt(``)),Bt=d(()=>{switch(I.value){case`plan`:return`visibility`;case`bypass`:return`flash_on`;case`strict`:return`lock`;case`interactive`:return`security`}}),Vt=d(()=>(Ft.value?.capabilities.permissionModes??Rt).map(e=>({value:e,label:E(`agentPermissionMode.${e}`),disabled:e===`plan`&&J.value}))),L=i([]),Ht=i([]),Ut=i(!1),Wt=i(!1);function Gt(e,t){t(()=>{Ht.value=e?L.value.filter(t=>t.toLowerCase().includes(e.toLowerCase())):L.value})}let Kt=me();function qt(){return Mt.value?.nativeEl??null}let{showSkills:Jt,selectedSkillIndex:R,groupedDropdown:Yt,flatDropdown:z,fetchSkills:Xt,detectSlashFragment:Zt,replaceFragmentWith:Qt,closeDropdown:$t}=Te(D,qt,{excludeKoboCommands:!0});Xt(),n(D,async()=>{await e(),await Zt()});function en(e){if(e.type===`template`){let t=Kt.templates.find(t=>t.slug===e.name);if(!t)return;Qt(we(t.content,Ee({workspace:null,gitStats:null,sessionName:null}))),$t();return}Qt(`/${e.name} `),$t()}function tn(e){!Jt.value||z.value.length===0||(e.key===`ArrowDown`?(e.preventDefault(),R.value=(R.value+1)%z.value.length):e.key===`ArrowUp`?(e.preventDefault(),R.value=(R.value-1+z.value.length)%z.value.length):e.key===`Enter`&&!e.shiftKey&&!e.ctrlKey&&!e.metaKey?(e.preventDefault(),en(z.value[R.value])):e.key===`Escape`&&(e.preventDefault(),$t()))}let nn=d(()=>xe.map(e=>({label:E(e.i18nLabelKey),value:e.value,description:E(e.i18nDescriptionKey)})));function B(e){let t=e.indexOf(`:`);return t>=0?e.slice(t+1).trim():e}let rn=d(()=>[{label:B(E(`reasoning.auto`)),value:`auto`,description:E(`reasoning.autoDescription`)},{label:B(E(`reasoning.low`)),value:`low`,description:E(`reasoning.lowDescription`)},{label:B(E(`reasoning.medium`)),value:`medium`,description:E(`reasoning.mediumDescription`)},{label:B(E(`reasoning.high`)),value:`high`,description:E(`reasoning.highDescription`)},{label:B(E(`reasoning.xhigh`)),value:`xhigh`,description:E(`reasoning.xhighDescription`)},{label:B(E(`reasoning.max`)),value:`max`,description:E(`reasoning.maxDescription`)}]),V=d(()=>O.value.trim().startsWith(`https://www.notion.so/`)),an=d(()=>/[?&]p=[0-9a-f]{32}(?:[&#]|$)/i.test(O.value)),H=i(`panel`);function on(){let e=O.value.trim();return H.value===`parent`&&an.value?e.replace(/([?&])p=[0-9a-f]{32}(?=[&#]|$)/i,`$1`).replace(/([?&])pm=[a-z]+(?=[&#]|$)/i,`$1`).replace(/[?&]+$/,``).replace(/\?&/,`?`):e}let U=i([]),W=i([]),G=i(``),K=i(``),sn=d(()=>!k.value||!V.value);function cn(){let e=G.value.trim();e&&(U.value.push(e),G.value=``)}function ln(e){U.value.splice(e,1)}function un(){let e=K.value.trim();e&&(W.value.push(e),K.value=``)}function dn(e){W.value.splice(e,1)}function fn(){k.value=!k.value,k.value||(O.value=``)}let q=i(!1),J=i(!1);n(()=>J.value,e=>{e&&I.value!==`bypass`&&(I.value=`bypass`,w.notify({type:`info`,message:E(`agentPermissionMode.autoLoopLocked`),timeout:4e3}))});let Y=i(``),X=d(()=>/\/issues\/\d+/.test(Y.value.trim()));function pn(){q.value=!q.value,q.value||(Y.value=``)}let Z=i(!1),Q=i(null),$=i([]),mn=i(!1);async function hn(){if(!M.value.trim()){$.value=[];return}mn.value=!0;try{$.value=await De.fetchOrphanWorktrees(M.value.trim())}catch{$.value=[]}finally{mn.value=!1}}function gn(){Z.value=!Z.value,Z.value?(k.value=!1,O.value=``,q.value=!1,Y.value=``,P.value=!0,hn()):Q.value=null}n(M,()=>{Q.value=null,Z.value&&hn()}),n(Q,e=>{if(!e)return;let t=$.value.find(t=>t.path===e);t&&(N.value=t.suggestedSourceBranch),P.value=!0});async function _n(e){if(!e.trim()){L.value=[],N.value=null;return}Ut.value=!0;try{let t=await fetch(`/api/git/branches?path=${encodeURIComponent(e.trim())}`);if(!t.ok)throw Error(`HTTP ${t.status}`);let n=await t.json();L.value=n.local??n.branches??[],L.value.length>0&&!N.value&&(N.value=L.value[0]??null)}catch{L.value=[],N.value=null}finally{Ut.value=!1}}function vn(e){let t=T.getProjectByPath(e);t&&(t.defaultSourceBranch&&(N.value=t.defaultSourceBranch),t.defaultModel?A.value=t.defaultModel:T.global.defaultModel&&(A.value=T.global.defaultModel)),I.value=zt(e)}let yn=null;n(M,e=>{yn&&clearTimeout(yn),yn=setTimeout(()=>{N.value=null,_n(e),vn(e)},500)});function bn(e,t){t(()=>{At.value=T.projectPaths.filter(t=>t.toLowerCase().includes(e.toLowerCase()))})}ee(async()=>{await T.fetchSettings();let e=Oe();e.autoLoop===!0&&(J.value=!0),e.projectPath&&T.projectPaths.includes(e.projectPath)&&(M.value=e.projectPath),I.value=zt(M.value);try{let e=await fetch(`/api/engines`);e.ok&&(Pt.value=await e.json())}catch{}}),te(()=>{yn&&clearTimeout(yn)});function xn(e){return e.normalize(`NFD`).replace(/[\u0300-\u036f]/g,``).toLowerCase().replace(/[^a-z0-9\s-]/g,``).trim().replace(/\s+/g,`-`).replace(/-+/g,`-`).substring(0,50)}function Sn(){return jt.value.trim()?jt.value.trim().substring(0,80):!k.value&&!q.value&&D.value.trim()&&(D.value.trim().split(`
1
+ import{E as e,F as t,H as n,L as r,M as ee,N as te,Q as i,T as ne,U as a,_t as re,bt as o,d as s,f as c,g as l,h as u,l as d,p as f,r as ie,rt as p,u as m,v as ae,vt as oe,x as se}from"./runtime-core.esm-bundler-C3IgBgY5.js";import{H as ce,R as le,U as ue,t as h}from"./QIcon-BJuyqdsT.js";import{r as g}from"./use-id-CuaR1RiE.js";import{s as de}from"./notifications-OnPq4FrH.js";import{t as _}from"./QBtn-a6jxWjmW.js";import{n as fe}from"./vue-i18n-BcfTCFFS.js";import{t as v}from"./QInput-Cm5-AGQ4.js";import{a as pe,h as me,l as he,s as ge}from"./index-DuK38XN5.js";import{n as y,t as b}from"./QItemSection-5YpFpPDm.js";import{t as x}from"./QItemLabel-DrTxqTqV.js";import{t as S}from"./QTooltip-fDNzBEfN.js";import{t as _e}from"./QExpansionItem-CH1ipL9n.js";import{t as ve}from"./_plugin-vue_export-helper-Cj6tcsj6.js";import{t as ye}from"./QSpace-CLtL3aPy.js";import{t as be}from"./use-quasar-Sdcq6zzV.js";import{n as C,t as xe}from"./models-BDkLiht9.js";import{t as Se}from"./QPage-DFi3K093.js";import{i as Ce,n as we,r as Te,t as Ee}from"./expand-template-BIPuNAYV.js";var w=`kobo:create-page-prefs`;function De(){try{let e=localStorage.getItem(w);return e===null?void 0:JSON.parse(e)}catch{return}}function T(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Oe(){let e=De();if(!T(e))return{};let t={};return typeof e.projectPath==`string`&&e.projectPath.length>0&&(t.projectPath=e.projectPath),typeof e.autoLoop==`boolean`&&(t.autoLoop=e.autoLoop),t}function ke(e){try{localStorage.setItem(w,JSON.stringify(e))}catch{}}var Ae={class:`create-inner`},je={class:`create-title text-center text-weight-bold q-mb-lg text-grey-3`},Me={class:`create-card rounded-borders`},Ne={class:`card-top-bar row items-center q-px-md q-py-xs`},Pe={class:`model-badge cursor-default row items-center q-gutter-xs`},Fe={class:`text-indigo-3 text-weight-medium text-caption`},Ie={key:0,class:`notion-url-wrap`},Le={key:0,class:`notion-error text-caption q-px-md q-pb-xs text-red-5`},Re={key:1,class:`notion-valid text-caption q-px-md q-pb-xs text-green-4`},ze={key:2,class:`notion-peek-choice q-px-md q-pb-sm`},Be={class:`text-caption text-grey-4 q-mb-sm`},Ve={class:`row q-gutter-sm`},He={class:`peek-card-text`},Ue={class:`peek-card-title`},We={class:`peek-card-desc`},Ge={class:`peek-card-text`},Ke={class:`peek-card-title`},qe={class:`peek-card-desc`},Je={key:0,class:`sentry-url-wrap`},Ye={key:0,class:`sentry-error text-caption q-px-md q-pb-xs text-red-5`},Xe={key:1,class:`sentry-valid text-caption q-px-md q-pb-xs text-red-4`},Ze={class:`card-name-wrap`},Qe={class:`card-textarea-wrap`},$e={class:`manual-hint q-px-md q-py-sm text-caption text-grey-6`},et={class:`q-pa-sm manual-section-body`},tt={class:`row items-center q-gutter-sm q-mb-sm`},nt={class:`col text-caption text-grey-4`},rt={class:`q-pa-sm manual-section-body`},it={class:`row items-center q-gutter-sm q-mb-sm`},at={class:`col text-caption text-grey-4`},ot={class:`card-bottom-bar`},st={class:`row q-col-gutter-xs q-px-xs`},ct={key:0,class:`col-12 col-sm-6 col-md-3`},lt={class:`bottom-select-label row items-center no-wrap`},ut={class:`col-12 col-sm-6 col-md-3`},dt={class:`bottom-select-label row items-center no-wrap`},ft={class:`col-12 col-sm-6 col-md-3`},pt={class:`bottom-select-label row items-center no-wrap`},mt={class:`col-12 col-md-3`},ht={class:`bottom-select-label row items-center no-wrap`},gt={class:`row q-col-gutter-xs q-pa-xs col-12 items-center justify-center`},_t={class:`col-12 col-md-auto`},vt={class:`col-12 col-md-auto`},yt={class:`col-12 col-md-auto`},bt={class:`row q-col-gutter-xs q-px-xs bottom-row-git`},xt={class:`col-12 col-md-4`},St={key:0,class:`col-12 col-md-4`},Ct={class:`bottom-select-label row items-center no-wrap`},wt={key:1,class:`col-12 col-md-4`},Tt={class:`bottom-select-label row items-center no-wrap`},Et={class:`col-12 col-md-4`},Dt={class:`bottom-select-label row items-center no-wrap`},Ot={class:`row justify-center q-px-sm q-py-sm`},kt={class:`create-hint text-center text-body2 q-mt-md text-grey-8`},E=ve(ae({__name:`CreatePage`,setup(ae){let ve=pe(),w=be(),De=he(),T=de(),{t:E}=fe(),At=i([]),jt=i(``),D=i(``),Mt=i(null),O=i(``),k=i(!1),A=i(`claude-opus-4-7`),j=i(`auto`),M=i(``),N=i(null),Nt=i(`feature`),P=i(!1),Pt=i([]),F=i(`claude-code`),Ft=d(()=>Pt.value.find(e=>e.id===F.value)),It=d(()=>Pt.value.map(e=>({value:e.id,label:e.displayName}))),Lt=[{label:`feature/`,value:`feature`},{label:`fix/`,value:`fix`},{label:`hotfix/`,value:`hotfix`},{label:`chore/`,value:`chore`},{label:`refactor/`,value:`refactor`},{label:`docs/`,value:`docs`},{label:`test/`,value:`test`}],Rt=[`plan`,`bypass`,`strict`,`interactive`];function zt(e){let t=e?T.getProjectByPath(e):void 0,n=t?.agentPermissionMode;if(typeof n==`string`&&Rt.includes(n))return n;let r=T.global.defaultPermissionMode;return Rt.includes(r)?r:r===`plan`?`plan`:t?.dangerouslySkipPermissions??T.global.dangerouslySkipPermissions??!0?`bypass`:`interactive`}let I=i(zt(``)),Bt=d(()=>{switch(I.value){case`plan`:return`visibility`;case`bypass`:return`flash_on`;case`strict`:return`lock`;case`interactive`:return`security`}}),Vt=d(()=>(Ft.value?.capabilities.permissionModes??Rt).map(e=>({value:e,label:E(`agentPermissionMode.${e}`),disabled:e===`plan`&&J.value}))),L=i([]),Ht=i([]),Ut=i(!1),Wt=i(!1);function Gt(e,t){t(()=>{Ht.value=e?L.value.filter(t=>t.toLowerCase().includes(e.toLowerCase())):L.value})}let Kt=me();function qt(){return Mt.value?.nativeEl??null}let{showSkills:Jt,selectedSkillIndex:R,groupedDropdown:Yt,flatDropdown:z,fetchSkills:Xt,detectSlashFragment:Zt,replaceFragmentWith:Qt,closeDropdown:$t}=Te(D,qt,{excludeKoboCommands:!0});Xt(),n(D,async()=>{await e(),await Zt()});function en(e){if(e.type===`template`){let t=Kt.templates.find(t=>t.slug===e.name);if(!t)return;Qt(we(t.content,Ee({workspace:null,gitStats:null,sessionName:null}))),$t();return}Qt(`/${e.name} `),$t()}function tn(e){!Jt.value||z.value.length===0||(e.key===`ArrowDown`?(e.preventDefault(),R.value=(R.value+1)%z.value.length):e.key===`ArrowUp`?(e.preventDefault(),R.value=(R.value-1+z.value.length)%z.value.length):e.key===`Enter`&&!e.shiftKey&&!e.ctrlKey&&!e.metaKey?(e.preventDefault(),en(z.value[R.value])):e.key===`Escape`&&(e.preventDefault(),$t()))}let nn=d(()=>xe.map(e=>({label:E(e.i18nLabelKey),value:e.value,description:E(e.i18nDescriptionKey)})));function B(e){let t=e.indexOf(`:`);return t>=0?e.slice(t+1).trim():e}let rn=d(()=>[{label:B(E(`reasoning.auto`)),value:`auto`,description:E(`reasoning.autoDescription`)},{label:B(E(`reasoning.low`)),value:`low`,description:E(`reasoning.lowDescription`)},{label:B(E(`reasoning.medium`)),value:`medium`,description:E(`reasoning.mediumDescription`)},{label:B(E(`reasoning.high`)),value:`high`,description:E(`reasoning.highDescription`)},{label:B(E(`reasoning.xhigh`)),value:`xhigh`,description:E(`reasoning.xhighDescription`)},{label:B(E(`reasoning.max`)),value:`max`,description:E(`reasoning.maxDescription`)}]),V=d(()=>O.value.trim().startsWith(`https://www.notion.so/`)),an=d(()=>/[?&]p=[0-9a-f]{32}(?:[&#]|$)/i.test(O.value)),H=i(`panel`);function on(){let e=O.value.trim();return H.value===`parent`&&an.value?e.replace(/([?&])p=[0-9a-f]{32}(?=[&#]|$)/i,`$1`).replace(/([?&])pm=[a-z]+(?=[&#]|$)/i,`$1`).replace(/[?&]+$/,``).replace(/\?&/,`?`):e}let U=i([]),W=i([]),G=i(``),K=i(``),sn=d(()=>!k.value||!V.value);function cn(){let e=G.value.trim();e&&(U.value.push(e),G.value=``)}function ln(e){U.value.splice(e,1)}function un(){let e=K.value.trim();e&&(W.value.push(e),K.value=``)}function dn(e){W.value.splice(e,1)}function fn(){k.value=!k.value,k.value||(O.value=``)}let q=i(!1),J=i(!1);n(()=>J.value,e=>{e&&I.value!==`bypass`&&(I.value=`bypass`,w.notify({type:`info`,message:E(`agentPermissionMode.autoLoopLocked`),timeout:4e3}))});let Y=i(``),X=d(()=>/\/issues\/\d+/.test(Y.value.trim()));function pn(){q.value=!q.value,q.value||(Y.value=``)}let Z=i(!1),Q=i(null),$=i([]),mn=i(!1);async function hn(){if(!M.value.trim()){$.value=[];return}mn.value=!0;try{$.value=await De.fetchOrphanWorktrees(M.value.trim())}catch{$.value=[]}finally{mn.value=!1}}function gn(){Z.value=!Z.value,Z.value?(k.value=!1,O.value=``,q.value=!1,Y.value=``,P.value=!0,hn()):Q.value=null}n(M,()=>{Q.value=null,Z.value&&hn()}),n(Q,e=>{if(!e)return;let t=$.value.find(t=>t.path===e);t&&(N.value=t.suggestedSourceBranch),P.value=!0});async function _n(e){if(!e.trim()){L.value=[],N.value=null;return}Ut.value=!0;try{let t=await fetch(`/api/git/branches?path=${encodeURIComponent(e.trim())}`);if(!t.ok)throw Error(`HTTP ${t.status}`);let n=await t.json();L.value=n.local??n.branches??[],L.value.length>0&&!N.value&&(N.value=L.value[0]??null)}catch{L.value=[],N.value=null}finally{Ut.value=!1}}function vn(e){let t=T.getProjectByPath(e);t&&(t.defaultSourceBranch&&(N.value=t.defaultSourceBranch),t.defaultModel?A.value=t.defaultModel:T.global.defaultModel&&(A.value=T.global.defaultModel)),I.value=zt(e)}let yn=null;n(M,e=>{yn&&clearTimeout(yn),yn=setTimeout(()=>{N.value=null,_n(e),vn(e)},500)});function bn(e,t){t(()=>{At.value=T.projectPaths.filter(t=>t.toLowerCase().includes(e.toLowerCase()))})}ee(async()=>{await T.fetchSettings();let e=Oe();e.autoLoop===!0&&(J.value=!0),e.projectPath&&T.projectPaths.includes(e.projectPath)&&(M.value=e.projectPath),I.value=zt(M.value);try{let e=await fetch(`/api/engines`);e.ok&&(Pt.value=await e.json())}catch{}}),te(()=>{yn&&clearTimeout(yn)});function xn(e){return e.normalize(`NFD`).replace(/[\u0300-\u036f]/g,``).toLowerCase().replace(/[^a-z0-9\s-]/g,``).trim().replace(/\s+/g,`-`).replace(/-+/g,`-`).substring(0,50)}function Sn(){return jt.value.trim()?jt.value.trim().substring(0,80):!k.value&&!q.value&&D.value.trim()&&(D.value.trim().split(`
2
2
  `)[0]??``).substring(0,80)||`workspace`}function Cn(e){let t=(e.split(`/`).pop()??``).split(`-`);t.length>1&&/^[0-9a-f]{12,}$/i.test(t[t.length-1])&&t.pop();let n=t.join(`-`).toLowerCase(),r=n.match(/tk-(\d+)/);if(r){let e=`TK-${r[1]}`,t=n.replace(/tk-\d+/i,``).replace(/-+/g,`-`).replace(/^-|-$/g,``).substring(0,40);return t?`${e}--${t}`:e}return n.substring(0,50)||`task-${Date.now()}`}function wn(){return k.value&&!V.value?E(`createPage.validationNotionUrl`):q.value&&!X.value?E(`createPage.sentryValidation`):!k.value&&!q.value&&!D.value.trim()?E(`createPage.validationDescription`):!k.value&&!q.value&&(!Sn()||Sn()===`workspace`)&&!jt.value.trim()&&!D.value.trim()?E(`createPage.validationName`):M.value.trim()?N.value?null:E(`createPage.validationBranch`):E(`createPage.validationPath`)}async function Tn(){let e=wn();if(e){w.notify({type:`negative`,message:e,position:`top`});return}if(Z.value&&!Q.value){w.notify({type:`negative`,message:E(`createPage.pickWorktreeRequired`),position:`top`});return}Wt.value=!0;try{let e=Sn(),t;t=k.value&&V.value?Cn(on()):e===`workspace`?`task-${Date.now()}`:xn(e);let n=`${Nt.value}/${t}`,r={name:e,projectPath:M.value.trim(),sourceBranch:N.value,...Z.value&&Q.value?{worktreePath:Q.value,skipSetupScript:!0}:{workingBranch:n},engine:F.value,model:A.value,reasoningEffort:j.value,...k.value&&V.value?{notionUrl:on()}:{},...q.value&&X.value?{sentryUrl:Y.value.trim()}:{},...sn.value&&U.value.length>0?{tasks:U.value}:{},...sn.value&&W.value.length>0?{acceptanceCriteria:W.value}:{},...P.value&&!Z.value?{skipSetupScript:!0}:{},...D.value.trim()?{description:D.value.trim()}:{},...J.value?{autoLoop:!0}:{},agentPermissionMode:J.value&&I.value===`plan`?`bypass`:I.value},ee=await De.createWorkspace(r);ke({projectPath:M.value.trim(),autoLoop:J.value}),ge().subscribe(ee.id),De.selectWorkspace(ee.id),ve.push({name:`workspace`,params:{id:ee.id}})}catch{w.notify({type:`negative`,message:E(`createPage.errorCreating`),position:`top`})}finally{Wt.value=!1}}return(e,n)=>(t(),s(Se,{class:`create-page flex flex-center column`},{default:a(()=>[m(`div`,Ae,[m(`div`,je,o(e.$t(`createPage.title`)),1),m(`div`,Me,[m(`div`,Ne,[m(`span`,Pe,[l(h,{name:`auto_awesome`,size:`14px`,color:`indigo-4`}),m(`span`,Fe,o(Ft.value?.displayName??e.$t(`createPage.claudeCode`)),1)]),l(ye),l(_,{flat:``,dense:``,"no-caps":``,size:`sm`,color:k.value?`green-4`:`grey-5`,class:`notion-toggle-btn text-caption rounded-borders`,disable:Z.value,onClick:fn},{default:a(()=>[l(h,{name:`description`,size:`14px`,class:`q-mr-xs`}),u(` `+o(k.value?e.$t(`createPage.notionEnabled`):e.$t(`createPage.importNotion`)),1)]),_:1},8,[`color`,`disable`]),l(_,{flat:``,dense:``,"no-caps":``,size:`sm`,color:q.value?`red-4`:`grey-5`,class:`sentry-toggle-btn text-caption rounded-borders q-ml-sm`,disable:Z.value,onClick:pn},{default:a(()=>[l(h,{name:`bug_report`,size:`14px`,class:`q-mr-xs`}),u(` `+o(q.value?e.$t(`createPage.sentryEnabled`):e.$t(`createPage.importSentry`)),1)]),_:1},8,[`color`,`disable`])]),l(g,{color:`grey-9`}),l(le,{name:`slide`},{default:a(()=>[k.value?(t(),f(`div`,Ie,[l(v,{modelValue:O.value,"onUpdate:modelValue":n[0]||=e=>O.value=e,borderless:``,dense:``,placeholder:e.$t(`createPage.notionPlaceholder`),class:`notion-url-input`,"input-class":`notion-url-input-inner`},{prepend:a(()=>[l(h,{name:`link`,size:`16px`,color:V.value?`green-4`:`grey-6`},null,8,[`color`])]),_:1},8,[`modelValue`,`placeholder`]),O.value.trim()&&!V.value?(t(),f(`div`,Le,o(e.$t(`createPage.notionValidation`)),1)):c(``,!0),V.value?(t(),f(`div`,Re,o(e.$t(`createPage.notionAutoExtract`)),1)):c(``,!0),V.value&&an.value?(t(),f(`div`,ze,[m(`div`,Be,[l(h,{name:`info`,size:`14px`,color:`indigo-4`,class:`q-mr-xs`}),u(` `+o(e.$t(`createPage.notionPanelChoiceLabel`)),1)]),m(`div`,Ve,[m(`button`,{type:`button`,class:re([`peek-card col`,{"peek-card--active":H.value===`panel`}]),onClick:n[1]||=e=>H.value=`panel`},[l(h,{name:`article`,size:`22px`,class:`peek-card-icon`}),m(`div`,He,[m(`div`,Ue,o(e.$t(`createPage.notionPanelOption`)),1),m(`div`,We,o(e.$t(`createPage.notionPanelOptionDesc`)),1)]),H.value===`panel`?(t(),s(h,{key:0,name:`check_circle`,size:`18px`,color:`indigo-4`,class:`peek-card-check`})):c(``,!0)],2),m(`button`,{type:`button`,class:re([`peek-card col`,{"peek-card--active":H.value===`parent`}]),onClick:n[2]||=e=>H.value=`parent`},[l(h,{name:`folder_open`,size:`22px`,class:`peek-card-icon`}),m(`div`,Ge,[m(`div`,Ke,o(e.$t(`createPage.notionParentOption`)),1),m(`div`,qe,o(e.$t(`createPage.notionParentOptionDesc`)),1)]),H.value===`parent`?(t(),s(h,{key:0,name:`check_circle`,size:`18px`,color:`indigo-4`,class:`peek-card-check`})):c(``,!0)],2)])])):c(``,!0)])):c(``,!0)]),_:1}),k.value?(t(),s(g,{key:0,color:`grey-9`})):c(``,!0),l(le,{name:`slide`},{default:a(()=>[q.value?(t(),f(`div`,Je,[l(v,{modelValue:Y.value,"onUpdate:modelValue":n[3]||=e=>Y.value=e,borderless:``,dense:``,placeholder:e.$t(`createPage.sentryPlaceholder`),class:`sentry-url-input`,"input-class":`sentry-url-input-inner`},{prepend:a(()=>[l(h,{name:`link`,size:`16px`,color:X.value?`red-4`:`grey-6`},null,8,[`color`])]),_:1},8,[`modelValue`,`placeholder`]),Y.value.trim()&&!X.value?(t(),f(`div`,Ye,o(e.$t(`createPage.sentryValidation`)),1)):c(``,!0),X.value?(t(),f(`div`,Xe,o(e.$t(`createPage.sentryAutoExtract`)),1)):c(``,!0)])):c(``,!0)]),_:1}),q.value?(t(),s(g,{key:1,color:`grey-9`})):c(``,!0),m(`div`,Ze,[l(v,{modelValue:jt.value,"onUpdate:modelValue":n[4]||=e=>jt.value=e,borderless:``,dense:``,placeholder:k.value&&V.value?e.$t(`createPage.workspaceName`):e.$t(`createPage.workspaceNamePlaceholder`),class:`name-input`,"input-class":`name-input-inner`},null,8,[`modelValue`,`placeholder`])]),l(g,{color:`grey-9`}),m(`div`,Qe,[l(v,{ref_key:`descriptionRef`,ref:Mt,modelValue:D.value,"onUpdate:modelValue":n[5]||=e=>D.value=e,type:`textarea`,borderless:``,autogrow:``,rows:3,placeholder:k.value?e.$t(`createPage.instructions`):e.$t(`createPage.instructionsPlaceholder`),class:`create-textarea`,"input-class":`create-textarea-input`,onKeydown:[tn,ce(ue(Tn,[`ctrl`]),[`enter`]),ce(ue(Tn,[`meta`]),[`enter`])]},null,8,[`modelValue`,`placeholder`,`onKeydown`]),p(Jt)&&p(z).length>0?(t(),s(Ce,{key:0,class:`create-slash-popup`,"grouped-dropdown":p(Yt),"flat-dropdown":p(z),"selected-index":p(R),onSelect:en},null,8,[`grouped-dropdown`,`flat-dropdown`,`selected-index`])):c(``,!0)]),l(g,{color:`grey-9`}),sn.value?(t(),f(ie,{key:2},[m(`div`,$e,o(e.$t(`createPage.manualHint`)),1),l(_e,{dark:``,dense:``,label:e.$t(`createPage.tasks`,{count:U.value.length}),"header-class":`text-grey-4 manual-expansion-header`,class:`manual-expansion q-mx-sm`},{default:a(()=>[m(`div`,et,[m(`div`,tt,[l(v,{modelValue:G.value,"onUpdate:modelValue":n[6]||=e=>G.value=e,dark:``,dense:``,borderless:``,placeholder:e.$t(`createPage.addTask`),class:`col manual-input`,"input-class":`manual-input-inner`,onKeydown:ce(ue(cn,[`prevent`]),[`enter`])},null,8,[`modelValue`,`placeholder`,`onKeydown`]),l(_,{flat:``,dense:``,round:``,icon:`add`,color:`indigo-4`,disable:!G.value.trim(),onClick:cn},{default:a(()=>[l(S,null,{default:a(()=>[u(o(e.$t(`tooltip.addTask`)),1)]),_:1})]),_:1},8,[`disable`])]),(t(!0),f(ie,null,r(U.value,(n,r)=>(t(),f(`div`,{key:`task-${r}`,class:`row items-center q-py-xs manual-item`},[m(`span`,nt,o(n),1),l(_,{flat:``,dense:``,round:``,icon:`close`,size:`xs`,color:`grey-6`,onClick:e=>ln(r)},{default:a(()=>[l(S,null,{default:a(()=>[u(o(e.$t(`tooltip.removeTask`)),1)]),_:1})]),_:1},8,[`onClick`])]))),128))])]),_:1},8,[`label`]),l(_e,{dark:``,dense:``,label:e.$t(`createPage.acceptanceCriteria`,{count:W.value.length}),"header-class":`text-grey-4 manual-expansion-header`,class:`manual-expansion q-mx-sm q-mb-sm`},{default:a(()=>[m(`div`,rt,[m(`div`,it,[l(v,{modelValue:K.value,"onUpdate:modelValue":n[7]||=e=>K.value=e,dark:``,dense:``,borderless:``,placeholder:e.$t(`createPage.addCriterion`),class:`col manual-input`,"input-class":`manual-input-inner`,onKeydown:ce(ue(un,[`prevent`]),[`enter`])},null,8,[`modelValue`,`placeholder`,`onKeydown`]),l(_,{flat:``,dense:``,round:``,icon:`add`,color:`indigo-4`,disable:!K.value.trim(),onClick:un},{default:a(()=>[l(S,null,{default:a(()=>[u(o(e.$t(`tooltip.addCriterion`)),1)]),_:1})]),_:1},8,[`disable`])]),(t(!0),f(ie,null,r(W.value,(n,r)=>(t(),f(`div`,{key:`crit-${r}`,class:`row items-center q-py-xs manual-item`},[m(`span`,at,o(n),1),l(_,{flat:``,dense:``,round:``,icon:`close`,size:`xs`,color:`grey-6`,onClick:e=>dn(r)},{default:a(()=>[l(S,null,{default:a(()=>[u(o(e.$t(`tooltip.removeCriterion`)),1)]),_:1})]),_:1},8,[`onClick`])]))),128))])]),_:1},8,[`label`]),l(g,{color:`grey-9`})],64)):c(``,!0),m(`div`,ot,[m(`div`,st,[It.value.length>0?(t(),f(`div`,ct,[l(C,{modelValue:F.value,"onUpdate:modelValue":n[8]||=e=>F.value=e,options:It.value,dense:``,borderless:``,class:`bottom-select rounded-borders`,"hide-dropdown-icon":``,"emit-value":``,"map-options":``,"option-value":`value`,"option-label":`label`},{selected:a(()=>[m(`span`,lt,[l(h,{name:`hub`,size:`12px`,color:`grey-5`,class:`q-mr-xs`}),u(` `+o(It.value.find(e=>e.value===F.value)?.label??F.value)+` `,1),l(h,{name:`expand_more`,size:`12px`,color:`grey-5`})])]),default:a(()=>[l(S,null,{default:a(()=>[u(o(e.$t(`engine.select`)),1)]),_:1})]),_:1},8,[`modelValue`,`options`])])):c(``,!0),m(`div`,ut,[l(C,{modelValue:A.value,"onUpdate:modelValue":n[9]||=e=>A.value=e,options:nn.value,dense:``,borderless:``,class:`bottom-select rounded-borders model-select`,"hide-dropdown-icon":``,"emit-value":``,"map-options":``,"option-value":`value`,"option-label":`label`},{selected:a(()=>[m(`span`,dt,[u(o(nn.value.find(e=>e.value===A.value)?.label??A.value)+` `,1),l(h,{name:`expand_more`,size:`12px`,color:`grey-5`})])]),option:a(({opt:e,itemProps:t})=>[l(y,ne(t,{class:`model-option`}),{default:a(()=>[l(b,null,{default:a(()=>[l(x,{class:`text-white`},{default:a(()=>[u(o(e.label),1)]),_:2},1024),l(x,{caption:``,class:`text-grey-5`},{default:a(()=>[u(o(e.description),1)]),_:2},1024)]),_:2},1024)]),_:2},1040)]),_:1},8,[`modelValue`,`options`])]),m(`div`,ft,[l(C,{modelValue:j.value,"onUpdate:modelValue":n[10]||=e=>j.value=e,options:rn.value,dense:``,borderless:``,class:`bottom-select rounded-borders`,"hide-dropdown-icon":``,"emit-value":``,"map-options":``,"option-value":`value`,"option-label":`label`},{selected:a(()=>[m(`span`,pt,[l(h,{name:`psychology`,size:`12px`,color:`grey-5`,class:`q-mr-xs`}),u(` `+o(rn.value.find(e=>e.value===j.value)?.label??j.value)+` `,1),l(h,{name:`expand_more`,size:`12px`,color:`grey-5`})])]),option:a(({opt:e,itemProps:t})=>[l(y,oe(se(t)),{default:a(()=>[l(b,null,{default:a(()=>[l(x,{class:`text-white`},{default:a(()=>[u(o(e.label),1)]),_:2},1024),l(x,{caption:``,class:`text-grey-5`},{default:a(()=>[u(o(e.description),1)]),_:2},1024)]),_:2},1024)]),_:2},1040)]),_:1},8,[`modelValue`,`options`])]),m(`div`,mt,[l(C,{modelValue:I.value,"onUpdate:modelValue":n[11]||=e=>I.value=e,options:Vt.value,disable:J.value,dense:``,borderless:``,class:`bottom-select rounded-borders`,"hide-dropdown-icon":``,"emit-value":``,"map-options":``,"option-value":`value`,"option-label":`label`,"option-disable":e=>e.disabled===!0},{selected:a(()=>[m(`span`,ht,[l(h,{name:Bt.value,size:`12px`,color:`amber-6`,class:`q-mr-xs`},null,8,[`name`]),u(` `+o(Vt.value.find(e=>e.value===I.value)?.label??I.value)+` `,1),J.value?c(``,!0):(t(),s(h,{key:0,name:`expand_more`,size:`12px`,color:`grey-5`}))])]),default:a(()=>[l(S,null,{default:a(()=>[u(o(J.value?e.$t(`agentPermissionMode.autoLoopLocked`):e.$t(`agentPermissionMode.tooltip`)),1)]),_:1})]),_:1},8,[`modelValue`,`options`,`disable`,`option-disable`])])]),m(`div`,gt,[m(`div`,_t,[l(_,{flat:``,dense:``,size:`sm`,"no-caps":``,icon:J.value?`autorenew`:`sync_disabled`,color:J.value?`amber-4`:`grey-5`,label:e.$t(`autoLoop.startInMode`),class:`skip-setup-btn`,onClick:n[12]||=e=>J.value=!J.value},{default:a(()=>[l(S,null,{default:a(()=>[u(o(e.$t(`autoLoop.startInMode`)),1)]),_:1})]),_:1},8,[`icon`,`color`,`label`])]),m(`div`,vt,[l(_,{flat:``,dense:``,size:`sm`,"no-caps":``,icon:P.value?`play_disabled`:`play_circle`,color:P.value?`orange-4`:`grey-5`,label:e.$t(`createPage.skipSetupScript`),class:`skip-setup-btn`,onClick:n[13]||=e=>P.value=!P.value},{default:a(()=>[l(S,null,{default:a(()=>[u(o(e.$t(`createPage.skipSetupScript`)),1)]),_:1})]),_:1},8,[`icon`,`color`,`label`])]),m(`div`,yt,[l(_,{flat:``,dense:``,size:`sm`,"no-caps":``,icon:`folder_open`,color:Z.value?`cyan-4`:`grey-5`,label:Z.value?e.$t(`createPage.attachWorktreeEnabled`):e.$t(`createPage.attachWorktreeToggle`),class:`skip-setup-btn`,onClick:gn},null,8,[`color`,`label`])])]),m(`div`,bt,[m(`div`,xt,[l(C,{modelValue:M.value,"onUpdate:modelValue":n[14]||=e=>M.value=e,options:At.value,dense:``,borderless:``,"use-input":``,"fill-input":``,"hide-selected":``,"input-debounce":`0`,"new-value-mode":`add`,class:`bottom-select rounded-borders repo-select`,"hide-dropdown-icon":``,"input-class":M.value?``:`repo-input-empty`,placeholder:e.$t(`createPage.projectPath`),behavior:p(T).projectPaths.length>0?`menu`:`dialog`,onFilter:bn,onInputValue:n[15]||=e=>{M.value=e}},{prepend:a(()=>[l(h,{name:`folder`,size:`14px`,color:`grey-5`})]),"no-option":a(()=>[l(y,null,{default:a(()=>[l(b,{class:`text-grey-6 text-caption`},{default:a(()=>[u(o(e.$t(`createPage.enterPath`)),1)]),_:1})]),_:1})]),_:1},8,[`modelValue`,`options`,`input-class`,`placeholder`,`behavior`])]),Z.value?c(``,!0):(t(),f(`div`,St,[l(C,{modelValue:Nt.value,"onUpdate:modelValue":n[16]||=e=>Nt.value=e,options:Lt,"emit-value":``,"map-options":``,dense:``,borderless:``,class:`bottom-select rounded-borders branch-type-select`,"hide-dropdown-icon":``},{selected:a(()=>[m(`span`,Ct,[l(h,{name:`account_tree`,size:`12px`,color:`grey-5`,class:`q-mr-xs`}),u(` `+o(Nt.value)+`/ `,1),l(h,{name:`expand_more`,size:`12px`,color:`grey-5`})])]),default:a(()=>[l(S,null,{default:a(()=>[u(o(e.$t(`createPage.branchType`)),1)]),_:1})]),_:1},8,[`modelValue`])])),Z.value?(t(),f(`div`,wt,[l(C,{modelValue:Q.value,"onUpdate:modelValue":n[17]||=e=>Q.value=e,options:$.value,"option-label":`branch`,"option-value":`path`,"emit-value":``,"map-options":``,"use-input":``,dense:``,borderless:``,class:`bottom-select rounded-borders worktree-select`,"hide-dropdown-icon":``,loading:mn.value,disable:!M.value.trim()||mn.value},{selected:a(()=>[m(`span`,Tt,[l(h,{name:`folder_open`,size:`12px`,color:`cyan-5`,class:`q-mr-xs`}),u(` `+o(Q.value?$.value.find(e=>e.path===Q.value)?.branch??Q.value:e.$t(`createPage.worktreePickerLabel`))+` `,1),l(h,{name:`expand_more`,size:`12px`,color:`grey-5`})])]),option:a(e=>[l(y,oe(se(e.itemProps)),{default:a(()=>[l(b,null,{default:a(()=>[l(x,null,{default:a(()=>[u(o(e.opt.branch),1)]),_:2},1024),l(x,{caption:``},{default:a(()=>[u(o(e.opt.path),1)]),_:2},1024)]),_:2},1024)]),_:2},1040)]),"no-option":a(()=>[l(y,null,{default:a(()=>[l(b,{class:`text-grey-6 text-caption`},{default:a(()=>[u(o(M.value.trim()?e.$t(`createPage.noOrphanWorktrees`):e.$t(`createPage.enterPath`)),1)]),_:1})]),_:1})]),_:1},8,[`modelValue`,`options`,`loading`,`disable`])])):c(``,!0),m(`div`,Et,[l(C,{modelValue:N.value,"onUpdate:modelValue":n[18]||=e=>N.value=e,options:Ht.value,dense:``,borderless:``,class:`bottom-select rounded-borders branch-select`,"hide-dropdown-icon":``,"use-input":``,"input-debounce":`0`,loading:Ut.value,disable:!M.value.trim()||Ut.value,onFilter:Gt},{selected:a(()=>[m(`span`,Dt,[l(h,{name:`call_split`,size:`12px`,color:`grey-5`,class:`q-mr-xs`}),u(` `+o(N.value??e.$t(`createPage.branch`))+` `,1),l(h,{name:`expand_more`,size:`12px`,color:`grey-5`})])]),"no-option":a(()=>[l(y,null,{default:a(()=>[l(b,{class:`text-grey-6 text-caption`},{default:a(()=>[u(o(M.value.trim()?e.$t(`createPage.noBranches`):e.$t(`createPage.enterPath`)),1)]),_:1})]),_:1})]),_:1},8,[`modelValue`,`options`,`loading`,`disable`])])])]),m(`div`,Ot,[l(_,{label:e.$t(`createPage.create`),"no-caps":``,unelevated:``,class:`create-btn text-weight-bold rounded-borders`,loading:Wt.value,disable:Wt.value||Z.value&&!Q.value,onClick:Tn},null,8,[`label`,`loading`,`disable`])])]),m(`div`,kt,o(k.value?e.$t(`createPage.notionExtractHint`):e.$t(`createPage.notionImportHint`)),1)])]),_:1}))}}),[[`__scopeId`,`data-v-79b6a61a`]]);export{E as default};