@imdeadpool/guardex 7.0.26 → 7.0.31
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 +38 -7
- package/SECURITY.md +1 -1
- package/package.json +4 -4
- package/src/cli/main.js +279 -23
- package/src/context.js +64 -21
- package/src/doctor/index.js +42 -20
- package/src/finish/index.js +1 -0
- package/src/output/index.js +151 -15
- package/src/toolchain/index.js +6 -0
- package/templates/scripts/agent-branch-finish.sh +48 -24
- package/templates/scripts/agent-branch-start.sh +29 -5
- package/templates/scripts/codex-agent.sh +16 -1
- package/templates/vscode/guardex-active-agents/extension.js +126 -5
- package/templates/vscode/guardex-active-agents/package.json +17 -7
- package/templates/vscode/guardex-active-agents/session-schema.js +23 -5
|
@@ -3,11 +3,13 @@ const path = require('node:path');
|
|
|
3
3
|
const cp = require('node:child_process');
|
|
4
4
|
const vscode = require('vscode');
|
|
5
5
|
const {
|
|
6
|
+
clearWorktreeActivityCache,
|
|
6
7
|
formatElapsedFrom,
|
|
7
8
|
readActiveSessions,
|
|
8
9
|
readRepoChanges,
|
|
9
10
|
readSessionInspectData,
|
|
10
11
|
sanitizeBranchForFile,
|
|
12
|
+
sessionFilePathForBranch,
|
|
11
13
|
} = require('./session-schema.js');
|
|
12
14
|
|
|
13
15
|
const SESSION_DECORATION_SCHEME = 'gitguardex-agent';
|
|
@@ -54,6 +56,7 @@ const MANAGED_REPO_SCAN_IGNORED_FOLDERS = [
|
|
|
54
56
|
const SESSION_ACTIVITY_GROUPS = [
|
|
55
57
|
{ kind: 'blocked', label: 'BLOCKED' },
|
|
56
58
|
{ kind: 'working', label: 'WORKING NOW' },
|
|
59
|
+
{ kind: 'finished', label: 'FINISHED' },
|
|
57
60
|
{ kind: 'idle', label: 'THINKING' },
|
|
58
61
|
{ kind: 'stalled', label: 'STALLED' },
|
|
59
62
|
{ kind: 'dead', label: 'DEAD' },
|
|
@@ -61,10 +64,12 @@ const SESSION_ACTIVITY_GROUPS = [
|
|
|
61
64
|
const SESSION_ACTIVITY_ICON_IDS = {
|
|
62
65
|
blocked: 'warning',
|
|
63
66
|
working: 'loading~spin',
|
|
67
|
+
finished: 'pass-filled',
|
|
64
68
|
idle: 'comment-discussion',
|
|
65
69
|
stalled: 'clock',
|
|
66
70
|
dead: 'error',
|
|
67
71
|
};
|
|
72
|
+
const DISMISSABLE_SESSION_ACTIVITY_KINDS = new Set(['stalled', 'dead']);
|
|
68
73
|
const SESSION_PROVIDER_BRANDS = {
|
|
69
74
|
openai: {
|
|
70
75
|
id: 'openai',
|
|
@@ -105,6 +110,10 @@ function iconColorId(iconId) {
|
|
|
105
110
|
return 'terminal.ansiCyan';
|
|
106
111
|
case 'list-tree':
|
|
107
112
|
return 'terminal.ansiBlue';
|
|
113
|
+
case 'pass-filled':
|
|
114
|
+
case 'pass':
|
|
115
|
+
case 'check':
|
|
116
|
+
return 'testing.iconPassed';
|
|
108
117
|
default:
|
|
109
118
|
return '';
|
|
110
119
|
}
|
|
@@ -465,9 +474,15 @@ function agentBadgeFromBranch(branch) {
|
|
|
465
474
|
|
|
466
475
|
function buildActiveAgentsStatusSummary(summary) {
|
|
467
476
|
const workingCount = summary?.workingCount || 0;
|
|
477
|
+
const finishedCount = summary?.finishedCount || 0;
|
|
468
478
|
const idleCount = summary?.idleCount || 0;
|
|
469
|
-
if (workingCount > 0 || idleCount > 0) {
|
|
470
|
-
|
|
479
|
+
if (workingCount > 0 || finishedCount > 0 || idleCount > 0) {
|
|
480
|
+
const parts = [`${workingCount} working`];
|
|
481
|
+
if (finishedCount > 0) {
|
|
482
|
+
parts.push(`${finishedCount} finished`);
|
|
483
|
+
}
|
|
484
|
+
parts.push(`${idleCount} idle`);
|
|
485
|
+
return `$(git-branch) ${parts.join(' · ')}`;
|
|
471
486
|
}
|
|
472
487
|
return `$(git-branch) ${formatCountLabel(summary?.sessionCount || 0, 'tracked session')}`;
|
|
473
488
|
}
|
|
@@ -487,6 +502,7 @@ function buildActiveAgentsStatusTooltip(selectedSession, summary) {
|
|
|
487
502
|
return [
|
|
488
503
|
formatCountLabel(activeCount, 'active agent'),
|
|
489
504
|
formatCountLabel(summary?.workingCount || 0, 'working now session', 'working now sessions'),
|
|
505
|
+
formatCountLabel(summary?.finishedCount || 0, 'finished session'),
|
|
490
506
|
formatCountLabel(summary?.idleCount || 0, 'idle session'),
|
|
491
507
|
formatCountLabel(summary?.unassignedChangeCount || 0, 'unassigned change'),
|
|
492
508
|
formatCountLabel(summary?.lockedFileCount || 0, 'locked file'),
|
|
@@ -531,6 +547,10 @@ function countWorkingSessions(sessions) {
|
|
|
531
547
|
)).length;
|
|
532
548
|
}
|
|
533
549
|
|
|
550
|
+
function countFinishedSessions(sessions) {
|
|
551
|
+
return sessions.filter((session) => session.activityKind === 'finished').length;
|
|
552
|
+
}
|
|
553
|
+
|
|
534
554
|
function countIdleSessions(sessions) {
|
|
535
555
|
return sessions.filter((session) => (
|
|
536
556
|
session.activityKind === 'idle' || session.activityKind === 'stalled'
|
|
@@ -568,6 +588,9 @@ function sessionFreshnessLabel(session, now = Date.now()) {
|
|
|
568
588
|
if (session.activityKind === 'blocked') {
|
|
569
589
|
return 'Needs attention';
|
|
570
590
|
}
|
|
591
|
+
if (session.activityKind === 'finished') {
|
|
592
|
+
return 'Finished';
|
|
593
|
+
}
|
|
571
594
|
if (session.activityKind === 'stalled') {
|
|
572
595
|
return 'Possibly stale';
|
|
573
596
|
}
|
|
@@ -595,6 +618,8 @@ function sessionStatusLabel(session) {
|
|
|
595
618
|
return 'Blocked';
|
|
596
619
|
case 'working':
|
|
597
620
|
return 'Working';
|
|
621
|
+
case 'finished':
|
|
622
|
+
return 'Finished';
|
|
598
623
|
case 'idle':
|
|
599
624
|
return 'Idle';
|
|
600
625
|
case 'stalled':
|
|
@@ -693,6 +718,14 @@ function changeRiskBadges(change) {
|
|
|
693
718
|
].filter(Boolean));
|
|
694
719
|
}
|
|
695
720
|
|
|
721
|
+
function changeNeedsWarningIcon(change) {
|
|
722
|
+
return Boolean(
|
|
723
|
+
change?.protectedBranch
|
|
724
|
+
|| change?.hasForeignLock
|
|
725
|
+
|| (!change?.hasForeignLock && change?.lockOwnerBranch),
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
|
|
696
729
|
function buildSessionCardDescription(session) {
|
|
697
730
|
const provider = resolveSessionProvider(session);
|
|
698
731
|
const statusAgentLabel = `${sessionStatusLabel(session)}: ${session.agentName || 'agent'}`;
|
|
@@ -793,6 +826,7 @@ function buildWorktreeBranchDescription(sessions) {
|
|
|
793
826
|
function buildOverviewDescription(summary) {
|
|
794
827
|
return [
|
|
795
828
|
formatCountLabel(summary?.workingCount || 0, 'working agent'),
|
|
829
|
+
formatCountLabel(summary?.finishedCount || 0, 'finished agent'),
|
|
796
830
|
formatCountLabel(summary?.idleCount || 0, 'idle agent'),
|
|
797
831
|
formatCountLabel(summary?.unassignedChangeCount || 0, 'unassigned change'),
|
|
798
832
|
formatCountLabel(summary?.lockedFileCount || 0, 'locked file'),
|
|
@@ -912,6 +946,9 @@ function workingSessionSortKey(session) {
|
|
|
912
946
|
if (session.deltaLabel === 'New') {
|
|
913
947
|
return 3;
|
|
914
948
|
}
|
|
949
|
+
if (session.activityKind === 'finished') {
|
|
950
|
+
return 5;
|
|
951
|
+
}
|
|
915
952
|
return 4;
|
|
916
953
|
}
|
|
917
954
|
|
|
@@ -1289,7 +1326,7 @@ class SessionItem extends vscode.TreeItem {
|
|
|
1289
1326
|
: buildSessionCardDescription(session);
|
|
1290
1327
|
this.tooltip = buildSessionTooltip(session, this.description);
|
|
1291
1328
|
this.iconPath = themeIcon(resolveSessionActivityIconId(session.activityKind));
|
|
1292
|
-
this.contextValue =
|
|
1329
|
+
this.contextValue = sessionContextValue(session);
|
|
1293
1330
|
this.command = {
|
|
1294
1331
|
command: 'gitguardex.activeAgents.openWorktree',
|
|
1295
1332
|
title: 'Open Agent Worktree',
|
|
@@ -1298,6 +1335,35 @@ class SessionItem extends vscode.TreeItem {
|
|
|
1298
1335
|
}
|
|
1299
1336
|
}
|
|
1300
1337
|
|
|
1338
|
+
function sessionContextValue(session) {
|
|
1339
|
+
const activityKind = typeof session?.activityKind === 'string' ? session.activityKind.trim() : '';
|
|
1340
|
+
return activityKind
|
|
1341
|
+
? `gitguardex.session.${activityKind}`
|
|
1342
|
+
: 'gitguardex.session';
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
function canDismissSession(session) {
|
|
1346
|
+
return DISMISSABLE_SESSION_ACTIVITY_KINDS.has(session?.activityKind);
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
function buildDismissSessionDetail(session, statePath) {
|
|
1350
|
+
const repoRoot = typeof session?.repoRoot === 'string' ? session.repoRoot.trim() : '';
|
|
1351
|
+
const relativeStatePath = repoRoot
|
|
1352
|
+
? path.relative(repoRoot, statePath) || path.basename(statePath)
|
|
1353
|
+
: path.basename(statePath);
|
|
1354
|
+
const detailParts = [
|
|
1355
|
+
`Remove ${relativeStatePath} and hide this session from Active Agents.`,
|
|
1356
|
+
];
|
|
1357
|
+
|
|
1358
|
+
if (session?.activityKind === 'stalled') {
|
|
1359
|
+
detailParts.push('This dismisses the stale sidebar row only; use Stop if you want to interrupt a live agent.');
|
|
1360
|
+
} else {
|
|
1361
|
+
detailParts.push('This clears the stale session record from the sidebar.');
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
return detailParts.join(' ');
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1301
1367
|
class FolderItem extends vscode.TreeItem {
|
|
1302
1368
|
constructor(label, relativePath, items, options = {}) {
|
|
1303
1369
|
super(
|
|
@@ -1845,6 +1911,51 @@ async function stopSession(session, refresh) {
|
|
|
1845
1911
|
}
|
|
1846
1912
|
}
|
|
1847
1913
|
|
|
1914
|
+
async function dismissSession(session, refresh) {
|
|
1915
|
+
if (!canDismissSession(session)) {
|
|
1916
|
+
showSessionMessage('Only stalled or dead sessions can be dismissed.');
|
|
1917
|
+
return;
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
const repoRoot = typeof session?.repoRoot === 'string' ? session.repoRoot.trim() : '';
|
|
1921
|
+
if (!repoRoot) {
|
|
1922
|
+
showSessionMessage('Cannot dismiss session: missing repo root.');
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1925
|
+
if (!session?.branch) {
|
|
1926
|
+
showSessionMessage('Cannot dismiss session: missing branch name.');
|
|
1927
|
+
return;
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
const statePath = sessionFilePathForBranch(repoRoot, session.branch);
|
|
1931
|
+
if (!fs.existsSync(statePath)) {
|
|
1932
|
+
clearWorktreeActivityCache(session.worktreePath);
|
|
1933
|
+
refresh();
|
|
1934
|
+
showSessionMessage(`Session record already gone for ${sessionDisplayLabel(session)}.`);
|
|
1935
|
+
return;
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
const confirmed = await vscode.window.showWarningMessage(
|
|
1939
|
+
`Dismiss ${sessionDisplayLabel(session)}?`,
|
|
1940
|
+
{
|
|
1941
|
+
modal: true,
|
|
1942
|
+
detail: buildDismissSessionDetail(session, statePath),
|
|
1943
|
+
},
|
|
1944
|
+
'Dismiss',
|
|
1945
|
+
);
|
|
1946
|
+
if (confirmed !== 'Dismiss') {
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
try {
|
|
1951
|
+
fs.unlinkSync(statePath);
|
|
1952
|
+
clearWorktreeActivityCache(session.worktreePath);
|
|
1953
|
+
refresh();
|
|
1954
|
+
} catch (error) {
|
|
1955
|
+
showSessionMessage(`Failed to dismiss session ${sessionDisplayLabel(session)}: ${error.message}`);
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1848
1959
|
function readGitDirPath(targetPath) {
|
|
1849
1960
|
const normalizedTargetPath = typeof targetPath === 'string' ? targetPath.trim() : '';
|
|
1850
1961
|
if (!normalizedTargetPath) {
|
|
@@ -2405,6 +2516,7 @@ function buildRepoOverview(sessions, unassignedChanges, lockEntries) {
|
|
|
2405
2516
|
return {
|
|
2406
2517
|
sessionCount: sessions.length,
|
|
2407
2518
|
workingCount: countWorkingSessions(sessions),
|
|
2519
|
+
finishedCount: countFinishedSessions(sessions),
|
|
2408
2520
|
idleCount: countIdleSessions(sessions),
|
|
2409
2521
|
unassignedChangeCount: (unassignedChanges || []).length,
|
|
2410
2522
|
lockedFileCount: Array.isArray(lockEntries) ? lockEntries.length : 0,
|
|
@@ -2719,7 +2831,7 @@ function buildUnassignedChangeNodes(changes) {
|
|
|
2719
2831
|
return sortUnassignedChanges(changes).map((change) => new ChangeItem(change, {
|
|
2720
2832
|
label: compactRelativePath(change.relativePath),
|
|
2721
2833
|
description: buildUnassignedChangeDescription(change),
|
|
2722
|
-
iconId:
|
|
2834
|
+
iconId: changeNeedsWarningIcon(change) ? 'warning' : undefined,
|
|
2723
2835
|
}));
|
|
2724
2836
|
}
|
|
2725
2837
|
|
|
@@ -2775,6 +2887,7 @@ class ActiveAgentsProvider {
|
|
|
2775
2887
|
this.viewSummary = {
|
|
2776
2888
|
sessionCount: 0,
|
|
2777
2889
|
workingCount: 0,
|
|
2890
|
+
finishedCount: 0,
|
|
2778
2891
|
idleCount: 0,
|
|
2779
2892
|
unassignedChangeCount: 0,
|
|
2780
2893
|
lockedFileCount: 0,
|
|
@@ -2793,6 +2906,7 @@ class ActiveAgentsProvider {
|
|
|
2793
2906
|
this.updateViewState({
|
|
2794
2907
|
sessionCount: 0,
|
|
2795
2908
|
workingCount: 0,
|
|
2909
|
+
finishedCount: 0,
|
|
2796
2910
|
idleCount: 0,
|
|
2797
2911
|
unassignedChangeCount: 0,
|
|
2798
2912
|
lockedFileCount: 0,
|
|
@@ -2915,6 +3029,10 @@ class ActiveAgentsProvider {
|
|
|
2915
3029
|
const summary = {
|
|
2916
3030
|
sessionCount: repoEntries.reduce((total, entry) => total + entry.sessions.length, 0),
|
|
2917
3031
|
workingCount: repoEntries.reduce((total, entry) => total + entry.overview.workingCount, 0),
|
|
3032
|
+
finishedCount: repoEntries.reduce(
|
|
3033
|
+
(total, entry) => total + (entry.overview.finishedCount || 0),
|
|
3034
|
+
0,
|
|
3035
|
+
),
|
|
2918
3036
|
idleCount: repoEntries.reduce((total, entry) => total + entry.overview.idleCount, 0),
|
|
2919
3037
|
unassignedChangeCount: repoEntries.reduce(
|
|
2920
3038
|
(total, entry) => total + entry.overview.unassignedChangeCount,
|
|
@@ -2964,6 +3082,7 @@ class ActiveAgentsProvider {
|
|
|
2964
3082
|
}),
|
|
2965
3083
|
], {
|
|
2966
3084
|
description: '1',
|
|
3085
|
+
collapsedState: vscode.TreeItemCollapsibleState.Collapsed,
|
|
2967
3086
|
}),
|
|
2968
3087
|
];
|
|
2969
3088
|
|
|
@@ -2971,6 +3090,7 @@ class ActiveAgentsProvider {
|
|
|
2971
3090
|
if (workingNowItems.length > 0) {
|
|
2972
3091
|
sectionItems.push(new SectionItem('Working now', workingNowItems, {
|
|
2973
3092
|
description: String(workingNowItems.length),
|
|
3093
|
+
collapsedState: vscode.TreeItemCollapsibleState.Collapsed,
|
|
2974
3094
|
iconId: 'loading~spin',
|
|
2975
3095
|
}));
|
|
2976
3096
|
}
|
|
@@ -3011,7 +3131,7 @@ class ActiveAgentsProvider {
|
|
|
3011
3131
|
if (advancedItems.length > 0) {
|
|
3012
3132
|
sectionItems.push(new SectionItem('Advanced details', advancedItems, {
|
|
3013
3133
|
description: String(advancedItems.length),
|
|
3014
|
-
collapsedState: vscode.TreeItemCollapsibleState.
|
|
3134
|
+
collapsedState: vscode.TreeItemCollapsibleState.Expanded,
|
|
3015
3135
|
iconId: 'list-tree',
|
|
3016
3136
|
}));
|
|
3017
3137
|
}
|
|
@@ -3358,6 +3478,7 @@ function activate(context) {
|
|
|
3358
3478
|
vscode.commands.registerCommand('gitguardex.activeAgents.finishSession', finishSession),
|
|
3359
3479
|
vscode.commands.registerCommand('gitguardex.activeAgents.syncSession', syncSession),
|
|
3360
3480
|
vscode.commands.registerCommand('gitguardex.activeAgents.stopSession', (session) => stopSession(session, refresh)),
|
|
3481
|
+
vscode.commands.registerCommand('gitguardex.activeAgents.dismissSession', (session) => dismissSession(session, refresh)),
|
|
3361
3482
|
vscode.workspace.onDidChangeWorkspaceFolders(handleWorkspaceFoldersChanged),
|
|
3362
3483
|
activeSessionsWatcher,
|
|
3363
3484
|
lockWatcher,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"displayName": "GitGuardex Active Agents",
|
|
4
4
|
"description": "Shows live Guardex sandbox sessions and repo changes in a dedicated VS Code Active Agents sidebar.",
|
|
5
5
|
"publisher": "recodeee",
|
|
6
|
-
"version": "0.0.
|
|
6
|
+
"version": "0.0.19",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"icon": "icon.png",
|
|
9
9
|
"engines": {
|
|
@@ -65,6 +65,11 @@
|
|
|
65
65
|
"title": "Stop",
|
|
66
66
|
"icon": "$(debug-stop)"
|
|
67
67
|
},
|
|
68
|
+
{
|
|
69
|
+
"command": "gitguardex.activeAgents.dismissSession",
|
|
70
|
+
"title": "Dismiss",
|
|
71
|
+
"icon": "$(trash)"
|
|
72
|
+
},
|
|
68
73
|
{
|
|
69
74
|
"command": "gitguardex.activeAgents.showSessionTerminal",
|
|
70
75
|
"title": "Show Terminal",
|
|
@@ -125,32 +130,37 @@
|
|
|
125
130
|
"view/item/context": [
|
|
126
131
|
{
|
|
127
132
|
"command": "gitguardex.activeAgents.openWorktree",
|
|
128
|
-
"when": "view == gitguardex.activeAgents && viewItem
|
|
133
|
+
"when": "view == gitguardex.activeAgents && viewItem =~ /^gitguardex\\.session(\\.|$)/",
|
|
129
134
|
"group": "inline"
|
|
130
135
|
},
|
|
131
136
|
{
|
|
132
137
|
"command": "gitguardex.activeAgents.inspect",
|
|
133
|
-
"when": "view == gitguardex.activeAgents && viewItem
|
|
138
|
+
"when": "view == gitguardex.activeAgents && viewItem =~ /^gitguardex\\.session(\\.|$)/",
|
|
134
139
|
"group": "inline"
|
|
135
140
|
},
|
|
136
141
|
{
|
|
137
142
|
"command": "gitguardex.activeAgents.showSessionTerminal",
|
|
138
|
-
"when": "view == gitguardex.activeAgents && viewItem
|
|
143
|
+
"when": "view == gitguardex.activeAgents && viewItem =~ /^gitguardex\\.session(\\.|$)/",
|
|
139
144
|
"group": "inline"
|
|
140
145
|
},
|
|
141
146
|
{
|
|
142
147
|
"command": "gitguardex.activeAgents.finishSession",
|
|
143
|
-
"when": "view == gitguardex.activeAgents && viewItem
|
|
148
|
+
"when": "view == gitguardex.activeAgents && viewItem =~ /^gitguardex\\.session(\\.|$)/",
|
|
144
149
|
"group": "inline"
|
|
145
150
|
},
|
|
146
151
|
{
|
|
147
152
|
"command": "gitguardex.activeAgents.syncSession",
|
|
148
|
-
"when": "view == gitguardex.activeAgents && viewItem
|
|
153
|
+
"when": "view == gitguardex.activeAgents && viewItem =~ /^gitguardex\\.session(\\.|$)/",
|
|
149
154
|
"group": "inline"
|
|
150
155
|
},
|
|
151
156
|
{
|
|
152
157
|
"command": "gitguardex.activeAgents.stopSession",
|
|
153
|
-
"when": "view == gitguardex.activeAgents && viewItem
|
|
158
|
+
"when": "view == gitguardex.activeAgents && viewItem =~ /^gitguardex\\.session(\\.|$)/",
|
|
159
|
+
"group": "inline"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"command": "gitguardex.activeAgents.dismissSession",
|
|
163
|
+
"when": "view == gitguardex.activeAgents && viewItem =~ /^gitguardex\\.session\\.(stalled|dead)$/",
|
|
154
164
|
"group": "inline"
|
|
155
165
|
}
|
|
156
166
|
]
|
|
@@ -700,17 +700,35 @@ function deriveSessionActivity(session, options = {}) {
|
|
|
700
700
|
.filter(Boolean))]
|
|
701
701
|
.sort((left, right) => left.localeCompare(right));
|
|
702
702
|
|
|
703
|
+
const workingLatestFileActivityMs = deriveLatestWorktreeFileActivity(session.worktreePath, {
|
|
704
|
+
now,
|
|
705
|
+
useCache: options.useCache,
|
|
706
|
+
});
|
|
707
|
+
const workingLastFileActivityAt = Number.isFinite(workingLatestFileActivityMs)
|
|
708
|
+
? new Date(workingLatestFileActivityMs).toISOString()
|
|
709
|
+
: '';
|
|
710
|
+
const workingLastFileActivityLabel = workingLastFileActivityAt
|
|
711
|
+
? formatElapsedFrom(workingLastFileActivityAt, now)
|
|
712
|
+
: '';
|
|
713
|
+
const workingFileActivityAgeMs = Number.isFinite(workingLatestFileActivityMs)
|
|
714
|
+
? Math.max(0, now - workingLatestFileActivityMs)
|
|
715
|
+
: null;
|
|
716
|
+
const isFinishedUncommitted = workingFileActivityAgeMs !== null
|
|
717
|
+
&& workingFileActivityAgeMs > IDLE_ACTIVITY_WINDOW_MS;
|
|
718
|
+
|
|
703
719
|
return {
|
|
704
|
-
activityKind: 'working',
|
|
705
|
-
activityLabel: 'working',
|
|
720
|
+
activityKind: isFinishedUncommitted ? 'finished' : 'working',
|
|
721
|
+
activityLabel: isFinishedUncommitted ? 'finished' : 'working',
|
|
706
722
|
activityCountLabel: formatFileCount(worktreeChangedPaths.length),
|
|
707
|
-
activitySummary:
|
|
723
|
+
activitySummary: isFinishedUncommitted && workingLastFileActivityLabel
|
|
724
|
+
? `${previewChangedPaths(worktreeChangedPaths)} · idle ${workingLastFileActivityLabel}`
|
|
725
|
+
: previewChangedPaths(worktreeChangedPaths),
|
|
708
726
|
changeCount: worktreeChangedPaths.length,
|
|
709
727
|
changedPaths,
|
|
710
728
|
worktreeChangedPaths: worktreeRelativePaths,
|
|
711
729
|
pidAlive,
|
|
712
|
-
lastFileActivityAt:
|
|
713
|
-
lastFileActivityLabel:
|
|
730
|
+
lastFileActivityAt: workingLastFileActivityAt,
|
|
731
|
+
lastFileActivityLabel: workingLastFileActivityLabel,
|
|
714
732
|
};
|
|
715
733
|
}
|
|
716
734
|
|