@imdeadpool/guardex 7.0.19 → 7.0.20
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 +7 -1
- package/bin/multiagent-safety.js +2 -7861
- package/package.json +2 -1
- package/src/cli/args.js +7 -0
- package/src/cli/dispatch.js +86 -0
- package/src/cli/main.js +7719 -0
- package/src/context.js +503 -0
- package/src/core/runtime.js +119 -0
- package/src/finish/index.js +425 -0
- package/src/git/index.js +112 -0
- package/src/hooks/index.js +74 -0
- package/src/output/index.js +398 -0
- package/src/sandbox/index.js +68 -0
- package/src/scaffold/index.js +169 -0
- package/src/toolchain/index.js +223 -0
- package/templates/scripts/agent-branch-start.sh +52 -8
- package/templates/scripts/codex-agent.sh +143 -7
- package/templates/vscode/guardex-active-agents/README.md +16 -11
- package/templates/vscode/guardex-active-agents/extension.js +876 -64
- package/templates/vscode/guardex-active-agents/package.json +61 -1
- package/templates/vscode/guardex-active-agents/session-schema.js +211 -17
|
@@ -19,13 +19,42 @@
|
|
|
19
19
|
"main": "./extension.js",
|
|
20
20
|
"contributes": {
|
|
21
21
|
"commands": [
|
|
22
|
+
{
|
|
23
|
+
"command": "gitguardex.activeAgents.startAgent",
|
|
24
|
+
"title": "Start Guardex Agent"
|
|
25
|
+
},
|
|
22
26
|
{
|
|
23
27
|
"command": "gitguardex.activeAgents.refresh",
|
|
24
28
|
"title": "Refresh Active Agents"
|
|
25
29
|
},
|
|
30
|
+
{
|
|
31
|
+
"command": "gitguardex.activeAgents.commitSelectedSession",
|
|
32
|
+
"title": "Commit Selected Session",
|
|
33
|
+
"icon": "$(check)"
|
|
34
|
+
},
|
|
26
35
|
{
|
|
27
36
|
"command": "gitguardex.activeAgents.openWorktree",
|
|
28
37
|
"title": "Open Agent Worktree"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"command": "gitguardex.activeAgents.finishSession",
|
|
41
|
+
"title": "Finish",
|
|
42
|
+
"icon": "$(check)"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"command": "gitguardex.activeAgents.syncSession",
|
|
46
|
+
"title": "Sync",
|
|
47
|
+
"icon": "$(sync)"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"command": "gitguardex.activeAgents.stopSession",
|
|
51
|
+
"title": "Stop",
|
|
52
|
+
"icon": "$(debug-stop)"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"command": "gitguardex.activeAgents.openSessionDiff",
|
|
56
|
+
"title": "Open Diff",
|
|
57
|
+
"icon": "$(diff)"
|
|
29
58
|
}
|
|
30
59
|
],
|
|
31
60
|
"views": {
|
|
@@ -37,12 +66,23 @@
|
|
|
37
66
|
}
|
|
38
67
|
]
|
|
39
68
|
},
|
|
69
|
+
"viewsWelcome": [
|
|
70
|
+
{
|
|
71
|
+
"view": "gitguardex.activeAgents",
|
|
72
|
+
"contents": "No active Guardex agents are visible in this workspace yet.\n\n[Start agent](command:gitguardex.activeAgents.startAgent)\n[Open guide](https://github.com/recodeee/gitguardex/blob/main/vscode/guardex-active-agents/README.md#quick-start)\n[Refresh](command:gitguardex.activeAgents.refresh)"
|
|
73
|
+
}
|
|
74
|
+
],
|
|
40
75
|
"menus": {
|
|
41
76
|
"view/title": [
|
|
77
|
+
{
|
|
78
|
+
"command": "gitguardex.activeAgents.commitSelectedSession",
|
|
79
|
+
"when": "view == gitguardex.activeAgents",
|
|
80
|
+
"group": "navigation@1"
|
|
81
|
+
},
|
|
42
82
|
{
|
|
43
83
|
"command": "gitguardex.activeAgents.refresh",
|
|
44
84
|
"when": "view == gitguardex.activeAgents",
|
|
45
|
-
"group": "navigation"
|
|
85
|
+
"group": "navigation@9"
|
|
46
86
|
}
|
|
47
87
|
],
|
|
48
88
|
"view/item/context": [
|
|
@@ -50,6 +90,26 @@
|
|
|
50
90
|
"command": "gitguardex.activeAgents.openWorktree",
|
|
51
91
|
"when": "view == gitguardex.activeAgents && viewItem == gitguardex.session",
|
|
52
92
|
"group": "inline"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"command": "gitguardex.activeAgents.finishSession",
|
|
96
|
+
"when": "view == gitguardex.activeAgents && viewItem == gitguardex.session",
|
|
97
|
+
"group": "inline"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"command": "gitguardex.activeAgents.syncSession",
|
|
101
|
+
"when": "view == gitguardex.activeAgents && viewItem == gitguardex.session",
|
|
102
|
+
"group": "inline"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"command": "gitguardex.activeAgents.stopSession",
|
|
106
|
+
"when": "view == gitguardex.activeAgents && viewItem == gitguardex.session",
|
|
107
|
+
"group": "inline"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"command": "gitguardex.activeAgents.openSessionDiff",
|
|
111
|
+
"when": "view == gitguardex.activeAgents && viewItem == gitguardex.session",
|
|
112
|
+
"group": "inline"
|
|
53
113
|
}
|
|
54
114
|
]
|
|
55
115
|
}
|
|
@@ -7,6 +7,23 @@ const SESSION_SCHEMA_VERSION = 1;
|
|
|
7
7
|
const LOCK_FILE_RELATIVE = path.join('.omx', 'state', 'agent-file-locks.json');
|
|
8
8
|
const MAX_CHANGED_PATH_PREVIEW = 3;
|
|
9
9
|
const ACTIVE_SESSIONS_FILTER_PREFIX = ACTIVE_SESSIONS_RELATIVE_DIR.split(path.sep).join('/');
|
|
10
|
+
const LOCK_FILE_FILTER_PATH = LOCK_FILE_RELATIVE.split(path.sep).join('/');
|
|
11
|
+
const IDLE_ACTIVITY_WINDOW_MS = 2 * 60 * 1000;
|
|
12
|
+
const STALLED_ACTIVITY_WINDOW_MS = 15 * 60 * 1000;
|
|
13
|
+
const BLOCKING_GIT_STATES = [
|
|
14
|
+
{
|
|
15
|
+
label: 'Rebase in progress.',
|
|
16
|
+
markers: ['REBASE_HEAD', 'rebase-apply', 'rebase-merge'],
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
label: 'Merge in progress.',
|
|
20
|
+
markers: ['MERGE_HEAD'],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
label: 'Cherry-pick in progress.',
|
|
24
|
+
markers: ['CHERRY_PICK_HEAD'],
|
|
25
|
+
},
|
|
26
|
+
];
|
|
10
27
|
|
|
11
28
|
function toNonEmptyString(value, fallback = '') {
|
|
12
29
|
const normalized = typeof value === 'string' ? value.trim() : String(value || '').trim();
|
|
@@ -18,6 +35,16 @@ function toPositiveInteger(value) {
|
|
|
18
35
|
return Number.isInteger(normalized) && normalized > 0 ? normalized : null;
|
|
19
36
|
}
|
|
20
37
|
|
|
38
|
+
function normalizeTaskMode(value) {
|
|
39
|
+
const normalized = toNonEmptyString(value).toLowerCase();
|
|
40
|
+
return normalized === 'caveman' || normalized === 'omx' ? normalized : '';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function normalizeOpenSpecTier(value) {
|
|
44
|
+
const normalized = toNonEmptyString(value).toUpperCase();
|
|
45
|
+
return ['T0', 'T1', 'T2', 'T3'].includes(normalized) ? normalized : '';
|
|
46
|
+
}
|
|
47
|
+
|
|
21
48
|
function sanitizeBranchForFile(branch) {
|
|
22
49
|
const normalized = toNonEmptyString(branch, 'session');
|
|
23
50
|
return normalized.replace(/[^a-zA-Z0-9._-]+/g, '__').replace(/^_+|_+$/g, '') || 'session';
|
|
@@ -45,6 +72,10 @@ function splitOutputLines(output) {
|
|
|
45
72
|
.filter((line) => line.trim().length > 0);
|
|
46
73
|
}
|
|
47
74
|
|
|
75
|
+
function normalizeRelativePath(value) {
|
|
76
|
+
return toNonEmptyString(value).replace(/\\/g, '/').replace(/^\.\//, '');
|
|
77
|
+
}
|
|
78
|
+
|
|
48
79
|
function runGitLines(worktreePath, args) {
|
|
49
80
|
try {
|
|
50
81
|
const output = cp.execFileSync('git', ['-C', worktreePath, ...args], {
|
|
@@ -150,7 +181,9 @@ function parseRepoChangeLine(repoRoot, line) {
|
|
|
150
181
|
|
|
151
182
|
const normalizedRelativePath = relativePath.split(path.sep).join('/');
|
|
152
183
|
if (
|
|
153
|
-
normalizedRelativePath ===
|
|
184
|
+
normalizedRelativePath === LOCK_FILE_FILTER_PATH
|
|
185
|
+
|| normalizedRelativePath.startsWith(`${LOCK_FILE_FILTER_PATH}/`)
|
|
186
|
+
|| normalizedRelativePath === ACTIVE_SESSIONS_FILTER_PREFIX
|
|
154
187
|
|| normalizedRelativePath.startsWith(`${ACTIVE_SESSIONS_FILTER_PREFIX}/`)
|
|
155
188
|
) {
|
|
156
189
|
return null;
|
|
@@ -181,37 +214,187 @@ function collectWorktreeChangedPaths(worktreePath) {
|
|
|
181
214
|
.sort((left, right) => left.localeCompare(right));
|
|
182
215
|
}
|
|
183
216
|
|
|
184
|
-
function
|
|
185
|
-
const
|
|
186
|
-
|
|
217
|
+
function resolveWorktreeGitDir(worktreePath) {
|
|
218
|
+
const gitPath = path.join(path.resolve(worktreePath), '.git');
|
|
219
|
+
try {
|
|
220
|
+
if (fs.statSync(gitPath).isDirectory()) {
|
|
221
|
+
return gitPath;
|
|
222
|
+
}
|
|
223
|
+
} catch (_error) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
const gitPointer = fs.readFileSync(gitPath, 'utf8');
|
|
229
|
+
const match = gitPointer.match(/^gitdir:\s*(.+)$/m);
|
|
230
|
+
if (match?.[1]) {
|
|
231
|
+
return path.resolve(worktreePath, match[1].trim());
|
|
232
|
+
}
|
|
233
|
+
} catch (_error) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function deriveBlockingGitLabel(worktreePath) {
|
|
241
|
+
const gitDir = resolveWorktreeGitDir(worktreePath);
|
|
242
|
+
if (!gitDir) {
|
|
243
|
+
return '';
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
for (const blockingState of BLOCKING_GIT_STATES) {
|
|
247
|
+
if (blockingState.markers.some((marker) => fs.existsSync(path.join(gitDir, marker)))) {
|
|
248
|
+
return blockingState.label;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return '';
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function collectWorktreeTrackedPaths(worktreePath) {
|
|
256
|
+
const trackedPaths = runGitLines(worktreePath, ['ls-files', '--cached', '--others', '--exclude-standard']);
|
|
257
|
+
if (!trackedPaths) {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return [...new Set(trackedPaths)]
|
|
262
|
+
.filter(Boolean)
|
|
263
|
+
.sort((left, right) => left.localeCompare(right));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function deriveLatestWorktreeFileActivity(worktreePath) {
|
|
267
|
+
const trackedPaths = collectWorktreeTrackedPaths(worktreePath);
|
|
268
|
+
if (!trackedPaths) {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
let latestMtimeMs = null;
|
|
273
|
+
for (const relativePath of trackedPaths) {
|
|
274
|
+
const absolutePath = path.join(worktreePath, relativePath);
|
|
275
|
+
try {
|
|
276
|
+
const stats = fs.statSync(absolutePath);
|
|
277
|
+
if (!stats.isFile() || !Number.isFinite(stats.mtimeMs)) {
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
latestMtimeMs = latestMtimeMs === null
|
|
281
|
+
? stats.mtimeMs
|
|
282
|
+
: Math.max(latestMtimeMs, stats.mtimeMs);
|
|
283
|
+
} catch (_error) {
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return latestMtimeMs;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function deriveSessionActivity(session, options = {}) {
|
|
292
|
+
const now = Number.isFinite(options.now) ? options.now : Date.now();
|
|
293
|
+
const blockingLabel = deriveBlockingGitLabel(session.worktreePath);
|
|
294
|
+
if (blockingLabel) {
|
|
187
295
|
return {
|
|
188
|
-
activityKind: '
|
|
189
|
-
activityLabel: '
|
|
296
|
+
activityKind: 'blocked',
|
|
297
|
+
activityLabel: 'blocked',
|
|
298
|
+
activityCountLabel: '',
|
|
299
|
+
activitySummary: blockingLabel,
|
|
300
|
+
changeCount: 0,
|
|
301
|
+
changedPaths: [],
|
|
302
|
+
pidAlive: isPidAlive(session.pid),
|
|
303
|
+
lastFileActivityAt: '',
|
|
304
|
+
lastFileActivityLabel: '',
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const pidAlive = isPidAlive(session.pid);
|
|
309
|
+
if (!pidAlive) {
|
|
310
|
+
return {
|
|
311
|
+
activityKind: 'dead',
|
|
312
|
+
activityLabel: 'dead',
|
|
313
|
+
activityCountLabel: '',
|
|
314
|
+
activitySummary: 'Recorded PID is not alive.',
|
|
315
|
+
changeCount: 0,
|
|
316
|
+
changedPaths: [],
|
|
317
|
+
pidAlive,
|
|
318
|
+
lastFileActivityAt: '',
|
|
319
|
+
lastFileActivityLabel: '',
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const worktreeChangedPaths = collectWorktreeChangedPaths(session.worktreePath);
|
|
324
|
+
if (!worktreeChangedPaths) {
|
|
325
|
+
return {
|
|
326
|
+
activityKind: 'idle',
|
|
327
|
+
activityLabel: 'idle',
|
|
190
328
|
activityCountLabel: '',
|
|
191
329
|
activitySummary: 'Worktree activity unavailable.',
|
|
192
330
|
changeCount: 0,
|
|
193
331
|
changedPaths: [],
|
|
332
|
+
pidAlive,
|
|
333
|
+
lastFileActivityAt: '',
|
|
334
|
+
lastFileActivityLabel: '',
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (worktreeChangedPaths.length > 0) {
|
|
339
|
+
const changedPaths = [...new Set(worktreeChangedPaths
|
|
340
|
+
.map((relativePath) => normalizeRelativePath(
|
|
341
|
+
path.relative(session.repoRoot, path.resolve(session.worktreePath, relativePath)),
|
|
342
|
+
))
|
|
343
|
+
.filter(Boolean))]
|
|
344
|
+
.sort((left, right) => left.localeCompare(right));
|
|
345
|
+
|
|
346
|
+
return {
|
|
347
|
+
activityKind: 'working',
|
|
348
|
+
activityLabel: 'working',
|
|
349
|
+
activityCountLabel: formatFileCount(worktreeChangedPaths.length),
|
|
350
|
+
activitySummary: previewChangedPaths(worktreeChangedPaths),
|
|
351
|
+
changeCount: worktreeChangedPaths.length,
|
|
352
|
+
changedPaths,
|
|
353
|
+
pidAlive,
|
|
354
|
+
lastFileActivityAt: '',
|
|
355
|
+
lastFileActivityLabel: '',
|
|
194
356
|
};
|
|
195
357
|
}
|
|
196
358
|
|
|
197
|
-
|
|
359
|
+
const latestFileActivityMs = deriveLatestWorktreeFileActivity(session.worktreePath);
|
|
360
|
+
const lastFileActivityAt = Number.isFinite(latestFileActivityMs)
|
|
361
|
+
? new Date(latestFileActivityMs).toISOString()
|
|
362
|
+
: '';
|
|
363
|
+
const lastFileActivityLabel = lastFileActivityAt
|
|
364
|
+
? formatElapsedFrom(lastFileActivityAt, now)
|
|
365
|
+
: '';
|
|
366
|
+
const lastFileActivityAgeMs = Number.isFinite(latestFileActivityMs)
|
|
367
|
+
? Math.max(0, now - latestFileActivityMs)
|
|
368
|
+
: null;
|
|
369
|
+
|
|
370
|
+
if (lastFileActivityAgeMs !== null && lastFileActivityAgeMs > STALLED_ACTIVITY_WINDOW_MS) {
|
|
198
371
|
return {
|
|
199
|
-
activityKind: '
|
|
200
|
-
activityLabel: '
|
|
372
|
+
activityKind: 'stalled',
|
|
373
|
+
activityLabel: 'stalled',
|
|
201
374
|
activityCountLabel: '',
|
|
202
|
-
activitySummary:
|
|
375
|
+
activitySummary: `Worktree clean. No file activity for ${lastFileActivityLabel}.`,
|
|
203
376
|
changeCount: 0,
|
|
204
377
|
changedPaths: [],
|
|
378
|
+
pidAlive,
|
|
379
|
+
lastFileActivityAt,
|
|
380
|
+
lastFileActivityLabel,
|
|
205
381
|
};
|
|
206
382
|
}
|
|
207
383
|
|
|
208
384
|
return {
|
|
209
|
-
activityKind: '
|
|
210
|
-
activityLabel: '
|
|
211
|
-
activityCountLabel:
|
|
212
|
-
activitySummary:
|
|
213
|
-
|
|
214
|
-
|
|
385
|
+
activityKind: 'idle',
|
|
386
|
+
activityLabel: 'idle',
|
|
387
|
+
activityCountLabel: '',
|
|
388
|
+
activitySummary: lastFileActivityAgeMs !== null && lastFileActivityAgeMs <= IDLE_ACTIVITY_WINDOW_MS
|
|
389
|
+
? `Worktree clean. Recent file activity ${lastFileActivityLabel} ago.`
|
|
390
|
+
: lastFileActivityLabel
|
|
391
|
+
? `Worktree clean. Last file activity ${lastFileActivityLabel} ago.`
|
|
392
|
+
: 'Worktree clean.',
|
|
393
|
+
changeCount: 0,
|
|
394
|
+
changedPaths: [],
|
|
395
|
+
pidAlive,
|
|
396
|
+
lastFileActivityAt,
|
|
397
|
+
lastFileActivityLabel,
|
|
215
398
|
};
|
|
216
399
|
}
|
|
217
400
|
|
|
@@ -247,6 +430,9 @@ function buildSessionRecord(input) {
|
|
|
247
430
|
worktreePath,
|
|
248
431
|
pid,
|
|
249
432
|
cliName: toNonEmptyString(input.cliName, 'codex'),
|
|
433
|
+
taskMode: normalizeTaskMode(input.taskMode),
|
|
434
|
+
openspecTier: normalizeOpenSpecTier(input.openspecTier),
|
|
435
|
+
taskRoutingReason: toNonEmptyString(input.taskRoutingReason),
|
|
250
436
|
startedAt: startedAt.toISOString(),
|
|
251
437
|
};
|
|
252
438
|
}
|
|
@@ -283,9 +469,13 @@ function normalizeSessionRecord(input, options = {}) {
|
|
|
283
469
|
worktreePath: path.resolve(worktreePath),
|
|
284
470
|
pid,
|
|
285
471
|
cliName: toNonEmptyString(input.cliName, 'codex'),
|
|
472
|
+
taskMode: normalizeTaskMode(input.taskMode),
|
|
473
|
+
openspecTier: normalizeOpenSpecTier(input.openspecTier),
|
|
474
|
+
taskRoutingReason: toNonEmptyString(input.taskRoutingReason),
|
|
286
475
|
startedAt: startedAt.toISOString(),
|
|
287
476
|
filePath: toNonEmptyString(options.filePath),
|
|
288
477
|
label: deriveSessionLabel(branch, worktreePath),
|
|
478
|
+
changedPaths: [],
|
|
289
479
|
};
|
|
290
480
|
}
|
|
291
481
|
|
|
@@ -357,7 +547,7 @@ function readActiveSessions(repoRoot, options = {}) {
|
|
|
357
547
|
}
|
|
358
548
|
|
|
359
549
|
normalized.elapsedLabel = formatElapsedFrom(normalized.startedAt, now);
|
|
360
|
-
Object.assign(normalized, deriveSessionActivity(normalized));
|
|
550
|
+
Object.assign(normalized, deriveSessionActivity(normalized, { now }));
|
|
361
551
|
sessions.push(normalized);
|
|
362
552
|
}
|
|
363
553
|
|
|
@@ -390,6 +580,9 @@ module.exports = {
|
|
|
390
580
|
activeSessionsDirForRepo,
|
|
391
581
|
buildSessionRecord,
|
|
392
582
|
collectWorktreeChangedPaths,
|
|
583
|
+
collectWorktreeTrackedPaths,
|
|
584
|
+
deriveBlockingGitLabel,
|
|
585
|
+
deriveLatestWorktreeFileActivity,
|
|
393
586
|
deriveSessionLabel,
|
|
394
587
|
deriveSessionActivity,
|
|
395
588
|
formatElapsedFrom,
|
|
@@ -401,6 +594,7 @@ module.exports = {
|
|
|
401
594
|
readActiveSessions,
|
|
402
595
|
readRepoChanges,
|
|
403
596
|
deriveRepoChangeStatus,
|
|
597
|
+
resolveWorktreeGitDir,
|
|
404
598
|
sanitizeBranchForFile,
|
|
405
599
|
sessionFileNameForBranch,
|
|
406
600
|
sessionFilePathForBranch,
|