circuschief 0.2.1 → 0.4.0
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/package.json +1 -1
- package/packages/server/src/api/git.js +15 -0
- package/packages/server/src/api/index.js +13 -2
- package/packages/server/src/api/projects-commandButtons.js +96 -0
- package/packages/server/src/api/projects-session-helpers.js +51 -33
- package/packages/server/src/api/projects.js +109 -113
- package/packages/server/src/api/sessions-archive.js +2 -1
- package/packages/server/src/api/sessions-lifecycle.js +2 -1
- package/packages/server/src/api/sessions-patch.js +2 -0
- package/packages/server/src/config.js +6 -0
- package/packages/server/src/database.js +1 -0
- package/packages/server/src/db/DatabaseManager.js +12 -1
- package/packages/server/src/db/ProjectRepository.js +22 -5
- package/packages/server/src/db/index.js +4 -0
- package/packages/server/src/db/migrations/index.js +7 -0
- package/packages/server/src/db/migrations/miscMigrations.js +78 -1
- package/packages/server/src/db/migrations/projectsMigrations.js +4 -0
- package/packages/server/src/index.js +10 -4
- package/packages/server/src/services/gitService.js +42 -6
- package/packages/server/src/services/gitSessionSetup.js +4 -3
- package/packages/server/src/services/kanbanTriggers.js +1 -0
- package/packages/server/src/services/schedulerService.js +32 -0
- package/packages/server/src/services/sessionExecution.js +2 -2
- package/packages/server/src/services/templateTriggerService.js +1 -0
- package/packages/shared/src/contracts/projects.js +4 -1
- package/packages/shared/src/types.js +4 -3
- package/packages/web/dist/assets/ActiveSessionsView-BVco8bPU.css +1 -0
- package/packages/web/dist/assets/ActiveSessionsView-D1daFFvI.js +1 -0
- package/packages/web/dist/assets/{AgentLogsView-D4l0N9ZA.js → AgentLogsView-B_NDIx2_.js} +1 -1
- package/packages/web/dist/assets/{ApiClient-Dbs1H78V.js → ApiClient-CcqJ-GAv.js} +1 -1
- package/packages/web/dist/assets/ArchiveConfirmModal-BHqbCCX2.js +1 -0
- package/packages/web/dist/assets/{ArchiveConfirmModal-CQZeuYBz.css → ArchiveConfirmModal-BQ-4gI0R.css} +1 -1
- package/packages/web/dist/assets/CommandButtonDetailView-B9crZey8.js +1 -0
- package/packages/web/dist/assets/EffortLevelSelector-HuOPegKI.js +1 -0
- package/packages/web/dist/assets/{GeneralSettingsView-BqCzCX-z.js → GeneralSettingsView-CzBagrDs.js} +1 -1
- package/packages/web/dist/assets/{PathChooser-CXFxb8Oj.js → InputWithButton-Cplkm8Ze.js} +1 -1
- package/packages/web/dist/assets/InputWithButton-cYdrEmTs.css +1 -0
- package/packages/web/dist/assets/InterpolationHelp-bG_y10VY.js +1 -0
- package/packages/web/dist/assets/MarkdownEditor-CXDVTLvp.js +2 -0
- package/packages/web/dist/assets/{ModelSelector-DSxaZWBL.js → ModelSelector-CgpqdZtV.js} +1 -1
- package/packages/web/dist/assets/{NewSessionView-BsI7JtO9.js → NewSessionView-91V-4qLi.js} +2 -2
- package/packages/web/dist/assets/{NewSessionView-Byoi1XdQ.css → NewSessionView-D_Hi7M9g.css} +1 -1
- package/packages/web/dist/assets/ProjectEditView-C3bOYnD6.js +1 -0
- package/packages/web/dist/assets/ProjectEditView-CpeKj-_w.css +1 -0
- package/packages/web/dist/assets/ProjectListView-BEo1p4dO.js +1 -0
- package/packages/web/dist/assets/ProjectListView-CumumZN-.css +1 -0
- package/packages/web/dist/assets/ProjectNewView-CXGjy3OL.js +1 -0
- package/packages/web/dist/assets/ProjectNewView-CaLXfFzd.css +1 -0
- package/packages/web/dist/assets/{ProvidersView-CgAr0qms.js → ProvidersView-CMAnPTEX.js} +1 -1
- package/packages/web/dist/assets/{ProvidersView-B_QQF3RM.css → ProvidersView-uD8SKWpA.css} +1 -1
- package/packages/web/dist/assets/{QuickResponseSettings-uDDpwaza.js → QuickResponseSettings-Ds4X-J-t.js} +1 -1
- package/packages/web/dist/assets/{QuickResponsesPanel-D0qs0Fm_.js → QuickResponsesPanel-BY2v23c5.js} +1 -1
- package/packages/web/dist/assets/{ResizableTextarea-_kHi1Mg3.js → ResizableTextarea-C2s6_7X9.js} +1 -1
- package/packages/web/dist/assets/{SessionCard-Be1-bK0C.js → SessionCard-C7vFzR16.js} +1 -1
- package/packages/web/dist/assets/{SessionDetailView-mnGRMaLY.css → SessionDetailView-BL83oPiI.css} +1 -1
- package/packages/web/dist/assets/SessionDetailView-D3f0FEV1.js +36 -0
- package/packages/web/dist/assets/{SessionFormOptions-DvhOyP6z.js → SessionFormOptions-3LfqiLiR.js} +1 -1
- package/packages/web/dist/assets/{SessionListView-CuHsWj85.js → SessionListView-NGW-u434.js} +1 -1
- package/packages/web/dist/assets/{SessionLogStream-Da_GniUZ.js → SessionLogStream-NR-AS676.js} +6 -6
- package/packages/web/dist/assets/SettingsView-DKINDb2z.js +1 -0
- package/packages/web/dist/assets/{SlashCommandWizard-B_8ifpxN.js → SlashCommandWizard-PXipO1yA.js} +1 -1
- package/packages/web/dist/assets/{SummarySettingsView-KvgSGHdd.js → SummarySettingsView-D_2bSsYD.js} +1 -1
- package/packages/web/dist/assets/{TemplateDetailView-BhOjYIvS.js → TemplateDetailView-C5rbgXwU.js} +1 -1
- package/packages/web/dist/assets/{commandButtons-B4OYZP0J.js → commandButtons-D_-wR8zJ.js} +1 -1
- package/packages/web/dist/assets/index-BCazaXF8.js +1 -0
- package/packages/web/dist/assets/index-BmQRt229.js +3 -0
- package/packages/web/dist/assets/index-CCQGqJXX.js +1 -0
- package/packages/web/dist/assets/index-CcCiJkwz.js +1 -0
- package/packages/web/dist/assets/index-Cs-001Bx.js +1 -0
- package/packages/web/dist/assets/index-CxiSnR0R.js +1 -0
- package/packages/web/dist/assets/index-D-lQSDZh.js +1 -0
- package/packages/web/dist/assets/{index-CSOPrlmq.js → index-D1Lg5reX.js} +3 -3
- package/packages/web/dist/assets/index-D9VgH58U.js +1 -0
- package/packages/web/dist/assets/index-D9Z6zsGS.js +1 -0
- package/packages/web/dist/assets/index-DP2i58hO.js +1 -0
- package/packages/web/dist/assets/index-DaL3nu0U.js +1 -0
- package/packages/web/dist/assets/index-Dfy1JGZs.js +1 -0
- package/packages/web/dist/assets/index-DgIOe3cM.js +1 -0
- package/packages/web/dist/assets/index-Dp3Eg3c0.js +1 -0
- package/packages/web/dist/assets/{index-BHVnr8MO.js → index-DveLfEiG.js} +2 -2
- package/packages/web/dist/assets/index-DyQ22-ut.js +1 -0
- package/packages/web/dist/assets/{index-BqVgX_Jy.js → index-Krfrs3sc.js} +3 -3
- package/packages/web/dist/assets/index-gylMFbgn.js +7 -0
- package/packages/web/dist/assets/{projects-B2du-GX8.js → projects-CMJJca64.js} +1 -1
- package/packages/web/dist/assets/{providers-B__J6FX0.js → providers-BCtNZWYw.js} +1 -1
- package/packages/web/dist/assets/{sessions-VDrd87yA.js → sessions-CoRS-wuR.js} +1 -1
- package/packages/web/dist/assets/{settings-CZ7Pc-Pt.js → settings-BN_W4nwV.js} +1 -1
- package/packages/web/dist/assets/useSummaryHelpers-GVg7sMWF.js +1 -0
- package/packages/web/dist/index.html +1 -1
- package/packages/web/dist/assets/ActiveSessionsView-3697sD8N.js +0 -1
- package/packages/web/dist/assets/ActiveSessionsView-DfYXc6dz.css +0 -1
- package/packages/web/dist/assets/ArchiveConfirmModal-Bv3vGOMM.js +0 -1
- package/packages/web/dist/assets/CommandButtonDetailView-Bk_SHxpu.js +0 -1
- package/packages/web/dist/assets/EffortLevelSelector-VfBEelvO.js +0 -1
- package/packages/web/dist/assets/InterpolationHelp-Dc1Y0T6v.js +0 -1
- package/packages/web/dist/assets/MarkdownEditor-DwBQkZbs.js +0 -2
- package/packages/web/dist/assets/PathChooser-BoMGzeg2.css +0 -1
- package/packages/web/dist/assets/ProjectEditView-Bes4Mib4.js +0 -1
- package/packages/web/dist/assets/ProjectEditView-DNwBUNRk.css +0 -1
- package/packages/web/dist/assets/ProjectListView-C55H1JHQ.css +0 -1
- package/packages/web/dist/assets/ProjectListView-DzEu-C36.js +0 -1
- package/packages/web/dist/assets/ProjectNewView-CpgE4R-l.css +0 -1
- package/packages/web/dist/assets/ProjectNewView-Cv-iEAgl.js +0 -1
- package/packages/web/dist/assets/SessionDetailView-DUYb7qTA.js +0 -36
- package/packages/web/dist/assets/SettingsView-5RDCXNUa.js +0 -1
- package/packages/web/dist/assets/index-80Qu7W6P.js +0 -1
- package/packages/web/dist/assets/index-B8_Iqwcq.js +0 -1
- package/packages/web/dist/assets/index-B9JErft2.js +0 -1
- package/packages/web/dist/assets/index-BarVnQIj.js +0 -3
- package/packages/web/dist/assets/index-BsvRdU0B.js +0 -1
- package/packages/web/dist/assets/index-Bugg2M-E.js +0 -1
- package/packages/web/dist/assets/index-C2Pjy-M8.js +0 -1
- package/packages/web/dist/assets/index-CS_wb_Vj.js +0 -1
- package/packages/web/dist/assets/index-ClzNIdCp.js +0 -1
- package/packages/web/dist/assets/index-Cn9Ajkye.js +0 -1
- package/packages/web/dist/assets/index-CucpVX4L.js +0 -1
- package/packages/web/dist/assets/index-D9hZYvW3.js +0 -1
- package/packages/web/dist/assets/index-DA0dK_PG.js +0 -7
- package/packages/web/dist/assets/index-DgpSn-jR.js +0 -1
- package/packages/web/dist/assets/index-HZwIyC9t.js +0 -1
- package/packages/web/dist/assets/index-SS3wA2sI.js +0 -1
|
@@ -19,7 +19,13 @@ export class ProjectRepository extends BaseRepository {
|
|
|
19
19
|
onSessionDeleted: row.on_session_deleted,
|
|
20
20
|
prPollInterval: row.pr_poll_interval,
|
|
21
21
|
repoUrl: row.repo_url,
|
|
22
|
-
|
|
22
|
+
worktreePath: row.worktree_path,
|
|
23
|
+
// Kanban is an experimental feature, disabled by default for new projects.
|
|
24
|
+
// Fallback to `false` when the column is absent so missing data is
|
|
25
|
+
// treated as opt-out (consistent with create()).
|
|
26
|
+
kanbanEnabled: row.kanban_enabled === undefined ? false : Boolean(row.kanban_enabled),
|
|
27
|
+
sessionCount: row.session_count ?? 0,
|
|
28
|
+
lastActivityAt: row.last_activity_at ?? null,
|
|
23
29
|
createdAt: row.created_at,
|
|
24
30
|
updatedAt: row.updated_at,
|
|
25
31
|
};
|
|
@@ -33,12 +39,13 @@ export class ProjectRepository extends BaseRepository {
|
|
|
33
39
|
onSessionDeleted = null,
|
|
34
40
|
prPollInterval = 60000,
|
|
35
41
|
repoUrl = null,
|
|
36
|
-
kanbanEnabled =
|
|
42
|
+
kanbanEnabled = false,
|
|
43
|
+
worktreePath = null,
|
|
37
44
|
} = options;
|
|
38
45
|
this.db
|
|
39
46
|
.prepare(
|
|
40
|
-
`INSERT INTO projects (id, name, working_directory, system_prompt, on_session_created, on_session_deleted, pr_poll_interval, repo_url, kanban_enabled, created_at, updated_at)
|
|
41
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
47
|
+
`INSERT INTO projects (id, name, working_directory, system_prompt, on_session_created, on_session_deleted, pr_poll_interval, repo_url, kanban_enabled, worktree_path, created_at, updated_at)
|
|
48
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
42
49
|
)
|
|
43
50
|
.run(
|
|
44
51
|
id,
|
|
@@ -50,6 +57,7 @@ export class ProjectRepository extends BaseRepository {
|
|
|
50
57
|
prPollInterval,
|
|
51
58
|
repoUrl,
|
|
52
59
|
kanbanEnabled ? 1 : 0,
|
|
60
|
+
worktreePath,
|
|
53
61
|
now,
|
|
54
62
|
now
|
|
55
63
|
);
|
|
@@ -57,7 +65,15 @@ export class ProjectRepository extends BaseRepository {
|
|
|
57
65
|
}
|
|
58
66
|
|
|
59
67
|
getAll() {
|
|
60
|
-
const rows = this.db.prepare(
|
|
68
|
+
const rows = this.db.prepare(`
|
|
69
|
+
SELECT p.*,
|
|
70
|
+
COUNT(CASE WHEN s.archived = 0 THEN s.id END) as session_count,
|
|
71
|
+
MAX(s.updated_at) as last_activity_at
|
|
72
|
+
FROM projects p
|
|
73
|
+
LEFT JOIN sessions s ON s.project_id = p.id
|
|
74
|
+
GROUP BY p.id
|
|
75
|
+
ORDER BY p.updated_at DESC
|
|
76
|
+
`).all();
|
|
61
77
|
return this.mapAll(rows);
|
|
62
78
|
}
|
|
63
79
|
|
|
@@ -73,6 +89,7 @@ export class ProjectRepository extends BaseRepository {
|
|
|
73
89
|
onSessionDeleted: { column: 'on_session_deleted' },
|
|
74
90
|
prPollInterval: { column: 'pr_poll_interval' },
|
|
75
91
|
repoUrl: { column: 'repo_url' },
|
|
92
|
+
worktreePath: { column: 'worktree_path' },
|
|
76
93
|
kanbanEnabled: { column: 'kanban_enabled', transform: (v) => v ? 1 : 0 },
|
|
77
94
|
};
|
|
78
95
|
|
|
@@ -65,6 +65,7 @@ export const allMigrations = validateMigrations([
|
|
|
65
65
|
p.get('projects-add-on_session_created'),
|
|
66
66
|
p.get('projects-add-on_session_deleted'),
|
|
67
67
|
p.get('projects-add-repo_url'),
|
|
68
|
+
p.get('projects-add-worktree_path'),
|
|
68
69
|
p.get('projects-drop-summary-columns'),
|
|
69
70
|
|
|
70
71
|
// --- Sessions scheduling columns ---
|
|
@@ -196,4 +197,10 @@ export const allMigrations = validateMigrations([
|
|
|
196
197
|
|
|
197
198
|
// --- Seed default global quick responses ---
|
|
198
199
|
m.get('quick_responses-seed-defaults'),
|
|
200
|
+
|
|
201
|
+
// --- Seed default global session templates ---
|
|
202
|
+
m.get('session_templates-seed-defaults'),
|
|
203
|
+
|
|
204
|
+
// --- Update built-in Opus model to 4.7 ---
|
|
205
|
+
m.get('providers-update-built-in-opus-4-7'),
|
|
199
206
|
]);
|
|
@@ -28,7 +28,8 @@ function seedBuiltInProvider(db) {
|
|
|
28
28
|
const defaultModels = [
|
|
29
29
|
{ id: 'anthropic-haiku', modelId: 'claude-haiku-4-5-20251001', displayName: 'Haiku 4.5', description: 'Fast & lightweight', tier: 'haiku' },
|
|
30
30
|
{ id: 'anthropic-sonnet', modelId: 'claude-sonnet-4-6', displayName: 'Sonnet 4.6', description: 'Balanced', tier: 'sonnet' },
|
|
31
|
-
{ id: 'anthropic-opus', modelId: 'claude-opus-4-6', displayName: 'Opus 4.6', description: '
|
|
31
|
+
{ id: 'anthropic-opus', modelId: 'claude-opus-4-6', displayName: 'Opus 4.6', description: 'Previous generation', tier: 'opus' },
|
|
32
|
+
{ id: 'anthropic-opus-4-7', modelId: 'claude-opus-4-7', displayName: 'Opus 4.7', description: 'Most capable (default)', tier: 'opus' },
|
|
32
33
|
];
|
|
33
34
|
|
|
34
35
|
const insertModel = db.prepare(
|
|
@@ -61,6 +62,55 @@ function updateBuiltInModels(db) {
|
|
|
61
62
|
).run('claude-opus-4-6', 'Opus 4.6', providerId, 'anthropic-opus');
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Prompt strings for the default global session templates.
|
|
67
|
+
* Exported so tests can assert verbatim equality.
|
|
68
|
+
*/
|
|
69
|
+
export const DEFAULT_SESSION_TEMPLATE_PROMPTS = {
|
|
70
|
+
REVIEW: `Review the plan on the canvas. If there's more than one plan then review the most recently updated plan. if there are no plans on the canvas then look for the most recently updated plan on the root session canvas.
|
|
71
|
+
|
|
72
|
+
Make sure there are tests explicitly called out for all changes. Make sure that all context necessary to hand off the task is included in the plan.
|
|
73
|
+
|
|
74
|
+
Are there any gaps in the plan? Is test coverage spelled out explicitly? Does the code match the assumptions in the plan?
|
|
75
|
+
|
|
76
|
+
Update the plan according to your review recommendations.`,
|
|
77
|
+
IMPLEMENT: `Implement the plan on the canvas. If there's more than one plan on the canvas then use the most recently updated plan. If you don't see a plan on the canvas then look at the parent session's canvas.`,
|
|
78
|
+
PR: `Ensure all relevant changes are committed and pushed. Then determine the session's goals. You can typically find details about the goals of the session by looking at the most recently modified markdown documents on the root session's canvas - these are typically plans that were implemented during the session. You can also look at the root session's summary, but don't trigger a new summary to be created if the summary is missing.
|
|
79
|
+
|
|
80
|
+
Create a draft pr and ensure all changes are committed and pushed.`,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Seed default global session templates when no global template exists.
|
|
85
|
+
* Idempotent: if any global session template already exists, does nothing.
|
|
86
|
+
*/
|
|
87
|
+
function seedDefaultSessionTemplates(db) {
|
|
88
|
+
const count = db.prepare(
|
|
89
|
+
'SELECT COUNT(*) AS cnt FROM session_templates WHERE project_id IS NULL'
|
|
90
|
+
).get().cnt;
|
|
91
|
+
if (count > 0) return;
|
|
92
|
+
|
|
93
|
+
const defaults = [
|
|
94
|
+
{ name: 'Review the plan', prompt: DEFAULT_SESSION_TEMPLATE_PROMPTS.REVIEW },
|
|
95
|
+
{ name: 'Implement the plan on the canvas', prompt: DEFAULT_SESSION_TEMPLATE_PROMPTS.IMPLEMENT },
|
|
96
|
+
{ name: 'Create/update PR', prompt: DEFAULT_SESSION_TEMPLATE_PROMPTS.PR },
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
const stmt = db.prepare(`
|
|
100
|
+
INSERT INTO session_templates (
|
|
101
|
+
id, project_id, name, prompt,
|
|
102
|
+
next_template_id, thinking_enabled,
|
|
103
|
+
git_branch, git_mode, model, mode, effort_level, target_lane_id,
|
|
104
|
+
created_at, updated_at
|
|
105
|
+
) VALUES (?, NULL, ?, ?, NULL, 1, NULL, NULL, NULL, 'yolo', NULL, NULL, ?, ?)
|
|
106
|
+
`);
|
|
107
|
+
|
|
108
|
+
const now = Date.now();
|
|
109
|
+
for (const item of defaults) {
|
|
110
|
+
stmt.run(randomUUID(), item.name, item.prompt, now, now);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
64
114
|
/**
|
|
65
115
|
* Seed default global quick responses when the table is empty.
|
|
66
116
|
*/
|
|
@@ -239,4 +289,31 @@ export const miscMigrations = [
|
|
|
239
289
|
name: 'quick_responses-seed-defaults',
|
|
240
290
|
up(db) { seedDefaultQuickResponses(db); },
|
|
241
291
|
},
|
|
292
|
+
|
|
293
|
+
// --- Seed default global session templates ---
|
|
294
|
+
{
|
|
295
|
+
name: 'session_templates-seed-defaults',
|
|
296
|
+
up(db) { seedDefaultSessionTemplates(db); },
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
// --- Add Opus 4.7 as a new built-in model (keep Opus 4.6 for existing sessions) ---
|
|
300
|
+
{
|
|
301
|
+
name: 'providers-update-built-in-opus-4-7',
|
|
302
|
+
up(db) {
|
|
303
|
+
const providerId = 'anthropic-default';
|
|
304
|
+
|
|
305
|
+
// Mark existing Opus 4.6 row as previous generation
|
|
306
|
+
db.prepare(
|
|
307
|
+
`UPDATE provider_models
|
|
308
|
+
SET description = ?
|
|
309
|
+
WHERE provider_id = ? AND id = ?`
|
|
310
|
+
).run('Previous generation', providerId, 'anthropic-opus');
|
|
311
|
+
|
|
312
|
+
// Insert new Opus 4.7 row (OR IGNORE in case seed already created it)
|
|
313
|
+
db.prepare(
|
|
314
|
+
`INSERT OR IGNORE INTO provider_models (id, provider_id, model_id, display_name, description, tier, created_at)
|
|
315
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
316
|
+
).run('anthropic-opus-4-7', providerId, 'claude-opus-4-7', 'Opus 4.7', 'Most capable (default)', 'opus', Date.now());
|
|
317
|
+
},
|
|
318
|
+
},
|
|
242
319
|
];
|
|
@@ -43,6 +43,10 @@ export const projectsMigrations = [
|
|
|
43
43
|
name: 'projects-add-repo_url',
|
|
44
44
|
up(db) { addColumnIfMissing(db, 'projects', 'repo_url', 'TEXT'); },
|
|
45
45
|
},
|
|
46
|
+
{
|
|
47
|
+
name: 'projects-add-worktree_path',
|
|
48
|
+
up(db) { addColumnIfMissing(db, 'projects', 'worktree_path', 'TEXT'); },
|
|
49
|
+
},
|
|
46
50
|
{
|
|
47
51
|
name: 'projects-drop-summary-columns',
|
|
48
52
|
up(db) { migrateProjectsDropSummaryColumns(db); },
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { createServer } from 'http';
|
|
2
2
|
import { execSync } from 'child_process';
|
|
3
|
+
import { mkdirSync } from 'fs';
|
|
4
|
+
import { dirname } from 'path';
|
|
3
5
|
import { createApp } from './app.js';
|
|
4
6
|
import { initDatabase } from './database.js';
|
|
5
7
|
import { initWebSocket, webSocketManager } from './websocket.js';
|
|
@@ -11,6 +13,7 @@ import { schedulerService } from './services/schedulerService.js';
|
|
|
11
13
|
import * as sessionManager from './services/sessionManager.js';
|
|
12
14
|
import { clearScheduledTimers } from './services/summaryService.js';
|
|
13
15
|
import { commandRunner } from './services/commandRunner.js';
|
|
16
|
+
import { getDefaultDbPath } from './config.js';
|
|
14
17
|
|
|
15
18
|
/**
|
|
16
19
|
* Validate Node.js environment at startup.
|
|
@@ -32,7 +35,7 @@ function validateNodeEnvironment() {
|
|
|
32
35
|
const { port, disableAnalytics } = parseCliOptions();
|
|
33
36
|
process.env.PORT = String(port);
|
|
34
37
|
const production = process.env.NODE_ENV === 'production';
|
|
35
|
-
const dbPath = process.env.DB_PATH ||
|
|
38
|
+
const dbPath = process.env.DB_PATH || getDefaultDbPath();
|
|
36
39
|
|
|
37
40
|
// Catch uncaught exceptions
|
|
38
41
|
process.on('uncaughtException', (err) => {
|
|
@@ -45,9 +48,13 @@ process.on('unhandledRejection', (reason, promise) => {
|
|
|
45
48
|
// Validate Node.js environment
|
|
46
49
|
validateNodeEnvironment();
|
|
47
50
|
|
|
51
|
+
// Ensure the database directory exists
|
|
52
|
+
mkdirSync(dirname(dbPath), { recursive: true });
|
|
53
|
+
|
|
48
54
|
// Initialize database
|
|
49
55
|
initDatabase(dbPath);
|
|
50
56
|
console.log(`Database initialized: ${dbPath}`);
|
|
57
|
+
console.log(`VCR_MODE: ${process.env.VCR_MODE || '(unset)'}`);
|
|
51
58
|
|
|
52
59
|
// Apply --no-analytics flag to persisted settings
|
|
53
60
|
if (disableAnalytics) {
|
|
@@ -64,9 +71,8 @@ const server = createServer(app);
|
|
|
64
71
|
// Initialize WebSocket for app
|
|
65
72
|
initWebSocket(server);
|
|
66
73
|
|
|
67
|
-
// Initialize and start scheduler service
|
|
68
|
-
schedulerService.
|
|
69
|
-
schedulerService.start();
|
|
74
|
+
// Initialize and start scheduler service (gated off under VCR_MODE)
|
|
75
|
+
schedulerService.startIfEnabled(sessionManager);
|
|
70
76
|
|
|
71
77
|
// Start PR status polling service
|
|
72
78
|
prStatusService.start();
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { exec } from 'child_process';
|
|
2
2
|
import { promisify } from 'util';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { realpath } from 'fs/promises';
|
|
3
5
|
|
|
4
6
|
const execAsync = promisify(exec);
|
|
5
7
|
|
|
@@ -239,12 +241,12 @@ export async function getCurrentBranch(directory) {
|
|
|
239
241
|
* Create a new worktree
|
|
240
242
|
* @param {string} directory
|
|
241
243
|
* @param {string} branch
|
|
242
|
-
* @param {string}
|
|
244
|
+
* @param {string} worktreePath
|
|
243
245
|
* @param {Object} options
|
|
244
246
|
* @param {boolean} options.skipFetch - Skip fetching from origin (default: false)
|
|
245
247
|
* @returns {Promise<{path: string, branch: string}>}
|
|
246
248
|
*/
|
|
247
|
-
export async function createWorktree(directory, branch,
|
|
249
|
+
export async function createWorktree(directory, branch, worktreePath, options = {}) {
|
|
248
250
|
const { skipFetch = false } = options;
|
|
249
251
|
|
|
250
252
|
// Fetch latest from origin to ensure we have up-to-date default branch
|
|
@@ -256,8 +258,8 @@ export async function createWorktree(directory, branch, path, options = {}) {
|
|
|
256
258
|
const defaultBranch = await getOriginDefaultBranch(directory);
|
|
257
259
|
// Base new branch on origin's default branch to avoid including unrelated commits from HEAD
|
|
258
260
|
// Use --no-track to prevent the new branch from tracking the start-point (main/master)
|
|
259
|
-
await git(directory, `worktree add --no-track "${
|
|
260
|
-
return { path, branch };
|
|
261
|
+
await git(directory, `worktree add --no-track "${worktreePath}" -b "${branch}" ${defaultBranch}`);
|
|
262
|
+
return { path: worktreePath, branch };
|
|
261
263
|
}
|
|
262
264
|
|
|
263
265
|
/**
|
|
@@ -266,9 +268,9 @@ export async function createWorktree(directory, branch, path, options = {}) {
|
|
|
266
268
|
* @param {string} path
|
|
267
269
|
* @param {boolean} force - Force removal even if worktree has uncommitted changes
|
|
268
270
|
*/
|
|
269
|
-
export async function removeWorktree(directory,
|
|
271
|
+
export async function removeWorktree(directory, worktreePath, force = false) {
|
|
270
272
|
const forceFlag = force ? '--force' : '';
|
|
271
|
-
await git(directory, `worktree remove ${forceFlag} "${
|
|
273
|
+
await git(directory, `worktree remove ${forceFlag} "${worktreePath}"`);
|
|
272
274
|
}
|
|
273
275
|
|
|
274
276
|
/**
|
|
@@ -518,3 +520,37 @@ export async function pinAuthorInWorktree(worktreePath, projectDir, { env } = {}
|
|
|
518
520
|
|
|
519
521
|
return true;
|
|
520
522
|
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Detect the worktree path for a directory by inspecting existing worktrees.
|
|
526
|
+
* If external worktrees exist, uses the parent directory of the first one.
|
|
527
|
+
* Otherwise, falls back to {directory}/.worktrees.
|
|
528
|
+
* @param {string} directory - The git repository directory
|
|
529
|
+
* @returns {Promise<{worktreePath: string, source: 'detected' | 'default'}>}
|
|
530
|
+
*/
|
|
531
|
+
export async function detectWorktreePath(directory) {
|
|
532
|
+
const isRepo = await isGitRepo(directory);
|
|
533
|
+
if (!isRepo) {
|
|
534
|
+
return { worktreePath: path.join(directory, '.worktrees'), source: 'default' };
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Resolve symlinks for consistent path comparison (e.g., /var -> /private/var on macOS)
|
|
538
|
+
let resolvedDir;
|
|
539
|
+
try {
|
|
540
|
+
resolvedDir = await realpath(directory);
|
|
541
|
+
} catch {
|
|
542
|
+
resolvedDir = path.resolve(directory);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const worktrees = await getWorktrees(directory);
|
|
546
|
+
// Filter out the main worktree (its path === directory or resolves to it)
|
|
547
|
+
const externalWorktrees = worktrees.filter(wt => path.resolve(wt.path) !== resolvedDir);
|
|
548
|
+
|
|
549
|
+
if (externalWorktrees.length > 0) {
|
|
550
|
+
// Use the parent directory of the first external worktree
|
|
551
|
+
const parentDir = path.dirname(path.resolve(externalWorktrees[0].path));
|
|
552
|
+
return { worktreePath: parentDir, source: 'detected' };
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
return { worktreePath: path.join(resolvedDir, '.worktrees'), source: 'default' };
|
|
556
|
+
}
|
|
@@ -8,9 +8,10 @@ import * as gitService from './gitService.js';
|
|
|
8
8
|
* @param {string|null} options.gitMode - 'branch', 'worktree', or null
|
|
9
9
|
* @param {string|null} options.gitBranch - Branch name
|
|
10
10
|
* @param {string} options.sessionId - Session ID
|
|
11
|
+
* @param {string|null} [options.worktreeBasePath] - Custom base path for worktrees (overrides default .worktrees)
|
|
11
12
|
* @returns {Promise<{workingDirectory: string, gitWorktree: string|null}>}
|
|
12
13
|
*/
|
|
13
|
-
export async function setupGitForSession({ projectDir, gitMode, gitBranch, sessionId }) {
|
|
14
|
+
export async function setupGitForSession({ projectDir, gitMode, gitBranch, sessionId, worktreeBasePath }) {
|
|
14
15
|
// No git operations if gitMode is not specified
|
|
15
16
|
if (!gitMode || !gitBranch) {
|
|
16
17
|
return {
|
|
@@ -29,8 +30,8 @@ export async function setupGitForSession({ projectDir, gitMode, gitBranch, sessi
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
if (gitMode === 'worktree') {
|
|
32
|
-
// Create a worktree in .worktrees/{sessionId}
|
|
33
|
-
const worktreePath = join(projectDir, '.worktrees', sessionId);
|
|
33
|
+
// Create a worktree in the configured base path or .worktrees/{sessionId}
|
|
34
|
+
const worktreePath = join(worktreeBasePath || join(projectDir, '.worktrees'), sessionId);
|
|
34
35
|
await gitService.createWorktreeForBranch(projectDir, gitBranch, worktreePath);
|
|
35
36
|
// Pin the human developer's git identity so they are the commit Author
|
|
36
37
|
await gitService.pinAuthorInWorktree(worktreePath, projectDir);
|
|
@@ -54,6 +54,7 @@ export async function determineWorkingDirectory(parentSession, project, gitOptio
|
|
|
54
54
|
gitMode: gitOptions.gitMode || null,
|
|
55
55
|
gitBranch: gitOptions.gitBranch || null,
|
|
56
56
|
sessionId: gitOptions.sessionId,
|
|
57
|
+
worktreeBasePath: project.worktreePath || null,
|
|
57
58
|
});
|
|
58
59
|
return { workingDirectory: gitSetup.workingDirectory, gitWorktree: gitSetup.gitWorktree };
|
|
59
60
|
}
|
|
@@ -49,6 +49,38 @@ class SchedulerService {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Check whether the scheduler is currently polling.
|
|
54
|
+
* @returns {boolean}
|
|
55
|
+
*/
|
|
56
|
+
isRunning() {
|
|
57
|
+
return this.intervalId !== null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Start the scheduler unless the environment opts out (e.g. VCR_MODE).
|
|
62
|
+
*
|
|
63
|
+
* This gate lives on the service so unit tests can exercise the decision
|
|
64
|
+
* without booting the whole server. The env parameter defaults to
|
|
65
|
+
* process.env but is injectable for tests.
|
|
66
|
+
*
|
|
67
|
+
* Note: an empty VCR_MODE string is treated the same as unset, matching
|
|
68
|
+
* the /api/server-info contract.
|
|
69
|
+
*
|
|
70
|
+
* @param {object} sessionManager - Session manager instance
|
|
71
|
+
* @param {object} [env=process.env] - Env-like object (for tests)
|
|
72
|
+
* @returns {boolean} true if started, false if gated off
|
|
73
|
+
*/
|
|
74
|
+
startIfEnabled(sessionManager, env = process.env) {
|
|
75
|
+
if (env.VCR_MODE && env.VCR_MODE.length > 0) {
|
|
76
|
+
console.log('[SchedulerService] VCR_MODE set, scheduler disabled');
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
this.initialize(sessionManager);
|
|
80
|
+
this.start();
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
52
84
|
/**
|
|
53
85
|
* Check for scheduled sessions that are due to start
|
|
54
86
|
*/
|
|
@@ -21,6 +21,8 @@ import {
|
|
|
21
21
|
import { shouldRescheduleOnError, _checkProactiveReschedule } from './sessionErrors.js';
|
|
22
22
|
import { schedulerService } from './schedulerService.js';
|
|
23
23
|
import { buildConversationContextForModelSwitch } from './conversationContext.js';
|
|
24
|
+
import { broadcastToSession } from '../websocket.js';
|
|
25
|
+
import { WS_MESSAGE_TYPES } from '../../../shared/src/index.js';
|
|
24
26
|
|
|
25
27
|
/**
|
|
26
28
|
* Create the agent for a session, using gateway + logging + VCR.
|
|
@@ -243,8 +245,6 @@ async function setupConversationAndMessage(sessionId, content, fileAttachments)
|
|
|
243
245
|
const activeConversation = conversations.ensureActiveConversation(sessionId);
|
|
244
246
|
activeConversationIds.set(sessionId, activeConversation.id);
|
|
245
247
|
|
|
246
|
-
const { broadcastToSession } = await import('../websocket.js');
|
|
247
|
-
const { WS_MESSAGE_TYPES } = await import('@circuschief/shared');
|
|
248
248
|
const message = messages.create(sessionId, 'user', content, { toolUse: null, conversationId: activeConversation.id });
|
|
249
249
|
broadcastToSession(sessionId, WS_MESSAGE_TYPES.SESSION_MESSAGE, {
|
|
250
250
|
message,
|
|
@@ -98,6 +98,7 @@ async function resolveWorkingDirectory(parentSession, project, settings, newSess
|
|
|
98
98
|
gitMode: settings.gitMode,
|
|
99
99
|
gitBranch: settings.gitBranch,
|
|
100
100
|
sessionId: newSessionId,
|
|
101
|
+
worktreeBasePath: project.worktreePath || null,
|
|
101
102
|
});
|
|
102
103
|
return { workingDirectory: gitSetup.workingDirectory, gitWorktree: gitSetup.gitWorktree };
|
|
103
104
|
}
|
|
@@ -8,7 +8,8 @@ export const CreateProjectRequest = z.object({
|
|
|
8
8
|
onSessionDeleted: z.string().nullable().optional(),
|
|
9
9
|
prPollInterval: z.number().int().min(10000).optional(), // Min 10 seconds
|
|
10
10
|
repoUrl: z.string().url().nullable().optional(),
|
|
11
|
-
kanbanEnabled: z.boolean().optional(), // Default
|
|
11
|
+
kanbanEnabled: z.boolean().optional(), // Default false (experimental feature)
|
|
12
|
+
worktreePath: z.string().nullable().optional(),
|
|
12
13
|
});
|
|
13
14
|
|
|
14
15
|
export const UpdateProjectRequest = z.object({
|
|
@@ -20,6 +21,7 @@ export const UpdateProjectRequest = z.object({
|
|
|
20
21
|
prPollInterval: z.number().int().min(10000).optional(), // Min 10 seconds
|
|
21
22
|
repoUrl: z.string().url().nullable().optional(),
|
|
22
23
|
kanbanEnabled: z.boolean().optional(),
|
|
24
|
+
worktreePath: z.string().nullable().optional(),
|
|
23
25
|
});
|
|
24
26
|
|
|
25
27
|
export const ProjectResponse = z.object({
|
|
@@ -32,6 +34,7 @@ export const ProjectResponse = z.object({
|
|
|
32
34
|
prPollInterval: z.number().int(),
|
|
33
35
|
repoUrl: z.string().url().nullable().optional(),
|
|
34
36
|
kanbanEnabled: z.boolean(),
|
|
37
|
+
worktreePath: z.string().nullable(),
|
|
35
38
|
createdAt: z.number(),
|
|
36
39
|
updatedAt: z.number(),
|
|
37
40
|
});
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* @typedef {'claude-sonnet-4-6' | 'claude-opus-4-6' | 'claude-haiku-4-5-20251001'} ClaudeModel
|
|
10
|
+
* @typedef {'claude-sonnet-4-6' | 'claude-opus-4-6' | 'claude-opus-4-7' | 'claude-haiku-4-5-20251001'} ClaudeModel
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -117,9 +117,10 @@ export const TOOL_TEMPLATE_PAYLOAD_TYPES = ['command', 'prompt'];
|
|
|
117
117
|
export const CLAUDE_MODELS = [
|
|
118
118
|
{ id: 'claude-haiku-4-5-20251001', name: 'Haiku 4.5', description: 'Fast & lightweight' },
|
|
119
119
|
{ id: 'claude-sonnet-4-6', name: 'Sonnet 4.6', description: 'Balanced' },
|
|
120
|
-
{ id: 'claude-opus-4-6', name: 'Opus 4.6', description: '
|
|
120
|
+
{ id: 'claude-opus-4-6', name: 'Opus 4.6', description: 'Previous generation' },
|
|
121
|
+
{ id: 'claude-opus-4-7', name: 'Opus 4.7', description: 'Most capable (default)' },
|
|
121
122
|
];
|
|
122
|
-
export const DEFAULT_MODEL = 'claude-opus-4-
|
|
123
|
+
export const DEFAULT_MODEL = 'claude-opus-4-7';
|
|
123
124
|
|
|
124
125
|
/**
|
|
125
126
|
* @typedef {Object} KanbanBoard
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.page-header[data-v-9a53578e]{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:2rem}.page-header h1[data-v-9a53578e]{margin:0}.page-description[data-v-9a53578e]{margin:.25rem 0 0;font-size:.875rem;color:var(--color-text-soft)}.skeleton-list[data-v-9a53578e]{display:flex;flex-direction:column;gap:1rem}.error-message[data-v-9a53578e]{color:var(--color-error);padding:1rem;background-color:#f851491a;border-radius:var(--border-radius)}.empty-state[data-v-9a53578e]{text-align:center;padding:3rem;color:var(--color-text-soft)}.empty-state p[data-v-9a53578e]{margin-bottom:1rem}.session-list[data-v-9a53578e]{display:flex;flex-direction:column;gap:1rem}.status-filters[data-v-9a53578e]{display:flex;align-items:center;gap:.5rem;margin-bottom:1rem}.filter-label[data-v-9a53578e]{font-size:.875rem;color:var(--color-text-soft)}.filter-btn[data-v-9a53578e]{background:none;border:1px solid var(--color-border);padding:.375rem .75rem;font-size:.8rem;color:var(--color-text-soft);cursor:pointer;border-radius:var(--border-radius);transition:all .15s;text-transform:capitalize}.filter-btn[data-v-9a53578e]:hover{border-color:var(--color-primary);color:var(--color-text)}.star-icon[data-v-9a53578e]{position:relative;display:inline-block}.filter-btn.star-filter-all[data-v-9a53578e]{background:transparent;border-color:var(--color-border);color:var(--color-text-soft)}.filter-btn.star-filter-all[data-v-9a53578e]:hover{border-color:var(--color-primary);color:var(--color-text)}.filter-btn.star-filter-active[data-v-9a53578e],.filter-btn.active[data-v-9a53578e]{background:var(--color-primary);border-color:var(--color-primary);color:#fff}.filter-btn.star-filter-unstarred[data-v-9a53578e]{background:transparent;border-color:#f97316;color:#f97316}.star-crossed[data-v-9a53578e]:after{content:"";position:absolute;top:50%;left:-10%;right:-10%;height:2px;background:currentColor;transform:translateY(-50%) rotate(-45deg);pointer-events:none}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{u as K}from"./sessions-CoRS-wuR.js";import{u as Q}from"./commandButtons-D_-wR8zJ.js";import{z as j,W as c,_ as X,o as Z,A as ee,l as te,a,c as l,b as f,F as A,r as C,E as O,u as S,t as b,d as se,w as re,f as ne,x as D,g as oe,n as ie,y as R,G as ae}from"./index-D1Lg5reX.js";import{a as x}from"./ApiClient-CcqJ-GAv.js";import{S as le}from"./SessionCard-C7vFzR16.js";import"./settings-BN_W4nwV.js";import"./SessionLogStream-NR-AS676.js";function ce(){const{on:E,off:s}=j(),d=h=>p=>{const i=F=>{F.session&&p(F.session,F.projectId)};return E(h,i),()=>s(h,i)},I=d(c.SESSION_CREATED),w=d(c.SESSION_UPDATED);return{onSessionCreated:I,onSessionUpdated:w,onSessionDeleted:h=>{const p=i=>{i.sessionId&&h(i.sessionId,i.projectId)};return E(c.SESSION_DELETED,p),()=>s(c.SESSION_DELETED,p)},onSessionSummaryUpdated:h=>{const p=i=>{i.sessionId&&i.summary&&h(i.sessionId,i.summary,i.projectId)};return E(c.SESSION_SUMMARY_UPDATED,p),()=>s(c.SESSION_SUMMARY_UPDATED,p)}}}const de=["data-state"],ue={class:"filters-container"},fe={class:"status-filters"},Se=["onClick"],pe=["title"],me={key:0,class:"star-icon"},he={key:1,class:"star-icon star-crossed"},_e={key:2,class:"star-icon"},ye={key:0,class:"skeleton-list"},ve={key:1,class:"error-message"},Fe={key:2,class:"empty-state","data-testid":"active-sessions-empty"},Ee={key:3,class:"empty-state","data-testid":"active-sessions-empty"},Ie={key:4,class:"session-list"},we={__name:"ActiveSessionsView",setup(E){const s=K(),d=Q(),I=["waiting","stopped","error"],w=["running","starting"],v=ie(new Set),g=e=>{s.statusFilter===e?s.setStatusFilter(null):s.setStatusFilter(e)},h=()=>{s.starredFilter===null?s.setStarredFilter("starred"):s.starredFilter==="starred"?s.setStarredFilter("unstarred"):s.setStarredFilter(null)},p=D(()=>s.starredFilter==="starred"?"Showing starred sessions only. Click to filter unstarred.":s.starredFilter==="unstarred"?"Showing unstarred sessions only. Click to show all.":"Showing all sessions. Click to filter by starred."),i=D(()=>{let e=s.activeSessions;return s.statusFilter&&(e=e.filter(t=>{const r=t.status;return!!(s.statusFilter==="idle"&&I.includes(r)||s.statusFilter==="running"&&w.includes(r))})),s.starredFilter==="starred"?e=e.filter(t=>t.starred):s.starredFilter==="unstarred"&&(e=e.filter(t=>!t.starred)),e}),F=D(()=>s.loading?"loading":s.error?"error":s.activeSessions.length===0?"empty-all":i.value.length===0?"empty-filtered":"results"),_=R({}),u=R({}),m=R({});let N=null;const y=[],{onSessionCreated:P,onSessionUpdated:B,onSessionDeleted:L,onSessionSummaryUpdated:V}=ce();async function M(){const e=new Set(s.activeSessions.map(t=>t.projectId).filter(t=>t&&!v.value.has(t)));for(const t of e)try{await d.fetchButtons(t),await d.fetchLatestRunsForProject(t),v.value.add(t)}catch(r){console.error(`Failed to fetch buttons for project ${t}:`,r)}}function U(e,t,r){d.runs[e]||(d.runs[e]={runId:e,buttonId:t,sessionId:r,status:"running",output:"",exitCode:null,startedAt:Date.now(),outputTruncated:!1})}function H(e){["running","waiting","starting"].includes(e.status)&&(s.activeSessions.some(r=>r.id===e.id)||(s.activeSessions.unshift(e),e.projectId&&!v.value.has(e.projectId)&&(d.fetchButtons(e.projectId),v.value.add(e.projectId))))}function G(e){const t=["running","waiting","starting"].includes(e.status),r=s.activeSessions.findIndex(n=>n.id===e.id);t?r>=0?s.activeSessions[r]=e:s.activeSessions.unshift(e):r>=0&&(s.activeSessions.splice(r,1),delete _[e.id],delete u[e.id],delete m[e.id])}function W(e){const t=s.activeSessions.findIndex(r=>r.id===e);t>=0&&s.activeSessions.splice(t,1),delete _[e],delete u[e],delete m[e]}function Y(e,t){_[e]=t,u[e]=!1,m[e]=!1}function $(e,t,r){const n=o=>{U(o.runId,o.buttonId,o.sessionId),d.appendOutput(o.runId,o.output)};e(c.COMMAND_RUN_OUTPUT,n),r.push(()=>t(c.COMMAND_RUN_OUTPUT,n));const k=o=>{U(o.runId,o.buttonId,o.sessionId),d.completeRun(o.runId,o.exitCode,o.output)};e(c.COMMAND_RUN_COMPLETE,k),r.push(()=>t(c.COMMAND_RUN_COMPLETE,k));const T=o=>{U(o.runId,o.buttonId,o.sessionId),d.errorRun(o.runId,o.error)};e(c.COMMAND_RUN_ERROR,T),r.push(()=>t(c.COMMAND_RUN_ERROR,T))}Z(async()=>{s.restoreStatusFilter(),s.restoreStarredFilter(),await s.fetchActiveSessions(),await M(),y.push(P(H)),y.push(B(G)),y.push(L(W)),y.push(V(Y));const{on:e,off:t}=j();$(e,t,y),N=setInterval(()=>{s.fetchActiveSessions(!1)},3e4)}),ee(()=>{y.forEach(e=>e()),N&&clearInterval(N)}),te(()=>s.activeSessions,()=>{z(),M()},{immediate:!0});async function z(){const t=s.activeSessions.filter(r=>!_[r.id]&&!u[r.id]).map(r=>r.id);if(t.length!==0){for(const r of t)u[r]=!0,m[r]=!1;try{const r=await x.getSessionSummariesBatch(t);for(const n of t)r[n]&&(_[n]=r[n]),u[n]=!1}catch(r){console.warn("Failed to fetch summaries batch:",r.message);for(const n of t)m[n]=!0,u[n]=!1}}}async function q(e){var t;u[e]=!0,m[e]=!1;try{const r=await x.getSessionSummary(e);r&&(_[e]=r)}catch(r){((t=r.response)==null?void 0:t.status)!==404&&(console.warn(`Failed to fetch summary for session ${e}:`,r.message),m[e]=!0)}finally{u[e]=!1}}async function J(e){m[e]=!1,await q(e)}return(e,t)=>{const r=oe("router-link");return a(),l("div",{class:"container","data-testid":"active-sessions-view","data-state":F.value},[t[3]||(t[3]=f("div",{class:"page-header"},[f("div",null,[f("p",{class:"page-description"}," All active sessions across your projects ")])],-1)),f("div",ue,[f("div",fe,[(a(),l(A,null,C(["running","idle"],n=>f("button",{key:n,class:O(["filter-btn",{active:S(s).statusFilter===n}]),onClick:k=>g(n)},b(n),11,Se)),64)),f("button",{class:O(["filter-btn star-btn",{"star-filter-active":S(s).starredFilter==="starred","star-filter-unstarred":S(s).starredFilter==="unstarred","star-filter-all":S(s).starredFilter===null}]),title:p.value,onClick:h},[S(s).starredFilter==="starred"?(a(),l("span",me,"⭐")):S(s).starredFilter==="unstarred"?(a(),l("span",he,"⭐")):(a(),l("span",_e,"☆"))],10,pe)])]),S(s).loading?(a(),l("div",ye,[(a(),l(A,null,C(3,n=>f("div",{key:n,class:"skeleton card",style:{height:"120px"}})),64))])):S(s).error?(a(),l("div",ve,b(S(s).error),1)):S(s).activeSessions.length===0?(a(),l("div",Fe,[t[1]||(t[1]=f("p",null,"No active sessions. All sessions are completed or there are no sessions yet.",-1)),se(r,{to:"/",class:"btn btn-primary"},{default:re(()=>[...t[0]||(t[0]=[ne(" View Projects ",-1)])]),_:1})])):i.value.length===0?(a(),l("div",Ee,[...t[2]||(t[2]=[f("p",null,"No sessions match the current filter.",-1)])])):(a(),l("div",Ie,[(a(!0),l(A,null,C(i.value,n=>(a(),ae(le,{key:n.id,session:n,"show-project":!0,"show-summary":!0,summary:_[n.id],"summary-loading":u[n.id],"summary-error":m[n.id],onRetrySummary:J},null,8,["session","summary","summary-loading","summary-error"]))),128))]))],8,de)}}},ge=X(we,[["__scopeId","data-v-9a53578e"]]);export{ge as default};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{L as q,_ as O,a as r,c as i,b as t,F as k,r as $,t as f,
|
|
1
|
+
import{L as q,_ as O,a as r,c as i,b as t,F as k,r as $,t as f,i as _,E as A,x as p,g as W,f as F,G as z,w as G,o as H,A as J,d as w,u as m,n as B}from"./index-D1Lg5reX.js";import{a as L}from"./ApiClient-CcqJ-GAv.js";const U=q("agentLogs",{state:()=>({logs:[],pagination:{total:0,limit:25,offset:0,hasMore:!1},filters:{agentType:null,callType:null,status:null,model:null,startDate:null,endDate:null,sessionId:null},filterOptions:{agentTypes:[],callTypes:[],statuses:[],models:[]},perPage:25,currentPage:1,sortBy:"started_at",sortOrder:"DESC",loading:!1,error:null}),getters:{totalPages:s=>Math.ceil(s.pagination.total/s.perPage)||0,activeFilters:s=>{const e={};for(const[y,h]of Object.entries(s.filters))h!=null&&(e[y]=h);return e}},actions:{async fetchLogs(){this.loading=!0,this.error=null;try{const s=(this.currentPage-1)*this.perPage,e=await L.getAgentCallLogs({limit:this.perPage,offset:s,...this.activeFilters,sortBy:this.sortBy,sortOrder:this.sortOrder});this.logs=e.logs,this.pagination=e.pagination}catch(s){this.error=s.message}finally{this.loading=!1}},async fetchFilterOptions(){try{const s=await L.getAgentCallFilterOptions();this.filterOptions=s}catch(s){console.error("Failed to fetch filter options:",s)}},setFilter(s,e){this.filters[s]=e||null,this.currentPage=1,this.fetchLogs()},clearFilters(){Object.keys(this.filters).forEach(s=>this.filters[s]=null),this.currentPage=1,this.fetchLogs()},setPage(s){this.currentPage=s,this.fetchLogs()},setPerPage(s){this.perPage=s,this.currentPage=1,this.fetchLogs()},setSort(s,e){this.sortBy=s,this.sortOrder=e,this.currentPage=1,this.fetchLogs()},async clearAllLogs(){this.loading=!0,this.error=null;try{await L.deleteAllAgentCallLogs(),this.logs=[],this.pagination={total:0,limit:this.perPage,offset:0,hasMore:!1},this.currentPage=1,await this.fetchFilterOptions()}catch(s){this.error=s.message}finally{this.loading=!1}}}}),Z={class:"filter-bar"},Q={class:"filter-row"},X={class:"filter-group"},Y=["value"],K={class:"filter-group"},tt=["value"],et={class:"filter-group"},st=["value"],lt=["value"],at={class:"filter-group"},nt=["value"],ot=["value"],rt={class:"filter-group"},it=["value"],ut=["value"],dt={class:"filter-group"},ct=["value"],ft=["value"],gt={__name:"AgentLogFilters",props:{filters:{type:Object,required:!0},filterOptions:{type:Object,required:!0},hasActiveFilters:{type:Boolean,default:!1},confirming:{type:Boolean,default:!1}},emits:["filter-change","date-change","clear-filters","clear-all"],setup(s){const e=s;function y(v){return new Date(v).toISOString().slice(0,10)}const h=p(()=>e.filters.startDate?y(e.filters.startDate):""),b=p(()=>e.filters.endDate?y(e.filters.endDate):"");return(v,a)=>(r(),i("div",Z,[t("div",Q,[t("div",X,[a[8]||(a[8]=t("label",{class:"filter-label"},"From",-1)),t("input",{type:"date",class:"filter-input",value:h.value,onChange:a[0]||(a[0]=n=>v.$emit("date-change","startDate",n))},null,40,Y)]),t("div",K,[a[9]||(a[9]=t("label",{class:"filter-label"},"To",-1)),t("input",{type:"date",class:"filter-input",value:b.value,onChange:a[1]||(a[1]=n=>v.$emit("date-change","endDate",n))},null,40,tt)]),t("div",et,[a[11]||(a[11]=t("label",{class:"filter-label"},"Agent Type",-1)),t("select",{class:"filter-select",value:s.filters.agentType||"",onChange:a[2]||(a[2]=n=>v.$emit("filter-change","agentType",n.target.value))},[a[10]||(a[10]=t("option",{value:""}," All ",-1)),(r(!0),i(k,null,$(s.filterOptions.agentTypes,n=>(r(),i("option",{key:n,value:n},f(n),9,lt))),128))],40,st)]),t("div",at,[a[13]||(a[13]=t("label",{class:"filter-label"},"Call Type",-1)),t("select",{class:"filter-select",value:s.filters.callType||"",onChange:a[3]||(a[3]=n=>v.$emit("filter-change","callType",n.target.value))},[a[12]||(a[12]=t("option",{value:""}," All ",-1)),(r(!0),i(k,null,$(s.filterOptions.callTypes,n=>(r(),i("option",{key:n,value:n},f(n),9,ot))),128))],40,nt)]),t("div",rt,[a[15]||(a[15]=t("label",{class:"filter-label"},"Status",-1)),t("select",{class:"filter-select",value:s.filters.status||"",onChange:a[4]||(a[4]=n=>v.$emit("filter-change","status",n.target.value))},[a[14]||(a[14]=t("option",{value:""}," All ",-1)),(r(!0),i(k,null,$(s.filterOptions.statuses,n=>(r(),i("option",{key:n,value:n},f(n),9,ut))),128))],40,it)]),t("div",dt,[a[17]||(a[17]=t("label",{class:"filter-label"},"Model",-1)),t("select",{class:"filter-select",value:s.filters.model||"",onChange:a[5]||(a[5]=n=>v.$emit("filter-change","model",n.target.value))},[a[16]||(a[16]=t("option",{value:""}," All ",-1)),(r(!0),i(k,null,$(s.filterOptions.models,n=>(r(),i("option",{key:n,value:n},f(n),9,ft))),128))],40,ct)]),s.hasActiveFilters?(r(),i("button",{key:0,class:"btn-clear",onClick:a[6]||(a[6]=n=>v.$emit("clear-filters"))}," Clear Filters ")):_("",!0),t("button",{class:A(["btn-clear-all",{confirming:s.confirming}]),onClick:a[7]||(a[7]=n=>v.$emit("clear-all"))},f(s.confirming?"Confirm Clear All?":"Clear All Logs"),3)])]))}},vt=O(gt,[["__scopeId","data-v-780329d3"]]),pt={class:"table-wrapper"},ht={key:0,class:"loading-overlay"},yt={class:"log-table"},bt=["onClick"],mt={key:0,class:"sort-indicator"},kt={key:0},$t=["colspan"],Ct={class:"td"},_t={class:"status-text"},Tt={class:"td font-mono text-sm"},At={class:"td font-mono text-sm"},Pt={class:"td model-cell"},Lt={class:"td"},Ot={key:1},St={class:"td"},Dt={key:1},Ft=["title"],wt={class:"td text-right"},Bt=["title"],Mt={__name:"AgentLogTable",props:{logs:{type:Array,required:!0},loading:{type:Boolean,default:!1},sortBy:{type:String,default:"started_at"},sortOrder:{type:String,default:"DESC"}},emits:["sort"],setup(s){const e=[{key:"status",label:"Status",sortable:!0},{key:"agent_type",label:"Agent Type",sortable:!0},{key:"call_type",label:"Call Type",sortable:!0},{key:"model",label:"Model",sortable:!0},{key:"session",label:"Session",sortable:!1},{key:"effort",label:"Effort",sortable:!1},{key:"total_tokens",label:"Tokens",sortable:!0},{key:"duration_ms",label:"Duration",sortable:!0},{key:"started_at",label:"Started",sortable:!0}];function y(l){switch(l){case"completed":return"dot-completed";case"error":return"dot-error";case"streaming":case"pending":return"dot-pending";default:return"dot-default"}}function h(l){const d=[];return l.inputTokens&&d.push(`Input: ${b(l.inputTokens)}`),l.outputTokens&&d.push(`Output: ${b(l.outputTokens)}`),l.thinkingTokens&&d.push(`Thinking: ${b(l.thinkingTokens)}`),l.cacheReadTokens&&d.push(`Cache Read: ${b(l.cacheReadTokens)}`),l.cacheWriteTokens&&d.push(`Cache Write: ${b(l.cacheWriteTokens)}`),d.join(`
|
|
2
2
|
`)||"No token data"}function b(l){return l==null||l===0?"0":l.toLocaleString()}function v(l){if(l==null)return"—";if(l<1e3)return`${l}ms`;if(l<6e4)return`${(l/1e3).toFixed(1)}s`;const d=Math.floor(l/6e4),C=Math.floor(l%6e4/1e3);return`${d}m ${C}s`}function a(l){const d=Date.now()-l;return d<6e4?"just now":d<36e5?`${Math.floor(d/6e4)}m ago`:d<864e5?`${Math.floor(d/36e5)}h ago`:`${Math.floor(d/864e5)}d ago`}function n(l){try{if(l.metadata)return(typeof l.metadata=="string"?JSON.parse(l.metadata):l.metadata).effortLevel||null}catch{}return null}function P(l){return{auto:"Auto",low:"Low",medium:"Med",high:"High",max:"Max"}[l]||l}return(l,d)=>{const C=W("router-link");return r(),i("div",pt,[s.loading?(r(),i("div",ht,[...d[0]||(d[0]=[t("div",{class:"spinner"},null,-1)])])):_("",!0),t("table",yt,[t("thead",null,[t("tr",null,[(r(),i(k,null,$(e,o=>t("th",{key:o.key,class:A(["th",o.sortable?"sortable":""]),onClick:S=>o.sortable?l.$emit("sort",o.key):null},[F(f(o.label)+" ",1),o.sortable&&s.sortBy===o.key?(r(),i("span",mt,f(s.sortOrder==="ASC"?"↑":"↓"),1)):_("",!0)],10,bt)),64))])]),t("tbody",null,[!s.loading&&s.logs.length===0?(r(),i("tr",kt,[t("td",{colspan:e.length,class:"empty-cell"}," No agent call logs found. ",8,$t)])):_("",!0),(r(!0),i(k,null,$(s.logs,o=>(r(),i("tr",{key:o.id,class:"log-row"},[t("td",Ct,[t("span",{class:A(["status-dot",y(o.status)])},null,2),t("span",_t,f(o.status),1)]),t("td",Tt,f(o.agentType||"—"),1),t("td",At,f(o.callType||"—"),1),t("td",Pt,f(o.model||"—"),1),t("td",Lt,[o.sessionId?(r(),z(C,{key:0,to:`/sessions/${o.sessionId}`,class:"session-link"},{default:G(()=>[F(f(o.sessionName||o.sessionId),1)]),_:2},1032,["to"])):(r(),i("span",Ot,"—"))]),t("td",St,[n(o)?(r(),i("span",{key:0,class:A(["effort-badge",`effort-${n(o)}`])},f(P(n(o))),3)):(r(),i("span",Dt,"—"))]),t("td",{class:"td text-right",title:h(o)},f(b(o.totalTokens)),9,Ft),t("td",wt,f(v(o.durationMs)),1),t("td",{class:"td text-right",title:o.startedAt?new Date(o.startedAt).toLocaleString():""},f(o.startedAt?a(o.startedAt):"—"),9,Bt)]))),128))])])])}}},It=O(Mt,[["__scopeId","data-v-20d5856a"]]),Et={class:"agent-logs"},Nt={key:0,class:"error-banner"},Vt={key:1,class:"pagination-bar"},xt={class:"per-page-control"},jt=["value"],Rt=["value"],qt={class:"page-info"},Wt={class:"page-buttons"},zt=["disabled"],Gt=["disabled"],Ht={key:0,class:"page-ellipsis"},Jt=["onClick"],Ut=["disabled"],Zt=["disabled"],Qt={__name:"AgentLogsView",setup(s){const e=U(),y=B(!1),h=B(null),b=p(()=>e.logs),v=p(()=>e.pagination),a=p(()=>e.filters),n=p(()=>e.filterOptions),P=p(()=>e.perPage),l=p(()=>e.currentPage),d=p(()=>e.totalPages),C=p(()=>e.sortBy),o=p(()=>e.sortOrder),S=p(()=>e.loading),D=p(()=>e.error),M=p(()=>Object.values(e.filters).some(g=>g!=null)),I=p(()=>v.value.total===0?0:v.value.offset+1),E=p(()=>Math.min(v.value.offset+P.value,v.value.total)),N=p(()=>{const g=d.value,c=l.value;if(g<=7)return Array.from({length:g},(T,R)=>R+1);const u=[];return c<=4?u.push(1,2,3,4,5,"...",g):c>=g-3?u.push(1,"...",g-4,g-3,g-2,g-1,g):u.push(1,"...",c-1,c,c+1,"...",g),u});function V(g){C.value===g?e.setSort(g,o.value==="ASC"?"DESC":"ASC"):e.setSort(g,"DESC")}function x(g,c){const u=c.target.value;if(!u){e.setFilter(g,null);return}const T=new Date(`${u}T00:00:00Z`).getTime();e.filters[g]=T,e.currentPage=1,e.fetchLogs()}function j(){y.value?(h.value&&(clearTimeout(h.value),h.value=null),y.value=!1,e.clearAllLogs()):(y.value=!0,h.value=setTimeout(()=>{y.value=!1,h.value=null},3e3))}return H(()=>{e.fetchLogs(),e.fetchFilterOptions()}),J(()=>{h.value&&clearTimeout(h.value)}),(g,c)=>(r(),i("div",Et,[w(vt,{filters:a.value,"filter-options":n.value,"has-active-filters":M.value,confirming:y.value,onFilterChange:c[0]||(c[0]=(u,T)=>m(e).setFilter(u,T)),onDateChange:x,onClearFilters:c[1]||(c[1]=u=>m(e).clearFilters()),onClearAll:j},null,8,["filters","filter-options","has-active-filters","confirming"]),D.value?(r(),i("div",Nt,[t("span",null,f(D.value),1),t("button",{class:"btn-retry",onClick:c[2]||(c[2]=u=>m(e).fetchLogs())}," Retry ")])):_("",!0),w(It,{logs:b.value,loading:S.value,"sort-by":C.value,"sort-order":o.value,onSort:V},null,8,["logs","loading","sort-by","sort-order"]),v.value.total>0?(r(),i("div",Vt,[t("div",xt,[c[8]||(c[8]=t("label",{class:"filter-label"},"Per page",-1)),t("select",{class:"filter-select per-page-select",value:P.value,onChange:c[3]||(c[3]=u=>m(e).setPerPage(parseInt(u.target.value)))},[(r(),i(k,null,$([10,25,50,100],u=>t("option",{key:u,value:u},f(u),9,Rt)),64))],40,jt)]),t("div",qt," Showing "+f(I.value)+"–"+f(E.value)+" of "+f(v.value.total)+" logs ",1),t("div",Wt,[t("button",{class:"page-btn",disabled:l.value===1,onClick:c[4]||(c[4]=u=>m(e).setPage(1))}," « ",8,zt),t("button",{class:"page-btn",disabled:l.value===1,onClick:c[5]||(c[5]=u=>m(e).setPage(l.value-1))}," ‹ ",8,Gt),(r(!0),i(k,null,$(N.value,u=>(r(),i(k,{key:u},[u==="..."?(r(),i("span",Ht,"…")):(r(),i("button",{key:1,class:A(["page-btn",{"page-btn-active":u===l.value}]),onClick:T=>m(e).setPage(u)},f(u),11,Jt))],64))),128)),t("button",{class:"page-btn",disabled:l.value===d.value,onClick:c[6]||(c[6]=u=>m(e).setPage(l.value+1))}," › ",8,Ut),t("button",{class:"page-btn",disabled:l.value===d.value,onClick:c[7]||(c[7]=u=>m(e).setPage(d.value))}," » ",8,Zt)])])):_("",!0)]))}},Kt=O(Qt,[["__scopeId","data-v-e1df2011"]]);export{Kt as default};
|