bosun 0.42.2 → 0.42.4
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/.env.example +9 -0
- package/agent/agent-event-bus.mjs +10 -0
- package/agent/agent-supervisor.mjs +20 -0
- package/bosun-tui.mjs +107 -105
- package/cli.mjs +10 -0
- package/config/config.mjs +25 -0
- package/config/executor-config.mjs +124 -1
- package/infra/container-runner.mjs +565 -1
- package/infra/monitor.mjs +18 -0
- package/infra/tracing.mjs +544 -240
- package/infra/tui-bridge.mjs +13 -1
- package/kanban/kanban-adapter.mjs +128 -4
- package/lib/repo-map.mjs +114 -3
- package/package.json +11 -4
- package/server/ui-server.mjs +3 -0
- package/task/task-archiver.mjs +18 -6
- package/task/task-attachments.mjs +14 -10
- package/task/task-cli.mjs +24 -4
- package/task/task-executor.mjs +19 -0
- package/task/task-store.mjs +194 -37
- package/telegram/telegram-bot.mjs +4 -1
- package/tui/app.mjs +131 -171
- package/tui/components/status-header.mjs +178 -75
- package/tui/lib/header-config.mjs +68 -0
- package/tui/lib/ws-bridge.mjs +61 -9
- package/tui/screens/agents.mjs +127 -0
- package/tui/screens/tasks.mjs +1 -48
- package/ui/app.js +8 -5
- package/ui/components/kanban-board.js +65 -3
- package/ui/components/session-list.js +18 -32
- package/ui/demo-defaults.js +52 -2
- package/ui/modules/session-api.js +100 -0
- package/ui/modules/state.js +71 -15
- package/ui/tabs/workflows.js +25 -1
- package/ui/tui/App.js +298 -0
- package/ui/tui/TasksScreen.js +564 -0
- package/ui/tui/constants.js +55 -0
- package/ui/tui/tasks-screen-helpers.js +301 -0
- package/ui/tui/useTasks.js +61 -0
- package/ui/tui/useWebSocket.js +166 -0
- package/ui/tui/useWorkflows.js +30 -0
- package/workflow/workflow-engine.mjs +412 -7
- package/workflow/workflow-nodes.mjs +616 -75
- package/workflow-templates/agents.mjs +3 -0
- package/workflow-templates/planning.mjs +7 -0
- package/workflow-templates/sub-workflows.mjs +5 -0
- package/workflow-templates/task-execution.mjs +3 -0
- package/workspace/command-diagnostics.mjs +1 -1
- package/workspace/context-cache.mjs +182 -9
package/task/task-store.mjs
CHANGED
|
@@ -328,6 +328,140 @@ export function normalizeWorkspaceStorageKey(rawKey) {
|
|
|
328
328
|
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
329
329
|
}
|
|
330
330
|
|
|
331
|
+
function isPlainObject(value) {
|
|
332
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function isLikelyUrl(value) {
|
|
336
|
+
return /^[a-z][a-z0-9+.-]*:\/\//i.test(String(value || "").trim());
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const PORTABLE_TASK_PATH_KEYS = Object.freeze([
|
|
340
|
+
"archivePath",
|
|
341
|
+
"archiveDir",
|
|
342
|
+
"artifactPath",
|
|
343
|
+
"artifactsDir",
|
|
344
|
+
"attachmentPath",
|
|
345
|
+
"attachmentsDir",
|
|
346
|
+
"exportPath",
|
|
347
|
+
"exportDir",
|
|
348
|
+
"importPath",
|
|
349
|
+
"importDir",
|
|
350
|
+
]);
|
|
351
|
+
|
|
352
|
+
export function normalizePortableTaskPath(rawPath) {
|
|
353
|
+
if (rawPath == null) return "";
|
|
354
|
+
const value = String(rawPath).trim();
|
|
355
|
+
if (!value || isLikelyUrl(value)) return value;
|
|
356
|
+
return normalizeWorkspaceStorageKey(value).toLowerCase();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function normalizePortablePathValue(rawPath) {
|
|
360
|
+
return normalizePortableTaskPath(rawPath);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function normalizePortablePathList(rawPaths) {
|
|
364
|
+
const values = Array.isArray(rawPaths) ? rawPaths : [rawPaths];
|
|
365
|
+
const normalized = [];
|
|
366
|
+
const seen = new Set();
|
|
367
|
+
for (const entry of values) {
|
|
368
|
+
const canonical = normalizePortablePathValue(entry);
|
|
369
|
+
if (!canonical || seen.has(canonical)) continue;
|
|
370
|
+
seen.add(canonical);
|
|
371
|
+
normalized.push(canonical);
|
|
372
|
+
}
|
|
373
|
+
return normalized;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function getTaskAttachmentCanonicalKey(attachment = {}) {
|
|
377
|
+
if (!attachment || typeof attachment !== "object") return "";
|
|
378
|
+
const url = String(attachment.url || attachment.uri || "").trim();
|
|
379
|
+
if (url) return `url:${url}`;
|
|
380
|
+
const location = normalizePortablePathValue(
|
|
381
|
+
attachment.filePath || attachment.path || attachment.localPath || "",
|
|
382
|
+
);
|
|
383
|
+
if (location) return `path:${location}`;
|
|
384
|
+
if (attachment.id) return `id:${attachment.id}`;
|
|
385
|
+
return `raw:${JSON.stringify(attachment)}`;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export function normalizeTaskAttachmentRecord(rawAttachment) {
|
|
389
|
+
if (!rawAttachment || typeof rawAttachment !== "object") return null;
|
|
390
|
+
const normalized = { ...rawAttachment };
|
|
391
|
+
const canonicalLocation = normalizePortablePathValue(
|
|
392
|
+
normalized.filePath || normalized.path || normalized.localPath || "",
|
|
393
|
+
);
|
|
394
|
+
if (canonicalLocation) {
|
|
395
|
+
normalized.filePath = canonicalLocation;
|
|
396
|
+
if (Object.prototype.hasOwnProperty.call(normalized, "path")) {
|
|
397
|
+
normalized.path = canonicalLocation;
|
|
398
|
+
}
|
|
399
|
+
if (Object.prototype.hasOwnProperty.call(normalized, "localPath")) {
|
|
400
|
+
normalized.localPath = canonicalLocation;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return normalized;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export function normalizeTaskAttachments(rawAttachments, options = {}) {
|
|
407
|
+
const values = Array.isArray(rawAttachments) ? rawAttachments : [];
|
|
408
|
+
const normalized = [];
|
|
409
|
+
const seen = new Set();
|
|
410
|
+
for (const value of values) {
|
|
411
|
+
const attachment = normalizeTaskAttachmentRecord(value);
|
|
412
|
+
if (!attachment) continue;
|
|
413
|
+
const key = getTaskAttachmentCanonicalKey(attachment);
|
|
414
|
+
if (!key || seen.has(key)) continue;
|
|
415
|
+
seen.add(key);
|
|
416
|
+
normalized.push(attachment);
|
|
417
|
+
}
|
|
418
|
+
if (options.limit && normalized.length > options.limit) {
|
|
419
|
+
return normalized.slice(0, options.limit);
|
|
420
|
+
}
|
|
421
|
+
return normalized;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function normalizeTaskPathPayload(rawPayload = {}, taskId = "", options = {}) {
|
|
425
|
+
if (!isPlainObject(rawPayload)) return {};
|
|
426
|
+
const normalized = { ...rawPayload };
|
|
427
|
+
for (const key of PORTABLE_TASK_PATH_KEYS) {
|
|
428
|
+
if (typeof normalized[key] === "string") {
|
|
429
|
+
normalized[key] = normalizePortablePathValue(normalized[key]);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if (options.includeWorkspace === true && typeof normalized.workspace === "string") {
|
|
433
|
+
normalized.workspace = normalizeWorkspaceStorageKey(normalized.workspace) || null;
|
|
434
|
+
}
|
|
435
|
+
if (options.includeRepository === true && typeof normalized.repository === "string") {
|
|
436
|
+
normalized.repository = normalizeWorkspaceStorageKey(normalized.repository) || null;
|
|
437
|
+
}
|
|
438
|
+
if (options.includeRepositories === true && normalized.repositories != null) {
|
|
439
|
+
normalized.repositories = normalizeWorkspaceStorageKeys(normalized.repositories, {
|
|
440
|
+
kind: `task:${taskId || "<unknown-task>"}:repositories`,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
if (normalized.attachments != null) {
|
|
444
|
+
normalized.attachments = normalizeTaskAttachments(normalized.attachments, {
|
|
445
|
+
kind: `task:${taskId || "<unknown-task>"}:${options.attachmentKind || "attachments"}`,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
if (normalized.filePaths != null) {
|
|
449
|
+
normalized.filePaths = normalizePortablePathList(normalized.filePaths);
|
|
450
|
+
}
|
|
451
|
+
if (normalized.paths != null) {
|
|
452
|
+
normalized.paths = normalizePortablePathList(normalized.paths);
|
|
453
|
+
}
|
|
454
|
+
return normalized;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function normalizeTaskMeta(rawMeta = {}, taskId = "") {
|
|
458
|
+
return normalizeTaskPathPayload(rawMeta, taskId, {
|
|
459
|
+
includeWorkspace: true,
|
|
460
|
+
includeRepository: true,
|
|
461
|
+
attachmentKind: "meta.attachments",
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
331
465
|
export function normalizeWorkspaceStorageKeys(rawKeys, options = {}) {
|
|
332
466
|
const kind = String(options.kind || "workspace-rooted storage").trim();
|
|
333
467
|
const values = Array.isArray(rawKeys) ? rawKeys : [rawKeys];
|
|
@@ -588,68 +722,79 @@ function validateTaskTransition(currentStatus, nextStatus, options = {}) {
|
|
|
588
722
|
function normalizeTaskStructure(rawTask = {}) {
|
|
589
723
|
const base = defaultTask(rawTask);
|
|
590
724
|
const taskId = String(base?.id || rawTask?.id || "").trim() || "<unknown-task>";
|
|
591
|
-
const
|
|
592
|
-
|
|
593
|
-
|
|
725
|
+
const normalizedBase = normalizeTaskPathPayload(base, taskId, {
|
|
726
|
+
attachmentKind: "attachments",
|
|
727
|
+
});
|
|
728
|
+
const workspaceKey = normalizeWorkspaceStorageKey(normalizedBase.workspace);
|
|
729
|
+
const repositoryKey = normalizeWorkspaceStorageKey(normalizedBase.repository);
|
|
730
|
+
const repositoryKeys = normalizeWorkspaceStorageKeys(normalizedBase.repositories || [], {
|
|
594
731
|
kind: `task:${taskId}:repositories`,
|
|
595
732
|
});
|
|
596
733
|
const scopedRepositoryKeys = normalizeWorkspaceStorageKeys(
|
|
597
734
|
[repositoryKey, ...repositoryKeys],
|
|
598
735
|
{ kind: `task:${taskId}:workspace-rooted` },
|
|
599
736
|
);
|
|
737
|
+
const attachments = normalizeTaskAttachments(normalizedBase.attachments, {
|
|
738
|
+
kind: `task:${taskId}:attachments`,
|
|
739
|
+
});
|
|
740
|
+
const normalizedMeta = normalizeTaskMeta(normalizedBase.meta, taskId);
|
|
600
741
|
const normalized = {
|
|
601
|
-
...
|
|
602
|
-
status: normalizeTaskStatus(
|
|
603
|
-
type: normalizeTaskType(
|
|
604
|
-
epicId:
|
|
605
|
-
parentTaskId:
|
|
606
|
-
childTaskIds: uniqueStringList(
|
|
607
|
-
dependencyTaskIds: uniqueStringList(
|
|
608
|
-
blockedByTaskIds: uniqueStringList(
|
|
609
|
-
dependsOn: uniqueStringList(
|
|
610
|
-
|
|
611
|
-
|
|
742
|
+
...normalizedBase,
|
|
743
|
+
status: normalizeTaskStatus(normalizedBase.status),
|
|
744
|
+
type: normalizeTaskType(normalizedBase.type),
|
|
745
|
+
epicId: normalizedBase.epicId ? String(normalizedBase.epicId) : null,
|
|
746
|
+
parentTaskId: normalizedBase.parentTaskId ? String(normalizedBase.parentTaskId) : null,
|
|
747
|
+
childTaskIds: uniqueStringList(normalizedBase.childTaskIds || []),
|
|
748
|
+
dependencyTaskIds: uniqueStringList(normalizedBase.dependencyTaskIds || []),
|
|
749
|
+
blockedByTaskIds: uniqueStringList(normalizedBase.blockedByTaskIds || []),
|
|
750
|
+
dependsOn: uniqueStringList(
|
|
751
|
+
normalizedBase.dependsOn || normalizedBase.dependencyTaskIds || [],
|
|
752
|
+
),
|
|
753
|
+
assignees: uniqueStringList(normalizedBase.assignees || []),
|
|
754
|
+
watchers: uniqueStringList(normalizedBase.watchers || []),
|
|
612
755
|
links: {
|
|
613
|
-
branches: uniqueStringList(
|
|
614
|
-
prs: uniqueStringList(
|
|
615
|
-
workflows: uniqueStringList(
|
|
756
|
+
branches: uniqueStringList(normalizedBase.links?.branches || []),
|
|
757
|
+
prs: uniqueStringList(normalizedBase.links?.prs || []),
|
|
758
|
+
workflows: uniqueStringList(normalizedBase.links?.workflows || []),
|
|
616
759
|
},
|
|
617
760
|
comments: normalizeTaskComments(
|
|
618
|
-
Array.isArray(
|
|
619
|
-
?
|
|
620
|
-
: Array.isArray(
|
|
621
|
-
?
|
|
761
|
+
Array.isArray(normalizedBase.comments) && normalizedBase.comments.length
|
|
762
|
+
? normalizedBase.comments
|
|
763
|
+
: Array.isArray(normalizedBase.meta?.comments)
|
|
764
|
+
? normalizedBase.meta.comments
|
|
622
765
|
: [],
|
|
623
766
|
),
|
|
624
767
|
timeline: normalizeTimelineEvents(
|
|
625
|
-
Array.isArray(
|
|
626
|
-
?
|
|
627
|
-
: Array.isArray(
|
|
628
|
-
?
|
|
768
|
+
Array.isArray(normalizedBase.timeline)
|
|
769
|
+
? normalizedBase.timeline
|
|
770
|
+
: Array.isArray(normalizedBase.meta?.timeline)
|
|
771
|
+
? normalizedBase.meta.timeline
|
|
629
772
|
: [],
|
|
630
773
|
),
|
|
631
774
|
workflowRuns: normalizeWorkflowRunLinks(
|
|
632
|
-
Array.isArray(
|
|
633
|
-
?
|
|
634
|
-
: Array.isArray(
|
|
635
|
-
?
|
|
775
|
+
Array.isArray(normalizedBase.workflowRuns)
|
|
776
|
+
? normalizedBase.workflowRuns
|
|
777
|
+
: Array.isArray(normalizedBase.meta?.workflowRuns)
|
|
778
|
+
? normalizedBase.meta.workflowRuns
|
|
636
779
|
: [],
|
|
637
780
|
),
|
|
638
781
|
runs: normalizeTaskRuns(
|
|
639
|
-
Array.isArray(
|
|
640
|
-
?
|
|
641
|
-
: Array.isArray(
|
|
642
|
-
?
|
|
782
|
+
Array.isArray(normalizedBase.runs)
|
|
783
|
+
? normalizedBase.runs
|
|
784
|
+
: Array.isArray(normalizedBase.meta?.runs)
|
|
785
|
+
? normalizedBase.meta.runs
|
|
643
786
|
: [],
|
|
644
787
|
),
|
|
645
|
-
stateVersion: Number.isFinite(Number(
|
|
646
|
-
? Number(
|
|
788
|
+
stateVersion: Number.isFinite(Number(normalizedBase.stateVersion))
|
|
789
|
+
? Number(normalizedBase.stateVersion)
|
|
647
790
|
: 2,
|
|
648
|
-
sprintId: normalizeSprintId(
|
|
649
|
-
sprintOrder: normalizeSprintOrder(
|
|
791
|
+
sprintId: normalizeSprintId(normalizedBase.sprintId),
|
|
792
|
+
sprintOrder: normalizeSprintOrder(normalizedBase.sprintOrder),
|
|
650
793
|
workspace: workspaceKey || null,
|
|
651
794
|
repository: repositoryKey || null,
|
|
652
795
|
repositories: scopedRepositoryKeys,
|
|
796
|
+
attachments,
|
|
797
|
+
meta: normalizedMeta,
|
|
653
798
|
};
|
|
654
799
|
if (normalized.status === "draft") {
|
|
655
800
|
normalized.draft = true;
|
|
@@ -657,6 +802,10 @@ function normalizeTaskStructure(rawTask = {}) {
|
|
|
657
802
|
return normalized;
|
|
658
803
|
}
|
|
659
804
|
|
|
805
|
+
export function normalizeTaskStorageRecord(rawTask = {}) {
|
|
806
|
+
return normalizeTaskStructure(rawTask);
|
|
807
|
+
}
|
|
808
|
+
|
|
660
809
|
function pushTaskTimeline(task, event = {}) {
|
|
661
810
|
task.timeline = normalizeTimelineEvents([...(Array.isArray(task.timeline) ? task.timeline : []), event]);
|
|
662
811
|
}
|
|
@@ -1367,6 +1516,7 @@ export function updateTask(taskId, updates) {
|
|
|
1367
1516
|
repositories: (next) => { task.repositories = next; },
|
|
1368
1517
|
baseBranch: (next) => { task.baseBranch = next; },
|
|
1369
1518
|
branchName: (next) => { task.branchName = next; },
|
|
1519
|
+
prLinkage: (next) => { task.prLinkage = next; },
|
|
1370
1520
|
prNumber: (next) => { task.prNumber = next; },
|
|
1371
1521
|
prUrl: (next) => { task.prUrl = next; },
|
|
1372
1522
|
epicId: (next) => { task.epicId = next; },
|
|
@@ -1404,6 +1554,12 @@ export function updateTask(taskId, updates) {
|
|
|
1404
1554
|
task.tags = normalizeTags(value);
|
|
1405
1555
|
continue;
|
|
1406
1556
|
}
|
|
1557
|
+
if (key === "attachments") {
|
|
1558
|
+
task.attachments = normalizeTaskAttachments(value, {
|
|
1559
|
+
kind: `task:${taskId}:attachments`,
|
|
1560
|
+
});
|
|
1561
|
+
continue;
|
|
1562
|
+
}
|
|
1407
1563
|
if (key === "status") {
|
|
1408
1564
|
task.status = normalizeTaskStatus(value);
|
|
1409
1565
|
continue;
|
|
@@ -3034,3 +3190,4 @@ export function getStaleInReviewTasks(maxAgeMs) {
|
|
|
3034
3190
|
(t) => t.status === "inreview" && t.lastActivityAt < cutoff,
|
|
3035
3191
|
);
|
|
3036
3192
|
}
|
|
3193
|
+
|
|
@@ -1264,6 +1264,7 @@ let _getErrorDetector = null;
|
|
|
1264
1264
|
let _getPrCleanupDaemon = null;
|
|
1265
1265
|
let _getWorkspaceMonitor = null;
|
|
1266
1266
|
let _getMonitorMonitorStatus = null;
|
|
1267
|
+
let _getTuiMonitorStats = null;
|
|
1267
1268
|
let _getTaskStoreStats = null;
|
|
1268
1269
|
let _getTasksPendingReview = null;
|
|
1269
1270
|
let _triggerTaskPlanner = null;
|
|
@@ -1300,6 +1301,7 @@ export function injectMonitorFunctions({
|
|
|
1300
1301
|
getPrCleanupDaemon,
|
|
1301
1302
|
getWorkspaceMonitor,
|
|
1302
1303
|
getMonitorMonitorStatus,
|
|
1304
|
+
getTuiMonitorStats,
|
|
1303
1305
|
getTaskStoreStats,
|
|
1304
1306
|
getTasksPendingReview,
|
|
1305
1307
|
triggerTaskPlanner,
|
|
@@ -1332,6 +1334,7 @@ export function injectMonitorFunctions({
|
|
|
1332
1334
|
_getPrCleanupDaemon = getPrCleanupDaemon || null;
|
|
1333
1335
|
_getWorkspaceMonitor = getWorkspaceMonitor || null;
|
|
1334
1336
|
_getMonitorMonitorStatus = getMonitorMonitorStatus || null;
|
|
1337
|
+
_getTuiMonitorStats = getTuiMonitorStats || null;
|
|
1335
1338
|
_getTaskStoreStats = getTaskStoreStats || null;
|
|
1336
1339
|
_getTasksPendingReview = getTasksPendingReview || null;
|
|
1337
1340
|
_triggerTaskPlanner = triggerTaskPlanner || null;
|
|
@@ -11633,6 +11636,7 @@ export async function startTelegramBot(options = {}) {
|
|
|
11633
11636
|
dependencies: {
|
|
11634
11637
|
execPrimaryPrompt,
|
|
11635
11638
|
getInternalExecutor: _getInternalExecutor,
|
|
11639
|
+
getTuiMonitorStats: _getTuiMonitorStats,
|
|
11636
11640
|
getExecutorMode: _getExecutorMode,
|
|
11637
11641
|
getAgentEventBus: _getAgentEventBus,
|
|
11638
11642
|
handleUiCommand: handleUiCommand,
|
|
@@ -12080,4 +12084,3 @@ export function stopStatusFileWriter() {
|
|
|
12080
12084
|
_statusWriterTimer = null;
|
|
12081
12085
|
}
|
|
12082
12086
|
}
|
|
12083
|
-
|