@saga-ai/dashboard 4.0.0 → 4.2.1
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/dist/cli.cjs +172 -141
- package/package.json +2 -2
package/dist/cli.cjs
CHANGED
|
@@ -28,8 +28,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
28
|
));
|
|
29
29
|
|
|
30
30
|
// src/cli.ts
|
|
31
|
-
var
|
|
32
|
-
var
|
|
31
|
+
var import_node_fs6 = require("node:fs");
|
|
32
|
+
var import_node_path7 = require("node:path");
|
|
33
33
|
var import_node_process6 = __toESM(require("node:process"), 1);
|
|
34
34
|
var import_commander = require("commander");
|
|
35
35
|
|
|
@@ -38,16 +38,50 @@ var import_node_process4 = __toESM(require("node:process"), 1);
|
|
|
38
38
|
|
|
39
39
|
// src/server/index.ts
|
|
40
40
|
var import_node_http = require("node:http");
|
|
41
|
-
var
|
|
41
|
+
var import_node_path5 = require("node:path");
|
|
42
42
|
var import_express3 = __toESM(require("express"), 1);
|
|
43
43
|
|
|
44
44
|
// src/server/routes.ts
|
|
45
45
|
var import_express2 = require("express");
|
|
46
46
|
|
|
47
47
|
// src/server/parser.ts
|
|
48
|
-
var
|
|
48
|
+
var import_node_fs2 = require("node:fs");
|
|
49
49
|
var import_promises = require("node:fs/promises");
|
|
50
50
|
|
|
51
|
+
// ../saga-utils/src/directory.ts
|
|
52
|
+
function normalizeRoot(projectRoot) {
|
|
53
|
+
return projectRoot.endsWith("/") ? projectRoot.slice(0, -1) : projectRoot;
|
|
54
|
+
}
|
|
55
|
+
function createSagaPaths(projectRoot) {
|
|
56
|
+
const root = normalizeRoot(projectRoot);
|
|
57
|
+
const saga = `${root}/.saga`;
|
|
58
|
+
return {
|
|
59
|
+
root,
|
|
60
|
+
saga,
|
|
61
|
+
epics: `${saga}/epics`,
|
|
62
|
+
stories: `${saga}/stories`,
|
|
63
|
+
worktrees: `${saga}/worktrees`,
|
|
64
|
+
archive: `${saga}/archive`
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function createStoryPaths(projectRoot, storyId) {
|
|
68
|
+
const { stories } = createSagaPaths(projectRoot);
|
|
69
|
+
const storyDir = `${stories}/${storyId}`;
|
|
70
|
+
return {
|
|
71
|
+
storyId,
|
|
72
|
+
storyDir,
|
|
73
|
+
storyJson: `${storyDir}/story.json`,
|
|
74
|
+
journalMd: `${storyDir}/journal.md`
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function createWorktreePaths(projectRoot, storyId) {
|
|
78
|
+
const { worktrees } = createSagaPaths(projectRoot);
|
|
79
|
+
return {
|
|
80
|
+
storyId,
|
|
81
|
+
worktreeDir: `${worktrees}/${storyId}`
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
51
85
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
|
|
52
86
|
var external_exports = {};
|
|
53
87
|
__export(external_exports, {
|
|
@@ -4089,7 +4123,7 @@ var coerce = {
|
|
|
4089
4123
|
};
|
|
4090
4124
|
var NEVER = INVALID;
|
|
4091
4125
|
|
|
4092
|
-
// ../saga-
|
|
4126
|
+
// ../saga-utils/src/schemas/task.ts
|
|
4093
4127
|
var TaskStatusSchema = external_exports.enum(["pending", "in_progress", "completed"]);
|
|
4094
4128
|
var TaskSchema = external_exports.object({
|
|
4095
4129
|
id: external_exports.string(),
|
|
@@ -4103,7 +4137,7 @@ var TaskSchema = external_exports.object({
|
|
|
4103
4137
|
});
|
|
4104
4138
|
var StoryIdSchema = external_exports.string().regex(/^[a-z0-9-]+$/);
|
|
4105
4139
|
|
|
4106
|
-
// ../saga-
|
|
4140
|
+
// ../saga-utils/src/schemas/claude-code-task.ts
|
|
4107
4141
|
var ClaudeCodeTaskSchema = external_exports.object({
|
|
4108
4142
|
id: external_exports.string(),
|
|
4109
4143
|
subject: external_exports.string(),
|
|
@@ -4116,34 +4150,7 @@ var ClaudeCodeTaskSchema = external_exports.object({
|
|
|
4116
4150
|
metadata: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
|
|
4117
4151
|
});
|
|
4118
4152
|
|
|
4119
|
-
// ../saga-
|
|
4120
|
-
function normalizeRoot(projectRoot) {
|
|
4121
|
-
return projectRoot.endsWith("/") ? projectRoot.slice(0, -1) : projectRoot;
|
|
4122
|
-
}
|
|
4123
|
-
function createSagaPaths(projectRoot) {
|
|
4124
|
-
const root = normalizeRoot(projectRoot);
|
|
4125
|
-
const saga = `${root}/.saga`;
|
|
4126
|
-
return {
|
|
4127
|
-
root,
|
|
4128
|
-
saga,
|
|
4129
|
-
epics: `${saga}/epics`,
|
|
4130
|
-
stories: `${saga}/stories`,
|
|
4131
|
-
worktrees: `${saga}/worktrees`,
|
|
4132
|
-
archive: `${saga}/archive`
|
|
4133
|
-
};
|
|
4134
|
-
}
|
|
4135
|
-
function createStoryPaths(projectRoot, storyId) {
|
|
4136
|
-
const { stories } = createSagaPaths(projectRoot);
|
|
4137
|
-
const storyDir = `${stories}/${storyId}`;
|
|
4138
|
-
return {
|
|
4139
|
-
storyId,
|
|
4140
|
-
storyDir,
|
|
4141
|
-
storyJson: `${storyDir}/story.json`,
|
|
4142
|
-
journalMd: `${storyDir}/journal.md`
|
|
4143
|
-
};
|
|
4144
|
-
}
|
|
4145
|
-
|
|
4146
|
-
// ../saga-types/src/epic.ts
|
|
4153
|
+
// ../saga-utils/src/schemas/epic.ts
|
|
4147
4154
|
var EpicChildSchema = external_exports.object({
|
|
4148
4155
|
id: external_exports.string(),
|
|
4149
4156
|
blockedBy: external_exports.array(external_exports.string())
|
|
@@ -4155,7 +4162,7 @@ var EpicSchema = external_exports.object({
|
|
|
4155
4162
|
children: external_exports.array(EpicChildSchema)
|
|
4156
4163
|
}).strict();
|
|
4157
4164
|
|
|
4158
|
-
// ../saga-
|
|
4165
|
+
// ../saga-utils/src/schemas/session.ts
|
|
4159
4166
|
var SessionStatusSchema = external_exports.enum(["running", "completed"]);
|
|
4160
4167
|
var SessionSchema = external_exports.object({
|
|
4161
4168
|
/** Unique session name (saga__<epic>__<story>__<pid>) */
|
|
@@ -4178,11 +4185,7 @@ var SessionSchema = external_exports.object({
|
|
|
4178
4185
|
outputPreview: external_exports.string().optional()
|
|
4179
4186
|
});
|
|
4180
4187
|
|
|
4181
|
-
// ../saga-
|
|
4182
|
-
var import_node_fs = require("node:fs");
|
|
4183
|
-
var import_node_path = require("node:path");
|
|
4184
|
-
|
|
4185
|
-
// ../saga-types/src/story.ts
|
|
4188
|
+
// ../saga-utils/src/schemas/story.ts
|
|
4186
4189
|
var StorySchema = external_exports.object({
|
|
4187
4190
|
id: external_exports.string(),
|
|
4188
4191
|
title: external_exports.string(),
|
|
@@ -4196,7 +4199,82 @@ var StorySchema = external_exports.object({
|
|
|
4196
4199
|
worktree: external_exports.string().optional()
|
|
4197
4200
|
}).strict();
|
|
4198
4201
|
|
|
4199
|
-
// ../saga-
|
|
4202
|
+
// ../saga-utils/src/storage.ts
|
|
4203
|
+
var import_node_fs = require("node:fs");
|
|
4204
|
+
var import_node_path = require("node:path");
|
|
4205
|
+
function buildScannedStory(storyDir, storyJsonPath, worktreePath) {
|
|
4206
|
+
if (!(0, import_node_fs.existsSync)(storyJsonPath)) {
|
|
4207
|
+
return null;
|
|
4208
|
+
}
|
|
4209
|
+
let storyData;
|
|
4210
|
+
try {
|
|
4211
|
+
const raw = (0, import_node_fs.readFileSync)(storyJsonPath, "utf-8");
|
|
4212
|
+
storyData = StorySchema.parse(JSON.parse(raw));
|
|
4213
|
+
} catch {
|
|
4214
|
+
return null;
|
|
4215
|
+
}
|
|
4216
|
+
let tasks = [];
|
|
4217
|
+
try {
|
|
4218
|
+
const files = (0, import_node_fs.readdirSync)(storyDir);
|
|
4219
|
+
tasks = files.filter((f) => f.endsWith(".json") && f !== "story.json").map((f) => {
|
|
4220
|
+
const raw = (0, import_node_fs.readFileSync)((0, import_node_path.join)(storyDir, f), "utf-8");
|
|
4221
|
+
return TaskSchema.parse(JSON.parse(raw));
|
|
4222
|
+
});
|
|
4223
|
+
} catch {
|
|
4224
|
+
}
|
|
4225
|
+
const status = deriveStoryStatus(tasks);
|
|
4226
|
+
const journalMdPath = (0, import_node_path.join)(storyDir, "journal.md");
|
|
4227
|
+
return {
|
|
4228
|
+
...storyData,
|
|
4229
|
+
status,
|
|
4230
|
+
storyPath: storyJsonPath,
|
|
4231
|
+
worktreePath,
|
|
4232
|
+
journalPath: (0, import_node_fs.existsSync)(journalMdPath) ? journalMdPath : void 0,
|
|
4233
|
+
tasks
|
|
4234
|
+
};
|
|
4235
|
+
}
|
|
4236
|
+
function scanMasterStories(projectRoot, storyMap) {
|
|
4237
|
+
const { stories } = createSagaPaths(projectRoot);
|
|
4238
|
+
if (!(0, import_node_fs.existsSync)(stories)) {
|
|
4239
|
+
return;
|
|
4240
|
+
}
|
|
4241
|
+
const entries = (0, import_node_fs.readdirSync)(stories, { withFileTypes: true });
|
|
4242
|
+
for (const entry of entries) {
|
|
4243
|
+
if (!entry.isDirectory()) {
|
|
4244
|
+
continue;
|
|
4245
|
+
}
|
|
4246
|
+
const storyPaths = createStoryPaths(projectRoot, entry.name);
|
|
4247
|
+
const scanned = buildScannedStory(storyPaths.storyDir, storyPaths.storyJson);
|
|
4248
|
+
if (!scanned) {
|
|
4249
|
+
continue;
|
|
4250
|
+
}
|
|
4251
|
+
const wtPaths = createWorktreePaths(projectRoot, entry.name);
|
|
4252
|
+
if ((0, import_node_fs.existsSync)(wtPaths.worktreeDir)) {
|
|
4253
|
+
scanned.worktreePath = wtPaths.worktreeDir;
|
|
4254
|
+
}
|
|
4255
|
+
storyMap.set(scanned.id, scanned);
|
|
4256
|
+
}
|
|
4257
|
+
}
|
|
4258
|
+
function scanWorktreeStories(projectRoot, storyMap) {
|
|
4259
|
+
const { worktrees } = createSagaPaths(projectRoot);
|
|
4260
|
+
if (!(0, import_node_fs.existsSync)(worktrees)) {
|
|
4261
|
+
return;
|
|
4262
|
+
}
|
|
4263
|
+
const entries = (0, import_node_fs.readdirSync)(worktrees, { withFileTypes: true });
|
|
4264
|
+
for (const wtEntry of entries) {
|
|
4265
|
+
if (!wtEntry.isDirectory()) {
|
|
4266
|
+
continue;
|
|
4267
|
+
}
|
|
4268
|
+
const storyId = wtEntry.name;
|
|
4269
|
+
const wtStoryDir = (0, import_node_path.join)(worktrees, storyId, ".saga", "stories", storyId);
|
|
4270
|
+
const wtStoryJson = (0, import_node_path.join)(wtStoryDir, "story.json");
|
|
4271
|
+
const wtPath = (0, import_node_path.join)(worktrees, storyId);
|
|
4272
|
+
const scanned = buildScannedStory(wtStoryDir, wtStoryJson, wtPath);
|
|
4273
|
+
if (scanned) {
|
|
4274
|
+
storyMap.set(scanned.id, scanned);
|
|
4275
|
+
}
|
|
4276
|
+
}
|
|
4277
|
+
}
|
|
4200
4278
|
function readStory(projectRoot, storyId) {
|
|
4201
4279
|
const { storyJson } = createStoryPaths(projectRoot, storyId);
|
|
4202
4280
|
if (!(0, import_node_fs.existsSync)(storyJson)) {
|
|
@@ -4247,27 +4325,6 @@ function listEpics(projectRoot) {
|
|
|
4247
4325
|
return EpicSchema.parse(parsed);
|
|
4248
4326
|
});
|
|
4249
4327
|
}
|
|
4250
|
-
function listStories(projectRoot) {
|
|
4251
|
-
const { stories } = createSagaPaths(projectRoot);
|
|
4252
|
-
if (!(0, import_node_fs.existsSync)(stories)) {
|
|
4253
|
-
throw new Error(`Stories directory not found: ${stories}`);
|
|
4254
|
-
}
|
|
4255
|
-
const entries = (0, import_node_fs.readdirSync)(stories, { withFileTypes: true });
|
|
4256
|
-
return entries.filter((entry) => entry.isDirectory()).map((entry) => {
|
|
4257
|
-
const storyJsonPath = (0, import_node_path.join)(stories, entry.name, "story.json");
|
|
4258
|
-
if (!(0, import_node_fs.existsSync)(storyJsonPath)) {
|
|
4259
|
-
return null;
|
|
4260
|
-
}
|
|
4261
|
-
const raw = (0, import_node_fs.readFileSync)(storyJsonPath, "utf-8");
|
|
4262
|
-
let parsed;
|
|
4263
|
-
try {
|
|
4264
|
-
parsed = JSON.parse(raw);
|
|
4265
|
-
} catch {
|
|
4266
|
-
throw new Error(`Malformed JSON in story file: ${storyJsonPath}`);
|
|
4267
|
-
}
|
|
4268
|
-
return StorySchema.parse(parsed);
|
|
4269
|
-
}).filter((story) => story !== null);
|
|
4270
|
-
}
|
|
4271
4328
|
function deriveStoryStatus(tasks) {
|
|
4272
4329
|
if (tasks.length === 0) {
|
|
4273
4330
|
return "pending";
|
|
@@ -4292,46 +4349,11 @@ function deriveEpicStatus(storyStatuses) {
|
|
|
4292
4349
|
}
|
|
4293
4350
|
return "pending";
|
|
4294
4351
|
}
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
const storiesDir = (0, import_node_path2.join)(sagaRoot, ".saga", "stories");
|
|
4301
|
-
if (!(0, import_node_fs2.existsSync)(storiesDir)) {
|
|
4302
|
-
return [];
|
|
4303
|
-
}
|
|
4304
|
-
const stories = listStories(sagaRoot);
|
|
4305
|
-
return stories.map((story) => {
|
|
4306
|
-
let tasks = [];
|
|
4307
|
-
try {
|
|
4308
|
-
tasks = listTasks(sagaRoot, story.id);
|
|
4309
|
-
} catch {
|
|
4310
|
-
}
|
|
4311
|
-
const { journalMd } = createStoryPaths(sagaRoot, story.id);
|
|
4312
|
-
const hasJournal = (0, import_node_fs2.existsSync)(journalMd);
|
|
4313
|
-
return {
|
|
4314
|
-
id: story.id,
|
|
4315
|
-
title: story.title,
|
|
4316
|
-
description: story.description,
|
|
4317
|
-
epicId: story.epic,
|
|
4318
|
-
guidance: story.guidance,
|
|
4319
|
-
doneWhen: story.doneWhen,
|
|
4320
|
-
avoid: story.avoid,
|
|
4321
|
-
branch: story.branch,
|
|
4322
|
-
pr: story.pr,
|
|
4323
|
-
worktree: story.worktree,
|
|
4324
|
-
journalPath: hasJournal ? journalMd : void 0,
|
|
4325
|
-
tasks
|
|
4326
|
-
};
|
|
4327
|
-
});
|
|
4328
|
-
}
|
|
4329
|
-
function scanEpics(sagaRoot) {
|
|
4330
|
-
const epicsDir = (0, import_node_path2.join)(sagaRoot, ".saga", "epics");
|
|
4331
|
-
if (!(0, import_node_fs2.existsSync)(epicsDir)) {
|
|
4332
|
-
return [];
|
|
4333
|
-
}
|
|
4334
|
-
return listEpics(sagaRoot);
|
|
4352
|
+
function scanStories(projectRoot) {
|
|
4353
|
+
const storyMap = /* @__PURE__ */ new Map();
|
|
4354
|
+
scanMasterStories(projectRoot, storyMap);
|
|
4355
|
+
scanWorktreeStories(projectRoot, storyMap);
|
|
4356
|
+
return Array.from(storyMap.values());
|
|
4335
4357
|
}
|
|
4336
4358
|
|
|
4337
4359
|
// src/server/parser.ts
|
|
@@ -4355,7 +4377,7 @@ function toStoryDetail(story) {
|
|
|
4355
4377
|
id: story.id,
|
|
4356
4378
|
title: story.title,
|
|
4357
4379
|
description: story.description,
|
|
4358
|
-
epic: story.
|
|
4380
|
+
epic: story.epic,
|
|
4359
4381
|
status: toApiStatus(derivedStatus),
|
|
4360
4382
|
tasks,
|
|
4361
4383
|
guidance: story.guidance,
|
|
@@ -4393,14 +4415,19 @@ function buildEpic(scannedEpic, epicStories) {
|
|
|
4393
4415
|
}
|
|
4394
4416
|
function parseStory(sagaRoot, storyId) {
|
|
4395
4417
|
try {
|
|
4396
|
-
const
|
|
4418
|
+
const wtPaths = createWorktreePaths(sagaRoot, storyId);
|
|
4419
|
+
const wtStoryDir = `${wtPaths.worktreeDir}/.saga/stories/${storyId}`;
|
|
4420
|
+
const wtStoryJson = `${wtStoryDir}/story.json`;
|
|
4421
|
+
const useWorktree = (0, import_node_fs2.existsSync)(wtStoryJson);
|
|
4422
|
+
const storyRoot = useWorktree ? wtPaths.worktreeDir : sagaRoot;
|
|
4423
|
+
const story = readStory(storyRoot, storyId);
|
|
4397
4424
|
let tasks = [];
|
|
4398
4425
|
try {
|
|
4399
|
-
tasks = listTasks(
|
|
4426
|
+
tasks = listTasks(storyRoot, storyId);
|
|
4400
4427
|
} catch {
|
|
4401
4428
|
}
|
|
4402
|
-
const { journalMd } = createStoryPaths(
|
|
4403
|
-
const hasJournal = (0,
|
|
4429
|
+
const { journalMd } = createStoryPaths(storyRoot, storyId);
|
|
4430
|
+
const hasJournal = (0, import_node_fs2.existsSync)(journalMd);
|
|
4404
4431
|
const derivedStatus = deriveStoryStatus(tasks);
|
|
4405
4432
|
return {
|
|
4406
4433
|
id: story.id,
|
|
@@ -4474,7 +4501,11 @@ async function parseJournal(journalPath) {
|
|
|
4474
4501
|
}
|
|
4475
4502
|
function scanSagaDirectory(sagaRoot) {
|
|
4476
4503
|
const scannedStories = scanStories(sagaRoot);
|
|
4477
|
-
|
|
4504
|
+
let scannedEpics = [];
|
|
4505
|
+
try {
|
|
4506
|
+
scannedEpics = listEpics(sagaRoot);
|
|
4507
|
+
} catch {
|
|
4508
|
+
}
|
|
4478
4509
|
const allStories = scannedStories.map(toStoryDetail);
|
|
4479
4510
|
const storiesByEpic = /* @__PURE__ */ new Map();
|
|
4480
4511
|
const standaloneStories = [];
|
|
@@ -4499,9 +4530,9 @@ var import_express = require("express");
|
|
|
4499
4530
|
|
|
4500
4531
|
// src/lib/sessions.ts
|
|
4501
4532
|
var import_node_child_process = require("node:child_process");
|
|
4502
|
-
var
|
|
4533
|
+
var import_node_fs3 = require("node:fs");
|
|
4503
4534
|
var import_promises2 = require("node:fs/promises");
|
|
4504
|
-
var
|
|
4535
|
+
var import_node_path2 = require("node:path");
|
|
4505
4536
|
var import_node_process = __toESM(require("node:process"), 1);
|
|
4506
4537
|
var PREVIEW_LINES_COUNT = 5;
|
|
4507
4538
|
var PREVIEW_MAX_LENGTH = 500;
|
|
@@ -4571,7 +4602,7 @@ function listSessions() {
|
|
|
4571
4602
|
name,
|
|
4572
4603
|
status: "running",
|
|
4573
4604
|
// If it shows up in tmux ls, it's running
|
|
4574
|
-
outputFile: (0,
|
|
4605
|
+
outputFile: (0, import_node_path2.join)(OUTPUT_DIR, `${name}.jsonl`)
|
|
4575
4606
|
});
|
|
4576
4607
|
}
|
|
4577
4608
|
}
|
|
@@ -4586,8 +4617,8 @@ function getSessionStatus(sessionName) {
|
|
|
4586
4617
|
};
|
|
4587
4618
|
}
|
|
4588
4619
|
function streamLogs(sessionName) {
|
|
4589
|
-
const outputFile = (0,
|
|
4590
|
-
if (!(0,
|
|
4620
|
+
const outputFile = (0, import_node_path2.join)(OUTPUT_DIR, `${sessionName}.jsonl`);
|
|
4621
|
+
if (!(0, import_node_fs3.existsSync)(outputFile)) {
|
|
4591
4622
|
throw new Error(`Output file not found: ${outputFile}`);
|
|
4592
4623
|
}
|
|
4593
4624
|
return new Promise((resolve, reject) => {
|
|
@@ -4640,8 +4671,8 @@ async function buildSessionInfo(name, status) {
|
|
|
4640
4671
|
if (!parsed) {
|
|
4641
4672
|
return null;
|
|
4642
4673
|
}
|
|
4643
|
-
const outputFile = (0,
|
|
4644
|
-
const outputAvailable = (0,
|
|
4674
|
+
const outputFile = (0, import_node_path2.join)(OUTPUT_DIR, `${name}.jsonl`);
|
|
4675
|
+
const outputAvailable = (0, import_node_fs3.existsSync)(outputFile);
|
|
4645
4676
|
let startTime = /* @__PURE__ */ new Date();
|
|
4646
4677
|
let endTime;
|
|
4647
4678
|
let outputPreview;
|
|
@@ -4887,9 +4918,9 @@ function createApiRouter(sagaRoot) {
|
|
|
4887
4918
|
var import_ws = require("ws");
|
|
4888
4919
|
|
|
4889
4920
|
// src/lib/log-stream-manager.ts
|
|
4890
|
-
var
|
|
4921
|
+
var import_node_fs4 = require("node:fs");
|
|
4891
4922
|
var import_promises3 = require("node:fs/promises");
|
|
4892
|
-
var
|
|
4923
|
+
var import_node_path3 = require("node:path");
|
|
4893
4924
|
var import_chokidar = __toESM(require("chokidar"), 1);
|
|
4894
4925
|
function parseJsonlLines(content) {
|
|
4895
4926
|
const messages = [];
|
|
@@ -4971,8 +5002,8 @@ var LogStreamManager = class {
|
|
|
4971
5002
|
* Creates a file watcher if this is the first subscriber.
|
|
4972
5003
|
*/
|
|
4973
5004
|
async subscribe(sessionName, ws) {
|
|
4974
|
-
const outputFile = (0,
|
|
4975
|
-
if (!(0,
|
|
5005
|
+
const outputFile = (0, import_node_path3.join)(OUTPUT_DIR, `${sessionName}.jsonl`);
|
|
5006
|
+
if (!(0, import_node_fs4.existsSync)(outputFile)) {
|
|
4976
5007
|
this.sendToClient(ws, {
|
|
4977
5008
|
type: "logs:error",
|
|
4978
5009
|
sessionName,
|
|
@@ -5101,10 +5132,10 @@ var LogStreamManager = class {
|
|
|
5101
5132
|
if (!subs || subs.size === 0) {
|
|
5102
5133
|
return;
|
|
5103
5134
|
}
|
|
5104
|
-
const outputFile = (0,
|
|
5135
|
+
const outputFile = (0, import_node_path3.join)(OUTPUT_DIR, `${sessionName}.jsonl`);
|
|
5105
5136
|
const finalMessages = [];
|
|
5106
5137
|
try {
|
|
5107
|
-
if ((0,
|
|
5138
|
+
if ((0, import_node_fs4.existsSync)(outputFile)) {
|
|
5108
5139
|
const content = await (0, import_promises3.readFile)(outputFile, "utf-8");
|
|
5109
5140
|
const lastLineCount = this.lineCounts.get(sessionName) ?? 0;
|
|
5110
5141
|
const nonEmptyLines = content.split("\n").filter((l) => l.trim());
|
|
@@ -5150,7 +5181,7 @@ var LogStreamManager = class {
|
|
|
5150
5181
|
|
|
5151
5182
|
// src/server/watcher.ts
|
|
5152
5183
|
var import_node_events = require("node:events");
|
|
5153
|
-
var
|
|
5184
|
+
var import_node_path4 = require("node:path");
|
|
5154
5185
|
var import_node_process2 = __toESM(require("node:process"), 1);
|
|
5155
5186
|
var import_chokidar2 = __toESM(require("chokidar"), 1);
|
|
5156
5187
|
var EPIC_PATH_PARTS = 3;
|
|
@@ -5163,8 +5194,8 @@ function shouldUsePolling() {
|
|
|
5163
5194
|
function parseEpicsPath(parts) {
|
|
5164
5195
|
if (parts.length === EPIC_PATH_PARTS) {
|
|
5165
5196
|
const fileName = parts[2];
|
|
5166
|
-
if ((0,
|
|
5167
|
-
const epicId = (0,
|
|
5197
|
+
if ((0, import_node_path4.extname)(fileName) === ".json") {
|
|
5198
|
+
const epicId = (0, import_node_path4.basename)(fileName, ".json");
|
|
5168
5199
|
return {
|
|
5169
5200
|
epicId,
|
|
5170
5201
|
isEpicFile: true,
|
|
@@ -5195,7 +5226,7 @@ function parseStoriesPath(parts) {
|
|
|
5195
5226
|
isMainStoryFile: false
|
|
5196
5227
|
};
|
|
5197
5228
|
}
|
|
5198
|
-
if ((0,
|
|
5229
|
+
if ((0, import_node_path4.extname)(fileName) === ".json") {
|
|
5199
5230
|
return {
|
|
5200
5231
|
storyId,
|
|
5201
5232
|
isEpicFile: false,
|
|
@@ -5207,8 +5238,8 @@ function parseStoriesPath(parts) {
|
|
|
5207
5238
|
return null;
|
|
5208
5239
|
}
|
|
5209
5240
|
function parseFilePath(filePath, sagaRoot) {
|
|
5210
|
-
const relativePath = (0,
|
|
5211
|
-
const parts = relativePath.split(
|
|
5241
|
+
const relativePath = (0, import_node_path4.relative)(sagaRoot, filePath);
|
|
5242
|
+
const parts = relativePath.split(import_node_path4.sep);
|
|
5212
5243
|
if (parts[0] !== ".saga" || parts.length < MIN_PATH_PARTS) {
|
|
5213
5244
|
return null;
|
|
5214
5245
|
}
|
|
@@ -5280,8 +5311,8 @@ function createDebounceKey(parsed) {
|
|
|
5280
5311
|
return `epic:${parsed.epicId}`;
|
|
5281
5312
|
}
|
|
5282
5313
|
function createChokidarWatcher(sagaRoot) {
|
|
5283
|
-
const storiesDir = (0,
|
|
5284
|
-
const epicsDir = (0,
|
|
5314
|
+
const storiesDir = (0, import_node_path4.join)(sagaRoot, ".saga", "stories");
|
|
5315
|
+
const epicsDir = (0, import_node_path4.join)(sagaRoot, ".saga", "epics");
|
|
5285
5316
|
const usePolling = shouldUsePolling();
|
|
5286
5317
|
return import_chokidar2.default.watch([storiesDir, epicsDir], {
|
|
5287
5318
|
persistent: true,
|
|
@@ -5313,7 +5344,7 @@ function createFileEventHandler(sagaRoot, debouncer, emitter, state) {
|
|
|
5313
5344
|
type: watcherEventType,
|
|
5314
5345
|
epicId: parsed.epicId,
|
|
5315
5346
|
storyId: parsed.storyId,
|
|
5316
|
-
path: (0,
|
|
5347
|
+
path: (0, import_node_path4.relative)(sagaRoot, filePath)
|
|
5317
5348
|
};
|
|
5318
5349
|
debouncer.schedule(createDebounceKey(parsed), event, (e) => {
|
|
5319
5350
|
if (!state.closed) {
|
|
@@ -5635,8 +5666,8 @@ function createApp(sagaRoot) {
|
|
|
5635
5666
|
res.json({ status: "ok" });
|
|
5636
5667
|
});
|
|
5637
5668
|
app.use("/api", createApiRouter(sagaRoot));
|
|
5638
|
-
const clientDistPath = (0,
|
|
5639
|
-
const _indexHtmlPath = (0,
|
|
5669
|
+
const clientDistPath = (0, import_node_path5.join)(__dirname, "client");
|
|
5670
|
+
const _indexHtmlPath = (0, import_node_path5.join)(clientDistPath, "index.html");
|
|
5640
5671
|
app.use(import_express3.default.static(clientDistPath));
|
|
5641
5672
|
app.get("/{*splat}", (_req, res) => {
|
|
5642
5673
|
res.sendFile("index.html", { root: clientDistPath });
|
|
@@ -5674,17 +5705,17 @@ async function startServer(config) {
|
|
|
5674
5705
|
}
|
|
5675
5706
|
|
|
5676
5707
|
// src/utils/project-discovery.ts
|
|
5677
|
-
var
|
|
5678
|
-
var
|
|
5708
|
+
var import_node_fs5 = require("node:fs");
|
|
5709
|
+
var import_node_path6 = require("node:path");
|
|
5679
5710
|
var import_node_process3 = __toESM(require("node:process"), 1);
|
|
5680
5711
|
function findProjectRoot(startDir) {
|
|
5681
5712
|
let currentDir = startDir ?? import_node_process3.default.cwd();
|
|
5682
5713
|
while (true) {
|
|
5683
|
-
const sagaDir = (0,
|
|
5684
|
-
if ((0,
|
|
5714
|
+
const sagaDir = (0, import_node_path6.join)(currentDir, ".saga");
|
|
5715
|
+
if ((0, import_node_fs5.existsSync)(sagaDir)) {
|
|
5685
5716
|
return currentDir;
|
|
5686
5717
|
}
|
|
5687
|
-
const parentDir = (0,
|
|
5718
|
+
const parentDir = (0, import_node_path6.dirname)(currentDir);
|
|
5688
5719
|
if (parentDir === currentDir) {
|
|
5689
5720
|
return null;
|
|
5690
5721
|
}
|
|
@@ -5693,8 +5724,8 @@ function findProjectRoot(startDir) {
|
|
|
5693
5724
|
}
|
|
5694
5725
|
function resolveProjectPath(explicitPath) {
|
|
5695
5726
|
if (explicitPath) {
|
|
5696
|
-
const sagaDir = (0,
|
|
5697
|
-
if (!(0,
|
|
5727
|
+
const sagaDir = (0, import_node_path6.join)(explicitPath, ".saga");
|
|
5728
|
+
if (!(0, import_node_fs5.existsSync)(sagaDir)) {
|
|
5698
5729
|
throw new Error(
|
|
5699
5730
|
`No .saga/ directory found at specified path: ${explicitPath}
|
|
5700
5731
|
Make sure the path points to a SAGA project root.`
|
|
@@ -5760,8 +5791,8 @@ async function sessionsLogsCommand(sessionName) {
|
|
|
5760
5791
|
}
|
|
5761
5792
|
|
|
5762
5793
|
// src/cli.ts
|
|
5763
|
-
var packageJsonPath = (0,
|
|
5764
|
-
var packageJson = JSON.parse((0,
|
|
5794
|
+
var packageJsonPath = (0, import_node_path7.join)(__dirname, "..", "package.json");
|
|
5795
|
+
var packageJson = JSON.parse((0, import_node_fs6.readFileSync)(packageJsonPath, "utf-8"));
|
|
5765
5796
|
var program = new import_commander.Command();
|
|
5766
5797
|
program.name("saga").description("Dashboard and session monitoring for SAGA - Structured Autonomous Goal Achievement").version(packageJson.version).addHelpCommand("help [command]", "Display help for a command");
|
|
5767
5798
|
program.option("-p, --path <dir>", "Path to SAGA project directory (overrides auto-discovery)");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saga-ai/dashboard",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.1",
|
|
4
4
|
"description": "Dashboard and session monitoring for SAGA - Structured Autonomous Goal Achievement",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"vite": "^7.3.1",
|
|
81
81
|
"vitest": "^4.0.18",
|
|
82
82
|
"xstate": "^5.26.0",
|
|
83
|
-
"@saga-ai/
|
|
83
|
+
"@saga-ai/utils": "0.0.0"
|
|
84
84
|
},
|
|
85
85
|
"engines": {
|
|
86
86
|
"node": ">=18.0.0"
|