@imdeadpool/guardex 7.0.20 → 7.0.21
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/README.md +44 -12
- package/package.json +1 -1
- package/src/cli/args.js +804 -2
- package/src/cli/main.js +173 -2328
- package/src/context.js +17 -3
- package/src/git/index.js +42 -32
- package/src/scaffold/index.js +1 -22
- package/templates/scripts/codex-agent.sh +14 -2
- package/templates/vscode/guardex-active-agents/README.md +1 -1
- package/templates/vscode/guardex-active-agents/extension.js +38 -7
- package/templates/vscode/guardex-active-agents/package.json +2 -0
- package/templates/vscode/guardex-active-agents/session-schema.js +233 -29
|
@@ -5,6 +5,11 @@ const cp = require('node:child_process');
|
|
|
5
5
|
const ACTIVE_SESSIONS_RELATIVE_DIR = path.join('.omx', 'state', 'active-sessions');
|
|
6
6
|
const SESSION_SCHEMA_VERSION = 1;
|
|
7
7
|
const LOCK_FILE_RELATIVE = path.join('.omx', 'state', 'agent-file-locks.json');
|
|
8
|
+
const AGENT_WORKTREE_LOCK_FILE = 'AGENT.lock';
|
|
9
|
+
const MANAGED_WORKTREE_ROOTS = [
|
|
10
|
+
path.join('.omx', 'agent-worktrees'),
|
|
11
|
+
path.join('.omc', 'agent-worktrees'),
|
|
12
|
+
];
|
|
8
13
|
const MAX_CHANGED_PATH_PREVIEW = 3;
|
|
9
14
|
const ACTIVE_SESSIONS_FILTER_PREFIX = ACTIVE_SESSIONS_RELATIVE_DIR.split(path.sep).join('/');
|
|
10
15
|
const LOCK_FILE_FILTER_PATH = LOCK_FILE_RELATIVE.split(path.sep).join('/');
|
|
@@ -62,6 +67,10 @@ function sessionFilePathForBranch(repoRoot, branch) {
|
|
|
62
67
|
return path.join(activeSessionsDirForRepo(repoRoot), sessionFileNameForBranch(branch));
|
|
63
68
|
}
|
|
64
69
|
|
|
70
|
+
function resolveManagedWorktreeRoots(repoRoot) {
|
|
71
|
+
return MANAGED_WORKTREE_ROOTS.map((relativeRoot) => path.join(path.resolve(repoRoot), relativeRoot));
|
|
72
|
+
}
|
|
73
|
+
|
|
65
74
|
function splitOutputLines(output) {
|
|
66
75
|
if (typeof output !== 'string') {
|
|
67
76
|
return null;
|
|
@@ -76,6 +85,24 @@ function normalizeRelativePath(value) {
|
|
|
76
85
|
return toNonEmptyString(value).replace(/\\/g, '/').replace(/^\.\//, '');
|
|
77
86
|
}
|
|
78
87
|
|
|
88
|
+
function readJsonFile(filePath) {
|
|
89
|
+
try {
|
|
90
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
91
|
+
} catch (_error) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function normalizeIsoString(value, fallback = '') {
|
|
97
|
+
const normalized = toNonEmptyString(value);
|
|
98
|
+
if (!normalized) {
|
|
99
|
+
return fallback;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const timestamp = Date.parse(normalized);
|
|
103
|
+
return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : fallback;
|
|
104
|
+
}
|
|
105
|
+
|
|
79
106
|
function runGitLines(worktreePath, args) {
|
|
80
107
|
try {
|
|
81
108
|
const output = cp.execFileSync('git', ['-C', worktreePath, ...args], {
|
|
@@ -200,8 +227,8 @@ function parseRepoChangeLine(repoRoot, line) {
|
|
|
200
227
|
|
|
201
228
|
function collectWorktreeChangedPaths(worktreePath) {
|
|
202
229
|
const changedGroups = [
|
|
203
|
-
runGitLines(worktreePath, ['diff', '--name-only', '--', '.', `:(exclude)${LOCK_FILE_RELATIVE}`]),
|
|
204
|
-
runGitLines(worktreePath, ['diff', '--cached', '--name-only', '--', '.', `:(exclude)${LOCK_FILE_RELATIVE}`]),
|
|
230
|
+
runGitLines(worktreePath, ['diff', '--name-only', '--', '.', `:(exclude)${LOCK_FILE_RELATIVE}`, `:(exclude)${AGENT_WORKTREE_LOCK_FILE}`]),
|
|
231
|
+
runGitLines(worktreePath, ['diff', '--cached', '--name-only', '--', '.', `:(exclude)${LOCK_FILE_RELATIVE}`, `:(exclude)${AGENT_WORKTREE_LOCK_FILE}`]),
|
|
205
232
|
runGitLines(worktreePath, ['ls-files', '--others', '--exclude-standard']),
|
|
206
233
|
];
|
|
207
234
|
|
|
@@ -210,7 +237,11 @@ function collectWorktreeChangedPaths(worktreePath) {
|
|
|
210
237
|
}
|
|
211
238
|
|
|
212
239
|
return [...new Set(changedGroups.flat())]
|
|
213
|
-
.filter((relativePath) =>
|
|
240
|
+
.filter((relativePath) => (
|
|
241
|
+
relativePath
|
|
242
|
+
&& relativePath !== LOCK_FILE_RELATIVE
|
|
243
|
+
&& relativePath !== AGENT_WORKTREE_LOCK_FILE
|
|
244
|
+
))
|
|
214
245
|
.sort((left, right) => left.localeCompare(right));
|
|
215
246
|
}
|
|
216
247
|
|
|
@@ -290,6 +321,8 @@ function deriveLatestWorktreeFileActivity(worktreePath) {
|
|
|
290
321
|
|
|
291
322
|
function deriveSessionActivity(session, options = {}) {
|
|
292
323
|
const now = Number.isFinite(options.now) ? options.now : Date.now();
|
|
324
|
+
const pid = toPositiveInteger(session?.pid);
|
|
325
|
+
const pidAlive = pid ? isPidAlive(pid) : null;
|
|
293
326
|
const blockingLabel = deriveBlockingGitLabel(session.worktreePath);
|
|
294
327
|
if (blockingLabel) {
|
|
295
328
|
return {
|
|
@@ -299,14 +332,13 @@ function deriveSessionActivity(session, options = {}) {
|
|
|
299
332
|
activitySummary: blockingLabel,
|
|
300
333
|
changeCount: 0,
|
|
301
334
|
changedPaths: [],
|
|
302
|
-
pidAlive
|
|
335
|
+
pidAlive,
|
|
303
336
|
lastFileActivityAt: '',
|
|
304
337
|
lastFileActivityLabel: '',
|
|
305
338
|
};
|
|
306
339
|
}
|
|
307
340
|
|
|
308
|
-
|
|
309
|
-
if (!pidAlive) {
|
|
341
|
+
if (pid && !pidAlive) {
|
|
310
342
|
return {
|
|
311
343
|
activityKind: 'dead',
|
|
312
344
|
activityLabel: 'dead',
|
|
@@ -426,6 +458,7 @@ function buildSessionRecord(input) {
|
|
|
426
458
|
repoRoot,
|
|
427
459
|
branch,
|
|
428
460
|
taskName: toNonEmptyString(input.taskName, 'task'),
|
|
461
|
+
latestTaskPreview: '',
|
|
429
462
|
agentName: toNonEmptyString(input.agentName, 'agent'),
|
|
430
463
|
worktreePath,
|
|
431
464
|
pid,
|
|
@@ -465,6 +498,7 @@ function normalizeSessionRecord(input, options = {}) {
|
|
|
465
498
|
repoRoot: path.resolve(repoRoot),
|
|
466
499
|
branch,
|
|
467
500
|
taskName: toNonEmptyString(input.taskName, 'task'),
|
|
501
|
+
latestTaskPreview: '',
|
|
468
502
|
agentName: toNonEmptyString(input.agentName, 'agent'),
|
|
469
503
|
worktreePath: path.resolve(worktreePath),
|
|
470
504
|
pid,
|
|
@@ -476,6 +510,12 @@ function normalizeSessionRecord(input, options = {}) {
|
|
|
476
510
|
filePath: toNonEmptyString(options.filePath),
|
|
477
511
|
label: deriveSessionLabel(branch, worktreePath),
|
|
478
512
|
changedPaths: [],
|
|
513
|
+
sourceKind: 'active-session',
|
|
514
|
+
telemetryUpdatedAt: '',
|
|
515
|
+
telemetrySource: '',
|
|
516
|
+
lockSnapshotCount: 0,
|
|
517
|
+
lockSessionCount: 0,
|
|
518
|
+
collaboration: false,
|
|
479
519
|
};
|
|
480
520
|
}
|
|
481
521
|
|
|
@@ -517,49 +557,212 @@ function isPidAlive(pid) {
|
|
|
517
557
|
}
|
|
518
558
|
}
|
|
519
559
|
|
|
520
|
-
function
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
|
|
560
|
+
function readWorktreeBranch(worktreePath) {
|
|
561
|
+
const lines = runGitLines(worktreePath, ['rev-parse', '--abbrev-ref', 'HEAD']);
|
|
562
|
+
return Array.isArray(lines) && typeof lines[0] === 'string' ? lines[0].trim() : '';
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function deriveAgentNameFromBranch(branch) {
|
|
566
|
+
const parts = toNonEmptyString(branch).split('/').filter(Boolean);
|
|
567
|
+
if (parts.length >= 2 && parts[0] === 'agent') {
|
|
568
|
+
return parts[1];
|
|
569
|
+
}
|
|
570
|
+
return 'agent';
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function flattenTelemetrySnapshotSessions(lockPayload) {
|
|
574
|
+
const flattened = [];
|
|
575
|
+
const snapshots = Array.isArray(lockPayload?.snapshots) ? lockPayload.snapshots : [];
|
|
576
|
+
for (const snapshot of snapshots) {
|
|
577
|
+
const snapshotSessions = Array.isArray(snapshot?.sessions) ? snapshot.sessions : [];
|
|
578
|
+
for (const session of snapshotSessions) {
|
|
579
|
+
flattened.push({
|
|
580
|
+
taskPreview: toNonEmptyString(session?.taskPreview),
|
|
581
|
+
taskUpdatedAt: normalizeIsoString(session?.taskUpdatedAt),
|
|
582
|
+
projectName: toNonEmptyString(session?.projectName),
|
|
583
|
+
projectPath: toNonEmptyString(session?.projectPath),
|
|
584
|
+
snapshotName: toNonEmptyString(snapshot?.snapshotName),
|
|
585
|
+
email: toNonEmptyString(snapshot?.email),
|
|
586
|
+
});
|
|
587
|
+
}
|
|
524
588
|
}
|
|
589
|
+
return flattened;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function sortSessionsByTimestamp(sessions) {
|
|
593
|
+
sessions.sort((left, right) => {
|
|
594
|
+
const timeDelta = Date.parse(right.startedAt) - Date.parse(left.startedAt);
|
|
595
|
+
if (timeDelta !== 0) {
|
|
596
|
+
return timeDelta;
|
|
597
|
+
}
|
|
598
|
+
return left.label.localeCompare(right.label);
|
|
599
|
+
});
|
|
600
|
+
return sessions;
|
|
601
|
+
}
|
|
525
602
|
|
|
603
|
+
function deriveLockTaskAnchor(entries, fallbackTaskName, fallbackTimestamp) {
|
|
604
|
+
const sortedEntries = [...entries].sort((left, right) => {
|
|
605
|
+
const timeDelta = Date.parse(right.taskUpdatedAt || '') - Date.parse(left.taskUpdatedAt || '');
|
|
606
|
+
if (timeDelta !== 0) {
|
|
607
|
+
return timeDelta;
|
|
608
|
+
}
|
|
609
|
+
if (Boolean(right.taskPreview) !== Boolean(left.taskPreview)) {
|
|
610
|
+
return Number(Boolean(right.taskPreview)) - Number(Boolean(left.taskPreview));
|
|
611
|
+
}
|
|
612
|
+
return (right.projectPath || '').localeCompare(left.projectPath || '');
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
const latestEntry = sortedEntries[0] || null;
|
|
616
|
+
return {
|
|
617
|
+
taskName: latestEntry?.taskPreview || fallbackTaskName || 'task',
|
|
618
|
+
latestTaskPreview: latestEntry?.taskPreview || '',
|
|
619
|
+
timestamp: latestEntry?.taskUpdatedAt || fallbackTimestamp || '',
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function buildWorktreeLockSession(repoRoot, worktreePath, lockPayload, options = {}) {
|
|
526
624
|
const now = options.now || Date.now();
|
|
625
|
+
const telemetryEntries = flattenTelemetrySnapshotSessions(lockPayload);
|
|
626
|
+
const telemetryUpdatedAt = normalizeIsoString(lockPayload?.updatedAt);
|
|
627
|
+
const branch = readWorktreeBranch(worktreePath);
|
|
628
|
+
const effectiveBranch = branch && branch !== 'HEAD'
|
|
629
|
+
? branch
|
|
630
|
+
: `agent/telemetry/${path.basename(worktreePath)}`;
|
|
631
|
+
const label = deriveSessionLabel(effectiveBranch, worktreePath);
|
|
632
|
+
const taskAnchor = deriveLockTaskAnchor(telemetryEntries, label, telemetryUpdatedAt);
|
|
633
|
+
const startedAt = taskAnchor.timestamp || telemetryUpdatedAt || new Date(now).toISOString();
|
|
634
|
+
|
|
635
|
+
const session = {
|
|
636
|
+
schemaVersion: toPositiveInteger(lockPayload?.schemaVersion) || SESSION_SCHEMA_VERSION,
|
|
637
|
+
repoRoot: path.resolve(repoRoot),
|
|
638
|
+
branch: effectiveBranch,
|
|
639
|
+
taskName: taskAnchor.taskName,
|
|
640
|
+
latestTaskPreview: taskAnchor.latestTaskPreview,
|
|
641
|
+
agentName: deriveAgentNameFromBranch(effectiveBranch),
|
|
642
|
+
worktreePath: path.resolve(worktreePath),
|
|
643
|
+
pid: null,
|
|
644
|
+
cliName: 'codex',
|
|
645
|
+
taskMode: '',
|
|
646
|
+
openspecTier: '',
|
|
647
|
+
taskRoutingReason: '',
|
|
648
|
+
startedAt,
|
|
649
|
+
filePath: path.join(worktreePath, AGENT_WORKTREE_LOCK_FILE),
|
|
650
|
+
label,
|
|
651
|
+
changedPaths: [],
|
|
652
|
+
sourceKind: 'worktree-lock',
|
|
653
|
+
telemetryUpdatedAt: telemetryUpdatedAt || startedAt,
|
|
654
|
+
telemetrySource: toNonEmptyString(lockPayload?.source, 'worktree-lock'),
|
|
655
|
+
lockSnapshotCount: toPositiveInteger(lockPayload?.snapshotCount) || 0,
|
|
656
|
+
lockSessionCount: toPositiveInteger(lockPayload?.sessionCount) || telemetryEntries.length,
|
|
657
|
+
collaboration: Boolean(lockPayload?.collaboration),
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
session.elapsedLabel = formatElapsedFrom(session.startedAt, now);
|
|
661
|
+
Object.assign(session, deriveSessionActivity(session, { now }));
|
|
662
|
+
return session;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function readWorktreeLockSessions(repoRoot, options = {}) {
|
|
527
666
|
const sessions = [];
|
|
528
|
-
for (const
|
|
529
|
-
if (!
|
|
667
|
+
for (const managedRoot of resolveManagedWorktreeRoots(repoRoot)) {
|
|
668
|
+
if (!fs.existsSync(managedRoot)) {
|
|
530
669
|
continue;
|
|
531
670
|
}
|
|
532
671
|
|
|
533
|
-
|
|
534
|
-
let parsed;
|
|
672
|
+
let entries;
|
|
535
673
|
try {
|
|
536
|
-
|
|
674
|
+
entries = fs.readdirSync(managedRoot, { withFileTypes: true });
|
|
537
675
|
} catch (_error) {
|
|
538
676
|
continue;
|
|
539
677
|
}
|
|
540
678
|
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
|
|
679
|
+
for (const entry of entries) {
|
|
680
|
+
if (!entry.isDirectory()) {
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
const worktreePath = path.join(managedRoot, entry.name);
|
|
685
|
+
const lockPath = path.join(worktreePath, AGENT_WORKTREE_LOCK_FILE);
|
|
686
|
+
if (!fs.existsSync(lockPath)) {
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const lockPayload = readJsonFile(lockPath);
|
|
691
|
+
if (!lockPayload || typeof lockPayload !== 'object' || Array.isArray(lockPayload)) {
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const telemetryEntries = flattenTelemetrySnapshotSessions(lockPayload);
|
|
696
|
+
if (telemetryEntries.length === 0 && !toPositiveInteger(lockPayload.sessionCount)) {
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
sessions.push(buildWorktreeLockSession(repoRoot, worktreePath, lockPayload, options));
|
|
544
701
|
}
|
|
545
|
-
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
return sortSessionsByTimestamp(sessions);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function mergeSessionSources(primarySessions, lockSessions) {
|
|
708
|
+
const lockSessionsByWorktree = new Map(
|
|
709
|
+
lockSessions.map((session) => [path.resolve(session.worktreePath), session]),
|
|
710
|
+
);
|
|
711
|
+
const consumedLockWorktrees = new Set();
|
|
712
|
+
const merged = [];
|
|
713
|
+
|
|
714
|
+
for (const session of primarySessions) {
|
|
715
|
+
const worktreeKey = path.resolve(session.worktreePath);
|
|
716
|
+
const lockSession = lockSessionsByWorktree.get(worktreeKey);
|
|
717
|
+
if (lockSession && session.activityKind === 'dead') {
|
|
546
718
|
continue;
|
|
547
719
|
}
|
|
720
|
+
if (lockSession) {
|
|
721
|
+
consumedLockWorktrees.add(worktreeKey);
|
|
722
|
+
}
|
|
723
|
+
merged.push(session);
|
|
724
|
+
}
|
|
548
725
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
726
|
+
for (const lockSession of lockSessions) {
|
|
727
|
+
const worktreeKey = path.resolve(lockSession.worktreePath);
|
|
728
|
+
if (!consumedLockWorktrees.has(worktreeKey)) {
|
|
729
|
+
merged.push(lockSession);
|
|
730
|
+
}
|
|
552
731
|
}
|
|
553
732
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
733
|
+
return sortSessionsByTimestamp(merged);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function readActiveSessions(repoRoot, options = {}) {
|
|
737
|
+
const activeSessionsDir = activeSessionsDirForRepo(repoRoot);
|
|
738
|
+
const now = options.now || Date.now();
|
|
739
|
+
const sessionFileSessions = [];
|
|
740
|
+
if (fs.existsSync(activeSessionsDir)) {
|
|
741
|
+
for (const entry of fs.readdirSync(activeSessionsDir, { withFileTypes: true })) {
|
|
742
|
+
if (!entry.isFile() || !entry.name.endsWith('.json')) {
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
const filePath = path.join(activeSessionsDir, entry.name);
|
|
747
|
+
const parsed = readJsonFile(filePath);
|
|
748
|
+
const normalized = normalizeSessionRecord(parsed, { filePath });
|
|
749
|
+
if (!normalized) {
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
if (!options.includeStale && !isPidAlive(normalized.pid)) {
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
normalized.elapsedLabel = formatElapsedFrom(normalized.startedAt, now);
|
|
757
|
+
Object.assign(normalized, deriveSessionActivity(normalized, { now }));
|
|
758
|
+
sessionFileSessions.push(normalized);
|
|
558
759
|
}
|
|
559
|
-
|
|
560
|
-
});
|
|
760
|
+
}
|
|
561
761
|
|
|
562
|
-
return
|
|
762
|
+
return mergeSessionSources(
|
|
763
|
+
sortSessionsByTimestamp(sessionFileSessions),
|
|
764
|
+
readWorktreeLockSessions(repoRoot, { now }),
|
|
765
|
+
);
|
|
563
766
|
}
|
|
564
767
|
|
|
565
768
|
function readRepoChanges(repoRoot) {
|
|
@@ -592,6 +795,7 @@ module.exports = {
|
|
|
592
795
|
parseRepoChangeLine,
|
|
593
796
|
previewChangedPaths,
|
|
594
797
|
readActiveSessions,
|
|
798
|
+
readWorktreeLockSessions,
|
|
595
799
|
readRepoChanges,
|
|
596
800
|
deriveRepoChangeStatus,
|
|
597
801
|
resolveWorktreeGitDir,
|