@gitkraken/core-gitlens 0.6.0 → 0.7.0
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/CHANGELOG.md +15 -1
- package/README.md +9 -2
- package/dist/_virtual/@oxc-project+runtime/helpers/esm/decorate.js +9 -0
- package/dist/_virtual/@oxc-project+runtime/helpers/esm/usingCtx.js +57 -0
- package/dist/git/cache.d.ts +297 -297
- package/dist/git/cache.js +948 -1276
- package/dist/git/cache.js.map +1 -1
- package/dist/git/context.d.ts +202 -197
- package/dist/git/context.js +12 -8
- package/dist/git/context.js.map +1 -1
- package/dist/git/errors.d.ts +268 -281
- package/dist/git/errors.js +928 -706
- package/dist/git/errors.js.map +1 -1
- package/dist/git/features.d.ts +3 -2
- package/dist/git/features.js +34 -55
- package/dist/git/features.js.map +1 -1
- package/dist/git/gitHealth.d.ts +88 -81
- package/dist/git/gitHealth.js +259 -329
- package/dist/git/gitHealth.js.map +1 -1
- package/dist/git/models/author.d.ts +12 -10
- package/dist/git/models/author.js +0 -2
- package/dist/git/models/autolink.d.ts +36 -37
- package/dist/git/models/autolink.js +0 -2
- package/dist/git/models/blame.d.ts +25 -23
- package/dist/git/models/blame.js +0 -2
- package/dist/git/models/branch.d.ts +74 -72
- package/dist/git/models/branch.js +132 -171
- package/dist/git/models/branch.js.map +1 -1
- package/dist/git/models/commit.d.ts +135 -133
- package/dist/git/models/commit.js +381 -484
- package/dist/git/models/commit.js.map +1 -1
- package/dist/git/models/contributor.d.ts +30 -29
- package/dist/git/models/contributor.js +56 -64
- package/dist/git/models/contributor.js.map +1 -1
- package/dist/git/models/defaultBranch.d.ts +5 -3
- package/dist/git/models/defaultBranch.js +0 -2
- package/dist/git/models/diff.d.ts +49 -47
- package/dist/git/models/diff.js +0 -2
- package/dist/git/models/file.d.ts +20 -18
- package/dist/git/models/file.js +0 -2
- package/dist/git/models/fileChange.d.ts +45 -43
- package/dist/git/models/fileChange.js +60 -66
- package/dist/git/models/fileChange.js.map +1 -1
- package/dist/git/models/fileStatus.d.ts +25 -23
- package/dist/git/models/fileStatus.js +30 -26
- package/dist/git/models/fileStatus.js.map +1 -1
- package/dist/git/models/graph.d.ts +328 -320
- package/dist/git/models/graph.js +31 -27
- package/dist/git/models/graph.js.map +1 -1
- package/dist/git/models/graphSearch.d.ts +37 -35
- package/dist/git/models/graphSearch.js +0 -2
- package/dist/git/models/graphSession.d.ts +158 -66
- package/dist/git/models/graphSession.js +72 -1
- package/dist/git/models/graphSession.js.map +1 -1
- package/dist/git/models/issue.d.ts +171 -150
- package/dist/git/models/issue.js +86 -88
- package/dist/git/models/issue.js.map +1 -1
- package/dist/git/models/issueOrPullRequest.d.ts +16 -14
- package/dist/git/models/issueOrPullRequest.js +0 -2
- package/dist/git/models/lineRange.d.ts +6 -4
- package/dist/git/models/lineRange.js +0 -2
- package/dist/git/models/log.d.ts +18 -16
- package/dist/git/models/log.js +0 -2
- package/dist/git/models/mergeConflicts.d.ts +17 -15
- package/dist/git/models/mergeConflicts.js +0 -2
- package/dist/git/models/patch.d.ts +14 -12
- package/dist/git/models/patch.js +0 -2
- package/dist/git/models/pausedOperationStatus.d.ts +42 -40
- package/dist/git/models/pausedOperationStatus.js +0 -2
- package/dist/git/models/pullRequest.d.ts +238 -236
- package/dist/git/models/pullRequest.js +151 -163
- package/dist/git/models/pullRequest.js.map +1 -1
- package/dist/git/models/rebase.d.ts +30 -28
- package/dist/git/models/rebase.js +0 -2
- package/dist/git/models/reference.d.ts +57 -55
- package/dist/git/models/reference.js +0 -2
- package/dist/git/models/reflog.d.ts +29 -27
- package/dist/git/models/reflog.js +63 -82
- package/dist/git/models/reflog.js.map +1 -1
- package/dist/git/models/remote.d.ts +34 -32
- package/dist/git/models/remote.js +74 -95
- package/dist/git/models/remote.js.map +1 -1
- package/dist/git/models/remoteProvider.d.ts +86 -84
- package/dist/git/models/remoteProvider.js +145 -150
- package/dist/git/models/remoteProvider.js.map +1 -1
- package/dist/git/models/remoteResource.d.ts +58 -56
- package/dist/git/models/remoteResource.js +15 -12
- package/dist/git/models/remoteResource.js.map +1 -1
- package/dist/git/models/repository.d.ts +144 -142
- package/dist/git/models/repository.js +358 -406
- package/dist/git/models/repository.js.map +1 -1
- package/dist/git/models/repositoryChangeEvent.d.ts +8 -6
- package/dist/git/models/repositoryChangeEvent.js +26 -24
- package/dist/git/models/repositoryChangeEvent.js.map +1 -1
- package/dist/git/models/repositoryIdentities.d.ts +50 -46
- package/dist/git/models/repositoryIdentities.js +7 -5
- package/dist/git/models/repositoryIdentities.js.map +1 -1
- package/dist/git/models/repositoryMetadata.d.ts +9 -7
- package/dist/git/models/repositoryMetadata.js +0 -2
- package/dist/git/models/resourceDescriptor.d.ts +11 -9
- package/dist/git/models/resourceDescriptor.js +0 -2
- package/dist/git/models/revision.d.ts +2 -0
- package/dist/git/models/revision.js +8 -5
- package/dist/git/models/revision.js.map +1 -1
- package/dist/git/models/search.d.ts +48 -47
- package/dist/git/models/search.js +53 -49
- package/dist/git/models/search.js.map +1 -1
- package/dist/git/models/shortlog.d.ts +5 -3
- package/dist/git/models/shortlog.js +0 -2
- package/dist/git/models/signature.d.ts +28 -26
- package/dist/git/models/signature.js +0 -2
- package/dist/git/models/staging.d.ts +18 -16
- package/dist/git/models/staging.js +0 -2
- package/dist/git/models/stash.d.ts +5 -3
- package/dist/git/models/stash.js +0 -2
- package/dist/git/models/status.d.ts +51 -49
- package/dist/git/models/status.js +172 -205
- package/dist/git/models/status.js.map +1 -1
- package/dist/git/models/statusFile.d.ts +35 -34
- package/dist/git/models/statusFile.js +112 -125
- package/dist/git/models/statusFile.js.map +1 -1
- package/dist/git/models/tag.d.ts +34 -32
- package/dist/git/models/tag.js +64 -77
- package/dist/git/models/tag.js.map +1 -1
- package/dist/git/models/tree.d.ts +8 -6
- package/dist/git/models/tree.js +0 -2
- package/dist/git/models/user.d.ts +6 -4
- package/dist/git/models/user.js +0 -2
- package/dist/git/models/worktree.d.ts +50 -48
- package/dist/git/models/worktree.js +102 -133
- package/dist/git/models/worktree.js.map +1 -1
- package/dist/git/parsers/diffParser.d.ts +7 -5
- package/dist/git/parsers/diffParser.js +438 -587
- package/dist/git/parsers/diffParser.js.map +1 -1
- package/dist/git/parsers/rebaseTodoParser.d.ts +3 -1
- package/dist/git/parsers/rebaseTodoParser.js +165 -208
- package/dist/git/parsers/rebaseTodoParser.js.map +1 -1
- package/dist/git/providers/blame.d.ts +12 -10
- package/dist/git/providers/blame.js +0 -2
- package/dist/git/providers/branches.d.ts +96 -94
- package/dist/git/providers/branches.js +0 -2
- package/dist/git/providers/commits.d.ts +105 -104
- package/dist/git/providers/commits.js +0 -2
- package/dist/git/providers/config.d.ts +80 -78
- package/dist/git/providers/config.js +0 -2
- package/dist/git/providers/contributors.d.ts +23 -21
- package/dist/git/providers/contributors.js +0 -2
- package/dist/git/providers/diff.d.ts +68 -66
- package/dist/git/providers/diff.js +0 -2
- package/dist/git/providers/graph.d.ts +73 -62
- package/dist/git/providers/graph.js +0 -2
- package/dist/git/providers/maintenance.d.ts +180 -178
- package/dist/git/providers/maintenance.js +0 -20
- package/dist/git/providers/operations.d.ts +114 -112
- package/dist/git/providers/operations.js +0 -2
- package/dist/git/providers/patch.d.ts +26 -24
- package/dist/git/providers/patch.js +0 -2
- package/dist/git/providers/pausedOperations.d.ts +19 -17
- package/dist/git/providers/pausedOperations.js +0 -2
- package/dist/git/providers/provider.d.ts +52 -50
- package/dist/git/providers/provider.js +0 -2
- package/dist/git/providers/refs.d.ts +42 -40
- package/dist/git/providers/refs.js +0 -2
- package/dist/git/providers/remotes.d.ts +24 -22
- package/dist/git/providers/remotes.js +0 -2
- package/dist/git/providers/revision.d.ts +25 -23
- package/dist/git/providers/revision.js +0 -2
- package/dist/git/providers/shared/remotes.d.ts +24 -22
- package/dist/git/providers/shared/remotes.js +52 -77
- package/dist/git/providers/shared/remotes.js.map +1 -1
- package/dist/git/providers/staging.d.ts +37 -35
- package/dist/git/providers/staging.js +0 -2
- package/dist/git/providers/stash.d.ts +38 -36
- package/dist/git/providers/stash.js +0 -2
- package/dist/git/providers/status.d.ts +32 -30
- package/dist/git/providers/status.js +0 -2
- package/dist/git/providers/tags.d.ts +20 -18
- package/dist/git/providers/tags.js +0 -2
- package/dist/git/providers/types.d.ts +19 -17
- package/dist/git/providers/types.js +0 -2
- package/dist/git/providers/worktrees.d.ts +26 -24
- package/dist/git/providers/worktrees.js +0 -2
- package/dist/git/remotes/azure-devops.d.ts +46 -45
- package/dist/git/remotes/azure-devops.js +252 -257
- package/dist/git/remotes/azure-devops.js.map +1 -1
- package/dist/git/remotes/bitbucket-server.d.ts +30 -29
- package/dist/git/remotes/bitbucket-server.js +141 -142
- package/dist/git/remotes/bitbucket-server.js.map +1 -1
- package/dist/git/remotes/bitbucket.d.ts +27 -26
- package/dist/git/remotes/bitbucket.js +114 -111
- package/dist/git/remotes/bitbucket.js.map +1 -1
- package/dist/git/remotes/custom.d.ts +26 -25
- package/dist/git/remotes/custom.js +130 -130
- package/dist/git/remotes/custom.js.map +1 -1
- package/dist/git/remotes/gerrit.d.ts +29 -28
- package/dist/git/remotes/gerrit.js +140 -143
- package/dist/git/remotes/gerrit.js.map +1 -1
- package/dist/git/remotes/gitea.d.ts +28 -27
- package/dist/git/remotes/gitea.js +141 -145
- package/dist/git/remotes/gitea.js.map +1 -1
- package/dist/git/remotes/github.d.ts +35 -34
- package/dist/git/remotes/github.js +196 -190
- package/dist/git/remotes/github.js.map +1 -1
- package/dist/git/remotes/gitlab.d.ts +31 -30
- package/dist/git/remotes/gitlab.js +245 -227
- package/dist/git/remotes/gitlab.js.map +1 -1
- package/dist/git/remotes/google-source.d.ts +16 -14
- package/dist/git/remotes/google-source.js +33 -31
- package/dist/git/remotes/google-source.js.map +1 -1
- package/dist/git/remotes/matcher.d.ts +16 -14
- package/dist/git/remotes/matcher.js +141 -124
- package/dist/git/remotes/matcher.js.map +1 -1
- package/dist/git/repositoryService.d.ts +47 -47
- package/dist/git/repositoryService.js +47 -44
- package/dist/git/repositoryService.js.map +1 -1
- package/dist/git/run.types.d.ts +91 -76
- package/dist/git/run.types.js +0 -2
- package/dist/git/service.d.ts +73 -71
- package/dist/git/service.js +286 -302
- package/dist/git/service.js.map +1 -1
- package/dist/git/utils/autolink.utils.d.ts +4 -2
- package/dist/git/utils/autolink.utils.js +103 -139
- package/dist/git/utils/autolink.utils.js.map +1 -1
- package/dist/git/utils/blame.utils.d.ts +22 -20
- package/dist/git/utils/blame.utils.js +196 -230
- package/dist/git/utils/blame.utils.js.map +1 -1
- package/dist/git/utils/branch.utils.d.ts +7 -9
- package/dist/git/utils/branch.utils.js +89 -115
- package/dist/git/utils/branch.utils.js.map +1 -1
- package/dist/git/utils/commit.utils.d.ts +10 -16
- package/dist/git/utils/commit.utils.js +28 -47
- package/dist/git/utils/commit.utils.js.map +1 -1
- package/dist/git/utils/config.utils.d.ts +2 -0
- package/dist/git/utils/config.utils.js +14 -5
- package/dist/git/utils/config.utils.js.map +1 -1
- package/dist/git/utils/conflictResolution.utils.d.ts +16 -13
- package/dist/git/utils/conflictResolution.utils.js +116 -113
- package/dist/git/utils/conflictResolution.utils.js.map +1 -1
- package/dist/git/utils/contributor.utils.d.ts +10 -8
- package/dist/git/utils/contributor.utils.js +47 -69
- package/dist/git/utils/contributor.utils.js.map +1 -1
- package/dist/git/utils/fetch.utils.d.ts +2 -0
- package/dist/git/utils/fetch.utils.js +33 -44
- package/dist/git/utils/fetch.utils.js.map +1 -1
- package/dist/git/utils/fileStatus.utils.d.ts +22 -21
- package/dist/git/utils/fileStatus.utils.js +59 -45
- package/dist/git/utils/fileStatus.utils.js.map +1 -1
- package/dist/git/utils/graph.utils.d.ts +19 -1
- package/dist/git/utils/graph.utils.js +60 -35
- package/dist/git/utils/graph.utils.js.map +1 -1
- package/dist/git/utils/issue.utils.d.ts +3 -1
- package/dist/git/utils/issue.utils.js +154 -158
- package/dist/git/utils/issue.utils.js.map +1 -1
- package/dist/git/utils/issueOrPullRequest.utils.d.ts +3 -1
- package/dist/git/utils/issueOrPullRequest.utils.js +23 -20
- package/dist/git/utils/issueOrPullRequest.utils.js.map +1 -1
- package/dist/git/utils/mergeConflicts.utils.d.ts +4 -1
- package/dist/git/utils/mergeConflicts.utils.js +28 -15
- package/dist/git/utils/mergeConflicts.utils.js.map +1 -1
- package/dist/git/utils/pausedOperationStatus.utils.d.ts +7 -42
- package/dist/git/utils/pausedOperationStatus.utils.js +83 -146
- package/dist/git/utils/pausedOperationStatus.utils.js.map +1 -1
- package/dist/git/utils/pullRequest.utils.d.ts +7 -5
- package/dist/git/utils/pullRequest.utils.js +180 -187
- package/dist/git/utils/pullRequest.utils.js.map +1 -1
- package/dist/git/utils/reachability.utils.d.ts +6 -6
- package/dist/git/utils/reachability.utils.js +120 -147
- package/dist/git/utils/reachability.utils.js.map +1 -1
- package/dist/git/utils/rebase.utils.d.ts +3 -1
- package/dist/git/utils/rebase.utils.js +87 -71
- package/dist/git/utils/rebase.utils.js.map +1 -1
- package/dist/git/utils/reference.utils.d.ts +33 -32
- package/dist/git/utils/reference.utils.js +152 -179
- package/dist/git/utils/reference.utils.js.map +1 -1
- package/dist/git/utils/remote.utils.d.ts +5 -3
- package/dist/git/utils/remote.utils.js +77 -116
- package/dist/git/utils/remote.utils.js.map +1 -1
- package/dist/git/utils/repository.utils.d.ts +3 -1
- package/dist/git/utils/repository.utils.js +12 -11
- package/dist/git/utils/repository.utils.js.map +1 -1
- package/dist/git/utils/resourceDescriptor.utils.d.ts +3 -1
- package/dist/git/utils/resourceDescriptor.utils.js +8 -14
- package/dist/git/utils/resourceDescriptor.utils.js.map +1 -1
- package/dist/git/utils/revision.utils.d.ts +11 -10
- package/dist/git/utils/revision.utils.js +69 -83
- package/dist/git/utils/revision.utils.js.map +1 -1
- package/dist/git/utils/search.utils.d.ts +5 -3
- package/dist/git/utils/search.utils.js +383 -522
- package/dist/git/utils/search.utils.js.map +1 -1
- package/dist/git/utils/sorting.d.ts +22 -20
- package/dist/git/utils/sorting.js +133 -227
- package/dist/git/utils/sorting.js.map +1 -1
- package/dist/git/utils/sshKey.utils.d.ts +2 -0
- package/dist/git/utils/sshKey.utils.js +10 -15
- package/dist/git/utils/sshKey.utils.js.map +1 -1
- package/dist/git/utils/status.utils.d.ts +20 -15
- package/dist/git/utils/status.utils.js +93 -79
- package/dist/git/utils/status.utils.js.map +1 -1
- package/dist/git/utils/statusFile.utils.d.ts +6 -4
- package/dist/git/utils/statusFile.utils.js +71 -72
- package/dist/git/utils/statusFile.utils.js.map +1 -1
- package/dist/git/utils/tag.utils.d.ts +4 -2
- package/dist/git/utils/tag.utils.js +10 -8
- package/dist/git/utils/tag.utils.js.map +1 -1
- package/dist/git/utils/tooltip.utils.d.ts +5 -2
- package/dist/git/utils/tooltip.utils.js +49 -24
- package/dist/git/utils/tooltip.utils.js.map +1 -1
- package/dist/git/utils/uriAuthority.d.ts +9 -7
- package/dist/git/utils/uriAuthority.js +19 -16
- package/dist/git/utils/uriAuthority.js.map +1 -1
- package/dist/git/utils/user.utils.d.ts +3 -1
- package/dist/git/utils/user.utils.js +6 -10
- package/dist/git/utils/user.utils.js.map +1 -1
- package/dist/git/utils/worktree.utils.d.ts +5 -3
- package/dist/git/utils/worktree.utils.js +14 -12
- package/dist/git/utils/worktree.utils.js.map +1 -1
- package/dist/git/watching/changeEvent.d.ts +38 -36
- package/dist/git/watching/changeEvent.js +60 -67
- package/dist/git/watching/changeEvent.js.map +1 -1
- package/dist/git/watching/classifyChange.d.ts +3 -1
- package/dist/git/watching/classifyChange.js +50 -79
- package/dist/git/watching/classifyChange.js.map +1 -1
- package/dist/git/watching/gitIgnoreFilter.d.ts +43 -41
- package/dist/git/watching/gitIgnoreFilter.js +92 -107
- package/dist/git/watching/gitIgnoreFilter.js.map +1 -1
- package/dist/git/watching/initWatcher.d.ts +22 -20
- package/dist/git/watching/initWatcher.js +58 -57
- package/dist/git/watching/initWatcher.js.map +1 -1
- package/dist/git/watching/provider.d.ts +8 -7
- package/dist/git/watching/provider.js +0 -2
- package/dist/git/watching/watchGroup.d.ts +29 -28
- package/dist/git/watching/watchGroup.js +105 -137
- package/dist/git/watching/watchGroup.js.map +1 -1
- package/dist/git/watching/watchService.d.ts +70 -68
- package/dist/git/watching/watchService.js +275 -339
- package/dist/git/watching/watchService.js.map +1 -1
- package/dist/git/watching/watchSession.d.ts +112 -111
- package/dist/git/watching/watchSession.js +260 -314
- package/dist/git/watching/watchSession.js.map +1 -1
- package/dist/git/watching/watcherPatterns.d.ts +4 -2
- package/dist/git/watching/watcherPatterns.js +21 -24
- package/dist/git/watching/watcherPatterns.js.map +1 -1
- package/dist/git-cli/cliGitProvider.d.ts +117 -114
- package/dist/git-cli/cliGitProvider.js +261 -283
- package/dist/git-cli/cliGitProvider.js.map +1 -1
- package/dist/git-cli/exec/eventLoopMonitor.d.ts +26 -0
- package/dist/git-cli/exec/eventLoopMonitor.js +61 -0
- package/dist/git-cli/exec/eventLoopMonitor.js.map +1 -0
- package/dist/git-cli/exec/exec.d.ts +3 -4
- package/dist/git-cli/exec/exec.errors.d.ts +2 -2
- package/dist/git-cli/exec/exec.errors.js +2 -2
- package/dist/git-cli/exec/exec.js +3 -3
- package/dist/git-cli/exec/exec.types.d.ts +2 -2
- package/dist/git-cli/exec/exec.types.js +0 -2
- package/dist/git-cli/exec/features.d.ts +2 -3
- package/dist/git-cli/exec/features.js +2 -2
- package/dist/git-cli/exec/git.d.ts +224 -205
- package/dist/git-cli/exec/git.js +834 -1042
- package/dist/git-cli/exec/git.js.map +1 -1
- package/dist/git-cli/exec/gitQueue.d.ts +51 -48
- package/dist/git-cli/exec/gitQueue.js +225 -260
- package/dist/git-cli/exec/gitQueue.js.map +1 -1
- package/dist/git-cli/exec/locator.d.ts +8 -6
- package/dist/git-cli/exec/locator.js +95 -121
- package/dist/git-cli/exec/locator.js.map +1 -1
- package/dist/git-cli/parsers/blameParser.d.ts +22 -20
- package/dist/git-cli/parsers/blameParser.js +238 -332
- package/dist/git-cli/parsers/blameParser.js.map +1 -1
- package/dist/git-cli/parsers/indexParser.d.ts +3 -1
- package/dist/git-cli/parsers/indexParser.js +146 -221
- package/dist/git-cli/parsers/indexParser.js.map +1 -1
- package/dist/git-cli/parsers/logParser.d.ts +103 -110
- package/dist/git-cli/parsers/logParser.js +998 -1199
- package/dist/git-cli/parsers/logParser.js.map +1 -1
- package/dist/git-cli/parsers/mergeTreeParser.d.ts +5 -3
- package/dist/git-cli/parsers/mergeTreeParser.js +28 -72
- package/dist/git-cli/parsers/mergeTreeParser.js.map +1 -1
- package/dist/git-cli/parsers/refParser.d.ts +5 -4
- package/dist/git-cli/parsers/refParser.js +92 -137
- package/dist/git-cli/parsers/refParser.js.map +1 -1
- package/dist/git-cli/parsers/reflogParser.d.ts +7 -6
- package/dist/git-cli/parsers/reflogParser.js +100 -173
- package/dist/git-cli/parsers/reflogParser.js.map +1 -1
- package/dist/git-cli/parsers/remoteParser.d.ts +4 -2
- package/dist/git-cli/parsers/remoteParser.js +74 -126
- package/dist/git-cli/parsers/remoteParser.js.map +1 -1
- package/dist/git-cli/parsers/shortlogParser.d.ts +4 -2
- package/dist/git-cli/parsers/shortlogParser.js +44 -49
- package/dist/git-cli/parsers/shortlogParser.js.map +1 -1
- package/dist/git-cli/parsers/signatureParser.d.ts +3 -1
- package/dist/git-cli/parsers/signatureParser.js +82 -124
- package/dist/git-cli/parsers/signatureParser.js.map +1 -1
- package/dist/git-cli/parsers/sshSignatureParser.d.ts +5 -3
- package/dist/git-cli/parsers/sshSignatureParser.js +55 -64
- package/dist/git-cli/parsers/sshSignatureParser.js.map +1 -1
- package/dist/git-cli/parsers/statusParser.d.ts +4 -2
- package/dist/git-cli/parsers/statusParser.js +148 -205
- package/dist/git-cli/parsers/statusParser.js.map +1 -1
- package/dist/git-cli/parsers/treeParser.d.ts +3 -1
- package/dist/git-cli/parsers/treeParser.js +57 -108
- package/dist/git-cli/parsers/treeParser.js.map +1 -1
- package/dist/git-cli/parsers/worktreeParser.d.ts +4 -3
- package/dist/git-cli/parsers/worktreeParser.js +76 -132
- package/dist/git-cli/parsers/worktreeParser.js.map +1 -1
- package/dist/git-cli/providers/blame.d.ts +25 -23
- package/dist/git-cli/providers/blame.js +404 -413
- package/dist/git-cli/providers/blame.js.map +1 -1
- package/dist/git-cli/providers/branches.d.ts +117 -116
- package/dist/git-cli/providers/branches.js +1132 -1468
- package/dist/git-cli/providers/branches.js.map +1 -1
- package/dist/git-cli/providers/commitFilesetUtils.d.ts +4 -2
- package/dist/git-cli/providers/commitFilesetUtils.js +36 -18
- package/dist/git-cli/providers/commitFilesetUtils.js.map +1 -1
- package/dist/git-cli/providers/commits.d.ts +58 -56
- package/dist/git-cli/providers/commits.js +1084 -1423
- package/dist/git-cli/providers/commits.js.map +1 -1
- package/dist/git-cli/providers/config.d.ts +129 -127
- package/dist/git-cli/providers/config.js +612 -831
- package/dist/git-cli/providers/config.js.map +1 -1
- package/dist/git-cli/providers/contributors.d.ts +29 -28
- package/dist/git-cli/providers/contributors.js +246 -351
- package/dist/git-cli/providers/contributors.js.map +1 -1
- package/dist/git-cli/providers/diff.d.ts +97 -94
- package/dist/git-cli/providers/diff.js +638 -845
- package/dist/git-cli/providers/diff.js.map +1 -1
- package/dist/git-cli/providers/graph.d.ts +66 -68
- package/dist/git-cli/providers/graph.js +1400 -1848
- package/dist/git-cli/providers/graph.js.map +1 -1
- package/dist/git-cli/providers/maintenance.d.ts +305 -303
- package/dist/git-cli/providers/maintenance.js +1526 -2078
- package/dist/git-cli/providers/maintenance.js.map +1 -1
- package/dist/git-cli/providers/operations.d.ts +96 -94
- package/dist/git-cli/providers/operations.js +664 -724
- package/dist/git-cli/providers/operations.js.map +1 -1
- package/dist/git-cli/providers/patch.d.ts +37 -35
- package/dist/git-cli/providers/patch.js +206 -307
- package/dist/git-cli/providers/patch.js.map +1 -1
- package/dist/git-cli/providers/pausedOperations.d.ts +30 -28
- package/dist/git-cli/providers/pausedOperations.js +475 -506
- package/dist/git-cli/providers/pausedOperations.js.map +1 -1
- package/dist/git-cli/providers/refs.d.ts +50 -48
- package/dist/git-cli/providers/refs.js +335 -479
- package/dist/git-cli/providers/refs.js.map +1 -1
- package/dist/git-cli/providers/remotes.d.ts +23 -21
- package/dist/git-cli/providers/remotes.js +71 -92
- package/dist/git-cli/providers/remotes.js.map +1 -1
- package/dist/git-cli/providers/revision.d.ts +27 -25
- package/dist/git-cli/providers/revision.js +288 -304
- package/dist/git-cli/providers/revision.js.map +1 -1
- package/dist/git-cli/providers/staging.d.ts +31 -29
- package/dist/git-cli/providers/staging.js +140 -189
- package/dist/git-cli/providers/staging.js.map +1 -1
- package/dist/git-cli/providers/stash.d.ts +44 -42
- package/dist/git-cli/providers/stash.js +288 -387
- package/dist/git-cli/providers/stash.js.map +1 -1
- package/dist/git-cli/providers/status.d.ts +61 -59
- package/dist/git-cli/providers/status.js +332 -453
- package/dist/git-cli/providers/status.js.map +1 -1
- package/dist/git-cli/providers/tags.d.ts +31 -29
- package/dist/git-cli/providers/tags.js +158 -215
- package/dist/git-cli/providers/tags.js.map +1 -1
- package/dist/git-cli/providers/worktrees.d.ts +35 -33
- package/dist/git-cli/providers/worktrees.js +178 -203
- package/dist/git-cli/providers/worktrees.js.map +1 -1
- package/dist/git-cli/service.d.ts +28 -26
- package/dist/git-cli/service.js +38 -40
- package/dist/git-cli/service.js.map +1 -1
- package/dist/ipc/discovery.d.ts +19 -17
- package/dist/ipc/discovery.js +104 -128
- package/dist/ipc/discovery.js.map +1 -1
- package/dist/ipc/ipcServer.d.ts +16 -14
- package/dist/ipc/ipcServer.js +139 -160
- package/dist/ipc/ipcServer.js.map +1 -1
- package/dist/plus/agents/agentCapabilities.d.ts +50 -37
- package/dist/plus/agents/agentCapabilities.js +138 -173
- package/dist/plus/agents/agentCapabilities.js.map +1 -1
- package/dist/plus/agents/providers/claudeCodeTranscript.d.ts +153 -151
- package/dist/plus/agents/providers/claudeCodeTranscript.js +605 -679
- package/dist/plus/agents/providers/claudeCodeTranscript.js.map +1 -1
- package/dist/plus/agents/providers/gkAgentProvider.d.ts +237 -240
- package/dist/plus/agents/providers/gkAgentProvider.js +2162 -3334
- package/dist/plus/agents/providers/gkAgentProvider.js.map +1 -1
- package/dist/plus/agents/sanitizePrompt.d.ts +2 -0
- package/dist/plus/agents/sanitizePrompt.js +58 -91
- package/dist/plus/agents/sanitizePrompt.js.map +1 -1
- package/dist/plus/agents/stateMachine.d.ts +7 -5
- package/dist/plus/agents/stateMachine.js +135 -163
- package/dist/plus/agents/stateMachine.js.map +1 -1
- package/dist/plus/agents/types.d.ts +333 -336
- package/dist/plus/agents/types.js +48 -48
- package/dist/plus/agents/types.js.map +1 -1
- package/dist/plus/ai/constants.d.ts +3 -1
- package/dist/plus/ai/constants.js +88 -84
- package/dist/plus/ai/constants.js.map +1 -1
- package/dist/plus/ai/errors.d.ts +22 -18
- package/dist/plus/ai/errors.js +158 -138
- package/dist/plus/ai/errors.js.map +1 -1
- package/dist/plus/ai/models/conversation.d.ts +11 -9
- package/dist/plus/ai/models/conversation.js +30 -26
- package/dist/plus/ai/models/conversation.js.map +1 -1
- package/dist/plus/ai/models/model.d.ts +29 -27
- package/dist/plus/ai/models/model.js +663 -471
- package/dist/plus/ai/models/model.js.map +1 -1
- package/dist/plus/ai/models/promptTemplates.d.ts +56 -55
- package/dist/plus/ai/models/promptTemplates.js +0 -2
- package/dist/plus/ai/models/provider.d.ts +79 -77
- package/dist/plus/ai/models/provider.js +0 -2
- package/dist/plus/ai/models/results.d.ts +27 -21
- package/dist/plus/ai/models/results.js +0 -2
- package/dist/plus/ai/prompts.d.ts +4 -2
- package/dist/plus/ai/prompts.js +301 -190
- package/dist/plus/ai/prompts.js.map +1 -1
- package/dist/plus/ai/providers/anthropicProvider.d.ts +33 -32
- package/dist/plus/ai/providers/anthropicProvider.js +484 -381
- package/dist/plus/ai/providers/anthropicProvider.js.map +1 -1
- package/dist/plus/ai/providers/azureProvider.d.ts +18 -16
- package/dist/plus/ai/providers/azureProvider.js +53 -53
- package/dist/plus/ai/providers/azureProvider.js.map +1 -1
- package/dist/plus/ai/providers/context.d.ts +23 -20
- package/dist/plus/ai/providers/context.js +0 -2
- package/dist/plus/ai/providers/deepSeekProvider.d.ts +17 -15
- package/dist/plus/ai/providers/deepSeekProvider.js +45 -39
- package/dist/plus/ai/providers/deepSeekProvider.js.map +1 -1
- package/dist/plus/ai/providers/geminiProvider.d.ts +16 -14
- package/dist/plus/ai/providers/geminiProvider.js +316 -230
- package/dist/plus/ai/providers/geminiProvider.js.map +1 -1
- package/dist/plus/ai/providers/gitkrakenProvider.d.ts +22 -20
- package/dist/plus/ai/providers/gitkrakenProvider.js +165 -260
- package/dist/plus/ai/providers/gitkrakenProvider.js.map +1 -1
- package/dist/plus/ai/providers/huggingFaceProvider.d.ts +16 -14
- package/dist/plus/ai/providers/huggingFaceProvider.js +45 -46
- package/dist/plus/ai/providers/huggingFaceProvider.js.map +1 -1
- package/dist/plus/ai/providers/mistralProvider.d.ts +22 -20
- package/dist/plus/ai/providers/mistralProvider.js +263 -195
- package/dist/plus/ai/providers/mistralProvider.js.map +1 -1
- package/dist/plus/ai/providers/ollamaProvider.d.ts +26 -24
- package/dist/plus/ai/providers/ollamaProvider.js +156 -189
- package/dist/plus/ai/providers/ollamaProvider.js.map +1 -1
- package/dist/plus/ai/providers/openAICompatibleProvider.d.ts +18 -16
- package/dist/plus/ai/providers/openAICompatibleProvider.js +50 -50
- package/dist/plus/ai/providers/openAICompatibleProvider.js.map +1 -1
- package/dist/plus/ai/providers/openAICompatibleProviderBase.d.ts +187 -185
- package/dist/plus/ai/providers/openAICompatibleProviderBase.js +356 -475
- package/dist/plus/ai/providers/openAICompatibleProviderBase.js.map +1 -1
- package/dist/plus/ai/providers/openRouterProvider.d.ts +20 -19
- package/dist/plus/ai/providers/openRouterProvider.js +60 -65
- package/dist/plus/ai/providers/openRouterProvider.js.map +1 -1
- package/dist/plus/ai/providers/openaiProvider.d.ts +17 -15
- package/dist/plus/ai/providers/openaiProvider.js +35 -34
- package/dist/plus/ai/providers/openaiProvider.js.map +1 -1
- package/dist/plus/ai/providers/responseFormatCache.d.ts +4 -2
- package/dist/plus/ai/providers/responseFormatCache.js +21 -17
- package/dist/plus/ai/providers/responseFormatCache.js.map +1 -1
- package/dist/plus/ai/providers/xaiProvider.d.ts +16 -14
- package/dist/plus/ai/providers/xaiProvider.js +61 -46
- package/dist/plus/ai/providers/xaiProvider.js.map +1 -1
- package/dist/plus/ai/utils/ai.utils.d.ts +4 -2
- package/dist/plus/ai/utils/ai.utils.js +50 -77
- package/dist/plus/ai/utils/ai.utils.js.map +1 -1
- package/dist/plus/ai/utils/results.utils.d.ts +7 -5
- package/dist/plus/ai/utils/results.utils.js +235 -269
- package/dist/plus/ai/utils/results.utils.js.map +1 -1
- package/dist/plus/ai/utils/truncation.utils.d.ts +3 -2
- package/dist/plus/ai/utils/truncation.utils.js +242 -205
- package/dist/plus/ai/utils/truncation.utils.js.map +1 -1
- package/dist/plus/git-github/api/config.d.ts +58 -56
- package/dist/plus/git-github/api/config.js +15 -11
- package/dist/plus/git-github/api/config.js.map +1 -1
- package/dist/plus/git-github/api/github.d.ts +500 -497
- package/dist/plus/git-github/api/github.js +2326 -3121
- package/dist/plus/git-github/api/github.js.map +1 -1
- package/dist/plus/git-github/api/github.utils.d.ts +5 -3
- package/dist/plus/git-github/api/github.utils.js +24 -19
- package/dist/plus/git-github/api/github.utils.js.map +1 -1
- package/dist/plus/git-github/api/issueSearchQuery.d.ts +5 -3
- package/dist/plus/git-github/api/issueSearchQuery.js +204 -221
- package/dist/plus/git-github/api/issueSearchQuery.js.map +1 -1
- package/dist/plus/git-github/api/pullRequestSearchQuery.d.ts +6 -5
- package/dist/plus/git-github/api/pullRequestSearchQuery.js +89 -116
- package/dist/plus/git-github/api/pullRequestSearchQuery.js.map +1 -1
- package/dist/plus/git-github/api/token.d.ts +10 -8
- package/dist/plus/git-github/api/token.js +0 -2
- package/dist/plus/git-github/api/tokenUtils.d.ts +4 -2
- package/dist/plus/git-github/api/tokenUtils.js +14 -10
- package/dist/plus/git-github/api/tokenUtils.js.map +1 -1
- package/dist/plus/git-github/api/types.d.ts +94 -92
- package/dist/plus/git-github/api/types.js +0 -2
- package/dist/plus/git-github/context.d.ts +65 -63
- package/dist/plus/git-github/context.js +13 -9
- package/dist/plus/git-github/context.js.map +1 -1
- package/dist/plus/git-github/models.d.ts +182 -182
- package/dist/plus/git-github/models.js +199 -265
- package/dist/plus/git-github/models.js.map +1 -1
- package/dist/plus/git-github/providers/github/blame.d.ts +32 -31
- package/dist/plus/git-github/providers/github/blame.js +137 -150
- package/dist/plus/git-github/providers/github/blame.js.map +1 -1
- package/dist/plus/git-github/providers/github/branches.d.ts +42 -40
- package/dist/plus/git-github/providers/github/branches.js +199 -274
- package/dist/plus/git-github/providers/github/branches.js.map +1 -1
- package/dist/plus/git-github/providers/github/commits.d.ts +35 -33
- package/dist/plus/git-github/providers/github/commits.js +527 -606
- package/dist/plus/git-github/providers/github/commits.js.map +1 -1
- package/dist/plus/git-github/providers/github/config.d.ts +10 -8
- package/dist/plus/git-github/providers/github/config.js +35 -45
- package/dist/plus/git-github/providers/github/config.js.map +1 -1
- package/dist/plus/git-github/providers/github/contributors.d.ts +25 -24
- package/dist/plus/git-github/providers/github/contributors.js +174 -216
- package/dist/plus/git-github/providers/github/contributors.js.map +1 -1
- package/dist/plus/git-github/providers/github/diff.d.ts +34 -32
- package/dist/plus/git-github/providers/github/diff.js +154 -194
- package/dist/plus/git-github/providers/github/diff.js.map +1 -1
- package/dist/plus/git-github/providers/github/graph.d.ts +33 -31
- package/dist/plus/git-github/providers/github/graph.js +400 -452
- package/dist/plus/git-github/providers/github/graph.js.map +1 -1
- package/dist/plus/git-github/providers/github/refs.d.ts +33 -31
- package/dist/plus/git-github/providers/github/refs.js +97 -131
- package/dist/plus/git-github/providers/github/refs.js.map +1 -1
- package/dist/plus/git-github/providers/github/remotes.d.ts +8 -6
- package/dist/plus/git-github/providers/github/remotes.js +30 -35
- package/dist/plus/git-github/providers/github/remotes.js.map +1 -1
- package/dist/plus/git-github/providers/github/revision.d.ts +13 -11
- package/dist/plus/git-github/providers/github/revision.js +106 -129
- package/dist/plus/git-github/providers/github/revision.js.map +1 -1
- package/dist/plus/git-github/providers/github/status.d.ts +24 -22
- package/dist/plus/git-github/providers/github/status.js +49 -47
- package/dist/plus/git-github/providers/github/status.js.map +1 -1
- package/dist/plus/git-github/providers/github/tags.d.ts +21 -19
- package/dist/plus/git-github/providers/github/tags.js +78 -103
- package/dist/plus/git-github/providers/github/tags.js.map +1 -1
- package/dist/plus/git-github/providers/githubGitProvider.d.ts +67 -64
- package/dist/plus/git-github/providers/githubGitProvider.js +131 -128
- package/dist/plus/git-github/providers/githubGitProvider.js.map +1 -1
- package/dist/plus/git-github/providers/githubProvider.d.ts +46 -44
- package/dist/plus/git-github/providers/githubProvider.js +0 -2
- package/dist/plus/integrations/authentication/cloudIntegrationService.d.ts +29 -27
- package/dist/plus/integrations/authentication/cloudIntegrationService.js +173 -208
- package/dist/plus/integrations/authentication/cloudIntegrationService.js.map +1 -1
- package/dist/plus/integrations/authentication/configuredIntegrationService.d.ts +126 -107
- package/dist/plus/integrations/authentication/configuredIntegrationService.js +399 -497
- package/dist/plus/integrations/authentication/configuredIntegrationService.js.map +1 -1
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.d.ts +73 -72
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.js +153 -232
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.js.map +1 -1
- package/dist/plus/integrations/authentication/integrationAuthenticationService.d.ts +61 -59
- package/dist/plus/integrations/authentication/integrationAuthenticationService.js +100 -124
- package/dist/plus/integrations/authentication/integrationAuthenticationService.js.map +1 -1
- package/dist/plus/integrations/authentication/manualTokenProvider.d.ts +14 -12
- package/dist/plus/integrations/authentication/manualTokenProvider.js +26 -51
- package/dist/plus/integrations/authentication/manualTokenProvider.js.map +1 -1
- package/dist/plus/integrations/authentication/models.d.ts +102 -85
- package/dist/plus/integrations/authentication/models.js +55 -54
- package/dist/plus/integrations/authentication/models.js.map +1 -1
- package/dist/plus/integrations/authentication/rejectedTokenTracker.d.ts +19 -17
- package/dist/plus/integrations/authentication/rejectedTokenTracker.js +53 -51
- package/dist/plus/integrations/authentication/rejectedTokenTracker.js.map +1 -1
- package/dist/plus/integrations/collectionMetadata.d.ts +12 -10
- package/dist/plus/integrations/collectionMetadata.js +204 -254
- package/dist/plus/integrations/collectionMetadata.js.map +1 -1
- package/dist/plus/integrations/constants.d.ts +60 -34
- package/dist/plus/integrations/constants.js +157 -132
- package/dist/plus/integrations/constants.js.map +1 -1
- package/dist/plus/integrations/context.d.ts +253 -252
- package/dist/plus/integrations/context.js +0 -2
- package/dist/plus/integrations/errors.d.ts +26 -25
- package/dist/plus/integrations/errors.js +116 -140
- package/dist/plus/integrations/errors.js.map +1 -1
- package/dist/plus/integrations/index.d.ts +36 -40
- package/dist/plus/integrations/index.js +46 -57
- package/dist/plus/integrations/index.js.map +1 -1
- package/dist/plus/integrations/integrationService.d.ts +445 -440
- package/dist/plus/integrations/integrationService.js +1054 -1324
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/lite.d.ts +33 -31
- package/dist/plus/integrations/lite.js +84 -122
- package/dist/plus/integrations/lite.js.map +1 -1
- package/dist/plus/integrations/manager.d.ts +592 -580
- package/dist/plus/integrations/manager.js +0 -2
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +420 -415
- package/dist/plus/integrations/models/gitHostIntegration.js +1180 -1293
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/integration.d.ts +200 -190
- package/dist/plus/integrations/models/integration.js +577 -722
- package/dist/plus/integrations/models/integration.js.map +1 -1
- package/dist/plus/integrations/models/issueCache.d.ts +15 -13
- package/dist/plus/integrations/models/issueCache.js +9 -7
- package/dist/plus/integrations/models/issueCache.js.map +1 -1
- package/dist/plus/integrations/models/issueReads.d.ts +108 -85
- package/dist/plus/integrations/models/issueReads.js +0 -2
- package/dist/plus/integrations/models/issuesIntegration.d.ts +72 -71
- package/dist/plus/integrations/models/issuesIntegration.js +160 -186
- package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
- package/dist/plus/integrations/models/pullRequestReads.d.ts +9 -7
- package/dist/plus/integrations/models/pullRequestReads.js +0 -2
- package/dist/plus/integrations/providerFilters.d.ts +8 -20
- package/dist/plus/integrations/providerFilters.js +11 -7
- package/dist/plus/integrations/providerFilters.js.map +1 -1
- package/dist/plus/integrations/providers/accounts.d.ts +7 -5
- package/dist/plus/integrations/providers/accounts.js +21 -31
- package/dist/plus/integrations/providers/accounts.js.map +1 -1
- package/dist/plus/integrations/providers/apiClients.d.ts +13 -10
- package/dist/plus/integrations/providers/apiClients.js +51 -36
- package/dist/plus/integrations/providers/apiClients.js.map +1 -1
- package/dist/plus/integrations/providers/apiConfig.d.ts +31 -29
- package/dist/plus/integrations/providers/apiConfig.js +18 -16
- package/dist/plus/integrations/providers/apiConfig.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.d.ts +69 -67
- package/dist/plus/integrations/providers/azure/azure.js +452 -577
- package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
- package/dist/plus/integrations/providers/azure/models.d.ts +228 -226
- package/dist/plus/integrations/providers/azure/models.js +223 -251
- package/dist/plus/integrations/providers/azure/models.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOps.d.ts +132 -131
- package/dist/plus/integrations/providers/azureDevOps.js +773 -931
- package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket/bitbucket.d.ts +50 -48
- package/dist/plus/integrations/providers/bitbucket/bitbucket.js +417 -585
- package/dist/plus/integrations/providers/bitbucket/bitbucket.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket/models.d.ts +179 -179
- package/dist/plus/integrations/providers/bitbucket/models.js +96 -144
- package/dist/plus/integrations/providers/bitbucket/models.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket-server/models.d.ts +111 -109
- package/dist/plus/integrations/providers/bitbucket-server/models.js +102 -121
- package/dist/plus/integrations/providers/bitbucket-server/models.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket-server.d.ts +79 -79
- package/dist/plus/integrations/providers/bitbucket-server.js +205 -241
- package/dist/plus/integrations/providers/bitbucket-server.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket.d.ts +80 -79
- package/dist/plus/integrations/providers/bitbucket.js +390 -477
- package/dist/plus/integrations/providers/bitbucket.js.map +1 -1
- package/dist/plus/integrations/providers/github/github.d.ts +5 -3
- package/dist/plus/integrations/providers/github/github.js +38 -45
- package/dist/plus/integrations/providers/github/github.js.map +1 -1
- package/dist/plus/integrations/providers/github/github.utils.d.ts +8 -5
- package/dist/plus/integrations/providers/github/github.utils.js +7 -4
- package/dist/plus/integrations/providers/github/github.utils.js.map +1 -1
- package/dist/plus/integrations/providers/github/models.d.ts +2 -3
- package/dist/plus/integrations/providers/github/models.js +2 -2
- package/dist/plus/integrations/providers/github.d.ts +174 -172
- package/dist/plus/integrations/providers/github.js +431 -523
- package/dist/plus/integrations/providers/github.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab/gitlab.d.ts +67 -66
- package/dist/plus/integrations/providers/gitlab/gitlab.js +546 -757
- package/dist/plus/integrations/providers/gitlab/gitlab.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab/gitlab.utils.d.ts +7 -5
- package/dist/plus/integrations/providers/gitlab/gitlab.utils.js +45 -46
- package/dist/plus/integrations/providers/gitlab/gitlab.utils.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab/models.d.ts +109 -107
- package/dist/plus/integrations/providers/gitlab/models.js +74 -89
- package/dist/plus/integrations/providers/gitlab/models.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab.d.ts +115 -114
- package/dist/plus/integrations/providers/gitlab.js +440 -528
- package/dist/plus/integrations/providers/gitlab.js.map +1 -1
- package/dist/plus/integrations/providers/issueSorts.d.ts +3 -1
- package/dist/plus/integrations/providers/issueSorts.js +66 -62
- package/dist/plus/integrations/providers/issueSorts.js.map +1 -1
- package/dist/plus/integrations/providers/jira-server.d.ts +90 -0
- package/dist/plus/integrations/providers/jira-server.js +322 -0
- package/dist/plus/integrations/providers/jira-server.js.map +1 -0
- package/dist/plus/integrations/providers/jira.d.ts +41 -39
- package/dist/plus/integrations/providers/jira.js +378 -440
- package/dist/plus/integrations/providers/jira.js.map +1 -1
- package/dist/plus/integrations/providers/jiraIssueByKey.d.ts +3 -1
- package/dist/plus/integrations/providers/jiraIssueByKey.js +85 -89
- package/dist/plus/integrations/providers/jiraIssueByKey.js.map +1 -1
- package/dist/plus/integrations/providers/linear.d.ts +63 -62
- package/dist/plus/integrations/providers/linear.js +315 -358
- package/dist/plus/integrations/providers/linear.js.map +1 -1
- package/dist/plus/integrations/providers/models.d.ts +514 -503
- package/dist/plus/integrations/providers/models.js +816 -865
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/providers/providerErrors.d.ts +35 -2
- package/dist/plus/integrations/providers/providerErrors.js +120 -81
- package/dist/plus/integrations/providers/providerErrors.js.map +1 -1
- package/dist/plus/integrations/providers/providersApi.d.ts +299 -271
- package/dist/plus/integrations/providers/providersApi.js +1048 -1001
- package/dist/plus/integrations/providers/providersApi.js.map +1 -1
- package/dist/plus/integrations/providers/pullRequestReviews.d.ts +21 -21
- package/dist/plus/integrations/providers/pullRequestReviews.js +64 -90
- package/dist/plus/integrations/providers/pullRequestReviews.js.map +1 -1
- package/dist/plus/integrations/providers/trello.d.ts +35 -34
- package/dist/plus/integrations/providers/trello.js +136 -143
- package/dist/plus/integrations/providers/trello.js.map +1 -1
- package/dist/plus/integrations/providers/utils/providerPaging.d.ts +4 -2
- package/dist/plus/integrations/providers/utils/providerPaging.js +131 -156
- package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
- package/dist/plus/integrations/providers/utils.d.ts +51 -50
- package/dist/plus/integrations/providers/utils.js +185 -296
- package/dist/plus/integrations/providers/utils.js.map +1 -1
- package/dist/plus/integrations/reads/broaden.d.ts +10 -8
- package/dist/plus/integrations/reads/broaden.js +320 -439
- package/dist/plus/integrations/reads/broaden.js.map +1 -1
- package/dist/plus/integrations/reads/context.d.ts +47 -45
- package/dist/plus/integrations/reads/context.js +0 -2
- package/dist/plus/integrations/reads/counts.d.ts +74 -72
- package/dist/plus/integrations/reads/counts.js +233 -314
- package/dist/plus/integrations/reads/counts.js.map +1 -1
- package/dist/plus/integrations/reads/cursors.d.ts +26 -24
- package/dist/plus/integrations/reads/cursors.js +80 -115
- package/dist/plus/integrations/reads/cursors.js.map +1 -1
- package/dist/plus/integrations/reads/drains.d.ts +24 -23
- package/dist/plus/integrations/reads/drains.js +225 -298
- package/dist/plus/integrations/reads/drains.js.map +1 -1
- package/dist/plus/integrations/reads/filters.d.ts +84 -80
- package/dist/plus/integrations/reads/filters.js +326 -414
- package/dist/plus/integrations/reads/filters.js.map +1 -1
- package/dist/plus/integrations/reads/hierarchy.d.ts +14 -12
- package/dist/plus/integrations/reads/hierarchy.js +248 -332
- package/dist/plus/integrations/reads/hierarchy.js.map +1 -1
- package/dist/plus/integrations/reads/hierarchy.utils.d.ts +11 -9
- package/dist/plus/integrations/reads/hierarchy.utils.js +109 -108
- package/dist/plus/integrations/reads/hierarchy.utils.js.map +1 -1
- package/dist/plus/integrations/reads/issueBatch.d.ts +28 -26
- package/dist/plus/integrations/reads/issueBatch.js +83 -85
- package/dist/plus/integrations/reads/issueBatch.js.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.d.ts +42 -33
- package/dist/plus/integrations/reads/issueTracker.js +265 -386
- package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
- package/dist/plus/integrations/reads/issues.d.ts +50 -47
- package/dist/plus/integrations/reads/issues.js +202 -331
- package/dist/plus/integrations/reads/issues.js.map +1 -1
- package/dist/plus/integrations/reads/ordering.d.ts +32 -30
- package/dist/plus/integrations/reads/ordering.js +47 -43
- package/dist/plus/integrations/reads/ordering.js.map +1 -1
- package/dist/plus/integrations/reads/paging.d.ts +59 -57
- package/dist/plus/integrations/reads/paging.js +244 -261
- package/dist/plus/integrations/reads/paging.js.map +1 -1
- package/dist/plus/integrations/reads/pullRequests.d.ts +37 -35
- package/dist/plus/integrations/reads/pullRequests.js +95 -148
- package/dist/plus/integrations/reads/pullRequests.js.map +1 -1
- package/dist/plus/integrations/reads/resolveRepository.d.ts +15 -13
- package/dist/plus/integrations/reads/resolveRepository.js +147 -231
- package/dist/plus/integrations/reads/resolveRepository.js.map +1 -1
- package/dist/plus/integrations/reads/searchIssues.d.ts +29 -27
- package/dist/plus/integrations/reads/searchIssues.js +104 -153
- package/dist/plus/integrations/reads/searchIssues.js.map +1 -1
- package/dist/plus/integrations/reads/searchPullRequests.d.ts +20 -18
- package/dist/plus/integrations/reads/searchPullRequests.js +91 -113
- package/dist/plus/integrations/reads/searchPullRequests.js.map +1 -1
- package/dist/plus/integrations/reads/sweeps.d.ts +6 -4
- package/dist/plus/integrations/reads/sweeps.js +143 -178
- package/dist/plus/integrations/reads/sweeps.js.map +1 -1
- package/dist/plus/integrations/reads/trackerIssue.d.ts +24 -16
- package/dist/plus/integrations/reads/trackerIssue.js +52 -49
- package/dist/plus/integrations/reads/trackerIssue.js.map +1 -1
- package/dist/plus/integrations/reads/warnings.d.ts +10 -7
- package/dist/plus/integrations/reads/warnings.js +264 -291
- package/dist/plus/integrations/reads/warnings.js.map +1 -1
- package/dist/plus/integrations/results.d.ts +201 -199
- package/dist/plus/integrations/results.js +105 -119
- package/dist/plus/integrations/results.js.map +1 -1
- package/dist/plus/integrations/telemetry.d.ts +7 -5
- package/dist/plus/integrations/telemetry.js +9 -9
- package/dist/plus/integrations/telemetry.js.map +1 -1
- package/dist/plus/integrations/utils/domain.utils.d.ts +21 -0
- package/dist/plus/integrations/utils/domain.utils.js +55 -22
- package/dist/plus/integrations/utils/domain.utils.js.map +1 -1
- package/dist/plus/integrations/utils/integration.utils.d.ts +30 -6
- package/dist/plus/integrations/utils/integration.utils.js +154 -161
- package/dist/plus/integrations/utils/integration.utils.js.map +1 -1
- package/dist/utils/array.d.ts +9 -7
- package/dist/utils/array.js +110 -126
- package/dist/utils/array.js.map +1 -1
- package/dist/utils/base64.d.ts +2 -2
- package/dist/utils/base64.js +2 -2
- package/dist/utils/brand.d.ts +4 -3
- package/dist/utils/brand.js +0 -2
- package/dist/utils/cancellation.d.ts +24 -24
- package/dist/utils/cancellation.js +152 -167
- package/dist/utils/cancellation.js.map +1 -1
- package/dist/utils/charCode.d.ts +31 -37
- package/dist/utils/charCode.js +41 -37
- package/dist/utils/charCode.js.map +1 -1
- package/dist/utils/coalescedRun.d.ts +10 -8
- package/dist/utils/coalescedRun.js +39 -41
- package/dist/utils/coalescedRun.js.map +1 -1
- package/dist/utils/color.d.ts +137 -135
- package/dist/utils/color.js +535 -660
- package/dist/utils/color.js.map +1 -1
- package/dist/utils/counter.d.ts +5 -3
- package/dist/utils/counter.js +19 -17
- package/dist/utils/counter.js.map +1 -1
- package/dist/utils/crypto.d.ts +2 -2
- package/dist/utils/crypto.js +2 -2
- package/dist/utils/date.d.ts +10 -9
- package/dist/utils/date.js +312 -350
- package/dist/utils/date.js.map +1 -1
- package/dist/utils/debounce.d.ts +45 -43
- package/dist/utils/debounce.js +175 -206
- package/dist/utils/debounce.js.map +1 -1
- package/dist/utils/decorators/debounce.d.ts +3 -1
- package/dist/utils/decorators/debounce.js +16 -19
- package/dist/utils/decorators/debounce.js.map +1 -1
- package/dist/utils/decorators/gate.d.ts +13 -11
- package/dist/utils/decorators/gate.js +97 -124
- package/dist/utils/decorators/gate.js.map +1 -1
- package/dist/utils/decorators/log.d.ts +25 -24
- package/dist/utils/decorators/log.js +166 -227
- package/dist/utils/decorators/log.js.map +1 -1
- package/dist/utils/decorators/memoize.d.ts +10 -8
- package/dist/utils/decorators/memoize.js +49 -57
- package/dist/utils/decorators/memoize.js.map +1 -1
- package/dist/utils/decorators/resolver.d.ts +2 -0
- package/dist/utils/decorators/resolver.js +64 -75
- package/dist/utils/decorators/resolver.js.map +1 -1
- package/dist/utils/decorators/sequentialize.d.ts +15 -14
- package/dist/utils/decorators/sequentialize.js +61 -80
- package/dist/utils/decorators/sequentialize.js.map +1 -1
- package/dist/utils/decorators/serializable.d.ts +2 -0
- package/dist/utils/decorators/serializable.js +30 -39
- package/dist/utils/decorators/serializable.js.map +1 -1
- package/dist/utils/dedupedAsyncCache.d.ts +23 -21
- package/dist/utils/dedupedAsyncCache.js +63 -66
- package/dist/utils/dedupedAsyncCache.js.map +1 -1
- package/dist/utils/diff.d.ts +2 -0
- package/dist/utils/diff.js +41 -51
- package/dist/utils/diff.js.map +1 -1
- package/dist/utils/disposable.d.ts +15 -13
- package/dist/utils/disposable.js +71 -80
- package/dist/utils/disposable.js.map +1 -1
- package/dist/utils/dom.d.ts +45 -0
- package/dist/utils/dom.js +106 -0
- package/dist/utils/dom.js.map +1 -0
- package/dist/utils/encoding.d.ts +2 -0
- package/dist/utils/encoding.js +7 -5
- package/dist/utils/encoding.js.map +1 -1
- package/dist/utils/env/browser/base64.d.ts +2 -0
- package/dist/utils/env/browser/base64.js +32 -34
- package/dist/utils/env/browser/base64.js.map +1 -1
- package/dist/utils/env/browser/crypto.d.ts +2 -0
- package/dist/utils/env/browser/crypto.js +17 -13
- package/dist/utils/env/browser/crypto.js.map +1 -1
- package/dist/utils/env/browser/fs.d.ts +2 -0
- package/dist/utils/env/browser/fs.js +6 -2
- package/dist/utils/env/browser/fs.js.map +1 -1
- package/dist/utils/env/browser/hex.d.ts +2 -0
- package/dist/utils/env/browser/hex.js +271 -29
- package/dist/utils/env/browser/hex.js.map +1 -1
- package/dist/utils/env/browser/hrtime.d.ts +2 -0
- package/dist/utils/env/browser/hrtime.js +17 -13
- package/dist/utils/env/browser/hrtime.js.map +1 -1
- package/dist/utils/env/browser/logScope.d.ts +3 -1
- package/dist/utils/env/browser/logScope.js +39 -43
- package/dist/utils/env/browser/logScope.js.map +1 -1
- package/dist/utils/env/browser/md5.d.ts +2 -0
- package/dist/utils/env/browser/md5.js +211 -184
- package/dist/utils/env/browser/md5.js.map +1 -1
- package/dist/utils/env/browser/platform.d.ts +3 -0
- package/dist/utils/env/browser/platform.js +11 -4
- package/dist/utils/env/browser/platform.js.map +1 -1
- package/dist/utils/env/node/base64.d.ts +2 -0
- package/dist/utils/env/node/base64.js +13 -11
- package/dist/utils/env/node/base64.js.map +1 -1
- package/dist/utils/env/node/crypto.d.ts +2 -0
- package/dist/utils/env/node/crypto.js +13 -9
- package/dist/utils/env/node/crypto.js.map +1 -1
- package/dist/utils/env/node/exec.d.ts +66 -65
- package/dist/utils/env/node/exec.js +255 -267
- package/dist/utils/env/node/exec.js.map +1 -1
- package/dist/utils/env/node/fs.d.ts +2 -0
- package/dist/utils/env/node/fs.js +7 -3
- package/dist/utils/env/node/fs.js.map +1 -1
- package/dist/utils/env/node/hex.d.ts +2 -0
- package/dist/utils/env/node/hex.js +10 -6
- package/dist/utils/env/node/hex.js.map +1 -1
- package/dist/utils/env/node/hrtime.d.ts +2 -2
- package/dist/utils/env/node/hrtime.js +2 -2
- package/dist/utils/env/node/logScope.d.ts +3 -1
- package/dist/utils/env/node/logScope.js +52 -42
- package/dist/utils/env/node/logScope.js.map +1 -1
- package/dist/utils/env/node/platform.d.ts +3 -0
- package/dist/utils/env/node/platform.js +11 -4
- package/dist/utils/env/node/platform.js.map +1 -1
- package/dist/utils/error.d.ts +8 -6
- package/dist/utils/error.js +16 -14
- package/dist/utils/error.js.map +1 -1
- package/dist/utils/event.d.ts +27 -25
- package/dist/utils/event.js +141 -170
- package/dist/utils/event.js.map +1 -1
- package/dist/utils/formatter.d.ts +15 -14
- package/dist/utils/formatter.js +96 -129
- package/dist/utils/formatter.js.map +1 -1
- package/dist/utils/fs.d.ts +2 -2
- package/dist/utils/fs.js +2 -2
- package/dist/utils/function.d.ts +5 -6
- package/dist/utils/function.js +73 -90
- package/dist/utils/function.js.map +1 -1
- package/dist/utils/fuzzy.d.ts +18 -16
- package/dist/utils/fuzzy.js +121 -127
- package/dist/utils/fuzzy.js.map +1 -1
- package/dist/utils/gitRefs.d.ts +11 -0
- package/dist/utils/gitRefs.js +21 -0
- package/dist/utils/gitRefs.js.map +1 -0
- package/dist/utils/hash.d.ts +2 -0
- package/dist/utils/hash.js +49 -47
- package/dist/utils/hash.js.map +1 -1
- package/dist/utils/hex.d.ts +2 -2
- package/dist/utils/hex.js +2 -2
- package/dist/utils/hostingServiceType.d.ts +8 -0
- package/dist/utils/hostingServiceType.js +0 -0
- package/dist/utils/hrtime.d.ts +4 -2
- package/dist/utils/hrtime.js +8 -5
- package/dist/utils/hrtime.js.map +1 -1
- package/dist/utils/iterable.d.ts +3 -1
- package/dist/utils/iterable.js +591 -616
- package/dist/utils/iterable.js.map +1 -1
- package/dist/utils/keys/chord.d.ts +27 -25
- package/dist/utils/keys/chord.js +108 -122
- package/dist/utils/keys/chord.js.map +1 -1
- package/dist/utils/keys/keybinding.d.ts +98 -22
- package/dist/utils/keys/keybinding.js +151 -53
- package/dist/utils/keys/keybinding.js.map +1 -1
- package/dist/utils/keys/keymapDispatcher.d.ts +66 -0
- package/dist/utils/keys/keymapDispatcher.js +187 -0
- package/dist/utils/keys/keymapDispatcher.js.map +1 -0
- package/dist/utils/lazy.d.ts +14 -12
- package/dist/utils/lazy.js +35 -36
- package/dist/utils/lazy.js.map +1 -1
- package/dist/utils/loggable.d.ts +3 -1
- package/dist/utils/loggable.js +6 -2
- package/dist/utils/loggable.js.map +1 -1
- package/dist/utils/logger.d.ts +48 -46
- package/dist/utils/logger.js +219 -304
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/logger.scoped.d.ts +30 -28
- package/dist/utils/logger.scoped.js +133 -147
- package/dist/utils/logger.scoped.js.map +1 -1
- package/dist/utils/lruMap.d.ts +34 -32
- package/dist/utils/lruMap.js +97 -101
- package/dist/utils/lruMap.js.map +1 -1
- package/dist/utils/markdown.d.ts +5 -3
- package/dist/utils/markdown.js +52 -88
- package/dist/utils/markdown.js.map +1 -1
- package/dist/utils/mru.d.ts +13 -11
- package/dist/utils/mru.js +57 -62
- package/dist/utils/mru.js.map +1 -1
- package/dist/utils/object.d.ts +9 -34
- package/dist/utils/object.js +132 -191
- package/dist/utils/object.js.map +1 -1
- package/dist/utils/paging.d.ts +36 -34
- package/dist/utils/paging.js +42 -45
- package/dist/utils/paging.js.map +1 -1
- package/dist/utils/path.d.ts +4 -1
- package/dist/utils/path.js +129 -162
- package/dist/utils/path.js.map +1 -1
- package/dist/utils/pausedOperation.d.ts +42 -0
- package/dist/utils/pausedOperation.js +62 -0
- package/dist/utils/pausedOperation.js.map +1 -0
- package/dist/utils/platform.d.ts +2 -2
- package/dist/utils/platform.js +2 -2
- package/dist/utils/plural.d.ts +21 -0
- package/dist/utils/plural.js +136 -0
- package/dist/utils/plural.js.map +1 -0
- package/dist/utils/promise.d.ts +19 -18
- package/dist/utils/promise.js +323 -360
- package/dist/utils/promise.js.map +1 -1
- package/dist/utils/promiseCache.d.ts +331 -330
- package/dist/utils/promiseCache.js +602 -717
- package/dist/utils/promiseCache.js.map +1 -1
- package/dist/utils/resourceUsage.d.ts +2 -0
- package/dist/utils/resourceUsage.js +0 -2
- package/dist/utils/searchTree.d.ts +51 -49
- package/dist/utils/searchTree.js +306 -396
- package/dist/utils/searchTree.js.map +1 -1
- package/dist/utils/stopwatch.d.ts +33 -32
- package/dist/utils/stopwatch.js +67 -81
- package/dist/utils/stopwatch.js.map +1 -1
- package/dist/utils/string.d.ts +17 -24
- package/dist/utils/string.js +787 -803
- package/dist/utils/string.js.map +1 -1
- package/dist/utils/subscriptionManager.d.ts +15 -14
- package/dist/utils/subscriptionManager.js +36 -33
- package/dist/utils/subscriptionManager.js.map +1 -1
- package/dist/utils/trie.d.ts +64 -62
- package/dist/utils/trie.js +438 -580
- package/dist/utils/trie.js.map +1 -1
- package/dist/utils/types.d.ts +8 -16
- package/dist/utils/types.js +0 -2
- package/dist/utils/uri.d.ts +27 -25
- package/dist/utils/uri.js +42 -42
- package/dist/utils/uri.js.map +1 -1
- package/dist/utils/validation.d.ts +13 -12
- package/dist/utils/validation.js +17 -23
- package/dist/utils/validation.js.map +1 -1
- package/dist/utils/version.d.ts +6 -5
- package/dist/utils/version.js +77 -104
- package/dist/utils/version.js.map +1 -1
- package/docs/integrations.md +70 -26
- package/package.json +18 -8
- package/dist/git/cache.d.ts.map +0 -1
- package/dist/git/context.d.ts.map +0 -1
- package/dist/git/errors.d.ts.map +0 -1
- package/dist/git/features.d.ts.map +0 -1
- package/dist/git/gitHealth.d.ts.map +0 -1
- package/dist/git/models/author.d.ts.map +0 -1
- package/dist/git/models/author.js.map +0 -1
- package/dist/git/models/autolink.d.ts.map +0 -1
- package/dist/git/models/autolink.js.map +0 -1
- package/dist/git/models/blame.d.ts.map +0 -1
- package/dist/git/models/blame.js.map +0 -1
- package/dist/git/models/branch.d.ts.map +0 -1
- package/dist/git/models/commit.d.ts.map +0 -1
- package/dist/git/models/contributor.d.ts.map +0 -1
- package/dist/git/models/defaultBranch.d.ts.map +0 -1
- package/dist/git/models/defaultBranch.js.map +0 -1
- package/dist/git/models/diff.d.ts.map +0 -1
- package/dist/git/models/diff.js.map +0 -1
- package/dist/git/models/file.d.ts.map +0 -1
- package/dist/git/models/file.js.map +0 -1
- package/dist/git/models/fileChange.d.ts.map +0 -1
- package/dist/git/models/fileStatus.d.ts.map +0 -1
- package/dist/git/models/graph.d.ts.map +0 -1
- package/dist/git/models/graphSearch.d.ts.map +0 -1
- package/dist/git/models/graphSearch.js.map +0 -1
- package/dist/git/models/graphSession.d.ts.map +0 -1
- package/dist/git/models/issue.d.ts.map +0 -1
- package/dist/git/models/issueOrPullRequest.d.ts.map +0 -1
- package/dist/git/models/issueOrPullRequest.js.map +0 -1
- package/dist/git/models/lineRange.d.ts.map +0 -1
- package/dist/git/models/lineRange.js.map +0 -1
- package/dist/git/models/log.d.ts.map +0 -1
- package/dist/git/models/log.js.map +0 -1
- package/dist/git/models/mergeConflicts.d.ts.map +0 -1
- package/dist/git/models/mergeConflicts.js.map +0 -1
- package/dist/git/models/patch.d.ts.map +0 -1
- package/dist/git/models/patch.js.map +0 -1
- package/dist/git/models/pausedOperationStatus.d.ts.map +0 -1
- package/dist/git/models/pausedOperationStatus.js.map +0 -1
- package/dist/git/models/pullRequest.d.ts.map +0 -1
- package/dist/git/models/rebase.d.ts.map +0 -1
- package/dist/git/models/rebase.js.map +0 -1
- package/dist/git/models/reference.d.ts.map +0 -1
- package/dist/git/models/reference.js.map +0 -1
- package/dist/git/models/reflog.d.ts.map +0 -1
- package/dist/git/models/remote.d.ts.map +0 -1
- package/dist/git/models/remoteProvider.d.ts.map +0 -1
- package/dist/git/models/remoteResource.d.ts.map +0 -1
- package/dist/git/models/repository.d.ts.map +0 -1
- package/dist/git/models/repositoryChangeEvent.d.ts.map +0 -1
- package/dist/git/models/repositoryIdentities.d.ts.map +0 -1
- package/dist/git/models/repositoryMetadata.d.ts.map +0 -1
- package/dist/git/models/repositoryMetadata.js.map +0 -1
- package/dist/git/models/resourceDescriptor.d.ts.map +0 -1
- package/dist/git/models/resourceDescriptor.js.map +0 -1
- package/dist/git/models/revision.d.ts.map +0 -1
- package/dist/git/models/search.d.ts.map +0 -1
- package/dist/git/models/shortlog.d.ts.map +0 -1
- package/dist/git/models/shortlog.js.map +0 -1
- package/dist/git/models/signature.d.ts.map +0 -1
- package/dist/git/models/signature.js.map +0 -1
- package/dist/git/models/staging.d.ts.map +0 -1
- package/dist/git/models/staging.js.map +0 -1
- package/dist/git/models/stash.d.ts.map +0 -1
- package/dist/git/models/stash.js.map +0 -1
- package/dist/git/models/status.d.ts.map +0 -1
- package/dist/git/models/statusFile.d.ts.map +0 -1
- package/dist/git/models/tag.d.ts.map +0 -1
- package/dist/git/models/tree.d.ts.map +0 -1
- package/dist/git/models/tree.js.map +0 -1
- package/dist/git/models/user.d.ts.map +0 -1
- package/dist/git/models/user.js.map +0 -1
- package/dist/git/models/worktree.d.ts.map +0 -1
- package/dist/git/parsers/diffParser.d.ts.map +0 -1
- package/dist/git/parsers/rebaseTodoParser.d.ts.map +0 -1
- package/dist/git/providers/blame.d.ts.map +0 -1
- package/dist/git/providers/blame.js.map +0 -1
- package/dist/git/providers/branches.d.ts.map +0 -1
- package/dist/git/providers/branches.js.map +0 -1
- package/dist/git/providers/commits.d.ts.map +0 -1
- package/dist/git/providers/commits.js.map +0 -1
- package/dist/git/providers/config.d.ts.map +0 -1
- package/dist/git/providers/config.js.map +0 -1
- package/dist/git/providers/contributors.d.ts.map +0 -1
- package/dist/git/providers/contributors.js.map +0 -1
- package/dist/git/providers/diff.d.ts.map +0 -1
- package/dist/git/providers/diff.js.map +0 -1
- package/dist/git/providers/graph.d.ts.map +0 -1
- package/dist/git/providers/graph.js.map +0 -1
- package/dist/git/providers/maintenance.d.ts.map +0 -1
- package/dist/git/providers/maintenance.js.map +0 -1
- package/dist/git/providers/operations.d.ts.map +0 -1
- package/dist/git/providers/operations.js.map +0 -1
- package/dist/git/providers/patch.d.ts.map +0 -1
- package/dist/git/providers/patch.js.map +0 -1
- package/dist/git/providers/pausedOperations.d.ts.map +0 -1
- package/dist/git/providers/pausedOperations.js.map +0 -1
- package/dist/git/providers/provider.d.ts.map +0 -1
- package/dist/git/providers/provider.js.map +0 -1
- package/dist/git/providers/refs.d.ts.map +0 -1
- package/dist/git/providers/refs.js.map +0 -1
- package/dist/git/providers/remotes.d.ts.map +0 -1
- package/dist/git/providers/remotes.js.map +0 -1
- package/dist/git/providers/revision.d.ts.map +0 -1
- package/dist/git/providers/revision.js.map +0 -1
- package/dist/git/providers/shared/remotes.d.ts.map +0 -1
- package/dist/git/providers/staging.d.ts.map +0 -1
- package/dist/git/providers/staging.js.map +0 -1
- package/dist/git/providers/stash.d.ts.map +0 -1
- package/dist/git/providers/stash.js.map +0 -1
- package/dist/git/providers/status.d.ts.map +0 -1
- package/dist/git/providers/status.js.map +0 -1
- package/dist/git/providers/tags.d.ts.map +0 -1
- package/dist/git/providers/tags.js.map +0 -1
- package/dist/git/providers/types.d.ts.map +0 -1
- package/dist/git/providers/types.js.map +0 -1
- package/dist/git/providers/worktrees.d.ts.map +0 -1
- package/dist/git/providers/worktrees.js.map +0 -1
- package/dist/git/remotes/azure-devops.d.ts.map +0 -1
- package/dist/git/remotes/bitbucket-server.d.ts.map +0 -1
- package/dist/git/remotes/bitbucket.d.ts.map +0 -1
- package/dist/git/remotes/custom.d.ts.map +0 -1
- package/dist/git/remotes/gerrit.d.ts.map +0 -1
- package/dist/git/remotes/gitea.d.ts.map +0 -1
- package/dist/git/remotes/github.d.ts.map +0 -1
- package/dist/git/remotes/gitlab.d.ts.map +0 -1
- package/dist/git/remotes/google-source.d.ts.map +0 -1
- package/dist/git/remotes/matcher.d.ts.map +0 -1
- package/dist/git/repositoryService.d.ts.map +0 -1
- package/dist/git/run.types.d.ts.map +0 -1
- package/dist/git/run.types.js.map +0 -1
- package/dist/git/service.d.ts.map +0 -1
- package/dist/git/utils/autolink.utils.d.ts.map +0 -1
- package/dist/git/utils/blame.utils.d.ts.map +0 -1
- package/dist/git/utils/branch.utils.d.ts.map +0 -1
- package/dist/git/utils/commit.utils.d.ts.map +0 -1
- package/dist/git/utils/config.utils.d.ts.map +0 -1
- package/dist/git/utils/conflictResolution.utils.d.ts.map +0 -1
- package/dist/git/utils/contributor.utils.d.ts.map +0 -1
- package/dist/git/utils/fetch.utils.d.ts.map +0 -1
- package/dist/git/utils/fileStatus.utils.d.ts.map +0 -1
- package/dist/git/utils/graph.utils.d.ts.map +0 -1
- package/dist/git/utils/issue.utils.d.ts.map +0 -1
- package/dist/git/utils/issueOrPullRequest.utils.d.ts.map +0 -1
- package/dist/git/utils/mergeConflicts.utils.d.ts.map +0 -1
- package/dist/git/utils/pausedOperationStatus.utils.d.ts.map +0 -1
- package/dist/git/utils/pullRequest.utils.d.ts.map +0 -1
- package/dist/git/utils/reachability.utils.d.ts.map +0 -1
- package/dist/git/utils/rebase.utils.d.ts.map +0 -1
- package/dist/git/utils/reference.utils.d.ts.map +0 -1
- package/dist/git/utils/remote.utils.d.ts.map +0 -1
- package/dist/git/utils/repository.utils.d.ts.map +0 -1
- package/dist/git/utils/resourceDescriptor.utils.d.ts.map +0 -1
- package/dist/git/utils/revision.utils.d.ts.map +0 -1
- package/dist/git/utils/search.utils.d.ts.map +0 -1
- package/dist/git/utils/sorting.d.ts.map +0 -1
- package/dist/git/utils/sshKey.utils.d.ts.map +0 -1
- package/dist/git/utils/status.utils.d.ts.map +0 -1
- package/dist/git/utils/statusFile.utils.d.ts.map +0 -1
- package/dist/git/utils/tag.utils.d.ts.map +0 -1
- package/dist/git/utils/tooltip.utils.d.ts.map +0 -1
- package/dist/git/utils/uriAuthority.d.ts.map +0 -1
- package/dist/git/utils/user.utils.d.ts.map +0 -1
- package/dist/git/utils/worktree.utils.d.ts.map +0 -1
- package/dist/git/watching/changeEvent.d.ts.map +0 -1
- package/dist/git/watching/classifyChange.d.ts.map +0 -1
- package/dist/git/watching/gitIgnoreFilter.d.ts.map +0 -1
- package/dist/git/watching/initWatcher.d.ts.map +0 -1
- package/dist/git/watching/provider.d.ts.map +0 -1
- package/dist/git/watching/provider.js.map +0 -1
- package/dist/git/watching/watchGroup.d.ts.map +0 -1
- package/dist/git/watching/watchService.d.ts.map +0 -1
- package/dist/git/watching/watchSession.d.ts.map +0 -1
- package/dist/git/watching/watcherPatterns.d.ts.map +0 -1
- package/dist/git-cli/cliGitProvider.d.ts.map +0 -1
- package/dist/git-cli/exec/exec.d.ts.map +0 -1
- package/dist/git-cli/exec/exec.errors.d.ts.map +0 -1
- package/dist/git-cli/exec/exec.errors.js.map +0 -1
- package/dist/git-cli/exec/exec.js.map +0 -1
- package/dist/git-cli/exec/exec.types.d.ts.map +0 -1
- package/dist/git-cli/exec/exec.types.js.map +0 -1
- package/dist/git-cli/exec/features.d.ts.map +0 -1
- package/dist/git-cli/exec/features.js.map +0 -1
- package/dist/git-cli/exec/git.d.ts.map +0 -1
- package/dist/git-cli/exec/gitQueue.d.ts.map +0 -1
- package/dist/git-cli/exec/locator.d.ts.map +0 -1
- package/dist/git-cli/parsers/blameParser.d.ts.map +0 -1
- package/dist/git-cli/parsers/indexParser.d.ts.map +0 -1
- package/dist/git-cli/parsers/logParser.d.ts.map +0 -1
- package/dist/git-cli/parsers/mergeTreeParser.d.ts.map +0 -1
- package/dist/git-cli/parsers/refParser.d.ts.map +0 -1
- package/dist/git-cli/parsers/reflogParser.d.ts.map +0 -1
- package/dist/git-cli/parsers/remoteParser.d.ts.map +0 -1
- package/dist/git-cli/parsers/shortlogParser.d.ts.map +0 -1
- package/dist/git-cli/parsers/signatureParser.d.ts.map +0 -1
- package/dist/git-cli/parsers/sshSignatureParser.d.ts.map +0 -1
- package/dist/git-cli/parsers/statusParser.d.ts.map +0 -1
- package/dist/git-cli/parsers/treeParser.d.ts.map +0 -1
- package/dist/git-cli/parsers/worktreeParser.d.ts.map +0 -1
- package/dist/git-cli/providers/blame.d.ts.map +0 -1
- package/dist/git-cli/providers/branches.d.ts.map +0 -1
- package/dist/git-cli/providers/commitFilesetUtils.d.ts.map +0 -1
- package/dist/git-cli/providers/commits.d.ts.map +0 -1
- package/dist/git-cli/providers/config.d.ts.map +0 -1
- package/dist/git-cli/providers/contributors.d.ts.map +0 -1
- package/dist/git-cli/providers/diff.d.ts.map +0 -1
- package/dist/git-cli/providers/graph.d.ts.map +0 -1
- package/dist/git-cli/providers/maintenance.d.ts.map +0 -1
- package/dist/git-cli/providers/operations.d.ts.map +0 -1
- package/dist/git-cli/providers/patch.d.ts.map +0 -1
- package/dist/git-cli/providers/pausedOperations.d.ts.map +0 -1
- package/dist/git-cli/providers/refs.d.ts.map +0 -1
- package/dist/git-cli/providers/remotes.d.ts.map +0 -1
- package/dist/git-cli/providers/revision.d.ts.map +0 -1
- package/dist/git-cli/providers/staging.d.ts.map +0 -1
- package/dist/git-cli/providers/stash.d.ts.map +0 -1
- package/dist/git-cli/providers/status.d.ts.map +0 -1
- package/dist/git-cli/providers/tags.d.ts.map +0 -1
- package/dist/git-cli/providers/worktrees.d.ts.map +0 -1
- package/dist/git-cli/service.d.ts.map +0 -1
- package/dist/ipc/discovery.d.ts.map +0 -1
- package/dist/ipc/ipcServer.d.ts.map +0 -1
- package/dist/plus/agents/agentCapabilities.d.ts.map +0 -1
- package/dist/plus/agents/providers/claudeCodeTranscript.d.ts.map +0 -1
- package/dist/plus/agents/providers/gkAgentProvider.d.ts.map +0 -1
- package/dist/plus/agents/sanitizePrompt.d.ts.map +0 -1
- package/dist/plus/agents/stateMachine.d.ts.map +0 -1
- package/dist/plus/agents/types.d.ts.map +0 -1
- package/dist/plus/ai/constants.d.ts.map +0 -1
- package/dist/plus/ai/errors.d.ts.map +0 -1
- package/dist/plus/ai/models/conversation.d.ts.map +0 -1
- package/dist/plus/ai/models/model.d.ts.map +0 -1
- package/dist/plus/ai/models/promptTemplates.d.ts.map +0 -1
- package/dist/plus/ai/models/promptTemplates.js.map +0 -1
- package/dist/plus/ai/models/provider.d.ts.map +0 -1
- package/dist/plus/ai/models/provider.js.map +0 -1
- package/dist/plus/ai/models/results.d.ts.map +0 -1
- package/dist/plus/ai/models/results.js.map +0 -1
- package/dist/plus/ai/prompts.d.ts.map +0 -1
- package/dist/plus/ai/providers/anthropicProvider.d.ts.map +0 -1
- package/dist/plus/ai/providers/azureProvider.d.ts.map +0 -1
- package/dist/plus/ai/providers/context.d.ts.map +0 -1
- package/dist/plus/ai/providers/context.js.map +0 -1
- package/dist/plus/ai/providers/deepSeekProvider.d.ts.map +0 -1
- package/dist/plus/ai/providers/geminiProvider.d.ts.map +0 -1
- package/dist/plus/ai/providers/gitkrakenProvider.d.ts.map +0 -1
- package/dist/plus/ai/providers/huggingFaceProvider.d.ts.map +0 -1
- package/dist/plus/ai/providers/mistralProvider.d.ts.map +0 -1
- package/dist/plus/ai/providers/ollamaProvider.d.ts.map +0 -1
- package/dist/plus/ai/providers/openAICompatibleProvider.d.ts.map +0 -1
- package/dist/plus/ai/providers/openAICompatibleProviderBase.d.ts.map +0 -1
- package/dist/plus/ai/providers/openRouterProvider.d.ts.map +0 -1
- package/dist/plus/ai/providers/openaiProvider.d.ts.map +0 -1
- package/dist/plus/ai/providers/responseFormatCache.d.ts.map +0 -1
- package/dist/plus/ai/providers/xaiProvider.d.ts.map +0 -1
- package/dist/plus/ai/utils/ai.utils.d.ts.map +0 -1
- package/dist/plus/ai/utils/results.utils.d.ts.map +0 -1
- package/dist/plus/ai/utils/truncation.utils.d.ts.map +0 -1
- package/dist/plus/git-github/api/config.d.ts.map +0 -1
- package/dist/plus/git-github/api/github.d.ts.map +0 -1
- package/dist/plus/git-github/api/github.utils.d.ts.map +0 -1
- package/dist/plus/git-github/api/issueSearchQuery.d.ts.map +0 -1
- package/dist/plus/git-github/api/pullRequestSearchQuery.d.ts.map +0 -1
- package/dist/plus/git-github/api/token.d.ts.map +0 -1
- package/dist/plus/git-github/api/token.js.map +0 -1
- package/dist/plus/git-github/api/tokenUtils.d.ts.map +0 -1
- package/dist/plus/git-github/api/types.d.ts.map +0 -1
- package/dist/plus/git-github/api/types.js.map +0 -1
- package/dist/plus/git-github/context.d.ts.map +0 -1
- package/dist/plus/git-github/models.d.ts.map +0 -1
- package/dist/plus/git-github/providers/github/blame.d.ts.map +0 -1
- package/dist/plus/git-github/providers/github/branches.d.ts.map +0 -1
- package/dist/plus/git-github/providers/github/commits.d.ts.map +0 -1
- package/dist/plus/git-github/providers/github/config.d.ts.map +0 -1
- package/dist/plus/git-github/providers/github/contributors.d.ts.map +0 -1
- package/dist/plus/git-github/providers/github/diff.d.ts.map +0 -1
- package/dist/plus/git-github/providers/github/graph.d.ts.map +0 -1
- package/dist/plus/git-github/providers/github/refs.d.ts.map +0 -1
- package/dist/plus/git-github/providers/github/remotes.d.ts.map +0 -1
- package/dist/plus/git-github/providers/github/revision.d.ts.map +0 -1
- package/dist/plus/git-github/providers/github/status.d.ts.map +0 -1
- package/dist/plus/git-github/providers/github/tags.d.ts.map +0 -1
- package/dist/plus/git-github/providers/githubGitProvider.d.ts.map +0 -1
- package/dist/plus/git-github/providers/githubProvider.d.ts.map +0 -1
- package/dist/plus/git-github/providers/githubProvider.js.map +0 -1
- package/dist/plus/integrations/authentication/cloudIntegrationService.d.ts.map +0 -1
- package/dist/plus/integrations/authentication/configuredIntegrationService.d.ts.map +0 -1
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.d.ts.map +0 -1
- package/dist/plus/integrations/authentication/integrationAuthenticationService.d.ts.map +0 -1
- package/dist/plus/integrations/authentication/manualTokenProvider.d.ts.map +0 -1
- package/dist/plus/integrations/authentication/models.d.ts.map +0 -1
- package/dist/plus/integrations/authentication/rejectedTokenTracker.d.ts.map +0 -1
- package/dist/plus/integrations/collectionMetadata.d.ts.map +0 -1
- package/dist/plus/integrations/constants.d.ts.map +0 -1
- package/dist/plus/integrations/context.d.ts.map +0 -1
- package/dist/plus/integrations/context.js.map +0 -1
- package/dist/plus/integrations/errors.d.ts.map +0 -1
- package/dist/plus/integrations/index.d.ts.map +0 -1
- package/dist/plus/integrations/integrationService.d.ts.map +0 -1
- package/dist/plus/integrations/lite.d.ts.map +0 -1
- package/dist/plus/integrations/manager.d.ts.map +0 -1
- package/dist/plus/integrations/manager.js.map +0 -1
- package/dist/plus/integrations/models/gitHostIntegration.d.ts.map +0 -1
- package/dist/plus/integrations/models/integration.d.ts.map +0 -1
- package/dist/plus/integrations/models/issueCache.d.ts.map +0 -1
- package/dist/plus/integrations/models/issueReads.d.ts.map +0 -1
- package/dist/plus/integrations/models/issueReads.js.map +0 -1
- package/dist/plus/integrations/models/issuesIntegration.d.ts.map +0 -1
- package/dist/plus/integrations/models/pullRequestReads.d.ts.map +0 -1
- package/dist/plus/integrations/models/pullRequestReads.js.map +0 -1
- package/dist/plus/integrations/providerFilters.d.ts.map +0 -1
- package/dist/plus/integrations/providers/accounts.d.ts.map +0 -1
- package/dist/plus/integrations/providers/apiClients.d.ts.map +0 -1
- package/dist/plus/integrations/providers/apiConfig.d.ts.map +0 -1
- package/dist/plus/integrations/providers/azure/azure.d.ts.map +0 -1
- package/dist/plus/integrations/providers/azure/models.d.ts.map +0 -1
- package/dist/plus/integrations/providers/azureDevOps.d.ts.map +0 -1
- package/dist/plus/integrations/providers/bitbucket/bitbucket.d.ts.map +0 -1
- package/dist/plus/integrations/providers/bitbucket/models.d.ts.map +0 -1
- package/dist/plus/integrations/providers/bitbucket-server/models.d.ts.map +0 -1
- package/dist/plus/integrations/providers/bitbucket-server.d.ts.map +0 -1
- package/dist/plus/integrations/providers/bitbucket.d.ts.map +0 -1
- package/dist/plus/integrations/providers/github/github.d.ts.map +0 -1
- package/dist/plus/integrations/providers/github/github.utils.d.ts.map +0 -1
- package/dist/plus/integrations/providers/github/models.d.ts.map +0 -1
- package/dist/plus/integrations/providers/github/models.js.map +0 -1
- package/dist/plus/integrations/providers/github.d.ts.map +0 -1
- package/dist/plus/integrations/providers/gitlab/gitlab.d.ts.map +0 -1
- package/dist/plus/integrations/providers/gitlab/gitlab.utils.d.ts.map +0 -1
- package/dist/plus/integrations/providers/gitlab/models.d.ts.map +0 -1
- package/dist/plus/integrations/providers/gitlab.d.ts.map +0 -1
- package/dist/plus/integrations/providers/issueSorts.d.ts.map +0 -1
- package/dist/plus/integrations/providers/jira.d.ts.map +0 -1
- package/dist/plus/integrations/providers/jiraIssueByKey.d.ts.map +0 -1
- package/dist/plus/integrations/providers/linear.d.ts.map +0 -1
- package/dist/plus/integrations/providers/models.d.ts.map +0 -1
- package/dist/plus/integrations/providers/providerErrors.d.ts.map +0 -1
- package/dist/plus/integrations/providers/providersApi.d.ts.map +0 -1
- package/dist/plus/integrations/providers/pullRequestReviews.d.ts.map +0 -1
- package/dist/plus/integrations/providers/trello.d.ts.map +0 -1
- package/dist/plus/integrations/providers/utils/providerPaging.d.ts.map +0 -1
- package/dist/plus/integrations/providers/utils.d.ts.map +0 -1
- package/dist/plus/integrations/reads/broaden.d.ts.map +0 -1
- package/dist/plus/integrations/reads/context.d.ts.map +0 -1
- package/dist/plus/integrations/reads/context.js.map +0 -1
- package/dist/plus/integrations/reads/counts.d.ts.map +0 -1
- package/dist/plus/integrations/reads/cursors.d.ts.map +0 -1
- package/dist/plus/integrations/reads/drains.d.ts.map +0 -1
- package/dist/plus/integrations/reads/filters.d.ts.map +0 -1
- package/dist/plus/integrations/reads/hierarchy.d.ts.map +0 -1
- package/dist/plus/integrations/reads/hierarchy.utils.d.ts.map +0 -1
- package/dist/plus/integrations/reads/issueBatch.d.ts.map +0 -1
- package/dist/plus/integrations/reads/issueTracker.d.ts.map +0 -1
- package/dist/plus/integrations/reads/issues.d.ts.map +0 -1
- package/dist/plus/integrations/reads/ordering.d.ts.map +0 -1
- package/dist/plus/integrations/reads/paging.d.ts.map +0 -1
- package/dist/plus/integrations/reads/pullRequests.d.ts.map +0 -1
- package/dist/plus/integrations/reads/resolveRepository.d.ts.map +0 -1
- package/dist/plus/integrations/reads/searchIssues.d.ts.map +0 -1
- package/dist/plus/integrations/reads/searchPullRequests.d.ts.map +0 -1
- package/dist/plus/integrations/reads/sweeps.d.ts.map +0 -1
- package/dist/plus/integrations/reads/trackerIssue.d.ts.map +0 -1
- package/dist/plus/integrations/reads/warnings.d.ts.map +0 -1
- package/dist/plus/integrations/results.d.ts.map +0 -1
- package/dist/plus/integrations/telemetry.d.ts.map +0 -1
- package/dist/plus/integrations/utils/domain.utils.d.ts.map +0 -1
- package/dist/plus/integrations/utils/integration.utils.d.ts.map +0 -1
- package/dist/utils/array.d.ts.map +0 -1
- package/dist/utils/base64.d.ts.map +0 -1
- package/dist/utils/base64.js.map +0 -1
- package/dist/utils/brand.d.ts.map +0 -1
- package/dist/utils/brand.js.map +0 -1
- package/dist/utils/cancellation.d.ts.map +0 -1
- package/dist/utils/charCode.d.ts.map +0 -1
- package/dist/utils/coalescedRun.d.ts.map +0 -1
- package/dist/utils/color.d.ts.map +0 -1
- package/dist/utils/counter.d.ts.map +0 -1
- package/dist/utils/crypto.d.ts.map +0 -1
- package/dist/utils/crypto.js.map +0 -1
- package/dist/utils/date.d.ts.map +0 -1
- package/dist/utils/debounce.d.ts.map +0 -1
- package/dist/utils/decorators/debounce.d.ts.map +0 -1
- package/dist/utils/decorators/gate.d.ts.map +0 -1
- package/dist/utils/decorators/log.d.ts.map +0 -1
- package/dist/utils/decorators/memoize.d.ts.map +0 -1
- package/dist/utils/decorators/resolver.d.ts.map +0 -1
- package/dist/utils/decorators/sequentialize.d.ts.map +0 -1
- package/dist/utils/decorators/serializable.d.ts.map +0 -1
- package/dist/utils/dedupedAsyncCache.d.ts.map +0 -1
- package/dist/utils/diff.d.ts.map +0 -1
- package/dist/utils/disposable.d.ts.map +0 -1
- package/dist/utils/encoding.d.ts.map +0 -1
- package/dist/utils/env/browser/base64.d.ts.map +0 -1
- package/dist/utils/env/browser/crypto.d.ts.map +0 -1
- package/dist/utils/env/browser/fs.d.ts.map +0 -1
- package/dist/utils/env/browser/hex.d.ts.map +0 -1
- package/dist/utils/env/browser/hrtime.d.ts.map +0 -1
- package/dist/utils/env/browser/logScope.d.ts.map +0 -1
- package/dist/utils/env/browser/md5.d.ts.map +0 -1
- package/dist/utils/env/browser/platform.d.ts.map +0 -1
- package/dist/utils/env/node/base64.d.ts.map +0 -1
- package/dist/utils/env/node/crypto.d.ts.map +0 -1
- package/dist/utils/env/node/exec.d.ts.map +0 -1
- package/dist/utils/env/node/fs.d.ts.map +0 -1
- package/dist/utils/env/node/hex.d.ts.map +0 -1
- package/dist/utils/env/node/hrtime.d.ts.map +0 -1
- package/dist/utils/env/node/hrtime.js.map +0 -1
- package/dist/utils/env/node/logScope.d.ts.map +0 -1
- package/dist/utils/env/node/platform.d.ts.map +0 -1
- package/dist/utils/error.d.ts.map +0 -1
- package/dist/utils/event.d.ts.map +0 -1
- package/dist/utils/formatter.d.ts.map +0 -1
- package/dist/utils/fs.d.ts.map +0 -1
- package/dist/utils/fs.js.map +0 -1
- package/dist/utils/function.d.ts.map +0 -1
- package/dist/utils/fuzzy.d.ts.map +0 -1
- package/dist/utils/hash.d.ts.map +0 -1
- package/dist/utils/hex.d.ts.map +0 -1
- package/dist/utils/hex.js.map +0 -1
- package/dist/utils/hrtime.d.ts.map +0 -1
- package/dist/utils/iterable.d.ts.map +0 -1
- package/dist/utils/keys/chord.d.ts.map +0 -1
- package/dist/utils/keys/keybinding.d.ts.map +0 -1
- package/dist/utils/lazy.d.ts.map +0 -1
- package/dist/utils/loggable.d.ts.map +0 -1
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/logger.scoped.d.ts.map +0 -1
- package/dist/utils/lruMap.d.ts.map +0 -1
- package/dist/utils/markdown.d.ts.map +0 -1
- package/dist/utils/mru.d.ts.map +0 -1
- package/dist/utils/object.d.ts.map +0 -1
- package/dist/utils/paging.d.ts.map +0 -1
- package/dist/utils/path.d.ts.map +0 -1
- package/dist/utils/platform.d.ts.map +0 -1
- package/dist/utils/platform.js.map +0 -1
- package/dist/utils/promise.d.ts.map +0 -1
- package/dist/utils/promiseCache.d.ts.map +0 -1
- package/dist/utils/resourceUsage.d.ts.map +0 -1
- package/dist/utils/resourceUsage.js.map +0 -1
- package/dist/utils/searchTree.d.ts.map +0 -1
- package/dist/utils/stopwatch.d.ts.map +0 -1
- package/dist/utils/string.d.ts.map +0 -1
- package/dist/utils/subscriptionManager.d.ts.map +0 -1
- package/dist/utils/trie.d.ts.map +0 -1
- package/dist/utils/types.d.ts.map +0 -1
- package/dist/utils/types.js.map +0 -1
- package/dist/utils/uri.d.ts.map +0 -1
- package/dist/utils/validation.d.ts.map +0 -1
- package/dist/utils/version.d.ts.map +0 -1
- package/src/git/cache.ts +0 -1859
- package/src/git/context.ts +0 -301
- package/src/git/errors.ts +0 -1168
- package/src/git/features.ts +0 -98
- package/src/git/gitHealth.ts +0 -522
- package/src/git/models/author.ts +0 -19
- package/src/git/models/autolink.ts +0 -59
- package/src/git/models/blame.ts +0 -46
- package/src/git/models/branch.ts +0 -237
- package/src/git/models/commit.ts +0 -673
- package/src/git/models/contributor.ts +0 -84
- package/src/git/models/defaultBranch.ts +0 -6
- package/src/git/models/diff.ts +0 -74
- package/src/git/models/file.ts +0 -29
- package/src/git/models/fileChange.ts +0 -82
- package/src/git/models/fileStatus.ts +0 -36
- package/src/git/models/graph.ts +0 -444
- package/src/git/models/graphSearch.ts +0 -53
- package/src/git/models/graphSession.ts +0 -102
- package/src/git/models/issue.ts +0 -273
- package/src/git/models/issueOrPullRequest.ts +0 -20
- package/src/git/models/lineRange.ts +0 -7
- package/src/git/models/log.ts +0 -21
- package/src/git/models/mergeConflicts.ts +0 -19
- package/src/git/models/patch.ts +0 -25
- package/src/git/models/pausedOperationStatus.ts +0 -63
- package/src/git/models/pullRequest.ts +0 -357
- package/src/git/models/rebase.ts +0 -60
- package/src/git/models/reference.ts +0 -80
- package/src/git/models/reflog.ts +0 -88
- package/src/git/models/remote.ts +0 -100
- package/src/git/models/remoteProvider.ts +0 -291
- package/src/git/models/remoteResource.ts +0 -87
- package/src/git/models/repository.ts +0 -510
- package/src/git/models/repositoryChangeEvent.ts +0 -30
- package/src/git/models/repositoryIdentities.ts +0 -99
- package/src/git/models/repositoryMetadata.ts +0 -12
- package/src/git/models/resourceDescriptor.ts +0 -28
- package/src/git/models/revision.ts +0 -12
- package/src/git/models/search.ts +0 -138
- package/src/git/models/shortlog.ts +0 -6
- package/src/git/models/signature.ts +0 -45
- package/src/git/models/staging.ts +0 -27
- package/src/git/models/stash.ts +0 -6
- package/src/git/models/status.ts +0 -246
- package/src/git/models/statusFile.ts +0 -131
- package/src/git/models/tag.ts +0 -76
- package/src/git/models/tree.ts +0 -10
- package/src/git/models/user.ts +0 -7
- package/src/git/models/worktree.ts +0 -160
- package/src/git/parsers/diffParser.ts +0 -648
- package/src/git/parsers/rebaseTodoParser.ts +0 -169
- package/src/git/providers/blame.ts +0 -40
- package/src/git/providers/branches.ts +0 -143
- package/src/git/providers/commits.ts +0 -174
- package/src/git/providers/config.ts +0 -167
- package/src/git/providers/contributors.ts +0 -34
- package/src/git/providers/diff.ts +0 -133
- package/src/git/providers/graph.ts +0 -95
- package/src/git/providers/maintenance.ts +0 -224
- package/src/git/providers/operations.ts +0 -164
- package/src/git/providers/patch.ts +0 -31
- package/src/git/providers/pausedOperations.ts +0 -23
- package/src/git/providers/provider.ts +0 -64
- package/src/git/providers/refs.ts +0 -68
- package/src/git/providers/remotes.ts +0 -33
- package/src/git/providers/revision.ts +0 -32
- package/src/git/providers/shared/remotes.ts +0 -95
- package/src/git/providers/staging.ts +0 -36
- package/src/git/providers/stash.ts +0 -51
- package/src/git/providers/status.ts +0 -48
- package/src/git/providers/tags.ts +0 -29
- package/src/git/providers/types.ts +0 -33
- package/src/git/providers/worktrees.ts +0 -37
- package/src/git/remotes/azure-devops.ts +0 -361
- package/src/git/remotes/bitbucket-server.ts +0 -189
- package/src/git/remotes/bitbucket.ts +0 -142
- package/src/git/remotes/custom.ts +0 -162
- package/src/git/remotes/gerrit.ts +0 -186
- package/src/git/remotes/gitea.ts +0 -184
- package/src/git/remotes/github.ts +0 -236
- package/src/git/remotes/gitlab.ts +0 -270
- package/src/git/remotes/google-source.ts +0 -51
- package/src/git/remotes/matcher.ts +0 -215
- package/src/git/repositoryService.ts +0 -180
- package/src/git/run.types.ts +0 -156
- package/src/git/service.ts +0 -369
- package/src/git/utils/autolink.utils.ts +0 -202
- package/src/git/utils/blame.utils.ts +0 -298
- package/src/git/utils/branch.utils.ts +0 -165
- package/src/git/utils/commit.utils.ts +0 -89
- package/src/git/utils/config.utils.ts +0 -8
- package/src/git/utils/conflictResolution.utils.ts +0 -157
- package/src/git/utils/contributor.utils.ts +0 -105
- package/src/git/utils/fetch.utils.ts +0 -48
- package/src/git/utils/fileStatus.utils.ts +0 -62
- package/src/git/utils/graph.utils.ts +0 -46
- package/src/git/utils/issue.utils.ts +0 -187
- package/src/git/utils/issueOrPullRequest.utils.ts +0 -23
- package/src/git/utils/mergeConflicts.utils.ts +0 -21
- package/src/git/utils/pausedOperationStatus.utils.ts +0 -184
- package/src/git/utils/pullRequest.utils.ts +0 -244
- package/src/git/utils/reachability.utils.ts +0 -180
- package/src/git/utils/rebase.utils.ts +0 -128
- package/src/git/utils/reference.utils.ts +0 -267
- package/src/git/utils/remote.utils.ts +0 -146
- package/src/git/utils/repository.utils.ts +0 -18
- package/src/git/utils/resourceDescriptor.utils.ts +0 -26
- package/src/git/utils/revision.utils.ts +0 -149
- package/src/git/utils/search.utils.ts +0 -624
- package/src/git/utils/sorting.ts +0 -329
- package/src/git/utils/sshKey.utils.ts +0 -30
- package/src/git/utils/status.utils.ts +0 -115
- package/src/git/utils/statusFile.utils.ts +0 -182
- package/src/git/utils/tag.utils.ts +0 -17
- package/src/git/utils/tooltip.utils.ts +0 -37
- package/src/git/utils/uriAuthority.ts +0 -34
- package/src/git/utils/user.utils.ts +0 -20
- package/src/git/utils/worktree.utils.ts +0 -21
- package/src/git/watching/changeEvent.ts +0 -95
- package/src/git/watching/classifyChange.ts +0 -104
- package/src/git/watching/gitIgnoreFilter.ts +0 -134
- package/src/git/watching/initWatcher.ts +0 -81
- package/src/git/watching/provider.ts +0 -24
- package/src/git/watching/watchGroup.ts +0 -185
- package/src/git/watching/watchService.ts +0 -430
- package/src/git/watching/watchSession.ts +0 -439
- package/src/git/watching/watcherPatterns.ts +0 -51
- package/src/git-cli/cliGitProvider.ts +0 -461
- package/src/git-cli/exec/exec.errors.ts +0 -1
- package/src/git-cli/exec/exec.ts +0 -11
- package/src/git-cli/exec/exec.types.ts +0 -8
- package/src/git-cli/exec/features.ts +0 -2
- package/src/git-cli/exec/git.ts +0 -1371
- package/src/git-cli/exec/gitQueue.ts +0 -326
- package/src/git-cli/exec/locator.ts +0 -157
- package/src/git-cli/parsers/blameParser.ts +0 -374
- package/src/git-cli/parsers/indexParser.ts +0 -192
- package/src/git-cli/parsers/logParser.ts +0 -1365
- package/src/git-cli/parsers/mergeTreeParser.ts +0 -22
- package/src/git-cli/parsers/refParser.ts +0 -110
- package/src/git-cli/parsers/reflogParser.ts +0 -153
- package/src/git-cli/parsers/remoteParser.ts +0 -95
- package/src/git-cli/parsers/shortlogParser.ts +0 -59
- package/src/git-cli/parsers/signatureParser.ts +0 -162
- package/src/git-cli/parsers/sshSignatureParser.ts +0 -85
- package/src/git-cli/parsers/statusParser.ts +0 -209
- package/src/git-cli/parsers/treeParser.ts +0 -61
- package/src/git-cli/parsers/worktreeParser.ts +0 -111
- package/src/git-cli/providers/blame.ts +0 -598
- package/src/git-cli/providers/branches.ts +0 -1931
- package/src/git-cli/providers/commitFilesetUtils.ts +0 -45
- package/src/git-cli/providers/commits.ts +0 -1856
- package/src/git-cli/providers/config.ts +0 -1025
- package/src/git-cli/providers/contributors.ts +0 -360
- package/src/git-cli/providers/diff.ts +0 -1172
- package/src/git-cli/providers/graph.ts +0 -2237
- package/src/git-cli/providers/maintenance.ts +0 -2599
- package/src/git-cli/providers/operations.ts +0 -1016
- package/src/git-cli/providers/patch.ts +0 -305
- package/src/git-cli/providers/pausedOperations.ts +0 -670
- package/src/git-cli/providers/refs.ts +0 -486
- package/src/git-cli/providers/remotes.ts +0 -115
- package/src/git-cli/providers/revision.ts +0 -383
- package/src/git-cli/providers/staging.ts +0 -210
- package/src/git-cli/providers/stash.ts +0 -577
- package/src/git-cli/providers/status.ts +0 -568
- package/src/git-cli/providers/tags.ts +0 -224
- package/src/git-cli/providers/worktrees.ts +0 -285
- package/src/git-cli/service.ts +0 -82
- package/src/ipc/discovery.ts +0 -186
- package/src/ipc/ipcServer.ts +0 -171
- package/src/plus/agents/agentCapabilities.ts +0 -286
- package/src/plus/agents/providers/claudeCodeTranscript.ts +0 -867
- package/src/plus/agents/providers/gkAgentProvider.ts +0 -4036
- package/src/plus/agents/sanitizePrompt.ts +0 -109
- package/src/plus/agents/stateMachine.ts +0 -197
- package/src/plus/agents/types.ts +0 -500
- package/src/plus/ai/constants.ts +0 -121
- package/src/plus/ai/errors.ts +0 -152
- package/src/plus/ai/models/conversation.ts +0 -35
- package/src/plus/ai/models/model.ts +0 -528
- package/src/plus/ai/models/promptTemplates.ts +0 -171
- package/src/plus/ai/models/provider.ts +0 -125
- package/src/plus/ai/models/results.ts +0 -31
- package/src/plus/ai/prompts.ts +0 -849
- package/src/plus/ai/providers/anthropicProvider.ts +0 -458
- package/src/plus/ai/providers/azureProvider.ts +0 -74
- package/src/plus/ai/providers/context.ts +0 -29
- package/src/plus/ai/providers/deepSeekProvider.ts +0 -46
- package/src/plus/ai/providers/geminiProvider.ts +0 -245
- package/src/plus/ai/providers/gitkrakenProvider.ts +0 -347
- package/src/plus/ai/providers/huggingFaceProvider.ts +0 -64
- package/src/plus/ai/providers/mistralProvider.ts +0 -243
- package/src/plus/ai/providers/ollamaProvider.ts +0 -292
- package/src/plus/ai/providers/openAICompatibleProvider.ts +0 -64
- package/src/plus/ai/providers/openAICompatibleProviderBase.ts +0 -654
- package/src/plus/ai/providers/openRouterProvider.ts +0 -101
- package/src/plus/ai/providers/openaiProvider.ts +0 -47
- package/src/plus/ai/providers/responseFormatCache.ts +0 -39
- package/src/plus/ai/providers/xaiProvider.ts +0 -54
- package/src/plus/ai/utils/ai.utils.ts +0 -95
- package/src/plus/ai/utils/results.utils.ts +0 -407
- package/src/plus/ai/utils/truncation.utils.ts +0 -296
- package/src/plus/git-github/api/config.ts +0 -91
- package/src/plus/git-github/api/github.ts +0 -5462
- package/src/plus/git-github/api/github.utils.ts +0 -36
- package/src/plus/git-github/api/issueSearchQuery.ts +0 -273
- package/src/plus/git-github/api/pullRequestSearchQuery.ts +0 -154
- package/src/plus/git-github/api/token.ts +0 -15
- package/src/plus/git-github/api/tokenUtils.ts +0 -15
- package/src/plus/git-github/api/types.ts +0 -197
- package/src/plus/git-github/context.ts +0 -107
- package/src/plus/git-github/models.ts +0 -623
- package/src/plus/git-github/providers/github/blame.ts +0 -225
- package/src/plus/git-github/providers/github/branches.ts +0 -401
- package/src/plus/git-github/providers/github/commits.ts +0 -929
- package/src/plus/git-github/providers/github/config.ts +0 -47
- package/src/plus/git-github/providers/github/contributors.ts +0 -313
- package/src/plus/git-github/providers/github/diff.ts +0 -281
- package/src/plus/git-github/providers/github/graph.ts +0 -635
- package/src/plus/git-github/providers/github/refs.ts +0 -172
- package/src/plus/git-github/providers/github/remotes.ts +0 -44
- package/src/plus/git-github/providers/github/revision.ts +0 -140
- package/src/plus/git-github/providers/github/status.ts +0 -69
- package/src/plus/git-github/providers/github/tags.ts +0 -148
- package/src/plus/git-github/providers/githubGitProvider.ts +0 -168
- package/src/plus/git-github/providers/githubProvider.ts +0 -65
- package/src/plus/integrations/authentication/cloudIntegrationService.ts +0 -292
- package/src/plus/integrations/authentication/configuredIntegrationService.ts +0 -656
- package/src/plus/integrations/authentication/integrationAuthenticationProvider.ts +0 -385
- package/src/plus/integrations/authentication/integrationAuthenticationService.ts +0 -148
- package/src/plus/integrations/authentication/manualTokenProvider.ts +0 -79
- package/src/plus/integrations/authentication/models.ts +0 -202
- package/src/plus/integrations/authentication/rejectedTokenTracker.ts +0 -56
- package/src/plus/integrations/collectionMetadata.ts +0 -325
- package/src/plus/integrations/constants.ts +0 -190
- package/src/plus/integrations/context.ts +0 -386
- package/src/plus/integrations/errors.ts +0 -188
- package/src/plus/integrations/index.ts +0 -231
- package/src/plus/integrations/integrationService.ts +0 -2003
- package/src/plus/integrations/lite.ts +0 -233
- package/src/plus/integrations/manager.ts +0 -728
- package/src/plus/integrations/models/gitHostIntegration.ts +0 -1862
- package/src/plus/integrations/models/integration.ts +0 -1005
- package/src/plus/integrations/models/issueCache.ts +0 -29
- package/src/plus/integrations/models/issueReads.ts +0 -152
- package/src/plus/integrations/models/issuesIntegration.ts +0 -248
- package/src/plus/integrations/models/pullRequestReads.ts +0 -18
- package/src/plus/integrations/providerFilters.ts +0 -36
- package/src/plus/integrations/providers/accounts.ts +0 -38
- package/src/plus/integrations/providers/apiClients.ts +0 -69
- package/src/plus/integrations/providers/apiConfig.ts +0 -71
- package/src/plus/integrations/providers/azure/azure.ts +0 -953
- package/src/plus/integrations/providers/azure/models.ts +0 -723
- package/src/plus/integrations/providers/azureDevOps.ts +0 -1355
- package/src/plus/integrations/providers/bitbucket/bitbucket.ts +0 -904
- package/src/plus/integrations/providers/bitbucket/models.ts +0 -446
- package/src/plus/integrations/providers/bitbucket-server/models.ts +0 -283
- package/src/plus/integrations/providers/bitbucket-server.ts +0 -445
- package/src/plus/integrations/providers/bitbucket.ts +0 -793
- package/src/plus/integrations/providers/github/github.ts +0 -64
- package/src/plus/integrations/providers/github/github.utils.ts +0 -23
- package/src/plus/integrations/providers/github/models.ts +0 -29
- package/src/plus/integrations/providers/github.ts +0 -940
- package/src/plus/integrations/providers/gitlab/gitlab.ts +0 -1320
- package/src/plus/integrations/providers/gitlab/gitlab.utils.ts +0 -80
- package/src/plus/integrations/providers/gitlab/models.ts +0 -271
- package/src/plus/integrations/providers/gitlab.ts +0 -841
- package/src/plus/integrations/providers/issueSorts.ts +0 -89
- package/src/plus/integrations/providers/jira.ts +0 -596
- package/src/plus/integrations/providers/jiraIssueByKey.ts +0 -155
- package/src/plus/integrations/providers/linear.ts +0 -484
- package/src/plus/integrations/providers/models.ts +0 -1833
- package/src/plus/integrations/providers/providerErrors.ts +0 -115
- package/src/plus/integrations/providers/providersApi.ts +0 -1881
- package/src/plus/integrations/providers/pullRequestReviews.ts +0 -122
- package/src/plus/integrations/providers/trello.ts +0 -208
- package/src/plus/integrations/providers/utils/providerPaging.ts +0 -212
- package/src/plus/integrations/providers/utils.ts +0 -419
- package/src/plus/integrations/reads/broaden.ts +0 -704
- package/src/plus/integrations/reads/context.ts +0 -99
- package/src/plus/integrations/reads/counts.ts +0 -610
- package/src/plus/integrations/reads/cursors.ts +0 -224
- package/src/plus/integrations/reads/drains.ts +0 -420
- package/src/plus/integrations/reads/filters.ts +0 -614
- package/src/plus/integrations/reads/hierarchy.ts +0 -486
- package/src/plus/integrations/reads/hierarchy.utils.ts +0 -155
- package/src/plus/integrations/reads/issueBatch.ts +0 -197
- package/src/plus/integrations/reads/issueTracker.ts +0 -556
- package/src/plus/integrations/reads/issues.ts +0 -600
- package/src/plus/integrations/reads/ordering.ts +0 -124
- package/src/plus/integrations/reads/paging.ts +0 -379
- package/src/plus/integrations/reads/pullRequests.ts +0 -260
- package/src/plus/integrations/reads/resolveRepository.ts +0 -275
- package/src/plus/integrations/reads/searchIssues.ts +0 -274
- package/src/plus/integrations/reads/searchPullRequests.ts +0 -235
- package/src/plus/integrations/reads/sweeps.ts +0 -266
- package/src/plus/integrations/reads/trackerIssue.ts +0 -105
- package/src/plus/integrations/reads/warnings.ts +0 -535
- package/src/plus/integrations/results.ts +0 -443
- package/src/plus/integrations/telemetry.ts +0 -29
- package/src/plus/integrations/utils/domain.utils.ts +0 -26
- package/src/plus/integrations/utils/integration.utils.ts +0 -215
- package/src/utils/array.ts +0 -194
- package/src/utils/base64.ts +0 -1
- package/src/utils/brand.ts +0 -7
- package/src/utils/cancellation.ts +0 -199
- package/src/utils/charCode.ts +0 -38
- package/src/utils/coalescedRun.ts +0 -45
- package/src/utils/color.ts +0 -817
- package/src/utils/counter.ts +0 -21
- package/src/utils/crypto.ts +0 -1
- package/src/utils/date.ts +0 -474
- package/src/utils/debounce.ts +0 -303
- package/src/utils/decorators/debounce.ts +0 -31
- package/src/utils/decorators/gate.ts +0 -167
- package/src/utils/decorators/log.ts +0 -320
- package/src/utils/decorators/memoize.ts +0 -98
- package/src/utils/decorators/resolver.ts +0 -95
- package/src/utils/decorators/sequentialize.ts +0 -118
- package/src/utils/decorators/serializable.ts +0 -43
- package/src/utils/dedupedAsyncCache.ts +0 -74
- package/src/utils/diff.ts +0 -61
- package/src/utils/disposable.ts +0 -156
- package/src/utils/encoding.ts +0 -8
- package/src/utils/env/browser/base64.ts +0 -50
- package/src/utils/env/browser/crypto.ts +0 -21
- package/src/utils/env/browser/fs.ts +0 -3
- package/src/utils/env/browser/hex.ts +0 -41
- package/src/utils/env/browser/hrtime.ts +0 -14
- package/src/utils/env/browser/logScope.ts +0 -57
- package/src/utils/env/browser/md5.ts +0 -217
- package/src/utils/env/browser/platform.ts +0 -10
- package/src/utils/env/node/base64.ts +0 -19
- package/src/utils/env/node/crypto.ts +0 -17
- package/src/utils/env/node/exec.ts +0 -488
- package/src/utils/env/node/fs.ts +0 -5
- package/src/utils/env/node/hex.ts +0 -11
- package/src/utils/env/node/hrtime.ts +0 -1
- package/src/utils/env/node/logScope.ts +0 -63
- package/src/utils/env/node/platform.ts +0 -5
- package/src/utils/error.ts +0 -18
- package/src/utils/event.ts +0 -230
- package/src/utils/formatter.ts +0 -198
- package/src/utils/fs.ts +0 -1
- package/src/utils/function.ts +0 -141
- package/src/utils/fuzzy.ts +0 -185
- package/src/utils/hash.ts +0 -60
- package/src/utils/hex.ts +0 -1
- package/src/utils/hrtime.ts +0 -8
- package/src/utils/iterable.ts +0 -797
- package/src/utils/keys/chord.ts +0 -184
- package/src/utils/keys/keybinding.ts +0 -116
- package/src/utils/lazy.ts +0 -39
- package/src/utils/loggable.ts +0 -7
- package/src/utils/logger.scoped.ts +0 -237
- package/src/utils/logger.ts +0 -403
- package/src/utils/lruMap.ts +0 -112
- package/src/utils/markdown.ts +0 -115
- package/src/utils/mru.ts +0 -68
- package/src/utils/object.ts +0 -278
- package/src/utils/paging.ts +0 -97
- package/src/utils/path.ts +0 -204
- package/src/utils/platform.ts +0 -1
- package/src/utils/promise.ts +0 -657
- package/src/utils/promiseCache.ts +0 -908
- package/src/utils/resourceUsage.ts +0 -5
- package/src/utils/searchTree.ts +0 -444
- package/src/utils/stopwatch.ts +0 -125
- package/src/utils/string.ts +0 -1003
- package/src/utils/subscriptionManager.ts +0 -40
- package/src/utils/trie.ts +0 -740
- package/src/utils/types.ts +0 -31
- package/src/utils/uri.ts +0 -118
- package/src/utils/validation.ts +0 -38
- package/src/utils/version.ts +0 -128
|
@@ -1,4036 +0,0 @@
|
|
|
1
|
-
import { readdir, readFile } from 'fs/promises';
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
import { gate } from '../../../utils/decorators/gate.js';
|
|
4
|
-
import type { UnifiedDisposable } from '../../../utils/disposable.js';
|
|
5
|
-
import { disposableInterval } from '../../../utils/disposable.js';
|
|
6
|
-
import { Emitter } from '../../../utils/event.js';
|
|
7
|
-
import { Logger } from '../../../utils/logger.js';
|
|
8
|
-
import { arePathsEqual, normalizePath } from '../../../utils/path.js';
|
|
9
|
-
import type { AgentCapabilities } from '../agentCapabilities.js';
|
|
10
|
-
import {
|
|
11
|
-
agentCapabilities,
|
|
12
|
-
claudeCodeCapabilities,
|
|
13
|
-
getAgentCapabilities,
|
|
14
|
-
getAgentCapabilitiesByProviderId,
|
|
15
|
-
resolveCanonicalHookEvent,
|
|
16
|
-
resolveCanonicalToolName,
|
|
17
|
-
} from '../agentCapabilities.js';
|
|
18
|
-
import { prepareStoredPrompt } from '../sanitizePrompt.js';
|
|
19
|
-
import {
|
|
20
|
-
classifyPermissionKind,
|
|
21
|
-
deriveStatusFromEvent,
|
|
22
|
-
describeToolInput,
|
|
23
|
-
extractPlanSummary,
|
|
24
|
-
extractQuestionDetails,
|
|
25
|
-
getToolFilePath,
|
|
26
|
-
getToolReadPath,
|
|
27
|
-
isProcessAlive,
|
|
28
|
-
rehydrateSubagents,
|
|
29
|
-
} from '../stateMachine.js';
|
|
30
|
-
import type {
|
|
31
|
-
AgentProviderCallbacks,
|
|
32
|
-
AgentSession,
|
|
33
|
-
AgentSessionHistoryItem,
|
|
34
|
-
AgentSessionHistoryOptions,
|
|
35
|
-
AgentSessionHistoryResult,
|
|
36
|
-
AgentSessionPhase,
|
|
37
|
-
AgentSessionProvider,
|
|
38
|
-
AgentSessionResumeOutcome,
|
|
39
|
-
AgentSessionResumeTarget,
|
|
40
|
-
AgentSessionStatus,
|
|
41
|
-
LiveAgentSession,
|
|
42
|
-
PendingPermission,
|
|
43
|
-
PendingPermissionKind,
|
|
44
|
-
PermissionDecision,
|
|
45
|
-
PermissionSuggestion,
|
|
46
|
-
} from '../types.js';
|
|
47
|
-
import { getPhaseForStatus } from '../types.js';
|
|
48
|
-
import type { EndedTranscriptDetails } from './claudeCodeTranscript.js';
|
|
49
|
-
import { ClaudeCodeTranscriptReader } from './claudeCodeTranscript.js';
|
|
50
|
-
|
|
51
|
-
interface AgentSessionEvent {
|
|
52
|
-
/** The originating agent's NATIVE hook event name, relayed verbatim by the CLI — NOT yet a
|
|
53
|
-
* canonical `AgentHookEvent`. Resolve it through {@link resolveCanonicalHookEvent} before
|
|
54
|
-
* switching on it. */
|
|
55
|
-
event: string;
|
|
56
|
-
sessionId: string;
|
|
57
|
-
/** `gk ai hook` client id of the originating host (`claude-code`, `codex`, …) — NOT
|
|
58
|
-
* `AgentSession.providerId`. Absent on older CLIs. */
|
|
59
|
-
providerId?: string;
|
|
60
|
-
cwd: string;
|
|
61
|
-
/** The agent's launch directory, captured first-hand by the CLI and preserved across events.
|
|
62
|
-
* Optional — older CLIs don't send it, in which case consumers fall back to deriving it from
|
|
63
|
-
* the first-seen `cwd`. */
|
|
64
|
-
initialCwd?: string;
|
|
65
|
-
pid?: number;
|
|
66
|
-
source?: string;
|
|
67
|
-
model?: string;
|
|
68
|
-
reason?: string;
|
|
69
|
-
toolName?: string;
|
|
70
|
-
agentId?: string;
|
|
71
|
-
agentType?: string;
|
|
72
|
-
planFile?: string;
|
|
73
|
-
notificationType?: string;
|
|
74
|
-
sessionName?: string;
|
|
75
|
-
prompt?: string;
|
|
76
|
-
firstPrompt?: string;
|
|
77
|
-
toolInput?: Record<string, unknown>;
|
|
78
|
-
permissionSuggestions?: PermissionSuggestion[];
|
|
79
|
-
hookInput?: Record<string, unknown>;
|
|
80
|
-
/** CLI-resolved worktree roots for this session's cwd visits, newest last — same shape as
|
|
81
|
-
* {@link SessionFileData.cwdTimeline}. The CLI runs `rev-parse` at hook time and includes this
|
|
82
|
-
* on every event, even though it isn't part of the raw hook payload. */
|
|
83
|
-
cwdTimeline?: { cwd: string; worktree?: string; at?: string }[];
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
interface PermissionResponse {
|
|
87
|
-
hookSpecificOutput: {
|
|
88
|
-
hookEventName: 'PermissionRequest';
|
|
89
|
-
decision: {
|
|
90
|
-
behavior: PermissionDecision;
|
|
91
|
-
updatedPermissions?: PermissionSuggestion[];
|
|
92
|
-
};
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
interface PendingPermissionEntry {
|
|
97
|
-
/** All connections waiting on this ask — duplicate deliveries (CLI retry after its 60s
|
|
98
|
-
* response-header timeout) attach here and settle together. */
|
|
99
|
-
resolvers: { resolve(response: PermissionResponse): void; reject(reason: Error): void }[];
|
|
100
|
-
toolName: string;
|
|
101
|
-
toolDescription: string;
|
|
102
|
-
/** From `hookInput.tool_use_id` when the CLI surfaces it; identity gate for stale-clearing. */
|
|
103
|
-
toolUseId?: string;
|
|
104
|
-
/** Serialized tool_input, for duplicate-delivery detection when `toolUseId` is absent. */
|
|
105
|
-
toolInputJson?: string;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
interface SessionBookkeeping {
|
|
109
|
-
activeToolCount: number;
|
|
110
|
-
pendingPermission?: PendingPermission;
|
|
111
|
-
/** Refcount of in-flight file-mutating tool calls per absolute path. Keyed by path (not
|
|
112
|
-
* `tool_use_id`) because the GK CLI hook payload doesn't reliably surface `tool_use_id` —
|
|
113
|
-
* refcounting per path is order-independent and tolerant of out-of-order Pre/Post pairs. */
|
|
114
|
-
currentFileCounts: Map<string, number>;
|
|
115
|
-
/** Per-path cooldown timers scheduled when a path's refcount hits zero, so back-to-back
|
|
116
|
-
* Edit/Write to the same path doesn't flicker the WIP decoration. A fresh Pre on the same
|
|
117
|
-
* path cancels its pending clear. */
|
|
118
|
-
pendingFileClears: Map<string, NodeJS.Timeout>;
|
|
119
|
-
/** Refcount of in-flight read-only file tool calls (Read/NotebookRead) per absolute path.
|
|
120
|
-
* Mirrors {@link currentFileCounts} so consumers can distinguish "looking at" from "working
|
|
121
|
-
* on". */
|
|
122
|
-
currentReadCounts: Map<string, number>;
|
|
123
|
-
/** Per-path cooldown timers for reads — same semantics as {@link pendingFileClears}, keeps a
|
|
124
|
-
* recently-read file visible briefly after the read completes. */
|
|
125
|
-
pendingReadClears: Map<string, NodeJS.Timeout>;
|
|
126
|
-
/** Epoch-ms timestamps of the last `PreToolUse` for each kind, per absolute path. Stamped on
|
|
127
|
-
* every Pre; the per-path `edit`/`read` slot survives through the cooldown window and is
|
|
128
|
-
* dropped only when the corresponding count map drops the path. Serialized into
|
|
129
|
-
* `AgentSession.fileActivity` as `editedAt = now - timestamp.edit` (relative ms), so the
|
|
130
|
-
* webview can compute heat decay without dealing with host clock skew. */
|
|
131
|
-
lastTouchedAt: Map<string, { edit?: number; read?: number }>;
|
|
132
|
-
/** Set to true when `resolveGitInfo` for the session's current cwd returned `undefined`
|
|
133
|
-
* (cwd is not a git repo). Prevents the `ensureSession` retry from firing forever on
|
|
134
|
-
* every subsequent hook event. Cleared when the session's cwd changes (re-resolution
|
|
135
|
-
* warranted) or on resetBookkeeping (Stop/SessionStart). */
|
|
136
|
-
gitInfoUnresolvable: boolean;
|
|
137
|
-
/** Set while the session's `worktreePath` came from the CLI (an event's `cwdTimeline`, or a poll
|
|
138
|
-
* record's). The CLI resolves the worktree for the exact cwd at hook time; the local probe answers
|
|
139
|
-
* for whichever repo the host registry matches, which for a nested cwd can be the parent worktree.
|
|
140
|
-
* While set, `resolveGitInfo` keeps the seated value instead of overwriting it. Cleared when the
|
|
141
|
-
* cwd moves without the CLI explaining the new location — attribution goes back to the probe. */
|
|
142
|
-
cliSeatedWorktree?: boolean;
|
|
143
|
-
/** Phase that immediately preceded the current one, with its original `phaseSince`. Used by
|
|
144
|
-
* `resolvePhaseSince` to restore continuity when phase oscillates back within a short
|
|
145
|
-
* window (e.g., Stop → idle → working after a continuation crosses the debounce). */
|
|
146
|
-
priorPhase?: { phase: AgentSessionPhase; phaseSince: Date };
|
|
147
|
-
/** Set once the reconciliation poll has seen this session's durable `ended` record. Gates
|
|
148
|
-
* ended-session removal: a session the live `SessionEnd` path just transitioned is never
|
|
149
|
-
* reconcile-removed by an already-in-flight poll that legitimately missed it — only after a poll
|
|
150
|
-
* has confirmed the CLI still lists it. */
|
|
151
|
-
polledAtLeastOnce?: boolean;
|
|
152
|
-
/** The durable record's cwd that a live listing superseded when a resume moved the session
|
|
153
|
-
* elsewhere. While the record still reports this exact cwd, a poll that no longer finds the
|
|
154
|
-
* session in the live listing must not re-seat the row (or trust the record's `endedAt`) —
|
|
155
|
-
* the record predates the resume. Cleared when the record's cwd changes (the CLI caught up). */
|
|
156
|
-
supersededRecordCwd?: string;
|
|
157
|
-
/** Set once an ended session's transcript has been read on demand (via
|
|
158
|
-
* `resolveEndedSessionDetails`). Terminal transcripts don't change, so the read happens at
|
|
159
|
-
* most once regardless of how many times the row is opened. */
|
|
160
|
-
endedDetailsResolved?: boolean;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const staleCheckIntervalMs = 15 * 60 * 1000; // 15 minutes
|
|
164
|
-
/** Minimum spacing between gated reconciliation polls while NOTHING is running. The quarter-hourly
|
|
165
|
-
* tick exists to reap agents that died without a `SessionEnd`; with no live session there's nothing
|
|
166
|
-
* to reap, leaving only terminal-history reconciliation and the CLI's retention sweep — both fine
|
|
167
|
-
* hourly. Window startup polls ungated, so this never delays a fresh window. */
|
|
168
|
-
const idleReconcileIntervalMs = 60 * 60 * 1000; // 1 hour
|
|
169
|
-
/** How long a freshly-ended row is protected from absence-based removal. The CLI persists the
|
|
170
|
-
* ended record (atomic temp+rename) BEFORE broadcasting the hook event, so a poll that starts after
|
|
171
|
-
* we mark a row ended will list it — this guards only against that ordering changing on the CLI
|
|
172
|
-
* side, where the cost of being wrong is an ended row silently vanishing. */
|
|
173
|
-
const endedRemovalGraceMs = 30 * 1000; // 30 seconds
|
|
174
|
-
/** How long the machine-global archived-session id list (fetched via a separate `--status archived`
|
|
175
|
-
* CLI query) is cached. `listSessionHistory` asks for it once per worktree on panel open, so without
|
|
176
|
-
* this a multi-worktree graph spawns N identical CLI processes. `archiveSession` invalidates the
|
|
177
|
-
* cache, so a just-archived row never lingers under "Past". */
|
|
178
|
-
const archivedSessionIdsCacheTtlMs = 10 * 1000; // 10 seconds
|
|
179
|
-
/** Ended sessions defer git + transcript resolution (a 30-day cold-start would otherwise fan out
|
|
180
|
-
* hundreds of probes). Exception: sessions ended within this window still surface on WIP rows /
|
|
181
|
-
* branch cards, which match strictly by resolved `worktreePath` — so their git info is resolved
|
|
182
|
-
* eagerly at poll time (a handful), keeping the older tail lazy. Mirrors the 24h WIP-row window. */
|
|
183
|
-
const recentEndedGitResolveThresholdMs = 24 * 60 * 60 * 1000; // 24 hours
|
|
184
|
-
/** Default cooldown between PostToolUse and dropping the file from `fileActivity`. Held long
|
|
185
|
-
* enough that the treemap activity overlay can render a decay tail well past the moment the tool
|
|
186
|
-
* call completed. The host may override per-call via `AgentProviderCallbacks.getActivityDecayMs`
|
|
187
|
-
* (driven by the user's `gitlens.graph.experimental.visualizations.activityDecay` setting). */
|
|
188
|
-
const defaultActivityDecayMs = 5 * 60 * 1000; // 5 minutes
|
|
189
|
-
|
|
190
|
-
/** Grace period before `Stop` commits to `idle`. Long enough to absorb a same-turn continuation
|
|
191
|
-
* (hook-driven re-prompt, IPC event reordering, auto-resume); short enough that legitimate idle
|
|
192
|
-
* is visible promptly. */
|
|
193
|
-
const stopToIdleDebounceMs = 750;
|
|
194
|
-
|
|
195
|
-
/** If a phase transitions away and then back within this window, restore the prior `phaseSince`
|
|
196
|
-
* so the displayed elapsed time doesn't snap to 0 on transient oscillations. Slightly longer
|
|
197
|
-
* than `stopToIdleDebounceMs` so a continuation that just barely crosses the debounce still
|
|
198
|
-
* restores continuity. */
|
|
199
|
-
const phaseSinceRestoreWindowMs = 2000;
|
|
200
|
-
|
|
201
|
-
interface SessionFileData {
|
|
202
|
-
sessionId: string;
|
|
203
|
-
/** `gk ai hook` client id of the owning host — see {@link AgentSessionEvent.providerId}. */
|
|
204
|
-
providerId?: string;
|
|
205
|
-
/** The owning agent's NATIVE hook event name — see {@link AgentSessionEvent.event}. */
|
|
206
|
-
event: string;
|
|
207
|
-
/** The agent's RAW hook payload for {@link event}, relayed verbatim by the CLI — same field the
|
|
208
|
-
* live path reads off {@link AgentSessionEvent.hookInput}. The poll needs it because some agents
|
|
209
|
-
* encode an event's meaning in the payload rather than its name (OpenCode's `session.status`),
|
|
210
|
-
* and {@link resolveCanonicalHookEvent} can't canonicalize those without being handed it. */
|
|
211
|
-
hookInput?: Record<string, unknown>;
|
|
212
|
-
cwd: string;
|
|
213
|
-
/** CLI-provided launch directory; absent on older CLIs (fall back to `cwd`). */
|
|
214
|
-
initialCwd?: string;
|
|
215
|
-
/** Worktree roots (`git rev-parse --show-toplevel`) the CLI resolved for this session's cwd
|
|
216
|
-
* visits, newest last. Absent on older CLIs. Lets us read `worktreePath` straight from the
|
|
217
|
-
* durable store — so ended sessions attach to branch cards / WIP rows / the resume picker at
|
|
218
|
-
* any age without a git probe (the CLI already ran `rev-parse` at hook time). */
|
|
219
|
-
cwdTimeline?: { cwd: string; worktree?: string; at?: string }[];
|
|
220
|
-
worktrees?: string[];
|
|
221
|
-
pid: number;
|
|
222
|
-
toolName?: string | null;
|
|
223
|
-
agentId?: string | null;
|
|
224
|
-
agentType?: string | null;
|
|
225
|
-
source?: string | null;
|
|
226
|
-
model?: string | null;
|
|
227
|
-
updatedAt: string;
|
|
228
|
-
subagents?: { agentId: string; agentType: string }[];
|
|
229
|
-
planFile?: string | null;
|
|
230
|
-
sessionName?: string | null;
|
|
231
|
-
prompt?: string | null;
|
|
232
|
-
firstPrompt?: string | null;
|
|
233
|
-
/** CLI durable-session lifecycle status. Absent on legacy files → treated as `active`. `ended`
|
|
234
|
-
* records are surfaced as terminal `ended` sessions; `archived` is excluded by the query. */
|
|
235
|
-
status?: 'active' | 'ended' | 'archived';
|
|
236
|
-
/** Why the session ended (`session-end`, `rotated`, `stale`, `dead-pid`, `pid-zero-idle`,
|
|
237
|
-
* `archived`). Present only on `ended`/`archived` records. */
|
|
238
|
-
endReason?: string;
|
|
239
|
-
/** RFC3339 timestamp the session ended; used as `lastActivity`/`phaseSince` for ended rows. */
|
|
240
|
-
endedAt?: string;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/** The session's current worktree root from the CLI's durable record — the last cwd-visit's resolved
|
|
244
|
-
* worktree, falling back to the last distinct worktree seen. `undefined` on older CLIs that don't
|
|
245
|
-
* record it, so the caller resolves git the slow way instead. */
|
|
246
|
-
function worktreeRootFromData(data: SessionFileData): string | undefined {
|
|
247
|
-
const fromTimeline = data.cwdTimeline?.at(-1)?.worktree;
|
|
248
|
-
if (fromTimeline) return fromTimeline;
|
|
249
|
-
|
|
250
|
-
return data.worktrees?.at(-1) || undefined;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/** The CLI resolves the worktree for `event.cwd` at hook time and includes it on every event —
|
|
254
|
-
* using its answer seats `worktreePath` synchronously, closing the in-flight-probe window where
|
|
255
|
-
* a session that just moved would otherwise still show its previous worktree until `resolveGitInfo`
|
|
256
|
-
* catches up. Scans from the end for the timeline entry matching the event's current cwd. */
|
|
257
|
-
function worktreeFromEvent(event: AgentSessionEvent): string | undefined {
|
|
258
|
-
const timeline = event.cwdTimeline;
|
|
259
|
-
if (timeline == null) return undefined;
|
|
260
|
-
|
|
261
|
-
for (let i = timeline.length - 1; i >= 0; i--) {
|
|
262
|
-
const entry = timeline[i];
|
|
263
|
-
if (entry.cwd === event.cwd) return entry.worktree || undefined;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
return undefined;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/** Reorders `paths` so each distinct (already-normalized) value is positioned by its LAST
|
|
270
|
-
* occurrence — an earlier occurrence of a value that recurs later is dropped, and the value's one
|
|
271
|
-
* remaining slot sits where that last occurrence falls. Implemented via delete-then-reinsert into
|
|
272
|
-
* a `Map`: re-inserting a key after deleting it moves it to the end of `Map` iteration order,
|
|
273
|
-
* which is exactly "this value's position is its most recent occurrence". */
|
|
274
|
-
function orderByLastOccurrence(paths: readonly string[]): string[] {
|
|
275
|
-
const seen = new Map<string, true>();
|
|
276
|
-
for (const path of paths) {
|
|
277
|
-
seen.delete(path);
|
|
278
|
-
seen.set(path, true);
|
|
279
|
-
}
|
|
280
|
-
return [...seen.keys()];
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/** Distinct, normalized, non-empty `worktree` values from a `cwdTimeline`, ordered by recency —
|
|
284
|
-
* positioned by each path's LAST occurrence in the (chronological) timeline, so a path the
|
|
285
|
-
* session returns to later sorts after one it doesn't. `undefined` when the timeline is absent or
|
|
286
|
-
* names no worktree — callers should NOT overwrite an existing `visitedWorktreePaths` with an
|
|
287
|
-
* empty array in that case. */
|
|
288
|
-
function visitedFromTimeline(
|
|
289
|
-
timeline: { cwd: string; worktree?: string; at?: string }[] | undefined,
|
|
290
|
-
): string[] | undefined {
|
|
291
|
-
if (timeline == null) return undefined;
|
|
292
|
-
|
|
293
|
-
const paths: string[] = [];
|
|
294
|
-
for (const entry of timeline) {
|
|
295
|
-
if (!entry.worktree) continue;
|
|
296
|
-
|
|
297
|
-
paths.push(normalizePath(entry.worktree));
|
|
298
|
-
}
|
|
299
|
-
if (paths.length === 0) return undefined;
|
|
300
|
-
|
|
301
|
-
return orderByLastOccurrence(paths);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/** Unions `adds` (each an array or a single path, `undefined` skipped) into `existing`, normalizing
|
|
305
|
-
* every path, and reorders the result by recency: every add — including one that names a path
|
|
306
|
-
* already in `existing` — moves that path to the end, same "last occurrence wins the slot" rule
|
|
307
|
-
* {@link orderByLastOccurrence} applies to a single timeline. Adds arrive in chronological order at
|
|
308
|
-
* every call site (timeline-derived paths first, the current `worktreePath` folded last), so the
|
|
309
|
-
* current worktree normally ends up last.
|
|
310
|
-
*
|
|
311
|
-
* **Reference-stability contract — load-bearing**: returns `existing` BY REFERENCE, unchanged,
|
|
312
|
-
* when the resulting sequence is element-for-element identical to `existing`. Callers
|
|
313
|
-
* (`ensureSession`'s identity gate) rely on this to avoid firing spurious change events on every
|
|
314
|
-
* hook event / poll cycle when the visited set hasn't actually changed — re-processing an
|
|
315
|
-
* unchanged timeline plus the same current worktree reproduces the identical sequence (each path
|
|
316
|
-
* "moves" to the same slot it already occupies), so the comparison below still holds. */
|
|
317
|
-
function unionVisited(
|
|
318
|
-
existing: readonly string[] | undefined,
|
|
319
|
-
...adds: (string | readonly string[] | undefined)[]
|
|
320
|
-
): readonly string[] | undefined {
|
|
321
|
-
const combined = existing != null ? [...existing] : [];
|
|
322
|
-
for (const add of adds) {
|
|
323
|
-
if (add == null) continue;
|
|
324
|
-
|
|
325
|
-
const values = typeof add === 'string' ? [add] : add;
|
|
326
|
-
for (const value of values) {
|
|
327
|
-
if (!value) continue;
|
|
328
|
-
|
|
329
|
-
combined.push(normalizePath(value));
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
const next = orderByLastOccurrence(combined);
|
|
334
|
-
if (existing != null && next.length === existing.length && next.every((path, i) => path === existing[i])) {
|
|
335
|
-
return existing;
|
|
336
|
-
}
|
|
337
|
-
return next.length > 0 ? next : undefined;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/** Peer-merge variant of {@link unionVisited}: same normalize-and-dedupe, but APPEND-ONLY —
|
|
341
|
-
* `existing`'s order is preserved and any path the peer names that we don't already have is
|
|
342
|
-
* appended in the peer's own order. A peer's order can't be trusted as fresher than ours without
|
|
343
|
-
* timestamps, so peer input never reorders what we already hold. Returns `existing` BY REFERENCE
|
|
344
|
-
* when nothing new is added — same rationale as {@link unionVisited}. */
|
|
345
|
-
function unionVisitedAppendOnly(
|
|
346
|
-
existing: readonly string[] | undefined,
|
|
347
|
-
...adds: (string | readonly string[] | undefined)[]
|
|
348
|
-
): readonly string[] | undefined {
|
|
349
|
-
const next = new Set(existing ?? []);
|
|
350
|
-
let grew = false;
|
|
351
|
-
for (const add of adds) {
|
|
352
|
-
if (add == null) continue;
|
|
353
|
-
|
|
354
|
-
const values = typeof add === 'string' ? [add] : add;
|
|
355
|
-
for (const value of values) {
|
|
356
|
-
if (!value) continue;
|
|
357
|
-
|
|
358
|
-
const normalized = normalizePath(value);
|
|
359
|
-
if (!next.has(normalized)) {
|
|
360
|
-
next.add(normalized);
|
|
361
|
-
grew = true;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
return grew ? [...next] : existing;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/** The event's tool name, translated from the agent's native vocabulary into the canonical (Claude
|
|
369
|
-
* Code) one. Prefers the raw `hookInput.tool_name` passthrough and falls back to the projected
|
|
370
|
-
* top-level field for older CLIs. Everything downstream — `describeToolInput`,
|
|
371
|
-
* `classifyPermissionKind`, `getToolFilePath`, `getToolReadPath`, and the display detail — expects
|
|
372
|
-
* canonical names, and Claude Code has no alias table, so its names pass through untouched. */
|
|
373
|
-
function canonicalToolNameFromEvent(capabilities: AgentCapabilities, event: AgentSessionEvent): string | undefined {
|
|
374
|
-
const native = (event.hookInput?.tool_name as string | undefined) ?? event.toolName;
|
|
375
|
-
return native != null ? resolveCanonicalToolName(capabilities, native) : undefined;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/** Whether GitLens can actually answer a blocking permission ask from this agent. Two conditions,
|
|
379
|
-
* deliberately separate: the agent must support blocking hooks at all, AND the response we build
|
|
380
|
-
* must be one it understands. {@link PermissionResponse} is Claude-shaped
|
|
381
|
-
* (`hookSpecificOutput.hookEventName: 'PermissionRequest'`), so an agent that supports blocking
|
|
382
|
-
* hooks but expects a different envelope stays observe-only until per-agent response builders land
|
|
383
|
-
* — at which point the second clause is what gets deleted. */
|
|
384
|
-
function canAnswerBlockingPermission(capabilities: AgentCapabilities): boolean {
|
|
385
|
-
return capabilities.supportsBlockingPermissions && capabilities.hookClientId === 'claude-code';
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/** Synthesizes an unresolvable ask for a `permission_requested` row that has no routable
|
|
389
|
-
* `PendingPermission` — a non-blocking `Notification`/`PermissionRequest` tail, or a
|
|
390
|
-
* poll-discovered live/revived row. None of these hold a blocking hook entry to route an
|
|
391
|
-
* Allow/Deny through, so the card must show what the agent is waiting on without offering
|
|
392
|
-
* actions the host can't fulfill. */
|
|
393
|
-
function synthesizeUnresolvableAsk(toolName: string | null | undefined, planFilePath?: string): PendingPermission {
|
|
394
|
-
if (toolName != null) {
|
|
395
|
-
const kind = classifyPermissionKind(toolName);
|
|
396
|
-
return {
|
|
397
|
-
kind: kind,
|
|
398
|
-
toolName: toolName,
|
|
399
|
-
toolDescription: toolName,
|
|
400
|
-
// A plan ask with no body at least links the plan the agent wrote, so the card can offer
|
|
401
|
-
// View Plan instead of a bare tool name.
|
|
402
|
-
planFilePath: kind === 'plan' ? planFilePath : undefined,
|
|
403
|
-
resolvable: false,
|
|
404
|
-
};
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
return {
|
|
408
|
-
kind: 'elicitation',
|
|
409
|
-
toolName: 'Input Required',
|
|
410
|
-
toolDescription: 'Waiting for input',
|
|
411
|
-
resolvable: false,
|
|
412
|
-
};
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
/** JSON.stringify with object keys recursively sorted — array order is preserved. `tool_input`
|
|
416
|
-
* can arrive with different key order (or via the top-level fallback field) between deliveries;
|
|
417
|
-
* canonicalizing makes input equality reliable across those variations. */
|
|
418
|
-
function stableStringify(value: unknown): string {
|
|
419
|
-
return JSON.stringify(value, (_key, val: unknown) => {
|
|
420
|
-
if (val == null || typeof val !== 'object' || Array.isArray(val)) return val;
|
|
421
|
-
|
|
422
|
-
const sorted: Record<string, unknown> = {};
|
|
423
|
-
for (const key of Object.keys(val).sort()) {
|
|
424
|
-
sorted[key] = (val as Record<string, unknown>)[key];
|
|
425
|
-
}
|
|
426
|
-
return sorted;
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
/** Field-wise equality for the ask a `permission_requested` row publishes. `updateSessionStatus`
|
|
431
|
-
* compares it before short-circuiting so a second ask arriving while the row is already
|
|
432
|
-
* `permission_requested` (a non-blocking tail for a different tool) still reaches subscribers. */
|
|
433
|
-
function isSameAsk(a: PendingPermission | undefined, b: PendingPermission | undefined): boolean {
|
|
434
|
-
if (a === b) return true;
|
|
435
|
-
if (a == null || b == null) return false;
|
|
436
|
-
|
|
437
|
-
return (
|
|
438
|
-
a.kind === b.kind &&
|
|
439
|
-
a.toolName === b.toolName &&
|
|
440
|
-
a.toolDescription === b.toolDescription &&
|
|
441
|
-
a.toolInputDescription === b.toolInputDescription &&
|
|
442
|
-
a.resolvable === b.resolvable &&
|
|
443
|
-
a.planFilePath === b.planFilePath &&
|
|
444
|
-
a.planSummary === b.planSummary &&
|
|
445
|
-
a.questionText === b.questionText &&
|
|
446
|
-
a.questionCount === b.questionCount
|
|
447
|
-
);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
interface DiscoveryFile {
|
|
451
|
-
token: string;
|
|
452
|
-
address: string;
|
|
453
|
-
port?: number;
|
|
454
|
-
workspacePaths?: string[];
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
interface SessionContext {
|
|
458
|
-
/** The owning agent's descriptor — the source of a created session's `providerId`/`providerName`.
|
|
459
|
-
* Omitted only by the deferred-commit paths (the `Stop → idle` timer, a permission resolve),
|
|
460
|
-
* which run against a row that already exists. */
|
|
461
|
-
capabilities?: AgentCapabilities;
|
|
462
|
-
pid?: number;
|
|
463
|
-
workspacePath?: string;
|
|
464
|
-
isInWorkspace?: boolean;
|
|
465
|
-
cwd?: string;
|
|
466
|
-
/** CLI-provided launch directory (authoritative when present). */
|
|
467
|
-
initialCwd?: string;
|
|
468
|
-
planFile?: string;
|
|
469
|
-
sessionName?: string;
|
|
470
|
-
/** The model the session is running, from the hook event. */
|
|
471
|
-
model?: string;
|
|
472
|
-
/** CLI-resolved worktree root for the current cwd (authoritative when present). */
|
|
473
|
-
worktreePath?: string;
|
|
474
|
-
/** Distinct worktree roots observed via this event's cwdTimeline. */
|
|
475
|
-
visitedWorktreePaths?: string[];
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
type SerializedAgentSession = Omit<AgentSession, 'lastActivity' | 'phaseSince' | 'subagents'> & {
|
|
479
|
-
lastActivity: string;
|
|
480
|
-
phaseSince: string;
|
|
481
|
-
subagents?: (Omit<AgentSession, 'lastActivity' | 'phaseSince'> & {
|
|
482
|
-
lastActivity: string;
|
|
483
|
-
phaseSince: string;
|
|
484
|
-
})[];
|
|
485
|
-
};
|
|
486
|
-
|
|
487
|
-
/** Normalize a workspace path to GitLens canonical form (forward slashes, lower-case drive letter
|
|
488
|
-
* on Windows). Comparisons throughout the home view assume this form, but `_workspacePaths` and
|
|
489
|
-
* peer-session `workspacePath` values originate from `Uri.fsPath`, which on Windows uses
|
|
490
|
-
* backslashes and preserves drive-letter casing. Without this, `session.workspacePath ===
|
|
491
|
-
* repository.path` always fails on Windows. */
|
|
492
|
-
function normalizeWorkspacePath(value: string | null | undefined): string | undefined {
|
|
493
|
-
return value ? normalizePath(value) : undefined;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
type FileActivityEntry = NonNullable<AgentSession['fileActivity']>[number];
|
|
497
|
-
|
|
498
|
-
/** Structural comparison of two `fileActivity` arrays — same set of paths, same kinds present,
|
|
499
|
-
* same `reading`/`editing` flags. Ignores numeric timestamp values because those drift between
|
|
500
|
-
* every call (Date.now() advances) even when nothing has actually changed. Used to gate event
|
|
501
|
-
* firing: timestamps still get refreshed via the `_sessions[]` write, but a fire only happens
|
|
502
|
-
* when the structural shape changed. Treats `undefined` and `[]` as equal. */
|
|
503
|
-
function fileActivityStructurallyEqual(
|
|
504
|
-
a: readonly FileActivityEntry[] | undefined,
|
|
505
|
-
b: readonly FileActivityEntry[] | undefined,
|
|
506
|
-
): boolean {
|
|
507
|
-
const aLen = a?.length ?? 0;
|
|
508
|
-
const bLen = b?.length ?? 0;
|
|
509
|
-
if (aLen !== bLen) return false;
|
|
510
|
-
if (aLen === 0) return true;
|
|
511
|
-
|
|
512
|
-
const byPath = new Map<string, FileActivityEntry>();
|
|
513
|
-
for (const entry of a!) {
|
|
514
|
-
byPath.set(entry.path, entry);
|
|
515
|
-
}
|
|
516
|
-
for (const entry of b!) {
|
|
517
|
-
const other = byPath.get(entry.path);
|
|
518
|
-
if (other == null) return false;
|
|
519
|
-
if ((entry.readAt != null) !== (other.readAt != null)) return false;
|
|
520
|
-
if ((entry.editedAt != null) !== (other.editedAt != null)) return false;
|
|
521
|
-
if ((entry.reading ?? false) !== (other.reading ?? false)) return false;
|
|
522
|
-
if ((entry.editing ?? false) !== (other.editing ?? false)) return false;
|
|
523
|
-
}
|
|
524
|
-
return true;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
/**
|
|
528
|
-
* Maps what `claude agents --json` reports for a live session onto `AgentSessionStatus`, per Claude's
|
|
529
|
-
* documented agent-view vocabulary.
|
|
530
|
-
*
|
|
531
|
-
* `kind: 'interactive'` entries carry `status` while their process is alive, plus `waitingFor` when
|
|
532
|
-
* that status is `waiting` — `'permission prompt'` is the one value with a distinct status of its own
|
|
533
|
-
* here, since a permission request is a different kind of block from an ordinary question.
|
|
534
|
-
*
|
|
535
|
-
* `kind: 'background'` entries carry `state` instead: `working` / `blocked` / `done` / `failed` /
|
|
536
|
-
* `stopped`, where `blocked` IS the documented "needs input" state.
|
|
537
|
-
*
|
|
538
|
-
* Returns `undefined` for a background session in a TERMINAL state. `--json` lists background
|
|
539
|
-
* sessions "still working or blocked even when their process has exited", so a listing is not by
|
|
540
|
-
* itself proof of life — without this, a genuinely finished session would be resurrected as `idle`,
|
|
541
|
-
* which is the opposite of the bug this mapping exists to fix. Callers must not treat such an entry
|
|
542
|
-
* as live: ended-record paths trust the durable record, while active discovery/repair paths skip or
|
|
543
|
-
* remove the stale active row.
|
|
544
|
-
*
|
|
545
|
-
* Anything unrecognized degrades to `'idle'` rather than guessing a working/waiting phase.
|
|
546
|
-
*/
|
|
547
|
-
/** Resolves a non-terminal listing override for an `ended` record. `undefined` means "let the ended
|
|
548
|
-
* record stand": either the listing doesn't mention this session, or its background state is
|
|
549
|
-
* terminal (see {@link statusFromLiveSession}). Active-record paths inspect the entry separately
|
|
550
|
-
* because they must distinguish absence (unknown; keep record status) from terminal (skip/remove). */
|
|
551
|
-
function liveOverrideFor(
|
|
552
|
-
liveSessions: ReadonlyMap<string, LiveAgentSession> | undefined,
|
|
553
|
-
sessionId: string,
|
|
554
|
-
): { live: LiveAgentSession; status: AgentSessionStatus } | undefined {
|
|
555
|
-
const live = liveSessions?.get(sessionId);
|
|
556
|
-
if (live == null) return undefined;
|
|
557
|
-
|
|
558
|
-
const status = statusFromLiveSession(live);
|
|
559
|
-
return status != null ? { live: live, status: status } : undefined;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
function statusFromLiveSession(live: {
|
|
563
|
-
status?: string;
|
|
564
|
-
state?: string;
|
|
565
|
-
waitingFor?: string;
|
|
566
|
-
}): AgentSessionStatus | undefined {
|
|
567
|
-
switch (live.status) {
|
|
568
|
-
case 'waiting':
|
|
569
|
-
return live.waitingFor === 'permission prompt' ? 'permission_requested' : 'waiting';
|
|
570
|
-
case 'busy':
|
|
571
|
-
return 'thinking';
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
switch (live.state) {
|
|
575
|
-
case 'blocked':
|
|
576
|
-
return 'waiting';
|
|
577
|
-
case 'working':
|
|
578
|
-
return 'thinking';
|
|
579
|
-
case 'done':
|
|
580
|
-
case 'failed':
|
|
581
|
-
case 'stopped':
|
|
582
|
-
return undefined;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
return 'idle';
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
/** True when a CLI invocation failed because it didn't recognize a flag/command. `--status` and the
|
|
589
|
-
* active-only `list-sessions` default shipped together (gk 3.1.69); an older CLI rejects `--status`
|
|
590
|
-
* and returns all sessions unfiltered, so the flagless retry is the correct legacy equivalent. */
|
|
591
|
-
function isUnknownFlagError(ex: unknown): boolean {
|
|
592
|
-
const message = ex instanceof Error ? ex.message : String(ex);
|
|
593
|
-
return /unknown (?:flag|shorthand flag|command)/i.test(message);
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
export class GkAgentProvider implements AgentSessionProvider {
|
|
597
|
-
/** Identifies the PROVIDER, not an agent: this one hosts every `gk ai hook` client GitLens has
|
|
598
|
-
* a descriptor for. A session's own agent identity lives on {@link AgentSession.providerId}
|
|
599
|
-
* (`claudeCode`, `codex`, …), sourced from {@link AgentCapabilities} — never from here. */
|
|
600
|
-
readonly id = 'gkAgents';
|
|
601
|
-
readonly name = 'Coding Agents';
|
|
602
|
-
readonly icon = 'robot';
|
|
603
|
-
readonly agentProviderIds: readonly string[] = agentCapabilities.map(c => c.providerId);
|
|
604
|
-
|
|
605
|
-
private readonly _onDidChangeSessions = new Emitter<void>();
|
|
606
|
-
readonly onDidChangeSessions = this._onDidChangeSessions.event;
|
|
607
|
-
|
|
608
|
-
private _sessions: AgentSession[] = [];
|
|
609
|
-
private _ipcStarted = false;
|
|
610
|
-
private _disposed = false;
|
|
611
|
-
private _handlerDisposables: UnifiedDisposable[] = [];
|
|
612
|
-
private readonly _pendingPermissions = new Map<string, PendingPermissionEntry>();
|
|
613
|
-
/** `sessionId -> cwd currently being probed`, and the newest cwd requested while that probe runs.
|
|
614
|
-
* A probe's answer is only valid for the cwd it was started with, so a session that moves
|
|
615
|
-
* mid-flight must supersede rather than dedupe — otherwise the older lookup lands last and
|
|
616
|
-
* overwrites the newer location with the directory the session already left. */
|
|
617
|
-
private readonly _resolveGitInfoInFlight = new Map<string, string>();
|
|
618
|
-
private readonly _resolveGitInfoPending = new Map<string, string>();
|
|
619
|
-
/** Short-TTL cache of the machine-global archived-session id list. Holds the in-flight promise so
|
|
620
|
-
* concurrent per-worktree `getPastSessions` calls share one CLI spawn; `archiveSession` clears it. */
|
|
621
|
-
private _archivedSessionIdsCache: { promise: Promise<string[]>; at: number } | undefined;
|
|
622
|
-
private readonly _sessionBookkeeping = new Map<string, SessionBookkeeping>();
|
|
623
|
-
/** Per-session timers for the deferred `Stop → idle` commit. The handle is cleared (and the
|
|
624
|
-
* transition cancelled) by any non-idle status update arriving before the timer fires. */
|
|
625
|
-
private readonly _pendingIdleTimers = new Map<string, NodeJS.Timeout>();
|
|
626
|
-
private _workspacePaths: string[] = [];
|
|
627
|
-
private _staleCheckTimer: UnifiedDisposable | undefined;
|
|
628
|
-
/** When the last poll actually ran (gated or not) — paces the idle cadence above. */
|
|
629
|
-
private _lastSyncAt = 0;
|
|
630
|
-
/** Whether agent hooks are installed, pushed by the host via {@link setHooksInstalled}.
|
|
631
|
-
* Fail-open (`true`) until the first push lands so a fresh window with real hooks isn't
|
|
632
|
-
* suppressed on its first ticks. Gates the reconciliation poll in {@link syncSessions}. */
|
|
633
|
-
private _hooksInstalled = true;
|
|
634
|
-
/** Whether the CLI supports `list-sessions --status` (and with it the durable ended-session
|
|
635
|
-
* store). Optimistic until the poll's flagless fallback proves otherwise. When unsupported,
|
|
636
|
-
* ended rows are untenable — no poll can ever confirm or archive one — so `SessionEnd`
|
|
637
|
-
* reverts to removing the session and reconciliation drops any ended stragglers. */
|
|
638
|
-
private _statusFilterSupported = true;
|
|
639
|
-
protected _transcriptReader: ClaudeCodeTranscriptReader = new ClaudeCodeTranscriptReader();
|
|
640
|
-
private _terminalGeneration = 0;
|
|
641
|
-
/** Ended-transcript detail results, cached forever per window — an ended transcript is
|
|
642
|
-
* immutable, so once resolved it never needs a second read. Keyed by session id. */
|
|
643
|
-
private readonly _sessionDetailsCache = new Map<string, Promise<EndedTranscriptDetails | undefined>>();
|
|
644
|
-
|
|
645
|
-
constructor(private readonly callbacks: AgentProviderCallbacks) {}
|
|
646
|
-
|
|
647
|
-
get sessions(): readonly AgentSession[] {
|
|
648
|
-
return this._sessions;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
get terminalGeneration(): number {
|
|
652
|
-
return this._terminalGeneration;
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
/** Single choke point for every terminal transition — a session ending, being removed on end
|
|
656
|
-
* (legacy path), being pruned, or a fresh ended record being discovered. Bumps the generation
|
|
657
|
-
* the host's history queries validate against (a transition mid-query means the answer may be
|
|
658
|
-
* missing a session) and drops the transcript listing caches, whose warm entries may predate
|
|
659
|
-
* the transcript the transition just left behind. */
|
|
660
|
-
private noteTerminalTransition(): void {
|
|
661
|
-
this._terminalGeneration++;
|
|
662
|
-
this._transcriptReader.invalidateListings();
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
start(workspacePaths: string[]): void {
|
|
666
|
-
this._workspacePaths = workspacePaths.map(p => normalizePath(p));
|
|
667
|
-
void this.ensureIpcServer();
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
stop(): void {
|
|
671
|
-
// IPC server stays up intentionally — hooks fire even when the window is unfocused
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
sync(): Promise<void> {
|
|
675
|
-
return this.syncSessions();
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
updateWorkspacePaths(workspacePaths: string[]): void {
|
|
679
|
-
this._workspacePaths = workspacePaths.map(p => normalizePath(p));
|
|
680
|
-
|
|
681
|
-
let changed = false;
|
|
682
|
-
for (let i = 0; i < this._sessions.length; i++) {
|
|
683
|
-
const s = this._sessions[i];
|
|
684
|
-
const nextWorkspacePath = s.cwd != null ? this.resolveWorkspacePath(s.cwd) : undefined;
|
|
685
|
-
const nextIsInWorkspace = nextWorkspacePath != null;
|
|
686
|
-
let subagentsChanged = false;
|
|
687
|
-
const subagents = s.subagents?.map(sub => {
|
|
688
|
-
const subNextWorkspacePath = sub.cwd != null ? this.resolveWorkspacePath(sub.cwd) : nextWorkspacePath;
|
|
689
|
-
const subNextIsInWorkspace = sub.cwd != null ? subNextWorkspacePath != null : nextIsInWorkspace;
|
|
690
|
-
if (sub.isInWorkspace === subNextIsInWorkspace && sub.workspacePath === subNextWorkspacePath) {
|
|
691
|
-
return sub;
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
subagentsChanged = true;
|
|
695
|
-
return { ...sub, isInWorkspace: subNextIsInWorkspace, workspacePath: subNextWorkspacePath };
|
|
696
|
-
});
|
|
697
|
-
if (s.isInWorkspace !== nextIsInWorkspace || s.workspacePath !== nextWorkspacePath || subagentsChanged) {
|
|
698
|
-
this._sessions[i] = {
|
|
699
|
-
...s,
|
|
700
|
-
isInWorkspace: nextIsInWorkspace,
|
|
701
|
-
workspacePath: nextWorkspacePath,
|
|
702
|
-
subagents: subagents,
|
|
703
|
-
};
|
|
704
|
-
changed = true;
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
if (changed) {
|
|
708
|
-
this._onDidChangeSessions.fire();
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
if (!this._ipcStarted) {
|
|
712
|
-
// IPC wasn't bootstrapped yet — start it. ensureIpcServer publishes with the
|
|
713
|
-
// current `_workspacePaths`.
|
|
714
|
-
void this.ensureIpcServer();
|
|
715
|
-
} else {
|
|
716
|
-
// Re-publish so the agents discovery file reflects the new workspacePaths.
|
|
717
|
-
this.callbacks.ipc
|
|
718
|
-
.publishAgents(this._workspacePaths)
|
|
719
|
-
.catch((ex: unknown) => Logger.error(ex, 'GkAgentProvider.updateWorkspacePaths: publishAgents failed'));
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
setHooksInstalled(installed: boolean): void {
|
|
724
|
-
const wasOff = !this._hooksInstalled;
|
|
725
|
-
this._hooksInstalled = installed;
|
|
726
|
-
// On a fresh off→on transition, reconcile immediately rather than waiting up to a full
|
|
727
|
-
// interval — picks up any session that was already running before hooks were installed.
|
|
728
|
-
// This is a deliberate discovery pass (like the cold-start bootstrap), not a routine tick:
|
|
729
|
-
// hooks were just off, so the live path couldn't have seen these sessions and anything we
|
|
730
|
-
// find here is expected, not drift. Run it ungated so it neither skips nor reports drift
|
|
731
|
-
// telemetry (an ungated `syncSessions()` always polls and never emits `onSyncDiscrepancy`).
|
|
732
|
-
if (installed && wasOff) {
|
|
733
|
-
void this.syncSessions();
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
/** Reads Claude history from its transcript store and tracked terminal records. The exact project
|
|
738
|
-
* directory preserves legacy sessions that predate the CLI's durable records. Recent ended
|
|
739
|
-
* records add an authoritative worktree-to-session association, so their ids are also recovered
|
|
740
|
-
* from other project directories when Claude left the transcript behind after a cwd/worktree
|
|
741
|
-
* move. A tracked record with no readable transcript remains visible and archivable, but does not
|
|
742
|
-
* advertise Resume. Archive filtering stays provider-local so ids from another harness cannot
|
|
743
|
-
* suppress Claude rows. `requireResume` drops the transcript-less tracked records, leaving only
|
|
744
|
-
* rows that carry a resume action — and a `total` counting only those. */
|
|
745
|
-
async listSessionHistory(cwd: string, options?: AgentSessionHistoryOptions): Promise<AgentSessionHistoryResult> {
|
|
746
|
-
const resumeOnly = options?.requireResume === true;
|
|
747
|
-
// No launcher means no row can carry resume, so a resume-only query has nothing to return.
|
|
748
|
-
if (resumeOnly && this.callbacks.resumeSession == null) return { sessions: [], total: 0 };
|
|
749
|
-
|
|
750
|
-
const archivedSessionIds = new Set(await this.getArchivedSessionIds());
|
|
751
|
-
const excludeSessionIds = new Set(options?.excludeSessionIds);
|
|
752
|
-
for (const id of archivedSessionIds) {
|
|
753
|
-
excludeSessionIds.add(id);
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
const trackedEndedSessions: AgentSession[] = [];
|
|
757
|
-
const endedSessionIds = new Set<string>();
|
|
758
|
-
for (const session of this._sessions) {
|
|
759
|
-
if (
|
|
760
|
-
session.status === 'ended' &&
|
|
761
|
-
session.worktreePath != null &&
|
|
762
|
-
arePathsEqual(session.worktreePath, cwd) &&
|
|
763
|
-
!excludeSessionIds.has(session.id)
|
|
764
|
-
) {
|
|
765
|
-
// A tracked record's own row carries archive but no resume; its id still drives transcript
|
|
766
|
-
// recovery below, which does produce a resumable row when the transcript moved with it.
|
|
767
|
-
if (!resumeOnly) {
|
|
768
|
-
trackedEndedSessions.push(session);
|
|
769
|
-
}
|
|
770
|
-
// Only an agent whose sessions can be resumed feeds transcript recovery. The reader is
|
|
771
|
-
// Claude-transcript-specific, so another agent's id could only ever miss — but stating it
|
|
772
|
-
// keeps the resume capability the reason, rather than leaving it to a coincidence of which
|
|
773
|
-
// store the reader happens to read.
|
|
774
|
-
if (getAgentCapabilitiesByProviderId(session.providerId)?.supportsResume !== false) {
|
|
775
|
-
endedSessionIds.add(session.id);
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
const [direct, recovered] = await Promise.all([
|
|
781
|
-
this._transcriptReader.listSessions(cwd, {
|
|
782
|
-
limit: options?.limit,
|
|
783
|
-
excludeSessionIds: excludeSessionIds,
|
|
784
|
-
}),
|
|
785
|
-
this._transcriptReader.listSessionsByIds(endedSessionIds, {
|
|
786
|
-
limit: options?.limit,
|
|
787
|
-
excludeCwd: cwd,
|
|
788
|
-
}),
|
|
789
|
-
]);
|
|
790
|
-
|
|
791
|
-
const historyById = new Map<string, AgentSessionHistoryItem>();
|
|
792
|
-
const addTranscript = (session: (typeof direct.sessions)[number], resumeCwd: string): void => {
|
|
793
|
-
if (excludeSessionIds.has(session.sessionId)) return;
|
|
794
|
-
|
|
795
|
-
const disposition = archivedSessionIds.has(session.sessionId) ? 'archived' : 'ended';
|
|
796
|
-
const resumeActions = this.callbacks.resumeSession != null ? { resume: { cwd: resumeCwd } } : {};
|
|
797
|
-
historyById.set(session.sessionId, {
|
|
798
|
-
id: session.sessionId,
|
|
799
|
-
// The reader only reads Claude Code's transcript store, so every row it produces is
|
|
800
|
-
// Claude's by construction.
|
|
801
|
-
providerId: claudeCodeCapabilities.providerId,
|
|
802
|
-
disposition: disposition,
|
|
803
|
-
actions: disposition === 'ended' ? { ...resumeActions, archive: true } : resumeActions,
|
|
804
|
-
lastActivity: new Date(session.lastActivityMs),
|
|
805
|
-
titles: session.titles,
|
|
806
|
-
lastPrompt: session.lastPrompt,
|
|
807
|
-
});
|
|
808
|
-
};
|
|
809
|
-
for (const session of direct.sessions) {
|
|
810
|
-
addTranscript(session, cwd);
|
|
811
|
-
}
|
|
812
|
-
for (const session of recovered.sessions) {
|
|
813
|
-
addTranscript(session, session.cwd);
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
for (const session of trackedEndedSessions) {
|
|
817
|
-
if (historyById.has(session.id)) continue;
|
|
818
|
-
|
|
819
|
-
historyById.set(session.id, {
|
|
820
|
-
id: session.id,
|
|
821
|
-
providerId: session.providerId,
|
|
822
|
-
disposition: 'ended',
|
|
823
|
-
actions: { archive: true },
|
|
824
|
-
lastActivity: session.lastActivity,
|
|
825
|
-
name: session.name,
|
|
826
|
-
titles: session.transcriptTitles,
|
|
827
|
-
firstPrompt: session.firstPrompt,
|
|
828
|
-
lastPrompt: session.lastPrompt,
|
|
829
|
-
});
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
const history = [...historyById.values()].sort((a, b) => b.lastActivity.getTime() - a.lastActivity.getTime());
|
|
833
|
-
const limited = options?.limit != null && options.limit > 0 ? history.slice(0, options.limit) : history;
|
|
834
|
-
|
|
835
|
-
return { sessions: limited, total: Math.max(direct.total + recovered.total, history.length) };
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
resumeSession(
|
|
839
|
-
sessionId: string,
|
|
840
|
-
cwd: string,
|
|
841
|
-
target: AgentSessionResumeTarget,
|
|
842
|
-
name?: string,
|
|
843
|
-
): Promise<AgentSessionResumeOutcome | false> {
|
|
844
|
-
return this.callbacks.resumeSession?.(sessionId, cwd, target, name) ?? Promise.resolve(false);
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
dispose(): void {
|
|
848
|
-
this._disposed = true;
|
|
849
|
-
this.stop();
|
|
850
|
-
this._staleCheckTimer?.dispose();
|
|
851
|
-
this._staleCheckTimer = undefined;
|
|
852
|
-
for (const d of this._handlerDisposables) {
|
|
853
|
-
d.dispose();
|
|
854
|
-
}
|
|
855
|
-
this._handlerDisposables = [];
|
|
856
|
-
for (const pending of this._pendingPermissions.values()) {
|
|
857
|
-
this.rejectAllPending(pending, new Error('Provider disposed'));
|
|
858
|
-
}
|
|
859
|
-
this._pendingPermissions.clear();
|
|
860
|
-
for (const bk of this._sessionBookkeeping.values()) {
|
|
861
|
-
this.cancelPendingFileClears(bk);
|
|
862
|
-
this.cancelPendingReadClears(bk);
|
|
863
|
-
}
|
|
864
|
-
for (const timer of this._pendingIdleTimers.values()) {
|
|
865
|
-
clearTimeout(timer);
|
|
866
|
-
}
|
|
867
|
-
this._pendingIdleTimers.clear();
|
|
868
|
-
this._sessionBookkeeping.clear();
|
|
869
|
-
if (this._ipcStarted) {
|
|
870
|
-
void this.callbacks.ipc.unpublishAgents();
|
|
871
|
-
this._ipcStarted = false;
|
|
872
|
-
}
|
|
873
|
-
this._onDidChangeSessions.dispose();
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
[Symbol.dispose](): void {
|
|
877
|
-
this.dispose();
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
@gate<typeof GkAgentProvider.prototype.ensureIpcServer>()
|
|
881
|
-
private async ensureIpcServer(): Promise<void> {
|
|
882
|
-
if (this._ipcStarted || this._disposed) return;
|
|
883
|
-
|
|
884
|
-
// Register handlers before any async work so blocking permission requests aren't delayed.
|
|
885
|
-
const handlers: UnifiedDisposable[] = [
|
|
886
|
-
this.callbacks.ipc.registerHandler('agents/session', (request, searchParams) => {
|
|
887
|
-
if (request != null) {
|
|
888
|
-
const isBlocking = searchParams.get('blocking') === 'true';
|
|
889
|
-
return this.handleSessionEvent(request as AgentSessionEvent, isBlocking);
|
|
890
|
-
}
|
|
891
|
-
return Promise.resolve();
|
|
892
|
-
}),
|
|
893
|
-
this.callbacks.ipc.registerHandler('agents/sessions/list', () =>
|
|
894
|
-
Promise.resolve(
|
|
895
|
-
// Exclude terminal `ended` rows: an older-version peer has no `ended` phase and
|
|
896
|
-
// would import them as phantom "idle" sessions. Our own poll surfaces ended sessions
|
|
897
|
-
// independently, so peers never need them from here.
|
|
898
|
-
this._sessions
|
|
899
|
-
.filter(s => s.status !== 'ended')
|
|
900
|
-
.map(s => ({
|
|
901
|
-
...s,
|
|
902
|
-
lastActivity: s.lastActivity.toISOString(),
|
|
903
|
-
phaseSince: s.phaseSince.toISOString(),
|
|
904
|
-
subagents: s.subagents?.map(sub => ({
|
|
905
|
-
...sub,
|
|
906
|
-
lastActivity: sub.lastActivity.toISOString(),
|
|
907
|
-
phaseSince: sub.phaseSince.toISOString(),
|
|
908
|
-
})),
|
|
909
|
-
})),
|
|
910
|
-
),
|
|
911
|
-
),
|
|
912
|
-
this.callbacks.ipc.registerHandler('agents/sessions/open', async request => {
|
|
913
|
-
const sessionId = (request as { sessionId?: string } | undefined)?.sessionId;
|
|
914
|
-
if (!sessionId || this.callbacks.openSessionInClaudeExtension == null) {
|
|
915
|
-
return { opened: false };
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
try {
|
|
919
|
-
await this.callbacks.openSessionInClaudeExtension(sessionId);
|
|
920
|
-
return { opened: true };
|
|
921
|
-
} catch (ex) {
|
|
922
|
-
Logger.warn(
|
|
923
|
-
`GkAgentProvider.agents/sessions/open: ${ex instanceof Error ? ex.message : String(ex)}`,
|
|
924
|
-
);
|
|
925
|
-
return { opened: false };
|
|
926
|
-
}
|
|
927
|
-
}),
|
|
928
|
-
];
|
|
929
|
-
|
|
930
|
-
try {
|
|
931
|
-
await this.callbacks.ipc.publishAgents(this._workspacePaths);
|
|
932
|
-
} catch (ex) {
|
|
933
|
-
// Unwind so a retry can re-register without hitting "already registered".
|
|
934
|
-
for (const d of handlers) {
|
|
935
|
-
d.dispose();
|
|
936
|
-
}
|
|
937
|
-
Logger.error(ex, 'GkAgentProvider.ensureIpcServer');
|
|
938
|
-
return;
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
// `publishAgents` no-ops silently if the IPC server failed to start; don't seal
|
|
942
|
-
// retries in that case — the next call should re-attempt.
|
|
943
|
-
if (this.callbacks.ipc.port == null) {
|
|
944
|
-
for (const d of handlers) {
|
|
945
|
-
d.dispose();
|
|
946
|
-
}
|
|
947
|
-
return;
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
if (this._disposed) {
|
|
951
|
-
for (const d of handlers) {
|
|
952
|
-
d.dispose();
|
|
953
|
-
}
|
|
954
|
-
return;
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
this._handlerDisposables = handlers;
|
|
958
|
-
this._ipcStarted = true;
|
|
959
|
-
|
|
960
|
-
try {
|
|
961
|
-
await this.syncSessions();
|
|
962
|
-
if (this._disposed) return;
|
|
963
|
-
|
|
964
|
-
void this.querySiblingWindowSessions();
|
|
965
|
-
|
|
966
|
-
this._staleCheckTimer?.dispose();
|
|
967
|
-
this._staleCheckTimer = disposableInterval(
|
|
968
|
-
() => void this.syncSessions({ gate: true }),
|
|
969
|
-
staleCheckIntervalMs,
|
|
970
|
-
);
|
|
971
|
-
} catch (ex) {
|
|
972
|
-
Logger.error(ex, 'GkAgentProvider.ensureIpcServer');
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
private handleSessionEvent(event: AgentSessionEvent, isBlocking: boolean): Promise<PermissionResponse | void> {
|
|
977
|
-
// The CLI broadcasts every AI host's events to every listener, stamped with its `gk ai hook`
|
|
978
|
-
// client id. Absent = `claude-code`: older CLIs don't stamp it, and no other client existed
|
|
979
|
-
// then. A client we hold no descriptor for is dropped — the CLI supports more than this table
|
|
980
|
-
// does, and we can't interpret an agent whose vocabulary we don't know. A dropped blocking
|
|
981
|
-
// request gets no decision, so the CLI waits out its own hook timeout, which is correct: we
|
|
982
|
-
// must not answer for an agent we don't track.
|
|
983
|
-
const hookClientId = event.providerId ?? 'claude-code';
|
|
984
|
-
const capabilities = getAgentCapabilities(hookClientId);
|
|
985
|
-
if (capabilities == null) {
|
|
986
|
-
Logger.debug(`GkAgentProvider.handleSessionEvent: ignoring ${event.event} from ${hookClientId}`);
|
|
987
|
-
return Promise.resolve();
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
// The CLI relays each agent's native event name verbatim, so translate before doing anything
|
|
991
|
-
// else. An unresolvable name must never reach the switch below: it would match no case while
|
|
992
|
-
// still having created/updated a row, and for a blocking delivery would answer nothing while
|
|
993
|
-
// looking handled.
|
|
994
|
-
const hookEvent = resolveCanonicalHookEvent(capabilities, event.event, event.hookInput);
|
|
995
|
-
if (hookEvent == null) {
|
|
996
|
-
Logger.debug(
|
|
997
|
-
`GkAgentProvider.handleSessionEvent: ignoring unmapped event ${event.event} from ${hookClientId}`,
|
|
998
|
-
);
|
|
999
|
-
return Promise.resolve();
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
const toolName = canonicalToolNameFromEvent(capabilities, event);
|
|
1003
|
-
const workspacePath = this.resolveWorkspacePath(event.cwd);
|
|
1004
|
-
const eventContext: SessionContext = {
|
|
1005
|
-
capabilities: capabilities,
|
|
1006
|
-
pid: event.pid,
|
|
1007
|
-
workspacePath: workspacePath,
|
|
1008
|
-
isInWorkspace: workspacePath != null,
|
|
1009
|
-
cwd: event.cwd,
|
|
1010
|
-
initialCwd: event.initialCwd,
|
|
1011
|
-
planFile: event.planFile,
|
|
1012
|
-
sessionName: event.sessionName,
|
|
1013
|
-
model: event.model,
|
|
1014
|
-
worktreePath: worktreeFromEvent(event),
|
|
1015
|
-
visitedWorktreePaths: visitedFromTimeline(event.cwdTimeline),
|
|
1016
|
-
};
|
|
1017
|
-
const tag = this.sessionTag(event.sessionId, event.sessionName ?? 'unnamed');
|
|
1018
|
-
// Log the native name alongside the canonical one — a mapping mistake is otherwise invisible.
|
|
1019
|
-
const eventTag = event.event === hookEvent ? hookEvent : `${hookEvent}(${event.event})`;
|
|
1020
|
-
|
|
1021
|
-
if (hookEvent === 'SessionStart' || hookEvent === 'SessionEnd') {
|
|
1022
|
-
Logger.info(`GkAgentProvider.handleSessionEvent: ${eventTag} ${tag}`);
|
|
1023
|
-
} else {
|
|
1024
|
-
Logger.debug(
|
|
1025
|
-
`GkAgentProvider.handleSessionEvent: ${eventTag} ${tag}${toolName ? ` tool=${toolName}` : ''}${event.agentId ? ` agent=${event.agentId}` : ''}${event.notificationType ? ` type=${event.notificationType}` : ''}`,
|
|
1026
|
-
);
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
switch (hookEvent) {
|
|
1030
|
-
case 'SessionStart': {
|
|
1031
|
-
const wasNew = this._sessions.findIndex(s => s.id === event.sessionId) < 0;
|
|
1032
|
-
const { index } = this.ensureSession(event.sessionId, eventContext);
|
|
1033
|
-
|
|
1034
|
-
if (wasNew) {
|
|
1035
|
-
// ensureSession created the session at 'idle' already. Just refresh pid if
|
|
1036
|
-
// the event carries one — bookkeeping was implicitly clean.
|
|
1037
|
-
this.resetBookkeeping(event.sessionId);
|
|
1038
|
-
if (event.pid != null) {
|
|
1039
|
-
const prev = this._sessions[index];
|
|
1040
|
-
if (prev.pid !== event.pid) {
|
|
1041
|
-
this._sessions[index] = { ...prev, pid: event.pid };
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
} else {
|
|
1045
|
-
const prev = this._sessions[index];
|
|
1046
|
-
if (prev.status === 'ended') {
|
|
1047
|
-
// Resuming a terminal session reuses its id, so a SessionStart on an ended
|
|
1048
|
-
// row IS the resume signal — revive it to a live `idle` row (clearing the
|
|
1049
|
-
// ended/archivable state) with clean bookkeeping that endSession tore
|
|
1050
|
-
// down. The next real event advances it out of idle. A `SessionStart` arrives on
|
|
1051
|
-
// THIS window's hook flow, so the resume is locally owned now: clear the peer
|
|
1052
|
-
// ownership carried over from the terminal record, and take the resume's pid
|
|
1053
|
-
// (never the old terminal/reused one — retaining it would let dispatch focus a
|
|
1054
|
-
// stale or unrelated process).
|
|
1055
|
-
this._sessions[index] = {
|
|
1056
|
-
...prev,
|
|
1057
|
-
status: 'idle',
|
|
1058
|
-
phase: 'idle',
|
|
1059
|
-
phaseSince: new Date(),
|
|
1060
|
-
lastActivity: new Date(),
|
|
1061
|
-
pid: event.pid,
|
|
1062
|
-
isPeerOwned: undefined,
|
|
1063
|
-
};
|
|
1064
|
-
this.resetBookkeeping(event.sessionId);
|
|
1065
|
-
} else if (event.pid != null && event.pid !== prev.pid) {
|
|
1066
|
-
// SessionStart for a live session we already track is almost always a CLI
|
|
1067
|
-
// replay (resume/reconnect/hook re-init). Don't clobber live state — the next
|
|
1068
|
-
// real event re-establishes status. Refresh pid only.
|
|
1069
|
-
this._sessions[index] = { ...prev, pid: event.pid, lastActivity: new Date() };
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
this._onDidChangeSessions.fire();
|
|
1074
|
-
void this.resolveTranscriptTitles(event.sessionId, event.cwd);
|
|
1075
|
-
this.callbacks.onSessionStarted?.(capabilities.providerId);
|
|
1076
|
-
break;
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
case 'SessionEnd': {
|
|
1080
|
-
const pending = this._pendingPermissions.get(event.sessionId);
|
|
1081
|
-
if (pending != null) {
|
|
1082
|
-
this.rejectAllPending(pending, new Error('Session ended'));
|
|
1083
|
-
this._pendingPermissions.delete(event.sessionId);
|
|
1084
|
-
}
|
|
1085
|
-
this.cancelPendingIdleTransition(event.sessionId);
|
|
1086
|
-
|
|
1087
|
-
const index = this._sessions.findIndex(s => s.id === event.sessionId);
|
|
1088
|
-
if (index >= 0) {
|
|
1089
|
-
if (this._statusFilterSupported) {
|
|
1090
|
-
// Transition to a terminal `ended` row rather than removing it — ended
|
|
1091
|
-
// sessions stay visible (de-emphasized) until archived or 30-day-purged by the CLI.
|
|
1092
|
-
this.endSession(index, new Date());
|
|
1093
|
-
} else {
|
|
1094
|
-
// Legacy CLI (no `--status` durable store): an ended row could never be
|
|
1095
|
-
// poll-confirmed nor archived, so keep the pre-ended remove-on-end behavior.
|
|
1096
|
-
this._sessions.splice(index, 1);
|
|
1097
|
-
const bk = this._sessionBookkeeping.get(event.sessionId);
|
|
1098
|
-
if (bk != null) {
|
|
1099
|
-
this.cancelPendingFileClears(bk);
|
|
1100
|
-
this.cancelPendingReadClears(bk);
|
|
1101
|
-
}
|
|
1102
|
-
this._sessionBookkeeping.delete(event.sessionId);
|
|
1103
|
-
this._transcriptReader.forget(event.sessionId);
|
|
1104
|
-
this.noteTerminalTransition();
|
|
1105
|
-
}
|
|
1106
|
-
this._onDidChangeSessions.fire();
|
|
1107
|
-
} else {
|
|
1108
|
-
// SessionEnd for a session we no longer track (already pruned/removed, or a duplicate
|
|
1109
|
-
// end): still clear any bookkeeping timers + transcript cache the row left behind, so
|
|
1110
|
-
// they don't leak. The tracked branches above already tear this down themselves.
|
|
1111
|
-
const bk = this._sessionBookkeeping.get(event.sessionId);
|
|
1112
|
-
if (bk != null) {
|
|
1113
|
-
this.cancelPendingFileClears(bk);
|
|
1114
|
-
this.cancelPendingReadClears(bk);
|
|
1115
|
-
}
|
|
1116
|
-
this._sessionBookkeeping.delete(event.sessionId);
|
|
1117
|
-
this._transcriptReader.forget(event.sessionId);
|
|
1118
|
-
// Untracked or not, a session just reached a terminal state and finalized its
|
|
1119
|
-
// transcript — an in-flight history query must not accept an answer that predates it.
|
|
1120
|
-
this.noteTerminalTransition();
|
|
1121
|
-
}
|
|
1122
|
-
this.callbacks.onSessionEnded?.(capabilities.providerId);
|
|
1123
|
-
break;
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
case 'UserPromptSubmit': {
|
|
1127
|
-
const { index, changed } = this.ensureSession(event.sessionId, eventContext);
|
|
1128
|
-
// Seated metadata (location, model, name) has to reach subscribers even when the prompt
|
|
1129
|
-
// turns out to be synthetic and the status update below never runs.
|
|
1130
|
-
if (changed) {
|
|
1131
|
-
this._onDidChangeSessions.fire();
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
const cleaned = prepareStoredPrompt(event.prompt);
|
|
1135
|
-
// A `UserPromptSubmit` whose payload sanitizes to nothing is harness-synthetic
|
|
1136
|
-
// (e.g. background-bash <task-notification>, slash-command stdout echo). Treat it
|
|
1137
|
-
// as informational so it doesn't flip the session to `thinking` or wipe a pending
|
|
1138
|
-
// permission when no real user activity occurred.
|
|
1139
|
-
if (!cleaned) break;
|
|
1140
|
-
|
|
1141
|
-
const existing = this._sessions[index];
|
|
1142
|
-
this._sessions[index] = {
|
|
1143
|
-
...existing,
|
|
1144
|
-
lastPrompt: cleaned,
|
|
1145
|
-
firstPrompt: existing.firstPrompt ?? prepareStoredPrompt(event.firstPrompt),
|
|
1146
|
-
};
|
|
1147
|
-
this.clearStalePermission(event.sessionId, 'UserPromptSubmit');
|
|
1148
|
-
this.updateSessionStatus(event.sessionId, 'thinking', eventContext);
|
|
1149
|
-
break;
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
case 'PreToolUse': {
|
|
1153
|
-
const bk = this.getBookkeeping(event.sessionId);
|
|
1154
|
-
bk.activeToolCount++;
|
|
1155
|
-
const filesChanged = this.trackToolUseFile(event.sessionId, event, capabilities);
|
|
1156
|
-
const readsChanged = this.trackToolUseRead(event.sessionId, event, capabilities);
|
|
1157
|
-
// Resolve a rich `Bash(grep …)`-style detail via the same tool_input passthrough the
|
|
1158
|
-
// PermissionRequest handler uses, so working sessions surface what their tool is
|
|
1159
|
-
// actually doing — not just the bare tool name. Falls back to bare name when no
|
|
1160
|
-
// toolInput is available (older CLI / unhandled tool).
|
|
1161
|
-
const toolInput =
|
|
1162
|
-
(event.hookInput?.tool_input as Record<string, unknown> | undefined) ?? event.toolInput;
|
|
1163
|
-
const statusDetail =
|
|
1164
|
-
toolInput != null && toolName ? describeToolInput(toolName, toolInput) : toolName || undefined;
|
|
1165
|
-
// Capture pre-update status so we can tell whether the upcoming updateSessionStatus
|
|
1166
|
-
// will fire on its own — needed to avoid dropping fileActivity changes when the
|
|
1167
|
-
// session is already in tool_use (back-to-back tool calls).
|
|
1168
|
-
const sessionIndex = this._sessions.findIndex(s => s.id === event.sessionId);
|
|
1169
|
-
const prevStatus = sessionIndex >= 0 ? this._sessions[sessionIndex].status : undefined;
|
|
1170
|
-
const prevDetail = sessionIndex >= 0 ? this._sessions[sessionIndex].statusDetail : undefined;
|
|
1171
|
-
const statusWillFire = prevStatus !== 'tool_use' || prevDetail !== statusDetail;
|
|
1172
|
-
this.updateSessionStatus(event.sessionId, 'tool_use', {
|
|
1173
|
-
...eventContext,
|
|
1174
|
-
statusDetail: statusDetail,
|
|
1175
|
-
});
|
|
1176
|
-
if ((filesChanged || readsChanged) && !statusWillFire) {
|
|
1177
|
-
this._onDidChangeSessions.fire();
|
|
1178
|
-
}
|
|
1179
|
-
break;
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
case 'PostToolUse':
|
|
1183
|
-
case 'PostToolUseFailure': {
|
|
1184
|
-
this.clearStalePermission(event.sessionId, hookEvent, this.settledByFromEvent(event, capabilities));
|
|
1185
|
-
const bk = this.getBookkeeping(event.sessionId);
|
|
1186
|
-
bk.activeToolCount = Math.max(0, bk.activeToolCount - 1);
|
|
1187
|
-
// Cooldown the file-edit decoration instead of dropping it immediately, so a quick
|
|
1188
|
-
// follow-up Edit/Write to the same path doesn't blink. The deferred clear fires its
|
|
1189
|
-
// own change event when it expires. The immediate re-sync inside each flips
|
|
1190
|
-
// `editing`/`reading` off the moment refcount hits zero, though — capture whether that
|
|
1191
|
-
// changed the published list so we fire it ourselves when the status update below short-
|
|
1192
|
-
// circuits (a parallel tool is still in flight, so activeToolCount stays > 0).
|
|
1193
|
-
const filesChanged = this.scheduleClearToolUseFile(event.sessionId, event, capabilities);
|
|
1194
|
-
const readsChanged = this.scheduleClearToolUseRead(event.sessionId, event, capabilities);
|
|
1195
|
-
if (bk.activeToolCount === 0) {
|
|
1196
|
-
// updateSessionStatus short-circuits without firing when status+detail are unchanged
|
|
1197
|
-
// (already 'thinking' with no detail) — which would drop the editing/reading flag-flip
|
|
1198
|
-
// above and leave a stale live pulse. Capture whether it will fire on its own and, if
|
|
1199
|
-
// not, fire explicitly — mirrors the PreToolUse / PermissionDenied guards.
|
|
1200
|
-
const idx = this._sessions.findIndex(s => s.id === event.sessionId);
|
|
1201
|
-
const statusWillFire =
|
|
1202
|
-
idx < 0 ||
|
|
1203
|
-
this._sessions[idx].status !== 'thinking' ||
|
|
1204
|
-
this._sessions[idx].statusDetail != null;
|
|
1205
|
-
this.updateSessionStatus(event.sessionId, 'thinking', eventContext);
|
|
1206
|
-
if ((filesChanged || readsChanged) && !statusWillFire) {
|
|
1207
|
-
this._onDidChangeSessions.fire();
|
|
1208
|
-
}
|
|
1209
|
-
} else if (filesChanged || readsChanged) {
|
|
1210
|
-
this._onDidChangeSessions.fire();
|
|
1211
|
-
}
|
|
1212
|
-
break;
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
case 'Stop':
|
|
1216
|
-
case 'StopFailure': {
|
|
1217
|
-
const pending = this._pendingPermissions.get(event.sessionId);
|
|
1218
|
-
if (pending != null) {
|
|
1219
|
-
this.rejectAllPending(pending, new Error('Session stopped'));
|
|
1220
|
-
this._pendingPermissions.delete(event.sessionId);
|
|
1221
|
-
}
|
|
1222
|
-
// Turn ended: stop the "live" pulse but keep the per-operation decay tail fading over
|
|
1223
|
-
// the configured window (anchored at each tool's completion, not at the turn end). A full
|
|
1224
|
-
// reset here would wipe the tail the instant the agent finishes a response.
|
|
1225
|
-
this.settleBookkeeping(event.sessionId);
|
|
1226
|
-
// Apply Stop's metadata synchronously so we don't carry stale context through the
|
|
1227
|
-
// debounce window — any in-window mutations (e.g. CwdChanged) survive when the
|
|
1228
|
-
// timer fires. ensureSession doesn't fire on metadata-only updates of existing
|
|
1229
|
-
// sessions, so fire here to match the original Stop-handler's UI-update semantics.
|
|
1230
|
-
const { changed } = this.ensureSession(event.sessionId, eventContext);
|
|
1231
|
-
if (changed) {
|
|
1232
|
-
this._onDidChangeSessions.fire();
|
|
1233
|
-
}
|
|
1234
|
-
// Defer the status change — if the agent continues within the debounce window
|
|
1235
|
-
// (hook-driven re-prompt, IPC reordering, auto-resume), the next non-idle event
|
|
1236
|
-
// cancels this and we never flick through idle.
|
|
1237
|
-
this.scheduleIdleTransition(event.sessionId);
|
|
1238
|
-
break;
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
case 'PreCompact':
|
|
1242
|
-
this.updateSessionStatus(event.sessionId, 'compacting', eventContext);
|
|
1243
|
-
break;
|
|
1244
|
-
|
|
1245
|
-
case 'PostCompact':
|
|
1246
|
-
this.updateSessionStatus(event.sessionId, 'thinking', eventContext);
|
|
1247
|
-
break;
|
|
1248
|
-
|
|
1249
|
-
case 'Notification':
|
|
1250
|
-
switch (event.notificationType) {
|
|
1251
|
-
case 'idle_prompt':
|
|
1252
|
-
// No-op — session is already idle from the preceding stop/session-start event
|
|
1253
|
-
break;
|
|
1254
|
-
case 'permission_prompt':
|
|
1255
|
-
this.updateSessionStatus(event.sessionId, 'permission_requested', {
|
|
1256
|
-
...eventContext,
|
|
1257
|
-
statusDetail: toolName,
|
|
1258
|
-
pendingPermission: synthesizeUnresolvableAsk(
|
|
1259
|
-
toolName,
|
|
1260
|
-
// Plan-kind body: prefer the session's planFile, fall back to the event's
|
|
1261
|
-
// — mirrors the blocking path.
|
|
1262
|
-
this._sessions.find(s => s.id === event.sessionId)?.planFile ?? event.planFile,
|
|
1263
|
-
),
|
|
1264
|
-
});
|
|
1265
|
-
break;
|
|
1266
|
-
case 'elicitation_dialog':
|
|
1267
|
-
// An MCP elicitation, not a tool permission — `toolName` names the elicitation, so
|
|
1268
|
-
// classifying it as a tool ask would mislabel the card. Same shape the `Elicitation`
|
|
1269
|
-
// hook produces.
|
|
1270
|
-
this.updateSessionStatus(event.sessionId, 'permission_requested', {
|
|
1271
|
-
...eventContext,
|
|
1272
|
-
statusDetail: toolName,
|
|
1273
|
-
pendingPermission: {
|
|
1274
|
-
kind: 'elicitation',
|
|
1275
|
-
toolName: toolName ?? 'Input Required',
|
|
1276
|
-
toolDescription: toolName ?? 'Waiting for input',
|
|
1277
|
-
resolvable: false,
|
|
1278
|
-
},
|
|
1279
|
-
});
|
|
1280
|
-
break;
|
|
1281
|
-
default:
|
|
1282
|
-
// auth_success, unknown, or missing: no status change, just update timestamp
|
|
1283
|
-
break;
|
|
1284
|
-
}
|
|
1285
|
-
break;
|
|
1286
|
-
|
|
1287
|
-
case 'PermissionRequest': {
|
|
1288
|
-
// hookInput is the raw passthrough; fall back to top-level fields for older CLI versions.
|
|
1289
|
-
const hookInput = event.hookInput;
|
|
1290
|
-
const toolInput = (hookInput?.tool_input as Record<string, unknown> | undefined) ?? event.toolInput;
|
|
1291
|
-
// An agent whose asks we can't answer falls through to the observe-only branch below,
|
|
1292
|
-
// which publishes the same card marked `resolvable: false`. Registering a pending entry
|
|
1293
|
-
// for it would promise an Allow/Deny this window can never route.
|
|
1294
|
-
if (canAnswerBlockingPermission(capabilities) && isBlocking && toolInput != null) {
|
|
1295
|
-
const askToolName = toolName ?? '';
|
|
1296
|
-
const toolDescription = describeToolInput(askToolName, toolInput);
|
|
1297
|
-
const toolInputDescription = (toolInput.description as string | undefined) || undefined;
|
|
1298
|
-
const kind: PendingPermissionKind = classifyPermissionKind(askToolName);
|
|
1299
|
-
const toolUseId = hookInput?.tool_use_id as string | undefined;
|
|
1300
|
-
const toolInputJson = stableStringify(toolInput);
|
|
1301
|
-
|
|
1302
|
-
return new Promise<PermissionResponse>((resolve, reject) => {
|
|
1303
|
-
const existing = this._pendingPermissions.get(event.sessionId);
|
|
1304
|
-
// Same identity as `settledByFromEvent`'s comparisons: `toolUseId` when both sides
|
|
1305
|
-
// have one, else toolName + canonical input.
|
|
1306
|
-
const isDuplicateDelivery =
|
|
1307
|
-
existing != null &&
|
|
1308
|
-
((existing.toolUseId != null && toolUseId != null && existing.toolUseId === toolUseId) ||
|
|
1309
|
-
(existing.toolUseId == null &&
|
|
1310
|
-
toolUseId == null &&
|
|
1311
|
-
existing.toolName === askToolName &&
|
|
1312
|
-
existing.toolInputJson === toolInputJson));
|
|
1313
|
-
|
|
1314
|
-
if (isDuplicateDelivery) {
|
|
1315
|
-
// Duplicate delivery of the same ask (CLI retry after its 60s response-header
|
|
1316
|
-
// timeout) — attach instead of replacing so both connections settle together.
|
|
1317
|
-
// Nothing about the display changes, so skip rebuilding it below. Accepted
|
|
1318
|
-
// residual: two independent, identical, concurrent asks are byte-for-byte
|
|
1319
|
-
// indistinguishable on the wire and coalesce the same way, receiving one
|
|
1320
|
-
// decision — evicting instead would falsely deny a live ask.
|
|
1321
|
-
existing.resolvers.push({ resolve: resolve, reject: reject });
|
|
1322
|
-
return;
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
if (existing != null) {
|
|
1326
|
-
Logger.debug(
|
|
1327
|
-
`GkAgentProvider.handleSessionEvent: auto-denying stale permission ${tag} tool=${existing.toolName}`,
|
|
1328
|
-
);
|
|
1329
|
-
this.resolveAllPending(existing, {
|
|
1330
|
-
hookSpecificOutput: {
|
|
1331
|
-
hookEventName: 'PermissionRequest',
|
|
1332
|
-
decision: { behavior: 'deny' },
|
|
1333
|
-
},
|
|
1334
|
-
});
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
this._pendingPermissions.set(event.sessionId, {
|
|
1338
|
-
resolvers: [{ resolve: resolve, reject: reject }],
|
|
1339
|
-
toolName: askToolName,
|
|
1340
|
-
toolDescription: toolDescription,
|
|
1341
|
-
toolUseId: toolUseId,
|
|
1342
|
-
toolInputJson: toolInputJson,
|
|
1343
|
-
});
|
|
1344
|
-
|
|
1345
|
-
// Plan-kind body: prefer the existing session's planFile (set via SessionStart/
|
|
1346
|
-
// PostToolUse) and fall back to the event's planFile for cold-start cases.
|
|
1347
|
-
const planFilePath =
|
|
1348
|
-
kind === 'plan'
|
|
1349
|
-
? (this._sessions.find(s => s.id === event.sessionId)?.planFile ?? event.planFile)
|
|
1350
|
-
: undefined;
|
|
1351
|
-
const planSummary = kind === 'plan' ? extractPlanSummary(toolInput) : undefined;
|
|
1352
|
-
const questionDetails = kind === 'question' ? extractQuestionDetails(toolInput) : undefined;
|
|
1353
|
-
|
|
1354
|
-
const permission: PendingPermission = {
|
|
1355
|
-
kind: kind,
|
|
1356
|
-
toolName: askToolName,
|
|
1357
|
-
toolDescription: toolDescription,
|
|
1358
|
-
toolInputDescription: toolInputDescription,
|
|
1359
|
-
suggestions:
|
|
1360
|
-
(hookInput?.permission_suggestions as PermissionSuggestion[] | undefined) ??
|
|
1361
|
-
event.permissionSuggestions,
|
|
1362
|
-
planFilePath: planFilePath,
|
|
1363
|
-
planSummary: planSummary,
|
|
1364
|
-
questionText: questionDetails?.text,
|
|
1365
|
-
questionCount: questionDetails?.count,
|
|
1366
|
-
};
|
|
1367
|
-
this.updateSessionWithPermission(event.sessionId, permission, eventContext);
|
|
1368
|
-
});
|
|
1369
|
-
}
|
|
1370
|
-
|
|
1371
|
-
{
|
|
1372
|
-
// Mirror the blocking payload's shape so the ask renders identically either way —
|
|
1373
|
-
// just unresolvable, since no blocking hook entry exists to route an answer through.
|
|
1374
|
-
const kind = toolName != null ? classifyPermissionKind(toolName) : undefined;
|
|
1375
|
-
// Plan-kind body: same session-first preference as the blocking path.
|
|
1376
|
-
const planFilePath =
|
|
1377
|
-
kind === 'plan'
|
|
1378
|
-
? (this._sessions.find(s => s.id === event.sessionId)?.planFile ?? event.planFile)
|
|
1379
|
-
: undefined;
|
|
1380
|
-
// Same body extraction as the blocking path — surfaces render `questionText`/
|
|
1381
|
-
// `planSummary` in place of the tool description, so omitting them would show a
|
|
1382
|
-
// generic "awaiting" card for a question the input actually carries.
|
|
1383
|
-
const questionDetails =
|
|
1384
|
-
kind === 'question' && toolInput != null ? extractQuestionDetails(toolInput) : undefined;
|
|
1385
|
-
const synthesized: PendingPermission =
|
|
1386
|
-
kind != null && toolName != null && toolInput != null
|
|
1387
|
-
? {
|
|
1388
|
-
kind: kind,
|
|
1389
|
-
toolName: toolName,
|
|
1390
|
-
toolDescription: describeToolInput(toolName, toolInput),
|
|
1391
|
-
toolInputDescription: (toolInput.description as string | undefined) || undefined,
|
|
1392
|
-
planFilePath: planFilePath,
|
|
1393
|
-
planSummary: kind === 'plan' ? extractPlanSummary(toolInput) : undefined,
|
|
1394
|
-
questionText: questionDetails?.text,
|
|
1395
|
-
questionCount: questionDetails?.count,
|
|
1396
|
-
resolvable: false,
|
|
1397
|
-
}
|
|
1398
|
-
: synthesizeUnresolvableAsk(toolName, planFilePath);
|
|
1399
|
-
this.updateSessionStatus(event.sessionId, 'permission_requested', {
|
|
1400
|
-
...eventContext,
|
|
1401
|
-
statusDetail: toolName,
|
|
1402
|
-
pendingPermission: synthesized,
|
|
1403
|
-
});
|
|
1404
|
-
}
|
|
1405
|
-
break;
|
|
1406
|
-
}
|
|
1407
|
-
|
|
1408
|
-
case 'PermissionDenied': {
|
|
1409
|
-
this.clearStalePermission(
|
|
1410
|
-
event.sessionId,
|
|
1411
|
-
'PermissionDenied',
|
|
1412
|
-
this.settledByFromEvent(event, capabilities),
|
|
1413
|
-
);
|
|
1414
|
-
const bk = this.getBookkeeping(event.sessionId);
|
|
1415
|
-
bk.activeToolCount = Math.max(0, bk.activeToolCount - 1);
|
|
1416
|
-
const filesChanged = this.untrackToolUseFile(event.sessionId, event, capabilities);
|
|
1417
|
-
const readsChanged = this.untrackToolUseRead(event.sessionId, event, capabilities);
|
|
1418
|
-
// When a parallel tool is still active the next status equals the current one and
|
|
1419
|
-
// updateSessionStatus short-circuits — without an explicit fire, the dropped path
|
|
1420
|
-
// would never reach subscribers (treemap activity overlay, WIP decoration).
|
|
1421
|
-
const nextStatus = bk.activeToolCount > 0 ? 'tool_use' : 'thinking';
|
|
1422
|
-
const sessionIndex = this._sessions.findIndex(s => s.id === event.sessionId);
|
|
1423
|
-
const prevStatus = sessionIndex >= 0 ? this._sessions[sessionIndex].status : undefined;
|
|
1424
|
-
const statusWillFire = prevStatus !== nextStatus;
|
|
1425
|
-
this.updateSessionStatus(event.sessionId, nextStatus, eventContext);
|
|
1426
|
-
if ((filesChanged || readsChanged) && !statusWillFire) {
|
|
1427
|
-
this._onDidChangeSessions.fire();
|
|
1428
|
-
}
|
|
1429
|
-
break;
|
|
1430
|
-
}
|
|
1431
|
-
|
|
1432
|
-
case 'Elicitation': {
|
|
1433
|
-
const bk = this.getBookkeeping(event.sessionId);
|
|
1434
|
-
bk.pendingPermission = {
|
|
1435
|
-
kind: 'elicitation',
|
|
1436
|
-
toolName: toolName ?? 'Input Required',
|
|
1437
|
-
toolDescription: toolName ?? 'Waiting for input',
|
|
1438
|
-
// `Elicitation` arrives on a non-blocking hook, so no entry exists for
|
|
1439
|
-
// `resolvePermission` to answer — the user responds in the agent's own session.
|
|
1440
|
-
resolvable: false,
|
|
1441
|
-
};
|
|
1442
|
-
this.updateSessionStatus(event.sessionId, 'permission_requested', {
|
|
1443
|
-
...eventContext,
|
|
1444
|
-
statusDetail: toolName,
|
|
1445
|
-
});
|
|
1446
|
-
break;
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
case 'ElicitationResult': {
|
|
1450
|
-
const bk = this.getBookkeeping(event.sessionId);
|
|
1451
|
-
bk.pendingPermission = undefined;
|
|
1452
|
-
this.updateSessionStatus(
|
|
1453
|
-
event.sessionId,
|
|
1454
|
-
bk.activeToolCount > 0 ? 'tool_use' : 'thinking',
|
|
1455
|
-
eventContext,
|
|
1456
|
-
);
|
|
1457
|
-
break;
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
|
-
case 'CwdChanged':
|
|
1461
|
-
if (event.cwd) {
|
|
1462
|
-
// Route through `ensureSession`: it owns the whole location transition — timeline
|
|
1463
|
-
// seat (or handing attribution back to the probe when the CLI didn't explain the
|
|
1464
|
-
// move), workspacePath/isInWorkspace recompute, the unresolvable-flag clear, and
|
|
1465
|
-
// the probe gating. Writing `cwd` directly here left `cliSeatedWorktree` set, so
|
|
1466
|
-
// the probe's answer for the new cwd could never replace the stale seated one.
|
|
1467
|
-
const { changed } = this.ensureSession(event.sessionId, eventContext);
|
|
1468
|
-
if (changed) {
|
|
1469
|
-
this._onDidChangeSessions.fire();
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
break;
|
|
1473
|
-
|
|
1474
|
-
case 'SubagentStart': {
|
|
1475
|
-
const { index: parentIdx } = this.ensureSession(event.sessionId, eventContext);
|
|
1476
|
-
const parent = this._sessions[parentIdx];
|
|
1477
|
-
if (event.agentId) {
|
|
1478
|
-
const now = new Date();
|
|
1479
|
-
const subagent: AgentSession = {
|
|
1480
|
-
id: event.agentId,
|
|
1481
|
-
providerId: capabilities.providerId,
|
|
1482
|
-
providerName: capabilities.displayName,
|
|
1483
|
-
name: event.agentType ?? 'Subagent',
|
|
1484
|
-
status: 'thinking',
|
|
1485
|
-
phase: 'working',
|
|
1486
|
-
phaseSince: now,
|
|
1487
|
-
lastActivity: now,
|
|
1488
|
-
isSubagent: true,
|
|
1489
|
-
parentId: event.sessionId,
|
|
1490
|
-
isInWorkspace: workspacePath != null,
|
|
1491
|
-
};
|
|
1492
|
-
const existingSubs = parent.subagents ? [...parent.subagents] : [];
|
|
1493
|
-
existingSubs.push(subagent);
|
|
1494
|
-
this._sessions[parentIdx] = { ...parent, subagents: existingSubs };
|
|
1495
|
-
this._onDidChangeSessions.fire();
|
|
1496
|
-
}
|
|
1497
|
-
break;
|
|
1498
|
-
}
|
|
1499
|
-
|
|
1500
|
-
case 'SubagentStop': {
|
|
1501
|
-
const { index: parentIdx } = this.ensureSession(event.sessionId, eventContext);
|
|
1502
|
-
const parentSession = this._sessions[parentIdx];
|
|
1503
|
-
if (parentSession.subagents != null && event.agentId) {
|
|
1504
|
-
const filtered = parentSession.subagents.filter(s => s.id !== event.agentId);
|
|
1505
|
-
if (filtered.length !== parentSession.subagents.length) {
|
|
1506
|
-
this._sessions[parentIdx] = {
|
|
1507
|
-
...parentSession,
|
|
1508
|
-
subagents: filtered.length > 0 ? filtered : undefined,
|
|
1509
|
-
};
|
|
1510
|
-
this._onDidChangeSessions.fire();
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
break;
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
case 'TeammateIdle':
|
|
1517
|
-
case 'TaskCompleted':
|
|
1518
|
-
case 'InstructionsLoaded':
|
|
1519
|
-
case 'ConfigChange':
|
|
1520
|
-
case 'WorktreeCreate':
|
|
1521
|
-
case 'WorktreeRemove':
|
|
1522
|
-
// Not yet handled — intentional no-op.
|
|
1523
|
-
break;
|
|
1524
|
-
}
|
|
1525
|
-
|
|
1526
|
-
return Promise.resolve();
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
|
-
private sessionTag(sessionId: string, name?: string): string {
|
|
1530
|
-
return name != null
|
|
1531
|
-
? `[session=${sessionId.substring(0, 8)}(${name})]`
|
|
1532
|
-
: `[session=${sessionId.substring(0, 8)}]`;
|
|
1533
|
-
}
|
|
1534
|
-
|
|
1535
|
-
/** The owning agent's descriptor for a tracked session, resolved from the session's own
|
|
1536
|
-
* `providerId`. `undefined` when the session isn't tracked (pruned/archived since) or carries a
|
|
1537
|
-
* `providerId` the descriptor table doesn't know — callers pick their own fallback rather than
|
|
1538
|
-
* silently assuming Claude Code. */
|
|
1539
|
-
private getSessionCapabilities(sessionId: string): AgentCapabilities | undefined {
|
|
1540
|
-
const session = this._sessions.find(s => s.id === sessionId);
|
|
1541
|
-
return session != null ? getAgentCapabilitiesByProviderId(session.providerId) : undefined;
|
|
1542
|
-
}
|
|
1543
|
-
|
|
1544
|
-
/** Whether the session's agent leaves a transcript on disk for {@link ClaudeCodeTranscriptReader}
|
|
1545
|
-
* to tail. Fails OPEN when no descriptor resolves, so an unknown/absent `providerId` can never
|
|
1546
|
-
* suppress the Claude path this reader exists for. */
|
|
1547
|
-
private supportsTranscripts(sessionId: string): boolean {
|
|
1548
|
-
return this.getSessionCapabilities(sessionId)?.supportsTranscripts ?? true;
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
|
-
private getBookkeeping(sessionId: string): SessionBookkeeping {
|
|
1552
|
-
let bk = this._sessionBookkeeping.get(sessionId);
|
|
1553
|
-
if (bk == null) {
|
|
1554
|
-
bk = {
|
|
1555
|
-
activeToolCount: 0,
|
|
1556
|
-
currentFileCounts: new Map(),
|
|
1557
|
-
pendingFileClears: new Map(),
|
|
1558
|
-
currentReadCounts: new Map(),
|
|
1559
|
-
pendingReadClears: new Map(),
|
|
1560
|
-
lastTouchedAt: new Map(),
|
|
1561
|
-
gitInfoUnresolvable: false,
|
|
1562
|
-
};
|
|
1563
|
-
this._sessionBookkeeping.set(sessionId, bk);
|
|
1564
|
-
}
|
|
1565
|
-
return bk;
|
|
1566
|
-
}
|
|
1567
|
-
|
|
1568
|
-
/** Returns the active decay window in milliseconds — the cooldown between PostToolUse and
|
|
1569
|
-
* dropping the path from the bookkeeping. Reads through the host callback every time so a
|
|
1570
|
-
* setting change takes effect on the next Pre/Post pair without re-wiring (existing timers
|
|
1571
|
-
* keep their original timeout — acceptable since they're short-lived relative to the setting). */
|
|
1572
|
-
private get activityDecayMs(): number {
|
|
1573
|
-
return this.callbacks.getActivityDecayMs?.() ?? defaultActivityDecayMs;
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
|
-
/** Locates the *parent* session id that owns the given session id — returns the id itself when
|
|
1577
|
-
* it matches a top-level session, otherwise the `id` of the parent that holds it under
|
|
1578
|
-
* `subagents[]`. Returns `undefined` when neither match (session no longer exists). Used to
|
|
1579
|
-
* route per-session bookkeeping changes to the right top-level `syncSessionFileActivity` call.
|
|
1580
|
-
* Tool events are parent-keyed (the CLI sends a sub-agent's tool events under the parent id),
|
|
1581
|
-
* so the `subagents[]` branch is a defensive fallback for any other-keyed caller. */
|
|
1582
|
-
private findOwningParentId(sessionId: string): string | undefined {
|
|
1583
|
-
for (const s of this._sessions) {
|
|
1584
|
-
if (s.id === sessionId) return s.id;
|
|
1585
|
-
|
|
1586
|
-
if (s.subagents != null) {
|
|
1587
|
-
for (const sub of s.subagents) {
|
|
1588
|
-
if (sub.id === sessionId) return s.id;
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
}
|
|
1592
|
-
return undefined;
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
private resetBookkeeping(sessionId: string): void {
|
|
1596
|
-
const bk = this.getBookkeeping(sessionId);
|
|
1597
|
-
bk.activeToolCount = 0;
|
|
1598
|
-
bk.pendingPermission = undefined;
|
|
1599
|
-
this.cancelPendingFileClears(bk);
|
|
1600
|
-
bk.currentFileCounts.clear();
|
|
1601
|
-
this.cancelPendingReadClears(bk);
|
|
1602
|
-
bk.currentReadCounts.clear();
|
|
1603
|
-
bk.lastTouchedAt.clear();
|
|
1604
|
-
bk.gitInfoUnresolvable = false;
|
|
1605
|
-
// `cliSeatedWorktree` deliberately survives: SessionStart seats the worktree via
|
|
1606
|
-
// `ensureSession` BEFORE resetting bookkeeping, and an unexplained cwd move clears it anyway.
|
|
1607
|
-
const parentId = this.findOwningParentId(sessionId);
|
|
1608
|
-
if (parentId != null) {
|
|
1609
|
-
this.syncSessionFileActivity(parentId);
|
|
1610
|
-
}
|
|
1611
|
-
}
|
|
1612
|
-
|
|
1613
|
-
/** Turn-end settle (Stop): the agent finished its turn, so nothing is "live" anymore — but
|
|
1614
|
-
* the per-operation decay tail must survive. Heat fades over {@link activityDecayMs} measured
|
|
1615
|
-
* from when each *tool* finished (the PostToolUse cooldown), NOT from when the turn ends, so a
|
|
1616
|
-
* Stop must not cancel those cooldowns or clear `lastTouchedAt`. We only reset turn-scoped
|
|
1617
|
-
* state and force-finish any path whose PostToolUse never arrived before Stop: drop its
|
|
1618
|
-
* refcount to 0 (flipping `editing`/`reading` off so the pulse stops) and schedule the same
|
|
1619
|
-
* decay eviction a normal completion would. Contrast {@link resetBookkeeping}, which hard-
|
|
1620
|
-
* wipes everything for SessionStart/SessionEnd (the agent is gone, not merely between turns). */
|
|
1621
|
-
private settleBookkeeping(sessionId: string): void {
|
|
1622
|
-
const bk = this.getBookkeeping(sessionId);
|
|
1623
|
-
bk.activeToolCount = 0;
|
|
1624
|
-
bk.pendingPermission = undefined;
|
|
1625
|
-
// Allow git re-resolution next turn (matches the prior reset-on-Stop intent).
|
|
1626
|
-
bk.gitInfoUnresolvable = false;
|
|
1627
|
-
for (const [path, count] of bk.currentFileCounts) {
|
|
1628
|
-
if (count > 0) {
|
|
1629
|
-
bk.currentFileCounts.set(path, 0);
|
|
1630
|
-
this.scheduleFileDecayEviction(bk, sessionId, path);
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
for (const [path, count] of bk.currentReadCounts) {
|
|
1634
|
-
if (count > 0) {
|
|
1635
|
-
bk.currentReadCounts.set(path, 0);
|
|
1636
|
-
this.scheduleReadDecayEviction(bk, sessionId, path);
|
|
1637
|
-
}
|
|
1638
|
-
}
|
|
1639
|
-
const parentId = this.findOwningParentId(sessionId);
|
|
1640
|
-
if (parentId != null && this.syncSessionFileActivity(parentId)) {
|
|
1641
|
-
this._onDidChangeSessions.fire();
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
|
|
1645
|
-
private cancelPendingFileClears(bk: SessionBookkeeping): void {
|
|
1646
|
-
for (const timer of bk.pendingFileClears.values()) {
|
|
1647
|
-
clearTimeout(timer);
|
|
1648
|
-
}
|
|
1649
|
-
bk.pendingFileClears.clear();
|
|
1650
|
-
}
|
|
1651
|
-
|
|
1652
|
-
private cancelPendingReadClears(bk: SessionBookkeeping): void {
|
|
1653
|
-
for (const timer of bk.pendingReadClears.values()) {
|
|
1654
|
-
clearTimeout(timer);
|
|
1655
|
-
}
|
|
1656
|
-
bk.pendingReadClears.clear();
|
|
1657
|
-
}
|
|
1658
|
-
|
|
1659
|
-
/** Extracts the targeted file path from a PreToolUse/PostToolUse event. Tries the raw
|
|
1660
|
-
* `hookInput.tool_input` passthrough first, falls back to the projected `event.toolInput`
|
|
1661
|
-
* for older CLI versions that only fill the top-level fields. Treats an empty-object
|
|
1662
|
-
* `hookInput.tool_input` as missing — `??` would have kept it (truthy) and shadowed a
|
|
1663
|
-
* populated `event.toolInput`. */
|
|
1664
|
-
private getEventFilePath(event: AgentSessionEvent, capabilities: AgentCapabilities): string | undefined {
|
|
1665
|
-
const toolName = canonicalToolNameFromEvent(capabilities, event);
|
|
1666
|
-
if (toolName == null) return undefined;
|
|
1667
|
-
|
|
1668
|
-
const rawHookInput = event.hookInput?.tool_input as Record<string, unknown> | undefined;
|
|
1669
|
-
const hookInputPopulated = rawHookInput != null && Object.keys(rawHookInput).length > 0;
|
|
1670
|
-
const toolInput = hookInputPopulated ? rawHookInput : event.toolInput;
|
|
1671
|
-
return getToolFilePath(toolName, toolInput);
|
|
1672
|
-
}
|
|
1673
|
-
|
|
1674
|
-
/** Stamps the per-path/per-kind `lastTouchedAt[kind]` to the current epoch ms. Stamped on every
|
|
1675
|
-
* PreToolUse for the matching kind so `serializeFileActivity` can emit `readAt`/`editedAt` as
|
|
1676
|
-
* a relative `now - timestamp` delta. */
|
|
1677
|
-
private stampLastTouched(bk: SessionBookkeeping, filePath: string, kind: 'read' | 'edit'): void {
|
|
1678
|
-
const existing = bk.lastTouchedAt.get(filePath);
|
|
1679
|
-
const next: { edit?: number; read?: number } = existing != null ? { ...existing } : {};
|
|
1680
|
-
next[kind] = Date.now();
|
|
1681
|
-
bk.lastTouchedAt.set(filePath, next);
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
|
-
/** Drops the `lastTouchedAt[kind]` slot for `filePath`, and the whole entry when neither kind
|
|
1685
|
-
* remains. Mirrors the cooldown-timer eviction in `scheduleClear*` — once the path is gone
|
|
1686
|
-
* from the count map for that kind, its timestamp is no longer load-bearing. */
|
|
1687
|
-
private clearLastTouched(bk: SessionBookkeeping, filePath: string, kind: 'read' | 'edit'): void {
|
|
1688
|
-
const existing = bk.lastTouchedAt.get(filePath);
|
|
1689
|
-
if (existing == null) return;
|
|
1690
|
-
if (existing[kind] == null) return;
|
|
1691
|
-
|
|
1692
|
-
const next: { edit?: number; read?: number } = { ...existing, [kind]: undefined };
|
|
1693
|
-
if (next.edit == null && next.read == null) {
|
|
1694
|
-
bk.lastTouchedAt.delete(filePath);
|
|
1695
|
-
} else {
|
|
1696
|
-
bk.lastTouchedAt.set(filePath, next);
|
|
1697
|
-
}
|
|
1698
|
-
}
|
|
1699
|
-
|
|
1700
|
-
/** Records that a file-mutating tool call is in flight against `filePath` so consumers can
|
|
1701
|
-
* highlight the path. Refcounts per path — keyed by path (not `tool_use_id`, which the GK CLI
|
|
1702
|
-
* doesn't reliably surface) so parallel calls bump the count and Post/Pre order doesn't matter.
|
|
1703
|
-
* Returns whether the parent's published `fileActivity` actually changed so callers can fire
|
|
1704
|
-
* when the surrounding {@link updateSessionStatus} short-circuits. */
|
|
1705
|
-
private trackToolUseFile(sessionId: string, event: AgentSessionEvent, capabilities: AgentCapabilities): boolean {
|
|
1706
|
-
const filePath = this.getEventFilePath(event, capabilities);
|
|
1707
|
-
if (filePath == null) return false;
|
|
1708
|
-
|
|
1709
|
-
const bk = this.getBookkeeping(sessionId);
|
|
1710
|
-
// A fresh Pre during a pending Post-cooldown means the file is live again — cancel the
|
|
1711
|
-
// scheduled clear so the deferred drop doesn't run after the new tool already finished.
|
|
1712
|
-
const pendingClear = bk.pendingFileClears.get(filePath);
|
|
1713
|
-
if (pendingClear != null) {
|
|
1714
|
-
clearTimeout(pendingClear);
|
|
1715
|
-
bk.pendingFileClears.delete(filePath);
|
|
1716
|
-
}
|
|
1717
|
-
bk.currentFileCounts.set(filePath, (bk.currentFileCounts.get(filePath) ?? 0) + 1);
|
|
1718
|
-
this.stampLastTouched(bk, filePath, 'edit');
|
|
1719
|
-
return this.syncSessionFileActivityForChild(sessionId);
|
|
1720
|
-
}
|
|
1721
|
-
|
|
1722
|
-
/** Decrements the in-flight refcount for a finished tool call's file. While the count stays
|
|
1723
|
-
* above zero (overlapping parallel calls) the path remains marked. When the count drops to
|
|
1724
|
-
* zero, leaves the entry in place at count `0` and schedules a {@link activityDecayMs}
|
|
1725
|
-
* cooldown before actually dropping it — so a recently-edited file lingers as a decay tail
|
|
1726
|
-
* on the treemap. A Pre arriving during cooldown cancels the timer and bumps the count back
|
|
1727
|
-
* above zero. Returns whether the parent's published `fileActivity` changed (i.e. `editing`
|
|
1728
|
-
* flipped off) so the PostToolUse caller can fire when its `updateSessionStatus` short-
|
|
1729
|
-
* circuits (a parallel tool still in flight keeps activeToolCount > 0). */
|
|
1730
|
-
private scheduleClearToolUseFile(
|
|
1731
|
-
sessionId: string,
|
|
1732
|
-
event: AgentSessionEvent,
|
|
1733
|
-
capabilities: AgentCapabilities,
|
|
1734
|
-
): boolean {
|
|
1735
|
-
const filePath = this.getEventFilePath(event, capabilities);
|
|
1736
|
-
if (filePath == null) return false;
|
|
1737
|
-
|
|
1738
|
-
const bk = this.getBookkeeping(sessionId);
|
|
1739
|
-
const count = bk.currentFileCounts.get(filePath);
|
|
1740
|
-
// A duplicate / out-of-order Post during cooldown (count is already 0) would drive the
|
|
1741
|
-
// refcount negative and schedule a second timer — ignore those so the cooldown stays
|
|
1742
|
-
// authoritative until either its timer fires or a fresh Pre bumps the count back up.
|
|
1743
|
-
if (count == null || count <= 0) return false;
|
|
1744
|
-
|
|
1745
|
-
const next = count - 1;
|
|
1746
|
-
bk.currentFileCounts.set(filePath, next);
|
|
1747
|
-
// Re-sync so `editing` drops from `true` to `undefined` the moment refcount hits zero —
|
|
1748
|
-
// the cooldown only governs when the path itself leaves the snapshot, not when the
|
|
1749
|
-
// "live" boolean flips off.
|
|
1750
|
-
const changed = this.syncSessionFileActivityForChild(sessionId);
|
|
1751
|
-
if (next > 0) return changed;
|
|
1752
|
-
|
|
1753
|
-
// Refcount just hit zero — keep the path in `currentFileCounts` for the cooldown window,
|
|
1754
|
-
// then drop it.
|
|
1755
|
-
this.scheduleFileDecayEviction(bk, sessionId, filePath);
|
|
1756
|
-
return changed;
|
|
1757
|
-
}
|
|
1758
|
-
|
|
1759
|
-
/** Schedules (replacing any prior timer) the {@link activityDecayMs} cooldown that finally
|
|
1760
|
-
* drops `filePath` from the edit bookkeeping once it has fully decayed. Shared by the
|
|
1761
|
-
* PostToolUse cooldown path and the Stop settle ({@link settleBookkeeping}); the path stays
|
|
1762
|
-
* in `currentFileCounts` at count 0 meanwhile so its `editedAt` keeps aging on the client. */
|
|
1763
|
-
private scheduleFileDecayEviction(bk: SessionBookkeeping, sessionId: string, filePath: string): void {
|
|
1764
|
-
const existing = bk.pendingFileClears.get(filePath);
|
|
1765
|
-
if (existing != null) {
|
|
1766
|
-
clearTimeout(existing);
|
|
1767
|
-
}
|
|
1768
|
-
|
|
1769
|
-
const timer = setTimeout(() => {
|
|
1770
|
-
if (this._disposed) return;
|
|
1771
|
-
|
|
1772
|
-
const cur = this._sessionBookkeeping.get(sessionId);
|
|
1773
|
-
if (cur == null) return;
|
|
1774
|
-
|
|
1775
|
-
cur.pendingFileClears.delete(filePath);
|
|
1776
|
-
// Re-check: a Pre during cooldown bumped the count and cancelled this timer, but a
|
|
1777
|
-
// concurrent reset could have cleared everything — bail in either case.
|
|
1778
|
-
if ((cur.currentFileCounts.get(filePath) ?? 0) > 0) return;
|
|
1779
|
-
|
|
1780
|
-
cur.currentFileCounts.delete(filePath);
|
|
1781
|
-
this.clearLastTouched(cur, filePath, 'edit');
|
|
1782
|
-
if (this.syncSessionFileActivityForChild(sessionId)) {
|
|
1783
|
-
this._onDidChangeSessions.fire();
|
|
1784
|
-
}
|
|
1785
|
-
}, this.activityDecayMs);
|
|
1786
|
-
bk.pendingFileClears.set(filePath, timer);
|
|
1787
|
-
}
|
|
1788
|
-
|
|
1789
|
-
/** Drops a tracked tool call's file immediately (no cooldown). Used for PermissionDenied,
|
|
1790
|
-
* where the tool never ran — keeping the "live" badge would be misleading.
|
|
1791
|
-
* Returns whether the published list changed. */
|
|
1792
|
-
private untrackToolUseFile(sessionId: string, event: AgentSessionEvent, capabilities: AgentCapabilities): boolean {
|
|
1793
|
-
const filePath = this.getEventFilePath(event, capabilities);
|
|
1794
|
-
if (filePath == null) return false;
|
|
1795
|
-
|
|
1796
|
-
const bk = this.getBookkeeping(sessionId);
|
|
1797
|
-
const count = bk.currentFileCounts.get(filePath);
|
|
1798
|
-
if (count == null) return false;
|
|
1799
|
-
if (count > 1) {
|
|
1800
|
-
bk.currentFileCounts.set(filePath, count - 1);
|
|
1801
|
-
return this.syncSessionFileActivityForChild(sessionId);
|
|
1802
|
-
}
|
|
1803
|
-
|
|
1804
|
-
const pending = bk.pendingFileClears.get(filePath);
|
|
1805
|
-
if (pending != null) {
|
|
1806
|
-
clearTimeout(pending);
|
|
1807
|
-
bk.pendingFileClears.delete(filePath);
|
|
1808
|
-
}
|
|
1809
|
-
bk.currentFileCounts.delete(filePath);
|
|
1810
|
-
this.clearLastTouched(bk, filePath, 'edit');
|
|
1811
|
-
return this.syncSessionFileActivityForChild(sessionId);
|
|
1812
|
-
}
|
|
1813
|
-
|
|
1814
|
-
/** Mirror of {@link getEventFilePath} for read-only file tools. */
|
|
1815
|
-
private getEventReadPath(event: AgentSessionEvent, capabilities: AgentCapabilities): string | undefined {
|
|
1816
|
-
const toolName = canonicalToolNameFromEvent(capabilities, event);
|
|
1817
|
-
if (toolName == null) return undefined;
|
|
1818
|
-
|
|
1819
|
-
const rawHookInput = event.hookInput?.tool_input as Record<string, unknown> | undefined;
|
|
1820
|
-
const hookInputPopulated = rawHookInput != null && Object.keys(rawHookInput).length > 0;
|
|
1821
|
-
const toolInput = hookInputPopulated ? rawHookInput : event.toolInput;
|
|
1822
|
-
return getToolReadPath(toolName, toolInput);
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
|
-
/** Refcount mirror of {@link trackToolUseFile} for read-only file tools. */
|
|
1826
|
-
private trackToolUseRead(sessionId: string, event: AgentSessionEvent, capabilities: AgentCapabilities): boolean {
|
|
1827
|
-
const filePath = this.getEventReadPath(event, capabilities);
|
|
1828
|
-
if (filePath == null) return false;
|
|
1829
|
-
|
|
1830
|
-
const bk = this.getBookkeeping(sessionId);
|
|
1831
|
-
const pendingClear = bk.pendingReadClears.get(filePath);
|
|
1832
|
-
if (pendingClear != null) {
|
|
1833
|
-
clearTimeout(pendingClear);
|
|
1834
|
-
bk.pendingReadClears.delete(filePath);
|
|
1835
|
-
}
|
|
1836
|
-
bk.currentReadCounts.set(filePath, (bk.currentReadCounts.get(filePath) ?? 0) + 1);
|
|
1837
|
-
this.stampLastTouched(bk, filePath, 'read');
|
|
1838
|
-
return this.syncSessionFileActivityForChild(sessionId);
|
|
1839
|
-
}
|
|
1840
|
-
|
|
1841
|
-
/** Cooldown mirror of {@link scheduleClearToolUseFile} for read-only file tools. Returns
|
|
1842
|
-
* whether the parent's published `fileActivity` changed (i.e. `reading` flipped off). */
|
|
1843
|
-
private scheduleClearToolUseRead(
|
|
1844
|
-
sessionId: string,
|
|
1845
|
-
event: AgentSessionEvent,
|
|
1846
|
-
capabilities: AgentCapabilities,
|
|
1847
|
-
): boolean {
|
|
1848
|
-
const filePath = this.getEventReadPath(event, capabilities);
|
|
1849
|
-
if (filePath == null) return false;
|
|
1850
|
-
|
|
1851
|
-
const bk = this.getBookkeeping(sessionId);
|
|
1852
|
-
const count = bk.currentReadCounts.get(filePath);
|
|
1853
|
-
// Same guard as scheduleClearToolUseFile — duplicate / out-of-order Posts during cooldown
|
|
1854
|
-
// would otherwise drive the refcount negative and stack up redundant clear timers.
|
|
1855
|
-
if (count == null || count <= 0) return false;
|
|
1856
|
-
|
|
1857
|
-
const next = count - 1;
|
|
1858
|
-
bk.currentReadCounts.set(filePath, next);
|
|
1859
|
-
// Re-sync so `reading` drops to `undefined` the moment refcount hits zero.
|
|
1860
|
-
const changed = this.syncSessionFileActivityForChild(sessionId);
|
|
1861
|
-
if (next > 0) return changed;
|
|
1862
|
-
|
|
1863
|
-
this.scheduleReadDecayEviction(bk, sessionId, filePath);
|
|
1864
|
-
return changed;
|
|
1865
|
-
}
|
|
1866
|
-
|
|
1867
|
-
/** Read-class mirror of {@link scheduleFileDecayEviction}. */
|
|
1868
|
-
private scheduleReadDecayEviction(bk: SessionBookkeeping, sessionId: string, filePath: string): void {
|
|
1869
|
-
const existing = bk.pendingReadClears.get(filePath);
|
|
1870
|
-
if (existing != null) {
|
|
1871
|
-
clearTimeout(existing);
|
|
1872
|
-
}
|
|
1873
|
-
|
|
1874
|
-
const timer = setTimeout(() => {
|
|
1875
|
-
if (this._disposed) return;
|
|
1876
|
-
|
|
1877
|
-
const cur = this._sessionBookkeeping.get(sessionId);
|
|
1878
|
-
if (cur == null) return;
|
|
1879
|
-
|
|
1880
|
-
cur.pendingReadClears.delete(filePath);
|
|
1881
|
-
if ((cur.currentReadCounts.get(filePath) ?? 0) > 0) return;
|
|
1882
|
-
|
|
1883
|
-
cur.currentReadCounts.delete(filePath);
|
|
1884
|
-
this.clearLastTouched(cur, filePath, 'read');
|
|
1885
|
-
if (this.syncSessionFileActivityForChild(sessionId)) {
|
|
1886
|
-
this._onDidChangeSessions.fire();
|
|
1887
|
-
}
|
|
1888
|
-
}, this.activityDecayMs);
|
|
1889
|
-
bk.pendingReadClears.set(filePath, timer);
|
|
1890
|
-
}
|
|
1891
|
-
|
|
1892
|
-
/** Immediate-drop mirror of {@link untrackToolUseFile} for read-only file tools. */
|
|
1893
|
-
private untrackToolUseRead(sessionId: string, event: AgentSessionEvent, capabilities: AgentCapabilities): boolean {
|
|
1894
|
-
const filePath = this.getEventReadPath(event, capabilities);
|
|
1895
|
-
if (filePath == null) return false;
|
|
1896
|
-
|
|
1897
|
-
const bk = this.getBookkeeping(sessionId);
|
|
1898
|
-
const count = bk.currentReadCounts.get(filePath);
|
|
1899
|
-
if (count == null) return false;
|
|
1900
|
-
if (count > 1) {
|
|
1901
|
-
bk.currentReadCounts.set(filePath, count - 1);
|
|
1902
|
-
return this.syncSessionFileActivityForChild(sessionId);
|
|
1903
|
-
}
|
|
1904
|
-
|
|
1905
|
-
const pending = bk.pendingReadClears.get(filePath);
|
|
1906
|
-
if (pending != null) {
|
|
1907
|
-
clearTimeout(pending);
|
|
1908
|
-
bk.pendingReadClears.delete(filePath);
|
|
1909
|
-
}
|
|
1910
|
-
bk.currentReadCounts.delete(filePath);
|
|
1911
|
-
this.clearLastTouched(bk, filePath, 'read');
|
|
1912
|
-
return this.syncSessionFileActivityForChild(sessionId);
|
|
1913
|
-
}
|
|
1914
|
-
|
|
1915
|
-
/** Convenience wrapper that resolves the parent for a possibly-sub-agent session id, then calls
|
|
1916
|
-
* {@link syncSessionFileActivity}. Tool-call handlers call this without knowing whether the
|
|
1917
|
-
* session is a top-level or sub-agent session. Returns `false` when the session isn't found
|
|
1918
|
-
* (already pruned / unknown peer id). */
|
|
1919
|
-
private syncSessionFileActivityForChild(sessionId: string): boolean {
|
|
1920
|
-
const parentId = this.findOwningParentId(sessionId);
|
|
1921
|
-
if (parentId == null) return false;
|
|
1922
|
-
return this.syncSessionFileActivity(parentId);
|
|
1923
|
-
}
|
|
1924
|
-
|
|
1925
|
-
/** Rebuilds the parent session's `fileActivity` array from its bookkeeping and writes it onto
|
|
1926
|
-
* the session in place. Returns whether the *structural* shape changed (paths/kinds/flags) —
|
|
1927
|
-
* timestamps are always refreshed regardless, but a fire is gated on structural change so
|
|
1928
|
-
* non-meaningful drift doesn't churn the wire.
|
|
1929
|
-
*
|
|
1930
|
-
* No separate sub-agent rollup is needed: the GK CLI keys a sub-agent's tool events under its
|
|
1931
|
-
* PARENT session id (with `agentId` set), so they accumulate in the parent's own bookkeeping
|
|
1932
|
-
* directly and are already reflected here. Sub-agent serialized objects carry no `fileActivity`. */
|
|
1933
|
-
private syncSessionFileActivity(parentSessionId: string): boolean {
|
|
1934
|
-
const index = this._sessions.findIndex(s => s.id === parentSessionId);
|
|
1935
|
-
if (index < 0) return false;
|
|
1936
|
-
|
|
1937
|
-
const parent = this._sessions[index];
|
|
1938
|
-
if (parent.status === 'ended') return false;
|
|
1939
|
-
|
|
1940
|
-
const parentBk = this._sessionBookkeeping.get(parentSessionId);
|
|
1941
|
-
|
|
1942
|
-
type Contrib = { editCount: number; readCount: number; editAt?: number; readAt?: number };
|
|
1943
|
-
const merged = new Map<string, Contrib>();
|
|
1944
|
-
|
|
1945
|
-
const contributeFrom = (bk: SessionBookkeeping | undefined): void => {
|
|
1946
|
-
if (bk == null) return;
|
|
1947
|
-
|
|
1948
|
-
for (const [path, count] of bk.currentFileCounts) {
|
|
1949
|
-
let entry = merged.get(path);
|
|
1950
|
-
if (entry == null) {
|
|
1951
|
-
entry = { editCount: 0, readCount: 0 };
|
|
1952
|
-
merged.set(path, entry);
|
|
1953
|
-
}
|
|
1954
|
-
entry.editCount += count;
|
|
1955
|
-
const ts = bk.lastTouchedAt.get(path)?.edit;
|
|
1956
|
-
if (ts != null && (entry.editAt == null || ts > entry.editAt)) {
|
|
1957
|
-
entry.editAt = ts;
|
|
1958
|
-
}
|
|
1959
|
-
}
|
|
1960
|
-
for (const [path, count] of bk.currentReadCounts) {
|
|
1961
|
-
let entry = merged.get(path);
|
|
1962
|
-
if (entry == null) {
|
|
1963
|
-
entry = { editCount: 0, readCount: 0 };
|
|
1964
|
-
merged.set(path, entry);
|
|
1965
|
-
}
|
|
1966
|
-
entry.readCount += count;
|
|
1967
|
-
const ts = bk.lastTouchedAt.get(path)?.read;
|
|
1968
|
-
if (ts != null && (entry.readAt == null || ts > entry.readAt)) {
|
|
1969
|
-
entry.readAt = ts;
|
|
1970
|
-
}
|
|
1971
|
-
}
|
|
1972
|
-
};
|
|
1973
|
-
|
|
1974
|
-
contributeFrom(parentBk);
|
|
1975
|
-
|
|
1976
|
-
const now = Date.now();
|
|
1977
|
-
let next: FileActivityEntry[] | undefined;
|
|
1978
|
-
if (merged.size > 0) {
|
|
1979
|
-
next = [];
|
|
1980
|
-
for (const [path, contrib] of merged) {
|
|
1981
|
-
const entry: FileActivityEntry = { path: path };
|
|
1982
|
-
if (contrib.editAt != null) {
|
|
1983
|
-
entry.editedAt = Math.max(0, now - contrib.editAt);
|
|
1984
|
-
}
|
|
1985
|
-
if (contrib.readAt != null) {
|
|
1986
|
-
entry.readAt = Math.max(0, now - contrib.readAt);
|
|
1987
|
-
}
|
|
1988
|
-
if (contrib.editCount > 0) {
|
|
1989
|
-
entry.editing = true;
|
|
1990
|
-
}
|
|
1991
|
-
if (contrib.readCount > 0) {
|
|
1992
|
-
entry.reading = true;
|
|
1993
|
-
}
|
|
1994
|
-
next.push(entry);
|
|
1995
|
-
}
|
|
1996
|
-
}
|
|
1997
|
-
|
|
1998
|
-
const changed = !fileActivityStructurallyEqual(parent.fileActivity, next);
|
|
1999
|
-
// Always replace so timestamps reflect the latest serialization, even when the structural
|
|
2000
|
-
// shape is unchanged (so a downstream fire from anywhere else carries fresh `sinceMs`).
|
|
2001
|
-
this._sessions[index] = { ...parent, fileActivity: next };
|
|
2002
|
-
return changed;
|
|
2003
|
-
}
|
|
2004
|
-
|
|
2005
|
-
/** Defer the `Stop → idle` commit. If the agent produces a non-idle event within
|
|
2006
|
-
* `stopToIdleDebounceMs`, `cancelPendingIdleTransition` (called from `updateSessionStatus`)
|
|
2007
|
-
* cancels this and the session stays in its prior phase — no working → idle → working flicker.
|
|
2008
|
-
*
|
|
2009
|
-
* Intentionally takes no context: the caller is expected to apply any event metadata
|
|
2010
|
-
* synchronously before scheduling. Carrying Stop-time context into the timer would let
|
|
2011
|
-
* in-window metadata mutations (e.g. `CwdChanged` updating `_sessions[i].cwd` directly)
|
|
2012
|
-
* get reverted when the deferred commit replays the stale context through `ensureSession`. */
|
|
2013
|
-
private scheduleIdleTransition(sessionId: string): void {
|
|
2014
|
-
this.cancelPendingIdleTransition(sessionId);
|
|
2015
|
-
const timer = setTimeout(() => {
|
|
2016
|
-
this._pendingIdleTimers.delete(sessionId);
|
|
2017
|
-
// Session may have been removed (SessionEnd, prune) during the window, or ended by a
|
|
2018
|
-
// poll/SessionEnd that raced this timer — reviving a terminal row to idle would zombie it.
|
|
2019
|
-
const idx = this._sessions.findIndex(s => s.id === sessionId);
|
|
2020
|
-
if (idx < 0 || this._sessions[idx].status === 'ended') return;
|
|
2021
|
-
|
|
2022
|
-
this.updateSessionStatus(sessionId, 'idle');
|
|
2023
|
-
}, stopToIdleDebounceMs);
|
|
2024
|
-
this._pendingIdleTimers.set(sessionId, timer);
|
|
2025
|
-
}
|
|
2026
|
-
|
|
2027
|
-
private cancelPendingIdleTransition(sessionId: string): void {
|
|
2028
|
-
const timer = this._pendingIdleTimers.get(sessionId);
|
|
2029
|
-
if (timer == null) return;
|
|
2030
|
-
|
|
2031
|
-
clearTimeout(timer);
|
|
2032
|
-
this._pendingIdleTimers.delete(sessionId);
|
|
2033
|
-
}
|
|
2034
|
-
|
|
2035
|
-
/** Stable phase-since: if we're oscillating back into the phase we were just in (within a
|
|
2036
|
-
* short window), restore its original timestamp so the displayed elapsed time doesn't snap
|
|
2037
|
-
* to 0. Otherwise records the current phase as the new "prior" and returns `now`. */
|
|
2038
|
-
private resolvePhaseSince(
|
|
2039
|
-
sessionId: string,
|
|
2040
|
-
prevPhase: AgentSessionPhase,
|
|
2041
|
-
prevPhaseSince: Date,
|
|
2042
|
-
newPhase: AgentSessionPhase,
|
|
2043
|
-
): Date {
|
|
2044
|
-
if (prevPhase === newPhase) return prevPhaseSince;
|
|
2045
|
-
|
|
2046
|
-
const bk = this.getBookkeeping(sessionId);
|
|
2047
|
-
const now = new Date();
|
|
2048
|
-
|
|
2049
|
-
if (bk.priorPhase?.phase === newPhase && now.getTime() - prevPhaseSince.getTime() < phaseSinceRestoreWindowMs) {
|
|
2050
|
-
const restored = bk.priorPhase.phaseSince;
|
|
2051
|
-
bk.priorPhase = { phase: prevPhase, phaseSince: prevPhaseSince };
|
|
2052
|
-
return restored;
|
|
2053
|
-
}
|
|
2054
|
-
|
|
2055
|
-
bk.priorPhase = { phase: prevPhase, phaseSince: prevPhaseSince };
|
|
2056
|
-
return now;
|
|
2057
|
-
}
|
|
2058
|
-
|
|
2059
|
-
/** Extracts the ask identity a settlement event refers to. `PostToolUse`/`PermissionDenied`
|
|
2060
|
-
* carry `hookInput.tool_use_id` and `hookInput.tool_input` in production; the top-level
|
|
2061
|
-
* `event.toolName`/`event.toolInput` are the older-CLI fallback. Input is canonicalized so key
|
|
2062
|
-
* order (or arriving via the fallback field instead of `hookInput`) doesn't defeat comparison. */
|
|
2063
|
-
private settledByFromEvent(
|
|
2064
|
-
event: AgentSessionEvent,
|
|
2065
|
-
capabilities: AgentCapabilities,
|
|
2066
|
-
): {
|
|
2067
|
-
toolUseId?: string;
|
|
2068
|
-
toolName?: string;
|
|
2069
|
-
toolInputJson?: string;
|
|
2070
|
-
} {
|
|
2071
|
-
const hookInput = event.hookInput;
|
|
2072
|
-
const toolInput = (hookInput?.tool_input as Record<string, unknown> | undefined) ?? event.toolInput;
|
|
2073
|
-
return {
|
|
2074
|
-
toolUseId: hookInput?.tool_use_id as string | undefined,
|
|
2075
|
-
// Canonical, because the registered ask's `toolName` is canonical too — comparing a native
|
|
2076
|
-
// name against it would look like an identity mismatch and strand the ask.
|
|
2077
|
-
toolName: canonicalToolNameFromEvent(capabilities, event),
|
|
2078
|
-
toolInputJson: toolInput != null ? stableStringify(toolInput) : undefined,
|
|
2079
|
-
};
|
|
2080
|
-
}
|
|
2081
|
-
|
|
2082
|
-
private resolveAllPending(entry: PendingPermissionEntry, response: PermissionResponse): void {
|
|
2083
|
-
for (const resolver of entry.resolvers) {
|
|
2084
|
-
resolver.resolve(response);
|
|
2085
|
-
}
|
|
2086
|
-
}
|
|
2087
|
-
|
|
2088
|
-
private rejectAllPending(entry: PendingPermissionEntry, reason: Error): void {
|
|
2089
|
-
for (const resolver of entry.resolvers) {
|
|
2090
|
-
resolver.reject(reason);
|
|
2091
|
-
}
|
|
2092
|
-
}
|
|
2093
|
-
|
|
2094
|
-
// Called when a pending permission was resolved outside GitLens (e.g. via the CLI terminal).
|
|
2095
|
-
// The 'deny' response is a safe no-op since the tool has already run or been denied upstream.
|
|
2096
|
-
// `settledBy` identifies the ask the settling event actually refers to — out-of-order/duplicate
|
|
2097
|
-
// hook deliveries (PostToolUse for an unrelated tool, a PermissionDenied for an already-evicted
|
|
2098
|
-
// earlier ask) must never deny a still-live blocking ask just because it shares a session id.
|
|
2099
|
-
// `PermissionDenied` itself only ever fires after Claude Code's auto-mode classifier denies a
|
|
2100
|
-
// tool call — never as an echo of a deny a `PermissionRequest` response sent.
|
|
2101
|
-
private clearStalePermission(
|
|
2102
|
-
sessionId: string,
|
|
2103
|
-
reason: string,
|
|
2104
|
-
settledBy?: { toolUseId?: string; toolName?: string; toolInputJson?: string },
|
|
2105
|
-
): void {
|
|
2106
|
-
const bk = this.getBookkeeping(sessionId);
|
|
2107
|
-
const pending = this._pendingPermissions.get(sessionId);
|
|
2108
|
-
|
|
2109
|
-
if (pending != null) {
|
|
2110
|
-
if (settledBy != null) {
|
|
2111
|
-
let mismatch = false;
|
|
2112
|
-
|
|
2113
|
-
if (pending.toolUseId != null && settledBy.toolUseId != null) {
|
|
2114
|
-
// Rule 1: both sides identified — the only trustworthy comparison. `PermissionDenied`/
|
|
2115
|
-
// `PostToolUse` hookInput DOES carry `tool_use_id`, but `PermissionRequest` never does,
|
|
2116
|
-
// so `pending` never actually has one — this branch only engages if a future CLI adds
|
|
2117
|
-
// it to requests too. The asymmetry is deliberate, not a gap to close.
|
|
2118
|
-
mismatch = pending.toolUseId !== settledBy.toolUseId;
|
|
2119
|
-
} else if (pending.toolName === '') {
|
|
2120
|
-
// Rule 2: no identity was captured at registration (degraded payload) — nothing to
|
|
2121
|
-
// gate on, so clear unconditionally rather than stranding the ask forever.
|
|
2122
|
-
mismatch = false;
|
|
2123
|
-
} else {
|
|
2124
|
-
// Rule 3: name must match; when both sides have input, it must match too. A
|
|
2125
|
-
// PostToolUse for a different invocation of the same tool (different input) leaves
|
|
2126
|
-
// the ask untouched.
|
|
2127
|
-
mismatch =
|
|
2128
|
-
settledBy.toolName !== pending.toolName ||
|
|
2129
|
-
(pending.toolInputJson != null &&
|
|
2130
|
-
settledBy.toolInputJson != null &&
|
|
2131
|
-
pending.toolInputJson !== settledBy.toolInputJson);
|
|
2132
|
-
}
|
|
2133
|
-
|
|
2134
|
-
if (mismatch) {
|
|
2135
|
-
Logger.debug(
|
|
2136
|
-
`GkAgentProvider.clearStalePermission: skip (identity mismatch) ${reason} ${this.sessionTag(sessionId)} pending=${pending.toolUseId ?? pending.toolName} settledBy=${settledBy.toolUseId ?? settledBy.toolName ?? 'unknown'}`,
|
|
2137
|
-
);
|
|
2138
|
-
return;
|
|
2139
|
-
}
|
|
2140
|
-
}
|
|
2141
|
-
|
|
2142
|
-
Logger.debug(`GkAgentProvider.clearStalePermission: ${reason} ${this.sessionTag(sessionId)}`);
|
|
2143
|
-
this.resolveAllPending(pending, {
|
|
2144
|
-
hookSpecificOutput: { hookEventName: 'PermissionRequest', decision: { behavior: 'deny' } },
|
|
2145
|
-
});
|
|
2146
|
-
this._pendingPermissions.delete(sessionId);
|
|
2147
|
-
bk.pendingPermission = undefined;
|
|
2148
|
-
return;
|
|
2149
|
-
}
|
|
2150
|
-
|
|
2151
|
-
if (bk.pendingPermission == null) return;
|
|
2152
|
-
|
|
2153
|
-
if (
|
|
2154
|
-
settledBy?.toolName != null &&
|
|
2155
|
-
bk.pendingPermission.toolName != null &&
|
|
2156
|
-
settledBy.toolName !== bk.pendingPermission.toolName
|
|
2157
|
-
) {
|
|
2158
|
-
Logger.debug(
|
|
2159
|
-
`GkAgentProvider.clearStalePermission: skip (display-only identity mismatch) ${reason} ${this.sessionTag(sessionId)} pending=${bk.pendingPermission.toolName} settledBy=${settledBy.toolName}`,
|
|
2160
|
-
);
|
|
2161
|
-
return;
|
|
2162
|
-
}
|
|
2163
|
-
|
|
2164
|
-
Logger.debug(`GkAgentProvider.clearStalePermission: ${reason} ${this.sessionTag(sessionId)}`);
|
|
2165
|
-
bk.pendingPermission = undefined;
|
|
2166
|
-
}
|
|
2167
|
-
|
|
2168
|
-
resolvePermission(
|
|
2169
|
-
sessionId: string,
|
|
2170
|
-
decision: PermissionDecision,
|
|
2171
|
-
updatedPermissions?: PermissionSuggestion[],
|
|
2172
|
-
): boolean {
|
|
2173
|
-
// Explicit refusal rather than an incidental one: an agent we can't answer for never registers
|
|
2174
|
-
// a pending entry, so the `pending == null` check below would already return `false` — but the
|
|
2175
|
-
// caller deserves to know WHY, and the guard has to survive a future entry-registering path.
|
|
2176
|
-
const capabilities = this.getSessionCapabilities(sessionId);
|
|
2177
|
-
if (capabilities != null && !canAnswerBlockingPermission(capabilities)) {
|
|
2178
|
-
Logger.debug(
|
|
2179
|
-
`GkAgentProvider.resolvePermission: refusing ${decision} for ${this.sessionTag(sessionId)} — ${capabilities.displayName} asks are observe-only`,
|
|
2180
|
-
);
|
|
2181
|
-
return false;
|
|
2182
|
-
}
|
|
2183
|
-
|
|
2184
|
-
const pending = this._pendingPermissions.get(sessionId);
|
|
2185
|
-
if (pending == null) return false;
|
|
2186
|
-
|
|
2187
|
-
Logger.debug(
|
|
2188
|
-
`GkAgentProvider.resolvePermission: ${decision} ${this.sessionTag(sessionId)} tool=${pending.toolName}`,
|
|
2189
|
-
);
|
|
2190
|
-
this.resolveAllPending(pending, {
|
|
2191
|
-
hookSpecificOutput: {
|
|
2192
|
-
hookEventName: 'PermissionRequest',
|
|
2193
|
-
decision: { behavior: decision, updatedPermissions: updatedPermissions },
|
|
2194
|
-
},
|
|
2195
|
-
});
|
|
2196
|
-
this.callbacks.onPermissionResolved?.({
|
|
2197
|
-
provider: capabilities?.providerId ?? claudeCodeCapabilities.providerId,
|
|
2198
|
-
tool: pending.toolName,
|
|
2199
|
-
decision: decision,
|
|
2200
|
-
});
|
|
2201
|
-
this._pendingPermissions.delete(sessionId);
|
|
2202
|
-
|
|
2203
|
-
const bk = this.getBookkeeping(sessionId);
|
|
2204
|
-
bk.pendingPermission = undefined;
|
|
2205
|
-
|
|
2206
|
-
const nextStatus = bk.activeToolCount > 0 ? 'tool_use' : 'thinking';
|
|
2207
|
-
this.updateSessionStatus(sessionId, nextStatus);
|
|
2208
|
-
return true;
|
|
2209
|
-
}
|
|
2210
|
-
|
|
2211
|
-
async archiveSession(sessionId: string): Promise<boolean> {
|
|
2212
|
-
// Authoritative status guard: the CLI's `archive-session` ends an active session first, so a
|
|
2213
|
-
// stale webview action (or a row that resumed out of `ended` since the click) must never
|
|
2214
|
-
// reach the CLI — archiving is only ever valid on a terminal row. Callers gate the UI too,
|
|
2215
|
-
// but this is the last line that owns live process safety. Returns `false` when refused so the
|
|
2216
|
-
// host doesn't record a success telemetry event for an archive that never happened.
|
|
2217
|
-
const session = this._sessions.find(s => s.id === sessionId);
|
|
2218
|
-
if (session != null && session.status !== 'ended') {
|
|
2219
|
-
Logger.warn(
|
|
2220
|
-
`GkAgentProvider.archiveSession: refusing to archive non-ended ${this.sessionTag(sessionId)} (status=${session.status})`,
|
|
2221
|
-
);
|
|
2222
|
-
return false;
|
|
2223
|
-
}
|
|
2224
|
-
|
|
2225
|
-
Logger.debug(`GkAgentProvider.archiveSession: ${this.sessionTag(sessionId)}`);
|
|
2226
|
-
await this.callbacks.runCLICommand(['ai', 'hook', 'archive-session', sessionId, '--json']);
|
|
2227
|
-
// Invalidate the archived-id cache so the next `getPastSessions` re-fetches and excludes this id.
|
|
2228
|
-
this._archivedSessionIdsCache = undefined;
|
|
2229
|
-
// Optimistically drop it — archived sessions are excluded from the `active,ended` poll, so the
|
|
2230
|
-
// next reconciliation confirms the removal regardless.
|
|
2231
|
-
const index = this._sessions.findIndex(s => s.id === sessionId);
|
|
2232
|
-
if (index >= 0) {
|
|
2233
|
-
this._sessions.splice(index, 1);
|
|
2234
|
-
this._sessionBookkeeping.delete(sessionId);
|
|
2235
|
-
this._transcriptReader.forget(sessionId);
|
|
2236
|
-
this._onDidChangeSessions.fire();
|
|
2237
|
-
}
|
|
2238
|
-
return true;
|
|
2239
|
-
}
|
|
2240
|
-
|
|
2241
|
-
/** Lists the ids of sessions the CLI has archived, so callers (the "Past" transcript listing) can
|
|
2242
|
-
* exclude them — the tracked row is gone, but the transcript on disk survives and would otherwise
|
|
2243
|
-
* resurface there. Resolves to `[]` on any error, including a CLI too old to support the
|
|
2244
|
-
* `archived` status filter. */
|
|
2245
|
-
async getArchivedSessionIds(): Promise<string[]> {
|
|
2246
|
-
const cached = this._archivedSessionIdsCache;
|
|
2247
|
-
if (cached != null && Date.now() - cached.at < archivedSessionIdsCacheTtlMs) {
|
|
2248
|
-
return cached.promise;
|
|
2249
|
-
}
|
|
2250
|
-
|
|
2251
|
-
const promise = this.fetchArchivedSessionIds();
|
|
2252
|
-
this._archivedSessionIdsCache = { promise: promise, at: Date.now() };
|
|
2253
|
-
return promise;
|
|
2254
|
-
}
|
|
2255
|
-
|
|
2256
|
-
private async fetchArchivedSessionIds(): Promise<string[]> {
|
|
2257
|
-
try {
|
|
2258
|
-
const output = await this.callbacks.runCLICommand([
|
|
2259
|
-
'ai',
|
|
2260
|
-
'hook',
|
|
2261
|
-
'list-sessions',
|
|
2262
|
-
'--status',
|
|
2263
|
-
'archived',
|
|
2264
|
-
'--json',
|
|
2265
|
-
]);
|
|
2266
|
-
const sessions = JSON.parse(output) as SessionFileData[];
|
|
2267
|
-
return sessions.map(s => s.sessionId).filter(id => id.length > 0);
|
|
2268
|
-
} catch {
|
|
2269
|
-
return [];
|
|
2270
|
-
}
|
|
2271
|
-
}
|
|
2272
|
-
|
|
2273
|
-
resolveEndedSessionDetails(sessionId: string): void {
|
|
2274
|
-
const session = this._sessions.find(s => s.id === sessionId);
|
|
2275
|
-
// Only ended sessions defer resolution; live sessions resolve eagerly on discovery.
|
|
2276
|
-
if (session?.status !== 'ended') return;
|
|
2277
|
-
|
|
2278
|
-
// The durable store drops the prompt (and only ever carries an auto-slug name) for most ended
|
|
2279
|
-
// sessions, so read the terminal transcript once on demand to recover a real title + the
|
|
2280
|
-
// first/last prompt. Both the git probe and transcript read sit under the guard so repeated
|
|
2281
|
-
// opens don't re-probe git or re-read a possibly-large file.
|
|
2282
|
-
const bk = this.getBookkeeping(sessionId);
|
|
2283
|
-
if (bk.endedDetailsResolved) return;
|
|
2284
|
-
|
|
2285
|
-
bk.endedDetailsResolved = true;
|
|
2286
|
-
|
|
2287
|
-
if (session.cwd) {
|
|
2288
|
-
void this.resolveGitInfo(sessionId, session.cwd);
|
|
2289
|
-
}
|
|
2290
|
-
// Git identity resolves for every agent; the transcript read is Claude-only (see
|
|
2291
|
-
// `resolveTranscriptTitles`).
|
|
2292
|
-
if (this.supportsTranscripts(sessionId)) {
|
|
2293
|
-
void this.applyEndedTranscriptDetails(sessionId, session.cwd);
|
|
2294
|
-
}
|
|
2295
|
-
}
|
|
2296
|
-
|
|
2297
|
-
/** No `_sessions` gate, unlike {@link resolveEndedSessionDetails} — must work for a
|
|
2298
|
-
* transcript-only id this window never tracked live (past-session-sheet callers). */
|
|
2299
|
-
async resolveSessionDetails(sessionId: string, cwd?: string): Promise<EndedTranscriptDetails | undefined> {
|
|
2300
|
-
// The reader only reads Claude Code's store, so an agent that leaves no transcript can only
|
|
2301
|
-
// ever miss here. Reachable rather than theoretical: an ended session of ANY agent becomes a
|
|
2302
|
-
// Past row, and opening its sheet routes here by `providerId`. `supportsTranscripts` fails
|
|
2303
|
-
// open, so the transcript-only ids this method exists for — untracked, therefore capability-
|
|
2304
|
-
// less, and Claude's by construction since only its store produces them — still resolve.
|
|
2305
|
-
if (!this.supportsTranscripts(sessionId)) return undefined;
|
|
2306
|
-
|
|
2307
|
-
let cached = this._sessionDetailsCache.get(sessionId);
|
|
2308
|
-
if (cached == null) {
|
|
2309
|
-
cached = this._transcriptReader.resolveEndedDetails(sessionId, cwd).catch((ex: unknown) => {
|
|
2310
|
-
// A read error (transient I/O, permissions) is not terminal state — drop the cache
|
|
2311
|
-
// entry so a later call retries. A resolved `undefined` IS terminal (no transcript on
|
|
2312
|
-
// disk) and stays cached.
|
|
2313
|
-
this._sessionDetailsCache.delete(sessionId);
|
|
2314
|
-
throw ex;
|
|
2315
|
-
});
|
|
2316
|
-
this._sessionDetailsCache.set(sessionId, cached);
|
|
2317
|
-
}
|
|
2318
|
-
|
|
2319
|
-
try {
|
|
2320
|
-
return await cached;
|
|
2321
|
-
} catch {
|
|
2322
|
-
return undefined;
|
|
2323
|
-
}
|
|
2324
|
-
}
|
|
2325
|
-
|
|
2326
|
-
private async applyEndedTranscriptDetails(sessionId: string, cwd: string | undefined): Promise<void> {
|
|
2327
|
-
let details;
|
|
2328
|
-
try {
|
|
2329
|
-
details = await this._transcriptReader.resolveEndedDetails(sessionId, cwd);
|
|
2330
|
-
} catch {
|
|
2331
|
-
// A read error (transient I/O, permissions) is not terminal state — clear the once-only
|
|
2332
|
-
// guard so a later open retries. A `undefined` result IS terminal (no transcript on disk),
|
|
2333
|
-
// so it keeps the guard and never re-scans.
|
|
2334
|
-
const bk = this._sessionBookkeeping.get(sessionId);
|
|
2335
|
-
if (bk != null) {
|
|
2336
|
-
bk.endedDetailsResolved = false;
|
|
2337
|
-
}
|
|
2338
|
-
return;
|
|
2339
|
-
}
|
|
2340
|
-
if (details == null) return;
|
|
2341
|
-
|
|
2342
|
-
const index = this._sessions.findIndex(s => s.id === sessionId);
|
|
2343
|
-
if (index < 0) return;
|
|
2344
|
-
|
|
2345
|
-
const session = this._sessions[index];
|
|
2346
|
-
// The session may have been archived/removed while we were reading.
|
|
2347
|
-
if (session.status !== 'ended') return;
|
|
2348
|
-
|
|
2349
|
-
const { titles } = details;
|
|
2350
|
-
const nextTitles =
|
|
2351
|
-
titles.custom != null || titles.ai != null || titles.agent != null
|
|
2352
|
-
? { custom: titles.custom, ai: titles.ai, agent: titles.agent }
|
|
2353
|
-
: session.transcriptTitles;
|
|
2354
|
-
// Durable-store prompts (full text) win over the transcript's (CLI-truncated) copy; only
|
|
2355
|
-
// backfill from the transcript when the field is empty.
|
|
2356
|
-
const nextFirstPrompt = session.firstPrompt ?? prepareStoredPrompt(details.firstPrompt);
|
|
2357
|
-
const nextLastPrompt = session.lastPrompt ?? prepareStoredPrompt(details.lastPrompt);
|
|
2358
|
-
|
|
2359
|
-
const titlesChanged =
|
|
2360
|
-
nextTitles?.custom !== session.transcriptTitles?.custom ||
|
|
2361
|
-
nextTitles?.ai !== session.transcriptTitles?.ai ||
|
|
2362
|
-
nextTitles?.agent !== session.transcriptTitles?.agent;
|
|
2363
|
-
if (!titlesChanged && nextFirstPrompt === session.firstPrompt && nextLastPrompt === session.lastPrompt) {
|
|
2364
|
-
return;
|
|
2365
|
-
}
|
|
2366
|
-
|
|
2367
|
-
this._sessions[index] = {
|
|
2368
|
-
...session,
|
|
2369
|
-
transcriptTitles: nextTitles,
|
|
2370
|
-
firstPrompt: nextFirstPrompt,
|
|
2371
|
-
lastPrompt: nextLastPrompt,
|
|
2372
|
-
};
|
|
2373
|
-
this._onDidChangeSessions.fire();
|
|
2374
|
-
}
|
|
2375
|
-
|
|
2376
|
-
private matchesWorkspace(workspacePath: string | undefined): boolean {
|
|
2377
|
-
if (!workspacePath) return false;
|
|
2378
|
-
|
|
2379
|
-
// `_workspacePaths` is normalized; normalize the input so prefix comparisons work
|
|
2380
|
-
// regardless of whether the caller passed a raw `fsPath` (CLI hook cwd, peer-session
|
|
2381
|
-
// workspacePath) or an already-normalized path.
|
|
2382
|
-
const normalized = normalizePath(workspacePath);
|
|
2383
|
-
return this._workspacePaths.some(
|
|
2384
|
-
p => normalized === p || normalized.startsWith(`${p}/`) || p.startsWith(`${normalized}/`),
|
|
2385
|
-
);
|
|
2386
|
-
}
|
|
2387
|
-
|
|
2388
|
-
private resolveWorkspacePath(cwd: string | undefined): string | undefined {
|
|
2389
|
-
if (!cwd) return undefined;
|
|
2390
|
-
|
|
2391
|
-
const normalized = normalizePath(cwd);
|
|
2392
|
-
return this._workspacePaths.find(
|
|
2393
|
-
p => normalized === p || normalized.startsWith(`${p}/`) || p.startsWith(`${normalized}/`),
|
|
2394
|
-
);
|
|
2395
|
-
}
|
|
2396
|
-
|
|
2397
|
-
private updateSessionWithPermission(
|
|
2398
|
-
sessionId: string,
|
|
2399
|
-
permission: PendingPermission,
|
|
2400
|
-
context?: SessionContext,
|
|
2401
|
-
): void {
|
|
2402
|
-
// A pending permission is a working/waiting transition — cancel any deferred Stop → idle
|
|
2403
|
-
// commit so the session doesn't briefly flip to idle before showing the prompt.
|
|
2404
|
-
this.cancelPendingIdleTransition(sessionId);
|
|
2405
|
-
|
|
2406
|
-
// The full event context, not just the pid: a blocking ask can be the first event of a
|
|
2407
|
-
// session, and a row created here with no location would sit unattributed for the whole block.
|
|
2408
|
-
const { index } = this.ensureSession(sessionId, context);
|
|
2409
|
-
|
|
2410
|
-
const prev = this._sessions[index];
|
|
2411
|
-
const newPhase = getPhaseForStatus('permission_requested');
|
|
2412
|
-
this.getBookkeeping(sessionId).pendingPermission = permission;
|
|
2413
|
-
this._sessions[index] = {
|
|
2414
|
-
...prev,
|
|
2415
|
-
status: 'permission_requested',
|
|
2416
|
-
phase: newPhase,
|
|
2417
|
-
phaseSince: this.resolvePhaseSince(sessionId, prev.phase, prev.phaseSince, newPhase),
|
|
2418
|
-
statusDetail: permission.toolDescription,
|
|
2419
|
-
pendingPermission: permission,
|
|
2420
|
-
lastActivity: new Date(),
|
|
2421
|
-
};
|
|
2422
|
-
this._onDidChangeSessions.fire();
|
|
2423
|
-
}
|
|
2424
|
-
|
|
2425
|
-
private updateSessionStatus(
|
|
2426
|
-
sessionId: string,
|
|
2427
|
-
status: AgentSessionStatus,
|
|
2428
|
-
options?: SessionContext & { statusDetail?: string; pendingPermission?: PendingPermission },
|
|
2429
|
-
): void {
|
|
2430
|
-
// Any non-idle status update implicitly cancels a deferred Stop → idle commit. The
|
|
2431
|
-
// timer is owned here so the schedule/cancel pair is uniformly enforced regardless of
|
|
2432
|
-
// which event path produced the next status.
|
|
2433
|
-
if (status !== 'idle') {
|
|
2434
|
-
this.cancelPendingIdleTransition(sessionId);
|
|
2435
|
-
}
|
|
2436
|
-
|
|
2437
|
-
const { index, changed: metadataChanged } = this.ensureSession(sessionId, options);
|
|
2438
|
-
|
|
2439
|
-
const prev = this._sessions[index];
|
|
2440
|
-
const bk = this.getBookkeeping(sessionId);
|
|
2441
|
-
|
|
2442
|
-
// If a permission/elicitation is pending, preserve permission_requested state.
|
|
2443
|
-
// Tool counts and other bookkeeping are still updated by callers before this
|
|
2444
|
-
// method, but the visible status remains locked until the wait is explicitly
|
|
2445
|
-
// resolved.
|
|
2446
|
-
if (bk.pendingPermission != null && status !== 'permission_requested') {
|
|
2447
|
-
this._sessions[index] = { ...prev, lastActivity: new Date() };
|
|
2448
|
-
if (metadataChanged) {
|
|
2449
|
-
this._onDidChangeSessions.fire();
|
|
2450
|
-
}
|
|
2451
|
-
return;
|
|
2452
|
-
}
|
|
2453
|
-
|
|
2454
|
-
const statusDetail = options?.statusDetail;
|
|
2455
|
-
// Resolved before the short-circuit: a second ask can arrive while the row already sits at
|
|
2456
|
-
// `permission_requested`, and comparing only status+detail would publish the first ask forever.
|
|
2457
|
-
const nextPendingPermission =
|
|
2458
|
-
status === 'permission_requested' ? (bk.pendingPermission ?? options?.pendingPermission) : undefined;
|
|
2459
|
-
if (
|
|
2460
|
-
prev.status === status &&
|
|
2461
|
-
prev.statusDetail === statusDetail &&
|
|
2462
|
-
isSameAsk(prev.pendingPermission, nextPendingPermission)
|
|
2463
|
-
) {
|
|
2464
|
-
if (metadataChanged) {
|
|
2465
|
-
this._onDidChangeSessions.fire();
|
|
2466
|
-
}
|
|
2467
|
-
return;
|
|
2468
|
-
}
|
|
2469
|
-
|
|
2470
|
-
const newPhase = getPhaseForStatus(status);
|
|
2471
|
-
this._sessions[index] = {
|
|
2472
|
-
...prev,
|
|
2473
|
-
status: status,
|
|
2474
|
-
phase: newPhase,
|
|
2475
|
-
phaseSince: this.resolvePhaseSince(sessionId, prev.phase, prev.phaseSince, newPhase),
|
|
2476
|
-
statusDetail: statusDetail,
|
|
2477
|
-
pendingPermission: nextPendingPermission,
|
|
2478
|
-
lastActivity: new Date(),
|
|
2479
|
-
};
|
|
2480
|
-
this._onDidChangeSessions.fire();
|
|
2481
|
-
|
|
2482
|
-
if (status === 'thinking' || status === 'tool_use' || status === 'compacting') {
|
|
2483
|
-
const sessionCwd = this._sessions[index].cwd;
|
|
2484
|
-
if (sessionCwd != null) {
|
|
2485
|
-
this.callbacks.onBranchAgentActivity?.(sessionCwd);
|
|
2486
|
-
}
|
|
2487
|
-
}
|
|
2488
|
-
|
|
2489
|
-
// On a fresh transition into idle, recheck transcript titles — Claude typically (re-)writes
|
|
2490
|
-
// `ai-title` right after finishing a response, so this is the prime moment to pick it up.
|
|
2491
|
-
// Tail-read caching makes repeated checks cheap.
|
|
2492
|
-
if (status === 'idle' && prev.status !== 'idle') {
|
|
2493
|
-
void this.resolveTranscriptTitles(sessionId, this._sessions[index].cwd);
|
|
2494
|
-
}
|
|
2495
|
-
}
|
|
2496
|
-
|
|
2497
|
-
private ensureSession(sessionId: string, context?: SessionContext): { index: number; changed: boolean } {
|
|
2498
|
-
const {
|
|
2499
|
-
capabilities,
|
|
2500
|
-
pid,
|
|
2501
|
-
workspacePath,
|
|
2502
|
-
isInWorkspace,
|
|
2503
|
-
cwd,
|
|
2504
|
-
initialCwd,
|
|
2505
|
-
planFile,
|
|
2506
|
-
sessionName,
|
|
2507
|
-
model,
|
|
2508
|
-
worktreePath,
|
|
2509
|
-
visitedWorktreePaths,
|
|
2510
|
-
} = context ?? {};
|
|
2511
|
-
|
|
2512
|
-
let index = this._sessions.findIndex(s => s.id === sessionId);
|
|
2513
|
-
if (index < 0) {
|
|
2514
|
-
const now = new Date();
|
|
2515
|
-
// Same worktree-derived fallback as the update branch below: a session created with a
|
|
2516
|
-
// scratch cwd but a CLI-seated worktree still belongs to that worktree's workspace.
|
|
2517
|
-
const createdWorkspacePath = workspacePath ?? this.resolveWorkspacePath(worktreePath);
|
|
2518
|
-
// Identity comes from the owning agent's descriptor, never from the provider — Claude's
|
|
2519
|
-
// `providerId` stays the `claudeCode` the descriptor encodes. The fallback covers the
|
|
2520
|
-
// deferred-commit paths (see `SessionContext.capabilities`), which only ever run against a
|
|
2521
|
-
// row that already exists; reaching it would mean a row created outside every event path.
|
|
2522
|
-
const identity = capabilities ?? claudeCodeCapabilities;
|
|
2523
|
-
index = this._sessions.length;
|
|
2524
|
-
this._sessions.push({
|
|
2525
|
-
id: sessionId,
|
|
2526
|
-
providerId: identity.providerId,
|
|
2527
|
-
providerName: identity.displayName,
|
|
2528
|
-
name: sessionName || undefined,
|
|
2529
|
-
status: 'idle',
|
|
2530
|
-
phase: getPhaseForStatus('idle'),
|
|
2531
|
-
phaseSince: now,
|
|
2532
|
-
pid: pid,
|
|
2533
|
-
lastActivity: now,
|
|
2534
|
-
isSubagent: false,
|
|
2535
|
-
workspacePath: createdWorkspacePath,
|
|
2536
|
-
cwd: cwd,
|
|
2537
|
-
initialCwd: initialCwd ?? cwd,
|
|
2538
|
-
planFile: planFile,
|
|
2539
|
-
isInWorkspace: createdWorkspacePath != null ? true : (isInWorkspace ?? false),
|
|
2540
|
-
model: model,
|
|
2541
|
-
worktreePath: worktreePath != null ? normalizePath(worktreePath) : undefined,
|
|
2542
|
-
visitedWorktreePaths: unionVisited(undefined, visitedWorktreePaths, worktreePath),
|
|
2543
|
-
});
|
|
2544
|
-
Logger.debug(
|
|
2545
|
-
`GkAgentProvider.ensureSession: implicitly created ${this.sessionTag(sessionId, sessionName ?? 'unnamed')}`,
|
|
2546
|
-
);
|
|
2547
|
-
if (worktreePath != null) {
|
|
2548
|
-
this.getBookkeeping(sessionId).cliSeatedWorktree = true;
|
|
2549
|
-
}
|
|
2550
|
-
this._onDidChangeSessions.fire();
|
|
2551
|
-
|
|
2552
|
-
if (cwd != null) {
|
|
2553
|
-
void this.resolveGitInfo(sessionId, cwd);
|
|
2554
|
-
}
|
|
2555
|
-
void this.resolveTranscriptTitles(sessionId, cwd);
|
|
2556
|
-
return { index: index, changed: true };
|
|
2557
|
-
}
|
|
2558
|
-
|
|
2559
|
-
const existing = this._sessions[index];
|
|
2560
|
-
const updatedPid = pid != null && existing.pid == null ? pid : existing.pid;
|
|
2561
|
-
const updatedPlanFile = planFile ?? existing.planFile;
|
|
2562
|
-
const updatedName = sessionName || existing.name;
|
|
2563
|
-
const updatedModel = model || existing.model;
|
|
2564
|
-
const updatedCwd = cwd ?? existing.cwd;
|
|
2565
|
-
// Prefer the CLI's authoritative `initialCwd` (the true launch dir it captured first-hand,
|
|
2566
|
-
// even for sessions that started before this window opened); otherwise keep whatever we
|
|
2567
|
-
// already captured, and only as a last resort backfill from the current cwd (older CLIs that
|
|
2568
|
-
// don't send `initialCwd`, or a cold-create before any cwd was known). Comparing live `cwd`
|
|
2569
|
-
// against `initialCwd` is how consumers detect launch-vs-current drift, so it stays stable.
|
|
2570
|
-
const updatedInitialCwd = initialCwd ?? existing.initialCwd ?? cwd;
|
|
2571
|
-
// The CLI resolves this at hook time and sends it on every event — authoritative when
|
|
2572
|
-
// present, so it seats ahead of the `resolveGitInfo` probe below (which still fills
|
|
2573
|
-
// `commonPath` and remains the fallback for older CLIs that don't send `cwdTimeline`).
|
|
2574
|
-
const updatedWorktreePath = worktreePath != null ? normalizePath(worktreePath) : existing.worktreePath;
|
|
2575
|
-
const updatedVisitedWorktreePaths = unionVisited(
|
|
2576
|
-
existing.visitedWorktreePaths,
|
|
2577
|
-
visitedWorktreePaths,
|
|
2578
|
-
updatedWorktreePath,
|
|
2579
|
-
);
|
|
2580
|
-
// Workspace membership follows the ATTRIBUTION, not the raw cwd: a scratch-dir excursion
|
|
2581
|
-
// (cwd in /tmp, worktree kept) stays in-workspace via its worktree, while a genuine move to
|
|
2582
|
-
// a repo outside the workspace honestly drops membership when events carry a cwd.
|
|
2583
|
-
const updatedWorkspacePath =
|
|
2584
|
-
workspacePath ??
|
|
2585
|
-
this.resolveWorkspacePath(updatedWorktreePath) ??
|
|
2586
|
-
(cwd == null ? existing.workspacePath : undefined);
|
|
2587
|
-
const updatedIsInWorkspace = cwd == null ? existing.isInWorkspace : updatedWorkspacePath != null;
|
|
2588
|
-
|
|
2589
|
-
const cwdMoved = cwd != null && updatedCwd !== existing.cwd;
|
|
2590
|
-
const worktreeMoved =
|
|
2591
|
-
updatedWorktreePath != null &&
|
|
2592
|
-
existing.worktreePath != null &&
|
|
2593
|
-
updatedWorktreePath !== existing.worktreePath;
|
|
2594
|
-
|
|
2595
|
-
// The CLI explaining the location owns attribution; a move it didn't explain hands it back to
|
|
2596
|
-
// the probe.
|
|
2597
|
-
if (worktreePath != null) {
|
|
2598
|
-
this.getBookkeeping(sessionId).cliSeatedWorktree = true;
|
|
2599
|
-
} else if (cwdMoved) {
|
|
2600
|
-
this.getBookkeeping(sessionId).cliSeatedWorktree = false;
|
|
2601
|
-
}
|
|
2602
|
-
|
|
2603
|
-
let changed = false;
|
|
2604
|
-
if (
|
|
2605
|
-
updatedPid !== existing.pid ||
|
|
2606
|
-
updatedWorkspacePath !== existing.workspacePath ||
|
|
2607
|
-
updatedIsInWorkspace !== existing.isInWorkspace ||
|
|
2608
|
-
updatedPlanFile !== existing.planFile ||
|
|
2609
|
-
updatedName !== existing.name ||
|
|
2610
|
-
updatedModel !== existing.model ||
|
|
2611
|
-
updatedCwd !== existing.cwd ||
|
|
2612
|
-
updatedInitialCwd !== existing.initialCwd ||
|
|
2613
|
-
updatedWorktreePath !== existing.worktreePath ||
|
|
2614
|
-
updatedVisitedWorktreePaths !== existing.visitedWorktreePaths
|
|
2615
|
-
) {
|
|
2616
|
-
this._sessions[index] = {
|
|
2617
|
-
...existing,
|
|
2618
|
-
name: updatedName,
|
|
2619
|
-
model: updatedModel,
|
|
2620
|
-
pid: updatedPid,
|
|
2621
|
-
workspacePath: updatedWorkspacePath,
|
|
2622
|
-
cwd: updatedCwd,
|
|
2623
|
-
initialCwd: updatedInitialCwd,
|
|
2624
|
-
planFile: updatedPlanFile,
|
|
2625
|
-
isInWorkspace: updatedIsInWorkspace,
|
|
2626
|
-
worktreePath: updatedWorktreePath,
|
|
2627
|
-
visitedWorktreePaths: updatedVisitedWorktreePaths,
|
|
2628
|
-
// A worktree move can cross repos, and the spread would otherwise carry the old repo
|
|
2629
|
-
// identity forward — which consumers prefer over the new worktree, pinning the card to
|
|
2630
|
-
// the wrong repo. Drop it; the probe below refills it. Mirrors the poll's ended-record
|
|
2631
|
-
// re-seat.
|
|
2632
|
-
commonPath: worktreeMoved ? undefined : existing.commonPath,
|
|
2633
|
-
};
|
|
2634
|
-
changed = true;
|
|
2635
|
-
}
|
|
2636
|
-
|
|
2637
|
-
// Re-fire `resolveGitInfo` if the session is missing either `worktreePath` or `commonPath`.
|
|
2638
|
-
// Peer-synced sessions arrive with `worktreePath` already set (from the peer that owns the
|
|
2639
|
-
// hook flow) but may lack `commonPath` if the peer was running pre-commonPath code; this
|
|
2640
|
-
// gives us a chance to fill it in locally. Idempotent — `resolveGitInfo` only mutates the
|
|
2641
|
-
// session when one of `cwd`/`worktreePath`/`commonPath` actually changes. The
|
|
2642
|
-
// `gitInfoUnresolvable` bookkeeping flag (see `SessionBookkeeping`) is set once
|
|
2643
|
-
// `resolveGitInfo` confirms the cwd isn't a git repo, preventing a retry storm on every
|
|
2644
|
-
// hook event for non-git-repo cwds. The flag is cleared on cwd change (`CwdChanged`) and
|
|
2645
|
-
// on `resetBookkeeping` (Stop/SessionStart) so re-resolution can happen when warranted.
|
|
2646
|
-
// A cwd move re-resolves too. `worktreePath`/`commonPath` are resolved from whichever cwd was
|
|
2647
|
-
// seen first, so an agent that moves between worktrees would otherwise stay pinned to its
|
|
2648
|
-
// launch repo — attaching to the wrong WIP row and leaving its real worktree showing no
|
|
2649
|
-
// agents at all. `CwdChanged` covers the move the CLI announces; this covers an ordinary
|
|
2650
|
-
// event that simply arrives carrying a new cwd.
|
|
2651
|
-
//
|
|
2652
|
-
// A cwd the CLI already attributed to the same worktree needs no probe at all — that's the
|
|
2653
|
-
// common case (an agent `cd`-ing around inside its own tree), and probing it would spawn a git
|
|
2654
|
-
// call per hook event for an answer we already have. A first seat (no prior worktree) still
|
|
2655
|
-
// counts as a location change: the move may be out of a non-git dir whose probe latched
|
|
2656
|
-
// `gitInfoUnresolvable`, and `commonPath` still needs the probe.
|
|
2657
|
-
//
|
|
2658
|
-
// A worktree move is a location change on its own, even with the SAME cwd (cwdTimeline
|
|
2659
|
-
// updated, cwd unchanged) — the `commonPath` drop above (`worktreeMoved ? undefined : …`)
|
|
2660
|
-
// needs the probe to refill it, and without `worktreeMoved` here nothing else would fire it.
|
|
2661
|
-
const locationNeedsProbe =
|
|
2662
|
-
worktreeMoved || (cwdMoved && (worktreePath == null || existing.worktreePath == null));
|
|
2663
|
-
if (locationNeedsProbe) {
|
|
2664
|
-
this.getBookkeeping(sessionId).gitInfoUnresolvable = false;
|
|
2665
|
-
}
|
|
2666
|
-
|
|
2667
|
-
if (
|
|
2668
|
-
cwd != null &&
|
|
2669
|
-
(locationNeedsProbe || existing.worktreePath == null || existing.commonPath == null) &&
|
|
2670
|
-
!this.getBookkeeping(sessionId).gitInfoUnresolvable
|
|
2671
|
-
) {
|
|
2672
|
-
void this.resolveGitInfo(sessionId, cwd);
|
|
2673
|
-
}
|
|
2674
|
-
|
|
2675
|
-
return { index: index, changed: changed };
|
|
2676
|
-
}
|
|
2677
|
-
|
|
2678
|
-
private async resolveGitInfo(sessionId: string, cwd: string): Promise<void> {
|
|
2679
|
-
const resolveGitInfo = this.callbacks.resolveGitInfo;
|
|
2680
|
-
if (resolveGitInfo == null) return;
|
|
2681
|
-
|
|
2682
|
-
// Dedupe concurrent calls for the same session — every hook event (PreToolUse, PostToolUse,
|
|
2683
|
-
// UserPromptSubmit, etc.) flows through `ensureSession`, which retries `resolveGitInfo`
|
|
2684
|
-
// whenever `commonPath`/`worktreePath` is missing. Without this guard a non-git-repo cwd
|
|
2685
|
-
// would spawn a fresh git probe per hook event. The `gitInfoUnresolvable` bookkeeping flag
|
|
2686
|
-
// (set below when `info == null`) is the long-lived suppressor across hook events; this
|
|
2687
|
-
// in-flight set only dedupes concurrent overlapping probes within a single resolution.
|
|
2688
|
-
//
|
|
2689
|
-
// Keyed by cwd as well: a probe for the SAME cwd is genuinely redundant, but one for a
|
|
2690
|
-
// different cwd (a missed `CwdChanged` corrected off the durable record, say) must not be
|
|
2691
|
-
// swallowed — the in-flight answer describes the old directory. Record it as pending so the
|
|
2692
|
-
// running probe hands off to it, and discard the stale answer below.
|
|
2693
|
-
const inFlightCwd = this._resolveGitInfoInFlight.get(sessionId);
|
|
2694
|
-
if (inFlightCwd != null) {
|
|
2695
|
-
if (inFlightCwd !== cwd) {
|
|
2696
|
-
this._resolveGitInfoPending.set(sessionId, cwd);
|
|
2697
|
-
}
|
|
2698
|
-
return;
|
|
2699
|
-
}
|
|
2700
|
-
|
|
2701
|
-
this._resolveGitInfoInFlight.set(sessionId, cwd);
|
|
2702
|
-
try {
|
|
2703
|
-
let info: Awaited<ReturnType<typeof resolveGitInfo>> | undefined;
|
|
2704
|
-
let failed = false;
|
|
2705
|
-
try {
|
|
2706
|
-
info = await resolveGitInfo(cwd);
|
|
2707
|
-
} catch {
|
|
2708
|
-
// A transient git/spawn failure, distinct from a confirmed "not a repo" (`info ==
|
|
2709
|
-
// null` below). Must not latch `gitInfoUnresolvable` — the next hook event retries
|
|
2710
|
-
// via `ensureSession`'s `commonPath == null` check.
|
|
2711
|
-
failed = true;
|
|
2712
|
-
info = undefined;
|
|
2713
|
-
}
|
|
2714
|
-
|
|
2715
|
-
const index = this._sessions.findIndex(s => s.id === sessionId);
|
|
2716
|
-
if (index < 0) return;
|
|
2717
|
-
|
|
2718
|
-
// Superseded while we were probing: this answer describes a directory the session has
|
|
2719
|
-
// already left, so applying it would undo the newer location. Drop it — the `finally`
|
|
2720
|
-
// below re-runs for the newest cwd.
|
|
2721
|
-
if (this._resolveGitInfoPending.has(sessionId)) return;
|
|
2722
|
-
|
|
2723
|
-
if (failed) return;
|
|
2724
|
-
|
|
2725
|
-
const session = this._sessions[index];
|
|
2726
|
-
|
|
2727
|
-
// `workspacePath` stays as the matched workspace folder (or undefined). Repo identity
|
|
2728
|
-
// flows through `commonPath`, populated here from `info.repoRoot` at the same step as
|
|
2729
|
-
// `worktreePath` — both available together from `resolveGitInfo`, much earlier than the
|
|
2730
|
-
// follow-on worktree-name refresh (which needs `getWorktrees()` on the parent repo).
|
|
2731
|
-
//
|
|
2732
|
-
// When `info == null` (cwd is not inside any git repo), set the `gitInfoUnresolvable`
|
|
2733
|
-
// bookkeeping flag so subsequent `ensureSession`/peer-sync retry checks skip re-firing.
|
|
2734
|
-
// We deliberately do NOT write a tombstone onto the session DTO — keeping `commonPath`
|
|
2735
|
-
// as `undefined` (a) keeps the wire-shape unambiguous for peers, (b) lets consumers
|
|
2736
|
-
// safely treat undefined as "no repo identity", and (c) means peers can still attempt
|
|
2737
|
-
// their own local resolution. The flag is cleared on cwd change (`CwdChanged`) and on
|
|
2738
|
-
// `resetBookkeeping` so re-resolution happens when warranted (e.g. user runs `git init`
|
|
2739
|
-
// followed by a new session start).
|
|
2740
|
-
if (info == null) {
|
|
2741
|
-
this.getBookkeeping(sessionId).gitInfoUnresolvable = true;
|
|
2742
|
-
|
|
2743
|
-
// The cwd itself isn't a repo, but a CLI-seated worktree (from `cwdTimeline`, see
|
|
2744
|
-
// `worktreeFromEvent`) may still name one — e.g. a scratch cwd like `/tmp/x` whose
|
|
2745
|
-
// session the CLI attributed to a real worktree. Probe the worktree root inline, in
|
|
2746
|
-
// the same in-flight window, so a scratch cwd doesn't leave `commonPath` permanently
|
|
2747
|
-
// unresolved.
|
|
2748
|
-
let worktreeInfo: Awaited<ReturnType<typeof resolveGitInfo>> | undefined;
|
|
2749
|
-
let liveIndex = index;
|
|
2750
|
-
let liveSession = session;
|
|
2751
|
-
if (session.worktreePath != null && session.worktreePath !== cwd && session.commonPath == null) {
|
|
2752
|
-
try {
|
|
2753
|
-
worktreeInfo = await resolveGitInfo(session.worktreePath);
|
|
2754
|
-
} catch {
|
|
2755
|
-
worktreeInfo = undefined;
|
|
2756
|
-
}
|
|
2757
|
-
|
|
2758
|
-
// Re-find and re-read after this second await, for both the success and
|
|
2759
|
-
// fall-through writes below — the array can have been reassigned/spliced (e.g.
|
|
2760
|
-
// `pruneDeadSessions`), or the session's own fields updated, while the probe ran.
|
|
2761
|
-
liveIndex = this._sessions.findIndex(s => s.id === sessionId);
|
|
2762
|
-
if (liveIndex < 0) return;
|
|
2763
|
-
|
|
2764
|
-
if (this._resolveGitInfoPending.has(sessionId)) return;
|
|
2765
|
-
|
|
2766
|
-
liveSession = this._sessions[liveIndex];
|
|
2767
|
-
}
|
|
2768
|
-
|
|
2769
|
-
if (worktreeInfo != null) {
|
|
2770
|
-
const worktreeFirstResolve = liveSession.initialCommonPath == null;
|
|
2771
|
-
this._sessions[liveIndex] = {
|
|
2772
|
-
...liveSession,
|
|
2773
|
-
cwd: cwd,
|
|
2774
|
-
commonPath: worktreeInfo.repoRoot,
|
|
2775
|
-
initialWorktreePath: worktreeFirstResolve
|
|
2776
|
-
? liveSession.worktreePath
|
|
2777
|
-
: liveSession.initialWorktreePath,
|
|
2778
|
-
initialCommonPath: worktreeFirstResolve ? worktreeInfo.repoRoot : liveSession.initialCommonPath,
|
|
2779
|
-
visitedWorktreePaths: unionVisited(liveSession.visitedWorktreePaths, liveSession.worktreePath),
|
|
2780
|
-
};
|
|
2781
|
-
this._onDidChangeSessions.fire();
|
|
2782
|
-
return;
|
|
2783
|
-
}
|
|
2784
|
-
|
|
2785
|
-
if (cwd !== liveSession.cwd) {
|
|
2786
|
-
this._sessions[liveIndex] = { ...liveSession, cwd: cwd };
|
|
2787
|
-
this._onDidChangeSessions.fire();
|
|
2788
|
-
}
|
|
2789
|
-
return;
|
|
2790
|
-
}
|
|
2791
|
-
|
|
2792
|
-
// A CLI-seated worktree wins over the probe's answer (see `cliSeatedWorktree`): the CLI
|
|
2793
|
-
// resolved it for this exact cwd, while the probe can name the parent worktree for a nested
|
|
2794
|
-
// one. `commonPath` always comes from the probe — the CLI doesn't resolve repo identity.
|
|
2795
|
-
const effectiveWorktreePath =
|
|
2796
|
-
this.getBookkeeping(sessionId).cliSeatedWorktree && session.worktreePath != null
|
|
2797
|
-
? session.worktreePath
|
|
2798
|
-
: info.worktreePath;
|
|
2799
|
-
|
|
2800
|
-
// Capture the worktree/commonPath at the first successful resolve so consumers can
|
|
2801
|
-
// detect drift (e.g. an agent that `cd`'d into a sibling worktree after launch). Gated
|
|
2802
|
-
// on `initialCommonPath` — captured together with `initialWorktreePath` so a first
|
|
2803
|
-
// resolve where `info.worktreePath` happens to be undefined doesn't leave the latter
|
|
2804
|
-
// open to a later overwrite. Once set, never overwritten.
|
|
2805
|
-
const firstResolve = session.initialCommonPath == null;
|
|
2806
|
-
const updatedInitialWorktreePath = firstResolve ? effectiveWorktreePath : session.initialWorktreePath;
|
|
2807
|
-
const updatedInitialCommonPath = firstResolve ? info.repoRoot : session.initialCommonPath;
|
|
2808
|
-
|
|
2809
|
-
if (
|
|
2810
|
-
cwd !== session.cwd ||
|
|
2811
|
-
effectiveWorktreePath !== session.worktreePath ||
|
|
2812
|
-
info.repoRoot !== session.commonPath ||
|
|
2813
|
-
updatedInitialWorktreePath !== session.initialWorktreePath ||
|
|
2814
|
-
updatedInitialCommonPath !== session.initialCommonPath
|
|
2815
|
-
) {
|
|
2816
|
-
this._sessions[index] = {
|
|
2817
|
-
...session,
|
|
2818
|
-
cwd: cwd,
|
|
2819
|
-
worktreePath: effectiveWorktreePath,
|
|
2820
|
-
commonPath: info.repoRoot,
|
|
2821
|
-
initialWorktreePath: updatedInitialWorktreePath,
|
|
2822
|
-
initialCommonPath: updatedInitialCommonPath,
|
|
2823
|
-
visitedWorktreePaths: unionVisited(session.visitedWorktreePaths, effectiveWorktreePath),
|
|
2824
|
-
};
|
|
2825
|
-
this._onDidChangeSessions.fire();
|
|
2826
|
-
}
|
|
2827
|
-
} finally {
|
|
2828
|
-
this._resolveGitInfoInFlight.delete(sessionId);
|
|
2829
|
-
|
|
2830
|
-
// Hand off to the newest cwd requested while this probe ran, so a move that arrived
|
|
2831
|
-
// mid-flight still resolves instead of being dropped by the dedupe.
|
|
2832
|
-
const pendingCwd = this._resolveGitInfoPending.get(sessionId);
|
|
2833
|
-
if (pendingCwd != null) {
|
|
2834
|
-
this._resolveGitInfoPending.delete(sessionId);
|
|
2835
|
-
void this.resolveGitInfo(sessionId, pendingCwd);
|
|
2836
|
-
}
|
|
2837
|
-
}
|
|
2838
|
-
}
|
|
2839
|
-
|
|
2840
|
-
private async resolveTranscriptTitles(sessionId: string, cwd: string | undefined): Promise<void> {
|
|
2841
|
-
// The reader only understands Claude Code's `~/.claude/projects/**` JSONL store, so for any
|
|
2842
|
-
// other agent this would be a guaranteed-miss directory scan on every event and poll.
|
|
2843
|
-
if (!this.supportsTranscripts(sessionId)) return;
|
|
2844
|
-
|
|
2845
|
-
let titles;
|
|
2846
|
-
try {
|
|
2847
|
-
titles = await this._transcriptReader.resolve(sessionId, cwd);
|
|
2848
|
-
} catch {
|
|
2849
|
-
return;
|
|
2850
|
-
}
|
|
2851
|
-
if (titles == null) return;
|
|
2852
|
-
|
|
2853
|
-
const index = this._sessions.findIndex(s => s.id === sessionId);
|
|
2854
|
-
if (index < 0) return;
|
|
2855
|
-
|
|
2856
|
-
const session = this._sessions[index];
|
|
2857
|
-
const prev = session.transcriptTitles;
|
|
2858
|
-
if (prev?.custom === titles.custom && prev?.ai === titles.ai && prev?.agent === titles.agent) return;
|
|
2859
|
-
|
|
2860
|
-
this._sessions[index] = {
|
|
2861
|
-
...session,
|
|
2862
|
-
transcriptTitles: { custom: titles.custom, ai: titles.ai, agent: titles.agent },
|
|
2863
|
-
};
|
|
2864
|
-
this._onDidChangeSessions.fire();
|
|
2865
|
-
}
|
|
2866
|
-
|
|
2867
|
-
/** Transitions the session at `index` to the terminal `ended` state in place: keeps the row,
|
|
2868
|
-
* stamps `endedAt` as its activity time, clears transient state (statusDetail, pendingPermission,
|
|
2869
|
-
* subagents), and tears down its bookkeeping/timers. Shared by the live `SessionEnd` path and the
|
|
2870
|
-
* poll backstop for a `SessionEnd` the live path missed. Does not fire change events — the caller
|
|
2871
|
-
* batches those. */
|
|
2872
|
-
private endSession(index: number, endedAt: Date): void {
|
|
2873
|
-
const prev = this._sessions[index];
|
|
2874
|
-
this._sessions[index] = {
|
|
2875
|
-
...prev,
|
|
2876
|
-
status: 'ended',
|
|
2877
|
-
phase: 'ended',
|
|
2878
|
-
phaseSince: endedAt,
|
|
2879
|
-
lastActivity: endedAt,
|
|
2880
|
-
statusDetail: undefined,
|
|
2881
|
-
pendingPermission: undefined,
|
|
2882
|
-
subagents: undefined,
|
|
2883
|
-
};
|
|
2884
|
-
// Terminal teardown, owned here so every caller gets it — the live `SessionEnd` path does
|
|
2885
|
-
// this before calling us, but the poll's missed-SessionEnd transition doesn't: reject any
|
|
2886
|
-
// in-flight permission promise (otherwise a blocking request leaks until dispose) and cancel a
|
|
2887
|
-
// pending Stop→idle timer (otherwise it fires inside its window and revives this row to a
|
|
2888
|
-
// live zombie).
|
|
2889
|
-
const pending = this._pendingPermissions.get(prev.id);
|
|
2890
|
-
if (pending != null) {
|
|
2891
|
-
this.rejectAllPending(pending, new Error('Session ended'));
|
|
2892
|
-
this._pendingPermissions.delete(prev.id);
|
|
2893
|
-
}
|
|
2894
|
-
this.cancelPendingIdleTransition(prev.id);
|
|
2895
|
-
|
|
2896
|
-
const bk = this._sessionBookkeeping.get(prev.id);
|
|
2897
|
-
if (bk != null) {
|
|
2898
|
-
this.cancelPendingFileClears(bk);
|
|
2899
|
-
this.cancelPendingReadClears(bk);
|
|
2900
|
-
}
|
|
2901
|
-
this._sessionBookkeeping.delete(prev.id);
|
|
2902
|
-
this._transcriptReader.forget(prev.id);
|
|
2903
|
-
this.noteTerminalTransition();
|
|
2904
|
-
}
|
|
2905
|
-
|
|
2906
|
-
private pruneDeadSessions(): boolean {
|
|
2907
|
-
const kept: AgentSession[] = [];
|
|
2908
|
-
const removedIds: string[] = [];
|
|
2909
|
-
for (const s of this._sessions) {
|
|
2910
|
-
// Ended sessions are terminal and durable — their PID is usually dead by design, so
|
|
2911
|
-
// liveness pruning must never drop them (they leave only via poll reconciliation).
|
|
2912
|
-
if (s.status === 'ended') {
|
|
2913
|
-
kept.push(s);
|
|
2914
|
-
continue;
|
|
2915
|
-
}
|
|
2916
|
-
|
|
2917
|
-
// An agent that multiplexes concurrent sessions in one process (Codex) reports the SAME
|
|
2918
|
-
// pid for all of them, so the pid answers "is the host process alive", not "is this
|
|
2919
|
-
// session alive" — in both directions. A live shared process would pin every session it
|
|
2920
|
-
// ever hosted, and a dead one would reap live siblings along with the finished session.
|
|
2921
|
-
// Neither is recoverable here, so pid liveness is not evidence for these rows at all:
|
|
2922
|
-
// keep them and let the paths that DO identify a session end them — a `SessionEnd`, or the
|
|
2923
|
-
// poll seeing the CLI's durable `ended` record (which the CLI writes for a stale session
|
|
2924
|
-
// of its own accord). The CLI takes the same position: for `pidSharingClients` a pid-only
|
|
2925
|
-
// match is a guess, so it keeps such an end revivable.
|
|
2926
|
-
if (getAgentCapabilitiesByProviderId(s.providerId)?.sharesPids === true) {
|
|
2927
|
-
kept.push(s);
|
|
2928
|
-
continue;
|
|
2929
|
-
}
|
|
2930
|
-
|
|
2931
|
-
// A session blocking on us for a permission decision is by definition alive,
|
|
2932
|
-
// even if `kill(pid, 0)` says otherwise (e.g. transient EPERM/ESRCH). Dropping
|
|
2933
|
-
// it here loses pendingPermission and lastPrompt; the next syncSessions then
|
|
2934
|
-
// re-adds it as a stale shell with `permission_requested` status but no detail.
|
|
2935
|
-
if (s.pid == null || isProcessAlive(s.pid) || this._pendingPermissions.has(s.id)) {
|
|
2936
|
-
kept.push(s);
|
|
2937
|
-
} else {
|
|
2938
|
-
removedIds.push(s.id);
|
|
2939
|
-
}
|
|
2940
|
-
}
|
|
2941
|
-
if (removedIds.length === 0) return false;
|
|
2942
|
-
|
|
2943
|
-
this._sessions = kept;
|
|
2944
|
-
for (const id of removedIds) {
|
|
2945
|
-
const pending = this._pendingPermissions.get(id);
|
|
2946
|
-
if (pending != null) {
|
|
2947
|
-
this.rejectAllPending(pending, new Error('Session pruned'));
|
|
2948
|
-
this._pendingPermissions.delete(id);
|
|
2949
|
-
}
|
|
2950
|
-
this.cancelPendingIdleTransition(id);
|
|
2951
|
-
// Cancel any in-flight decay-eviction timers before dropping the bookkeeping (mirrors
|
|
2952
|
-
// SessionEnd). A Stopped-then-killed session reaches here with armed cooldown timers
|
|
2953
|
-
// (settleBookkeeping leaves them running), so without this they'd leak until they fire.
|
|
2954
|
-
const bk = this._sessionBookkeeping.get(id);
|
|
2955
|
-
if (bk != null) {
|
|
2956
|
-
this.cancelPendingFileClears(bk);
|
|
2957
|
-
this.cancelPendingReadClears(bk);
|
|
2958
|
-
}
|
|
2959
|
-
this._sessionBookkeeping.delete(id);
|
|
2960
|
-
this._transcriptReader.forget(id);
|
|
2961
|
-
}
|
|
2962
|
-
this.noteTerminalTransition();
|
|
2963
|
-
Logger.debug(
|
|
2964
|
-
`GkAgentProvider.syncSessions: removed ${removedIds.length} stale session(s): ${removedIds.map(id => id.substring(0, 8)).join(', ')}`,
|
|
2965
|
-
);
|
|
2966
|
-
return true;
|
|
2967
|
-
}
|
|
2968
|
-
|
|
2969
|
-
protected async syncSessions(options?: { gate?: boolean }): Promise<void> {
|
|
2970
|
-
// Recurring (gated) calls skip the CLI spawn when there's nothing to reconcile: no tracked
|
|
2971
|
-
// sessions AND no installed hooks. Sessions only ever appear via the IPC push path or a peer,
|
|
2972
|
-
// so an empty list with hooks off means no agents are reachable here. We still poll whenever
|
|
2973
|
-
// sessions exist — that's the only local backstop for pruning agents that die without firing
|
|
2974
|
-
// `SessionEnd`, and it keeps us correct even if hook detection is stale/wrong. The bootstrap
|
|
2975
|
-
// call in `ensureIpcServer` passes no options, so cold-start discovery always runs.
|
|
2976
|
-
// Deliberately counts ALL tracked sessions, ended included. An ended row is something
|
|
2977
|
-
// we have to reconcile — it's removed only when the CLI stops listing it (archived from
|
|
2978
|
-
// another window, or aged out) — and this poll is the sole mechanism that does so. It's also
|
|
2979
|
-
// what triggers the CLI's own 30-day retention sweep, which runs on `list-sessions` and
|
|
2980
|
-
// nothing else: no timer, no daemon. Gating it out to save a spawn wouldn't avoid work, it
|
|
2981
|
-
// would strand the rows on screen AND the records on disk until the window reloads.
|
|
2982
|
-
if (options?.gate && this._sessions.length === 0 && !this._hooksInstalled) return;
|
|
2983
|
-
|
|
2984
|
-
// Narrowly: hooks off AND nothing running, i.e. we hold only terminal history. There's no
|
|
2985
|
-
// dying process to reap and no live state to correct — just rows to reconcile and the
|
|
2986
|
-
// retention sweep to trigger, neither of which needs quarter-hourly resolution. Back the
|
|
2987
|
-
// cadence off rather than stopping (window startup polls ungated, so a reload is current).
|
|
2988
|
-
// Hooks ON keeps the full cadence deliberately: there the tick is also the backstop for a
|
|
2989
|
-
// session whose hook events never reached our IPC server.
|
|
2990
|
-
if (
|
|
2991
|
-
options?.gate &&
|
|
2992
|
-
!this._hooksInstalled &&
|
|
2993
|
-
!this._sessions.some(s => s.status !== 'ended') &&
|
|
2994
|
-
Date.now() - this._lastSyncAt < idleReconcileIntervalMs
|
|
2995
|
-
) {
|
|
2996
|
-
return;
|
|
2997
|
-
}
|
|
2998
|
-
|
|
2999
|
-
// Stamped BEFORE the CLI call so the reconciliation below can tell "this poll's snapshot was
|
|
3000
|
-
// taken after we ended the row" from "an in-flight poll that predates it" — see the
|
|
3001
|
-
// ended-removal filter.
|
|
3002
|
-
const pollStartedAt = Date.now();
|
|
3003
|
-
this._lastSyncAt = pollStartedAt;
|
|
3004
|
-
|
|
3005
|
-
let sessions: SessionFileData[];
|
|
3006
|
-
try {
|
|
3007
|
-
let output: string;
|
|
3008
|
-
try {
|
|
3009
|
-
// `--status active,ended` surfaces both live sessions and the CLI's durable ended records
|
|
3010
|
-
// (excluding `archived`). Newer CLIs default `list-sessions` to active-only, so the flag
|
|
3011
|
-
// is required to see ended sessions at all.
|
|
3012
|
-
output = await this.callbacks.runCLICommand([
|
|
3013
|
-
'ai',
|
|
3014
|
-
'hook',
|
|
3015
|
-
'list-sessions',
|
|
3016
|
-
'--status',
|
|
3017
|
-
'active,ended',
|
|
3018
|
-
'--json',
|
|
3019
|
-
]);
|
|
3020
|
-
this._statusFilterSupported = true;
|
|
3021
|
-
} catch (ex) {
|
|
3022
|
-
// Only retry on an unknown-flag error — a pre-3.1.69 CLI rejects `--status` but returns
|
|
3023
|
-
// all sessions unfiltered, so the flagless call is the correct legacy equivalent. Real
|
|
3024
|
-
// failures (CLI missing, spawn error) rethrow to the prune-and-return handler below.
|
|
3025
|
-
if (!isUnknownFlagError(ex)) throw ex;
|
|
3026
|
-
|
|
3027
|
-
this._statusFilterSupported = false;
|
|
3028
|
-
output = await this.callbacks.runCLICommand(['ai', 'hook', 'list-sessions', '--json']);
|
|
3029
|
-
}
|
|
3030
|
-
sessions = JSON.parse(output) as SessionFileData[];
|
|
3031
|
-
} catch (ex) {
|
|
3032
|
-
// Expected whenever the CLI is missing or still installing, so not an error — but log it:
|
|
3033
|
-
// every session in the window silently stops reconciling until the next tick, and without
|
|
3034
|
-
// this the symptom ("no agents anywhere") is indistinguishable from having no agents.
|
|
3035
|
-
Logger.debug(
|
|
3036
|
-
`GkAgentProvider.syncSessions: poll skipped — ${ex instanceof Error ? ex.message : String(ex)}`,
|
|
3037
|
-
);
|
|
3038
|
-
if (this.pruneDeadSessions()) {
|
|
3039
|
-
this._onDidChangeSessions.fire();
|
|
3040
|
-
}
|
|
3041
|
-
return;
|
|
3042
|
-
}
|
|
3043
|
-
|
|
3044
|
-
// Snapshot what the live IPC path had already tracked, so we can detect drift between it and
|
|
3045
|
-
// what the poll returns. Ideally they match exactly — any difference means the live push path
|
|
3046
|
-
// missed an add (poll discovers it) or a teardown (poll no longer reports a session we track).
|
|
3047
|
-
// Drift is measured over *live* sessions only; ended sessions are durable history, so their
|
|
3048
|
-
// absence from `polledAlive` is expected and must not skew the discrepancy signal.
|
|
3049
|
-
//
|
|
3050
|
-
// Both maps carry the owning agent's `providerId` per session so the report below can be split
|
|
3051
|
-
// per agent: the two paths' reliability differs by agent (one may never reach the live path at
|
|
3052
|
-
// all), and one pooled number would average that away into noise.
|
|
3053
|
-
const trackedLiveBefore = new Map(
|
|
3054
|
-
this._sessions.filter(s => s.status !== 'ended').map(s => [s.id, s.providerId] as const),
|
|
3055
|
-
);
|
|
3056
|
-
const polledAlive = new Map<string, string>();
|
|
3057
|
-
// Every id the poll returned (live *and* ended). Drives ended-session reconciliation:
|
|
3058
|
-
// a tracked ended session absent from this set was archived/purged by the CLI.
|
|
3059
|
-
const polledIds = new Set<string>();
|
|
3060
|
-
// Accumulates ids already tracked or added during this poll, so the membership check is O(1)
|
|
3061
|
-
// and duplicate ids within a single response are only added once.
|
|
3062
|
-
const known = new Set(this._sessions.map(s => s.id));
|
|
3063
|
-
|
|
3064
|
-
// The CLI's durable store never revives a resumed session — once it moves a record to its
|
|
3065
|
-
// `ended` store, nothing moves it back, so a record reporting `ended` isn't proof the session
|
|
3066
|
-
// is over. The listing also repairs the two active-record cases that have no fresher hook state:
|
|
3067
|
-
// first discovery and synthesized, unresolvable permission asks. Fetched once per poll (not per
|
|
3068
|
-
// row); the host caches the underlying `claude agents --json` invocation behind a short TTL, so
|
|
3069
|
-
// this doesn't spawn a process per session.
|
|
3070
|
-
const liveSessions = await this.callbacks.getLiveAgentSessions?.();
|
|
3071
|
-
|
|
3072
|
-
let changed = false;
|
|
3073
|
-
const discoveredByAgent = new Map<string, number>();
|
|
3074
|
-
const noteDiscovered = (providerId: string): void => {
|
|
3075
|
-
discoveredByAgent.set(providerId, (discoveredByAgent.get(providerId) ?? 0) + 1);
|
|
3076
|
-
};
|
|
3077
|
-
/** Ids kept alive against a stale `ended` record. Logged below — this path has no other
|
|
3078
|
-
* observability, and "the agent isn't showing up" is otherwise indistinguishable from
|
|
3079
|
-
* "the listing never ran". */
|
|
3080
|
-
const keptAlive: string[] = [];
|
|
3081
|
-
|
|
3082
|
-
for (const data of sessions) {
|
|
3083
|
-
if (!data.sessionId) continue;
|
|
3084
|
-
|
|
3085
|
-
// A record from a client we hold no descriptor for (the CLI supports more than this table
|
|
3086
|
-
// does). Must stay ahead of `polledIds`/`polledAlive`: an id in either would skew ended-row
|
|
3087
|
-
// reconciliation and the `onSyncDiscrepancy` drift signal below. Absent = `claude-code`,
|
|
3088
|
-
// matching the live path — older CLIs don't stamp it.
|
|
3089
|
-
const capabilities = getAgentCapabilities(data.providerId ?? 'claude-code');
|
|
3090
|
-
if (capabilities == null) continue;
|
|
3091
|
-
|
|
3092
|
-
// The record's `event` is the agent's native name. Canonicalize it so `deriveStatusFromEvent`
|
|
3093
|
-
// sees a vocabulary it knows — an unmapped native name falls through to `data.event`, which
|
|
3094
|
-
// that function's `default` already resolves to `idle`. Deliberately NOT a skip: unlike the
|
|
3095
|
-
// live path, nothing here switches exhaustively on the name, and dropping the record would
|
|
3096
|
-
// hide a real session whose last event simply has no canonical analogue.
|
|
3097
|
-
//
|
|
3098
|
-
// `data.hookInput` must be passed: an agent whose event name alone can't determine the
|
|
3099
|
-
// canonical event (OpenCode's `session.status`) is resolved from the payload, so withholding
|
|
3100
|
-
// it makes those names permanently unresolvable on this path — and the poll is the only way
|
|
3101
|
-
// a pre-existing session enters after a window reload.
|
|
3102
|
-
const canonicalEvent = resolveCanonicalHookEvent(capabilities, data.event, data.hookInput) ?? data.event;
|
|
3103
|
-
// Same translation for the record's tool name — `classifyPermissionKind` and the display
|
|
3104
|
-
// detail expect canonical names.
|
|
3105
|
-
const polledToolName =
|
|
3106
|
-
data.toolName != null ? resolveCanonicalToolName(capabilities, data.toolName) : undefined;
|
|
3107
|
-
|
|
3108
|
-
const effectiveStatus = data.status ?? 'active';
|
|
3109
|
-
|
|
3110
|
-
// Archived records are excluded by the `active,ended` query; guard anyway.
|
|
3111
|
-
if (effectiveStatus === 'archived') continue;
|
|
3112
|
-
|
|
3113
|
-
if (effectiveStatus === 'ended') {
|
|
3114
|
-
polledIds.add(data.sessionId);
|
|
3115
|
-
const endedDate = new Date(data.endedAt ?? data.updatedAt);
|
|
3116
|
-
const override = liveOverrideFor(liveSessions, data.sessionId);
|
|
3117
|
-
const liveCwd = override?.live.cwd;
|
|
3118
|
-
const currentCwd = liveCwd ?? data.cwd;
|
|
3119
|
-
const liveCwdMoved = liveCwd != null && liveCwd !== data.cwd;
|
|
3120
|
-
const recordWorktreeRoot = worktreeRootFromData(data);
|
|
3121
|
-
const currentWorktreeRoot = liveCwdMoved ? undefined : recordWorktreeRoot;
|
|
3122
|
-
|
|
3123
|
-
const existingIndex = this._sessions.findIndex(s => s.id === data.sessionId);
|
|
3124
|
-
if (existingIndex >= 0) {
|
|
3125
|
-
const bookkeeping = this.getBookkeeping(data.sessionId);
|
|
3126
|
-
if (liveCwdMoved) {
|
|
3127
|
-
bookkeeping.supersededRecordCwd = data.cwd;
|
|
3128
|
-
} else if (
|
|
3129
|
-
bookkeeping.supersededRecordCwd != null &&
|
|
3130
|
-
bookkeeping.supersededRecordCwd !== data.cwd
|
|
3131
|
-
) {
|
|
3132
|
-
// The CLI rewrote the record for the resumed run — it describes the session again.
|
|
3133
|
-
bookkeeping.supersededRecordCwd = undefined;
|
|
3134
|
-
}
|
|
3135
|
-
|
|
3136
|
-
// The record still names the cwd a resume superseded, and this poll no longer lists the
|
|
3137
|
-
// session live: the resumed process exited (or died) before the CLI rewrote the record,
|
|
3138
|
-
// so neither its cwd nor its `endedAt` describe where or when the session last ran.
|
|
3139
|
-
const recordLocationStale = override == null && bookkeeping.supersededRecordCwd === data.cwd;
|
|
3140
|
-
|
|
3141
|
-
if (override != null) {
|
|
3142
|
-
const { live: liveSession, status: liveStatus } = override;
|
|
3143
|
-
// `claude agents --json` still lists this exact session id as live — the store's
|
|
3144
|
-
// `ended` record is stale. Keep the row alive instead of ending it, and prefer the
|
|
3145
|
-
// listing's pid — the record's own may already be dead, or reused. Background sessions
|
|
3146
|
-
// carry no pid at all, so fall back to whatever pid the row already has.
|
|
3147
|
-
//
|
|
3148
|
-
// The listing establishes LIVENESS, not status. It's a poll SAMPLE of a value that flips
|
|
3149
|
-
// many times per turn, so adopting it every poll stamps a row back to `idle` moments after
|
|
3150
|
-
// a hook event moved it to `tool_use`/`thinking` — the row then reads idle almost always
|
|
3151
|
-
// and working almost never. Hook events are strictly fresher, so the sample is only taken
|
|
3152
|
-
// when the row has nothing better: it was ended, and this poll is what revives it.
|
|
3153
|
-
const trackedSession = this._sessions[existingIndex];
|
|
3154
|
-
// The listing's pid, or none. Never fall back to the pid we already hold: the listing
|
|
3155
|
-
// omits one only for background sessions, which have no process of their own, and the
|
|
3156
|
-
// pid on the row is the dead one whose exit produced the `ended` record. Carrying it
|
|
3157
|
-
// onto a live row makes `pruneDeadSessions` reap the row later in this same poll.
|
|
3158
|
-
const nextPid = liveSession.pid;
|
|
3159
|
-
const revived = trackedSession.status === 'ended';
|
|
3160
|
-
const nextStatus = revived ? liveStatus : trackedSession.status;
|
|
3161
|
-
if (
|
|
3162
|
-
trackedSession.status !== nextStatus ||
|
|
3163
|
-
trackedSession.pid !== nextPid ||
|
|
3164
|
-
trackedSession.endReason != null ||
|
|
3165
|
-
trackedSession.endedAt != null
|
|
3166
|
-
) {
|
|
3167
|
-
this._sessions[existingIndex] = {
|
|
3168
|
-
...trackedSession,
|
|
3169
|
-
status: nextStatus,
|
|
3170
|
-
phase: getPhaseForStatus(nextStatus),
|
|
3171
|
-
// Only re-stamp the phase/activity clocks on the revive itself; otherwise a poll
|
|
3172
|
-
// keeps resetting them under a row whose status it didn't change.
|
|
3173
|
-
phaseSince: revived ? new Date(pollStartedAt) : trackedSession.phaseSince,
|
|
3174
|
-
lastActivity: revived ? new Date(pollStartedAt) : trackedSession.lastActivity,
|
|
3175
|
-
pid: nextPid,
|
|
3176
|
-
statusDetail: revived ? undefined : trackedSession.statusDetail,
|
|
3177
|
-
endReason: undefined,
|
|
3178
|
-
endedAt: undefined,
|
|
3179
|
-
};
|
|
3180
|
-
changed = true;
|
|
3181
|
-
}
|
|
3182
|
-
keptAlive.push(`${data.sessionId.substring(0, 8)}:${nextStatus}`);
|
|
3183
|
-
polledAlive.set(data.sessionId, capabilities.providerId);
|
|
3184
|
-
} else if (this._sessions[existingIndex].status !== 'ended') {
|
|
3185
|
-
// Already tracked, and Claude no longer lists it as live. If the live path missed the
|
|
3186
|
-
// SessionEnd (e.g. `/clear` kept the PID alive under a new id), transition it to
|
|
3187
|
-
// ended now — otherwise a live-status row would linger as a zombie, since
|
|
3188
|
-
// pruneDeadSessions can't reap a live PID.
|
|
3189
|
-
// The record's `endedAt` can predate activity this window already observed — a
|
|
3190
|
-
// superseded record after a cwd move, or a same-cwd resume the CLI hasn't rewritten
|
|
3191
|
-
// the record for yet — so never move the row's clock backward.
|
|
3192
|
-
const { lastActivity } = this._sessions[existingIndex];
|
|
3193
|
-
this.endSession(
|
|
3194
|
-
existingIndex,
|
|
3195
|
-
lastActivity.getTime() > endedDate.getTime() ? lastActivity : endedDate,
|
|
3196
|
-
);
|
|
3197
|
-
changed = true;
|
|
3198
|
-
}
|
|
3199
|
-
|
|
3200
|
-
// Re-seat the row on its current location. The durable record is authoritative for a
|
|
3201
|
-
// terminal session, but an independent Claude listing can prove that record stale and
|
|
3202
|
-
// supply the resumed process's current cwd. Keep the prior worktree attribution until
|
|
3203
|
-
// the live cwd's git probe confirms a replacement — a resumed session can legitimately
|
|
3204
|
-
// sit in scratch space — but never let a later poll move it back to the ended record's
|
|
3205
|
-
// directory. A record already known superseded stays ignored until the CLI rewrites it,
|
|
3206
|
-
// so the row survives the resumed process exiting before that write lands. Prompts get a
|
|
3207
|
-
// hole-fill from the record below when nothing has been set yet; titles keep their own
|
|
3208
|
-
// on-demand transcript resolution, which holds richer values than the record does.
|
|
3209
|
-
const existing = this._sessions[existingIndex];
|
|
3210
|
-
const cwdChanged = !recordLocationStale && currentCwd !== existing.cwd;
|
|
3211
|
-
// Pre-`cwdTimeline` records carry no worktree data. If the cwd moved, whatever we hold
|
|
3212
|
-
// is no longer trustworthy — it may point into a different repo entirely — so drop it
|
|
3213
|
-
// rather than leave the card filed under the wrong branch, and re-resolve below. A cwd
|
|
3214
|
-
// supplied by the live listing is different: preserve the old attribution as a fallback
|
|
3215
|
-
// until the current cwd's probe succeeds.
|
|
3216
|
-
const nextWorktreePath = recordLocationStale
|
|
3217
|
-
? existing.worktreePath
|
|
3218
|
-
: currentWorktreeRoot != null
|
|
3219
|
-
? normalizePath(currentWorktreeRoot)
|
|
3220
|
-
: cwdChanged && !liveCwdMoved
|
|
3221
|
-
? undefined
|
|
3222
|
-
: existing.worktreePath;
|
|
3223
|
-
const worktreeMoved = nextWorktreePath !== existing.worktreePath;
|
|
3224
|
-
if (cwdChanged || worktreeMoved) {
|
|
3225
|
-
const workspacePath = this.resolveWorkspacePath(currentCwd);
|
|
3226
|
-
this._sessions[existingIndex] = {
|
|
3227
|
-
...existing,
|
|
3228
|
-
cwd: currentCwd,
|
|
3229
|
-
initialCwd: existing.initialCwd ?? data.initialCwd ?? data.cwd,
|
|
3230
|
-
worktreePath: nextWorktreePath,
|
|
3231
|
-
workspacePath: workspacePath,
|
|
3232
|
-
isInWorkspace: workspacePath != null,
|
|
3233
|
-
// A worktree move can cross repos, and the spread would otherwise carry the old
|
|
3234
|
-
// repo identity forward — which consumers PREFER over the freshly-resolved
|
|
3235
|
-
// worktree metadata, pinning the card to the wrong repo. Drop it and let the
|
|
3236
|
-
// host backfill from the new worktree.
|
|
3237
|
-
commonPath: worktreeMoved ? undefined : existing.commonPath,
|
|
3238
|
-
visitedWorktreePaths: unionVisited(
|
|
3239
|
-
existing.visitedWorktreePaths,
|
|
3240
|
-
visitedFromTimeline(data.cwdTimeline),
|
|
3241
|
-
data.worktrees,
|
|
3242
|
-
nextWorktreePath,
|
|
3243
|
-
),
|
|
3244
|
-
};
|
|
3245
|
-
changed = true;
|
|
3246
|
-
|
|
3247
|
-
// Nothing in the record to re-seat it with (older CLI), or the record names a
|
|
3248
|
-
// NEW worktree the drop above just cleared `commonPath` for — either way, resolve
|
|
3249
|
-
// the cwd once rather than leaving the row location-less until some later event
|
|
3250
|
-
// happens to change `cwd`/`worktreePath` again (a same-worktree poll afterward
|
|
3251
|
-
// skips this branch entirely, since `worktreeMoved` goes false).
|
|
3252
|
-
if (liveCwdMoved || nextWorktreePath == null || worktreeMoved) {
|
|
3253
|
-
this.getBookkeeping(data.sessionId).cliSeatedWorktree = currentWorktreeRoot != null;
|
|
3254
|
-
void this.resolveGitInfo(data.sessionId, currentCwd);
|
|
3255
|
-
}
|
|
3256
|
-
}
|
|
3257
|
-
|
|
3258
|
-
// Prompts backfill-only: the record's prompt/firstPrompt sanitized the same way the
|
|
3259
|
-
// discovery path does. Never overwrites a value the hook path or a transcript resolve
|
|
3260
|
-
// already set.
|
|
3261
|
-
const currentSession = this._sessions[existingIndex];
|
|
3262
|
-
const nextLastPrompt = currentSession.lastPrompt ?? prepareStoredPrompt(data.prompt ?? undefined);
|
|
3263
|
-
const nextFirstPrompt =
|
|
3264
|
-
currentSession.firstPrompt ?? prepareStoredPrompt(data.firstPrompt ?? undefined);
|
|
3265
|
-
if (
|
|
3266
|
-
nextLastPrompt !== currentSession.lastPrompt ||
|
|
3267
|
-
nextFirstPrompt !== currentSession.firstPrompt
|
|
3268
|
-
) {
|
|
3269
|
-
this._sessions[existingIndex] = {
|
|
3270
|
-
...currentSession,
|
|
3271
|
-
lastPrompt: nextLastPrompt,
|
|
3272
|
-
firstPrompt: nextFirstPrompt,
|
|
3273
|
-
};
|
|
3274
|
-
changed = true;
|
|
3275
|
-
}
|
|
3276
|
-
|
|
3277
|
-
const nextBookkeeping = this.getBookkeeping(data.sessionId);
|
|
3278
|
-
nextBookkeeping.polledAtLeastOnce = true;
|
|
3279
|
-
// `endSession` above drops the bookkeeping entry, but the latch has to outlive that
|
|
3280
|
-
// transition — otherwise the next poll finds none and re-seats the row onto the very
|
|
3281
|
-
// record this one already knew was superseded.
|
|
3282
|
-
nextBookkeeping.supersededRecordCwd = bookkeeping.supersededRecordCwd;
|
|
3283
|
-
continue;
|
|
3284
|
-
}
|
|
3285
|
-
|
|
3286
|
-
known.add(data.sessionId);
|
|
3287
|
-
const workspacePath = this.resolveWorkspacePath(currentCwd);
|
|
3288
|
-
|
|
3289
|
-
if (override != null) {
|
|
3290
|
-
const { live: liveSession, status: liveStatus } = override;
|
|
3291
|
-
// This window is learning about the session for the first time via its `ended`
|
|
3292
|
-
// record, but `claude agents --json` still lists it as live — same override as the
|
|
3293
|
-
// tracked branch above, just pushing a fresh row instead of updating one in place. The
|
|
3294
|
-
// listing's cwd is current; the ended record's worktree remains only as a fallback until
|
|
3295
|
-
// the live cwd's git probe resolves it.
|
|
3296
|
-
this._sessions.push({
|
|
3297
|
-
id: data.sessionId,
|
|
3298
|
-
providerId: capabilities.providerId,
|
|
3299
|
-
providerName: capabilities.displayName,
|
|
3300
|
-
name: data.sessionName || undefined,
|
|
3301
|
-
status: liveStatus,
|
|
3302
|
-
phase: getPhaseForStatus(liveStatus),
|
|
3303
|
-
phaseSince: new Date(pollStartedAt),
|
|
3304
|
-
// Listing's pid or none — never the record's, which is the dead process whose exit
|
|
3305
|
-
// wrote the `ended` record (see the tracked branch above).
|
|
3306
|
-
pid: liveSession.pid,
|
|
3307
|
-
lastActivity: new Date(pollStartedAt),
|
|
3308
|
-
isSubagent: false,
|
|
3309
|
-
workspacePath: workspacePath,
|
|
3310
|
-
worktreePath: recordWorktreeRoot != null ? normalizePath(recordWorktreeRoot) : undefined,
|
|
3311
|
-
visitedWorktreePaths: unionVisited(
|
|
3312
|
-
undefined,
|
|
3313
|
-
visitedFromTimeline(data.cwdTimeline),
|
|
3314
|
-
data.worktrees,
|
|
3315
|
-
recordWorktreeRoot != null ? normalizePath(recordWorktreeRoot) : undefined,
|
|
3316
|
-
),
|
|
3317
|
-
cwd: currentCwd,
|
|
3318
|
-
initialCwd: data.initialCwd ?? data.cwd,
|
|
3319
|
-
planFile: data.planFile ?? undefined,
|
|
3320
|
-
isInWorkspace: workspacePath != null,
|
|
3321
|
-
lastPrompt: prepareStoredPrompt(data.prompt ?? undefined),
|
|
3322
|
-
firstPrompt: prepareStoredPrompt(data.firstPrompt ?? undefined),
|
|
3323
|
-
model: data.model ?? undefined,
|
|
3324
|
-
});
|
|
3325
|
-
this.getBookkeeping(data.sessionId).polledAtLeastOnce = true;
|
|
3326
|
-
this.getBookkeeping(data.sessionId).cliSeatedWorktree = currentWorktreeRoot != null;
|
|
3327
|
-
if (liveCwdMoved) {
|
|
3328
|
-
// Latch the cwd this listing superseded, so a later poll that no longer finds the
|
|
3329
|
-
// session live doesn't re-seat the row back onto the not-yet-rewritten record.
|
|
3330
|
-
this.getBookkeeping(data.sessionId).supersededRecordCwd = data.cwd;
|
|
3331
|
-
}
|
|
3332
|
-
changed = true;
|
|
3333
|
-
noteDiscovered(capabilities.providerId);
|
|
3334
|
-
keptAlive.push(`${data.sessionId.substring(0, 8)}:${liveStatus}`);
|
|
3335
|
-
polledAlive.set(data.sessionId, capabilities.providerId);
|
|
3336
|
-
if (liveCwdMoved || recordWorktreeRoot == null) {
|
|
3337
|
-
void this.resolveGitInfo(data.sessionId, currentCwd);
|
|
3338
|
-
}
|
|
3339
|
-
void this.resolveTranscriptTitles(data.sessionId, currentCwd);
|
|
3340
|
-
continue;
|
|
3341
|
-
}
|
|
3342
|
-
|
|
3343
|
-
this._sessions.push({
|
|
3344
|
-
id: data.sessionId,
|
|
3345
|
-
providerId: capabilities.providerId,
|
|
3346
|
-
providerName: capabilities.displayName,
|
|
3347
|
-
name: data.sessionName || undefined,
|
|
3348
|
-
status: 'ended',
|
|
3349
|
-
phase: 'ended',
|
|
3350
|
-
phaseSince: endedDate,
|
|
3351
|
-
pid: data.pid,
|
|
3352
|
-
lastActivity: endedDate,
|
|
3353
|
-
isSubagent: false,
|
|
3354
|
-
workspacePath: workspacePath,
|
|
3355
|
-
// Read straight from the CLI's durable record so ended sessions attach to branch
|
|
3356
|
-
// cards / WIP rows / the resume picker at any age, no git probe.
|
|
3357
|
-
worktreePath: recordWorktreeRoot != null ? normalizePath(recordWorktreeRoot) : undefined,
|
|
3358
|
-
visitedWorktreePaths: unionVisited(
|
|
3359
|
-
undefined,
|
|
3360
|
-
visitedFromTimeline(data.cwdTimeline),
|
|
3361
|
-
data.worktrees,
|
|
3362
|
-
recordWorktreeRoot != null ? normalizePath(recordWorktreeRoot) : undefined,
|
|
3363
|
-
),
|
|
3364
|
-
cwd: data.cwd,
|
|
3365
|
-
initialCwd: data.initialCwd ?? data.cwd,
|
|
3366
|
-
planFile: data.planFile ?? undefined,
|
|
3367
|
-
isInWorkspace: workspacePath != null,
|
|
3368
|
-
lastPrompt: prepareStoredPrompt(data.prompt ?? undefined),
|
|
3369
|
-
firstPrompt: prepareStoredPrompt(data.firstPrompt ?? undefined),
|
|
3370
|
-
model: data.model ?? undefined,
|
|
3371
|
-
endReason: data.endReason ?? undefined,
|
|
3372
|
-
endedAt: data.endedAt != null ? new Date(data.endedAt).getTime() : undefined,
|
|
3373
|
-
// Subagents are meaningless once terminal.
|
|
3374
|
-
subagents: undefined,
|
|
3375
|
-
});
|
|
3376
|
-
this.getBookkeeping(data.sessionId).polledAtLeastOnce = true;
|
|
3377
|
-
changed = true;
|
|
3378
|
-
this.noteTerminalTransition();
|
|
3379
|
-
// Titles/prompts still resolve lazily via resolveEndedSessionDetails (a 30-day
|
|
3380
|
-
// cold-start must not fan out transcript reads). worktreePath comes from the record
|
|
3381
|
-
// above; only fall back to an eager git probe when the record predates that CLI field,
|
|
3382
|
-
// and only for the recent (WIP-window) tail so the fallback stays bounded.
|
|
3383
|
-
if (
|
|
3384
|
-
recordWorktreeRoot == null &&
|
|
3385
|
-
data.cwd &&
|
|
3386
|
-
Date.now() - endedDate.getTime() < recentEndedGitResolveThresholdMs
|
|
3387
|
-
) {
|
|
3388
|
-
void this.resolveGitInfo(data.sessionId, data.cwd);
|
|
3389
|
-
}
|
|
3390
|
-
continue;
|
|
3391
|
-
}
|
|
3392
|
-
|
|
3393
|
-
// Active / legacy (absent-status) path — only genuinely-live processes are discoverable.
|
|
3394
|
-
if (!data.pid || !isProcessAlive(data.pid)) continue;
|
|
3395
|
-
|
|
3396
|
-
polledAlive.set(data.sessionId, capabilities.providerId);
|
|
3397
|
-
polledIds.add(data.sessionId);
|
|
3398
|
-
|
|
3399
|
-
if (known.has(data.sessionId)) {
|
|
3400
|
-
// Already tracked.
|
|
3401
|
-
const existingIndex = this._sessions.findIndex(s => s.id === data.sessionId);
|
|
3402
|
-
if (existingIndex >= 0) {
|
|
3403
|
-
const existing = this._sessions[existingIndex];
|
|
3404
|
-
// The status the CLI's last event implies — a resumed session may already be mid-work
|
|
3405
|
-
// (`PreToolUse` → tool_use, `UserPromptSubmit` → thinking), and hardcoding `idle` would
|
|
3406
|
-
// mislabel active work and risk a concurrent-write resume.
|
|
3407
|
-
const polledStatus = deriveStatusFromEvent(canonicalEvent);
|
|
3408
|
-
const isSynthesizedAsk =
|
|
3409
|
-
existing.status === 'permission_requested' && existing.pendingPermission?.resolvable === false;
|
|
3410
|
-
const liveEntry = isSynthesizedAsk ? liveSessions?.get(data.sessionId) : undefined;
|
|
3411
|
-
const liveStatus = liveEntry != null ? statusFromLiveSession(liveEntry) : undefined;
|
|
3412
|
-
if (isSynthesizedAsk && liveEntry != null && liveStatus == null) {
|
|
3413
|
-
// A terminal listing entry proves this active record is stale, even if its last event
|
|
3414
|
-
// advanced after the ask. It carries no authoritative terminal timestamp/details, so
|
|
3415
|
-
// remove the false live row and let the durable ended record restore it later. Run the
|
|
3416
|
-
// standard terminal teardown first so no timers or caches survive the row.
|
|
3417
|
-
this.endSession(existingIndex, new Date(pollStartedAt));
|
|
3418
|
-
this._sessions.splice(existingIndex, 1);
|
|
3419
|
-
polledAlive.delete(data.sessionId);
|
|
3420
|
-
changed = true;
|
|
3421
|
-
continue;
|
|
3422
|
-
}
|
|
3423
|
-
|
|
3424
|
-
if (existing.status === 'ended') {
|
|
3425
|
-
// We hold it as `ended` but the CLI now reports it as a live process (a
|
|
3426
|
-
// resume the live SessionStart path missed) — revive it, the symmetric backstop
|
|
3427
|
-
// to the ended-branch's live→ended transition above. Leave genuinely-live
|
|
3428
|
-
// rows to the IPC path.
|
|
3429
|
-
const revivedAt = new Date(data.updatedAt);
|
|
3430
|
-
// Only when the record is at least as new as the end. `SessionEnd` fires
|
|
3431
|
-
// while the process is still winding down, so a poll whose CLI call started
|
|
3432
|
-
// BEFORE it returns a snapshot that still says "active" with a live pid — reviving
|
|
3433
|
-
// off that stale record would resurrect the row, and `pruneDeadSessions` would
|
|
3434
|
-
// then delete it outright once the process exits, losing the ended row.
|
|
3435
|
-
if (revivedAt.getTime() < existing.phaseSince.getTime()) continue;
|
|
3436
|
-
|
|
3437
|
-
// The poll has no blocking hook entry for this ask (it's discovered off the CLI's
|
|
3438
|
-
// durable record, not this window's IPC path) — synthesize an unresolvable one so
|
|
3439
|
-
// the row shows what it's waiting on instead of an empty "Needs input" card.
|
|
3440
|
-
const revivedPendingPermission =
|
|
3441
|
-
polledStatus === 'permission_requested'
|
|
3442
|
-
? synthesizeUnresolvableAsk(polledToolName, data.planFile ?? undefined)
|
|
3443
|
-
: undefined;
|
|
3444
|
-
// Seat worktreePath from the record, mirroring the ended-record re-seat above — the
|
|
3445
|
-
// existing row is otherwise stuck with whatever (or nothing) it had while ended.
|
|
3446
|
-
const revivedWorktreeRoot = worktreeRootFromData(data);
|
|
3447
|
-
// No record worktree data (older CLI): KEEP the ended row's attribution. A
|
|
3448
|
-
// resume whose cwd is scratch space (e.g. /tmp) still belongs to its worktree —
|
|
3449
|
-
// re-rooting happens only when git affirmatively resolves a new one (the probe
|
|
3450
|
-
// below, on success). Attribution persists until something better is known.
|
|
3451
|
-
const revivedWorktreePath =
|
|
3452
|
-
revivedWorktreeRoot != null ? normalizePath(revivedWorktreeRoot) : existing.worktreePath;
|
|
3453
|
-
// Membership follows the attribution, mirroring `ensureSession`: cwd first, the
|
|
3454
|
-
// kept worktree as the fallback for a scratch-dir resume.
|
|
3455
|
-
const revivedWorkspacePath =
|
|
3456
|
-
this.resolveWorkspacePath(data.cwd) ?? this.resolveWorkspacePath(revivedWorktreePath);
|
|
3457
|
-
this._sessions[existingIndex] = {
|
|
3458
|
-
...existing,
|
|
3459
|
-
status: polledStatus,
|
|
3460
|
-
phase: getPhaseForStatus(polledStatus),
|
|
3461
|
-
phaseSince: revivedAt,
|
|
3462
|
-
lastActivity: revivedAt,
|
|
3463
|
-
// Fresh pid from the CLI, and re-classify ownership on next open rather than
|
|
3464
|
-
// trusting the terminal record's `isPeerOwned`.
|
|
3465
|
-
pid: data.pid,
|
|
3466
|
-
isPeerOwned: undefined,
|
|
3467
|
-
pendingPermission: revivedPendingPermission,
|
|
3468
|
-
// The resume can be running somewhere else entirely, so take the record's
|
|
3469
|
-
// location rather than leaving the row filed under where it ended.
|
|
3470
|
-
cwd: data.cwd ?? existing.cwd,
|
|
3471
|
-
workspacePath:
|
|
3472
|
-
revivedWorkspacePath ?? (data.cwd == null ? existing.workspacePath : undefined),
|
|
3473
|
-
isInWorkspace: data.cwd == null ? existing.isInWorkspace : revivedWorkspacePath != null,
|
|
3474
|
-
worktreePath: revivedWorktreePath,
|
|
3475
|
-
visitedWorktreePaths: unionVisited(
|
|
3476
|
-
existing.visitedWorktreePaths,
|
|
3477
|
-
visitedFromTimeline(data.cwdTimeline),
|
|
3478
|
-
data.worktrees,
|
|
3479
|
-
revivedWorktreePath,
|
|
3480
|
-
),
|
|
3481
|
-
// A worktree move can cross repos — drop the stale repo identity and let the
|
|
3482
|
-
// probe below refill it.
|
|
3483
|
-
commonPath: revivedWorktreePath !== existing.worktreePath ? undefined : existing.commonPath,
|
|
3484
|
-
// No longer terminal: leaving these set makes the row read as ended to
|
|
3485
|
-
// consumers that key off them.
|
|
3486
|
-
endReason: undefined,
|
|
3487
|
-
endedAt: undefined,
|
|
3488
|
-
};
|
|
3489
|
-
this.resetBookkeeping(data.sessionId);
|
|
3490
|
-
// The record's worktree is the CLI's own resolution; without one the probe owns
|
|
3491
|
-
// attribution again.
|
|
3492
|
-
this.getBookkeeping(data.sessionId).cliSeatedWorktree = revivedWorktreeRoot != null;
|
|
3493
|
-
// A revive whose worktree moved but whose cwd happens to equal the prior one still
|
|
3494
|
-
// dropped `commonPath` above and needs the probe to refill it.
|
|
3495
|
-
if (
|
|
3496
|
-
data.cwd != null &&
|
|
3497
|
-
(data.cwd !== existing.cwd || revivedWorktreePath !== existing.worktreePath)
|
|
3498
|
-
) {
|
|
3499
|
-
void this.resolveGitInfo(data.sessionId, data.cwd);
|
|
3500
|
-
}
|
|
3501
|
-
changed = true;
|
|
3502
|
-
} else if (
|
|
3503
|
-
existing.pendingPermission?.resolvable === false &&
|
|
3504
|
-
polledStatus !== 'permission_requested' &&
|
|
3505
|
-
new Date(data.updatedAt).getTime() > existing.lastActivity.getTime()
|
|
3506
|
-
) {
|
|
3507
|
-
// The record has moved past the ask this row still shows. Only a synthesized
|
|
3508
|
-
// (`resolvable: false`) ask is repairable this way — a routable one is bookkeeping-
|
|
3509
|
-
// owned and cleared by its own resolution paths, which the record can't see.
|
|
3510
|
-
this._sessions[existingIndex] = {
|
|
3511
|
-
...existing,
|
|
3512
|
-
status: polledStatus,
|
|
3513
|
-
phase: getPhaseForStatus(polledStatus),
|
|
3514
|
-
phaseSince: new Date(data.updatedAt),
|
|
3515
|
-
// The detail described the ask's tool; carry the record's current tool for
|
|
3516
|
-
// tool_use, else clear — keeping it would show the answered ask's tool as
|
|
3517
|
-
// the active one.
|
|
3518
|
-
statusDetail: polledStatus === 'tool_use' ? polledToolName : undefined,
|
|
3519
|
-
pendingPermission: undefined,
|
|
3520
|
-
lastActivity: new Date(data.updatedAt),
|
|
3521
|
-
};
|
|
3522
|
-
changed = true;
|
|
3523
|
-
} else if (isSynthesizedAsk) {
|
|
3524
|
-
// The record never moved past the ask it froze on — the ask was resolved while no
|
|
3525
|
-
// window was listening, so nothing ever advanced it — but Claude's own listing
|
|
3526
|
-
// says the session has moved on. Correct the row so it heals on the next poll
|
|
3527
|
-
// instead of waiting for a reload or fresh hook events. Only synthesized asks
|
|
3528
|
-
// are repairable here: a routable one is bookkeeping-owned and cleared by its
|
|
3529
|
-
// own resolution paths, which the listing must never preempt.
|
|
3530
|
-
if (liveStatus != null && liveStatus !== 'permission_requested') {
|
|
3531
|
-
this._sessions[existingIndex] = {
|
|
3532
|
-
...existing,
|
|
3533
|
-
status: liveStatus,
|
|
3534
|
-
phase: getPhaseForStatus(liveStatus),
|
|
3535
|
-
phaseSince: new Date(pollStartedAt),
|
|
3536
|
-
pendingPermission: undefined,
|
|
3537
|
-
statusDetail: undefined,
|
|
3538
|
-
};
|
|
3539
|
-
changed = true;
|
|
3540
|
-
}
|
|
3541
|
-
} else if (existing.transcriptTitles?.ai == null && existing.transcriptTitles?.custom == null) {
|
|
3542
|
-
// A live session we discovered via the poll (another worktree/window owns its
|
|
3543
|
-
// hook flow) gets none of the local IPC re-resolves (SessionStart, idle). So one
|
|
3544
|
-
// discovered before Claude wrote its `ai-title` would show the repo slug forever.
|
|
3545
|
-
// Re-check the transcript each poll until a real title lands — cheap: the reader
|
|
3546
|
-
// tail-reads from its mtime cache and no-ops when the file hasn't grown.
|
|
3547
|
-
void this.resolveTranscriptTitles(data.sessionId, data.cwd);
|
|
3548
|
-
}
|
|
3549
|
-
}
|
|
3550
|
-
continue;
|
|
3551
|
-
}
|
|
3552
|
-
|
|
3553
|
-
known.add(data.sessionId);
|
|
3554
|
-
|
|
3555
|
-
const workspacePath = this.resolveWorkspacePath(data.cwd);
|
|
3556
|
-
const isInWorkspace = workspacePath != null;
|
|
3557
|
-
const activityDate = new Date(data.updatedAt);
|
|
3558
|
-
let status = deriveStatusFromEvent(canonicalEvent);
|
|
3559
|
-
let phaseSince = activityDate;
|
|
3560
|
-
// The record's last event can be arbitrarily stale — an ask answered while no window
|
|
3561
|
-
// was listening (e.g. mid-reload), or a session stopped right there, never advances it,
|
|
3562
|
-
// so every window start would re-seed the frozen state. Claude's own listing is
|
|
3563
|
-
// authoritative for what the session is doing right now — same contract as the
|
|
3564
|
-
// ended-record revival above. Absence from the listing (including its fail-soft empty
|
|
3565
|
-
// map on spawn failure) keeps record-derived status: absence must never mean dead.
|
|
3566
|
-
const liveEntry = liveSessions?.get(data.sessionId);
|
|
3567
|
-
if (liveEntry != null) {
|
|
3568
|
-
const liveStatus = statusFromLiveSession(liveEntry);
|
|
3569
|
-
if (liveStatus == null) {
|
|
3570
|
-
polledAlive.delete(data.sessionId);
|
|
3571
|
-
continue;
|
|
3572
|
-
}
|
|
3573
|
-
|
|
3574
|
-
if (liveStatus !== status) {
|
|
3575
|
-
status = liveStatus;
|
|
3576
|
-
phaseSince = new Date(pollStartedAt);
|
|
3577
|
-
}
|
|
3578
|
-
}
|
|
3579
|
-
|
|
3580
|
-
const phase = getPhaseForStatus(status);
|
|
3581
|
-
// Same rationale as the revived-session branch above: no blocking hook entry exists for a
|
|
3582
|
-
// poll-discovered ask, so synthesize an unresolvable one for the card.
|
|
3583
|
-
const pendingPermission =
|
|
3584
|
-
status === 'permission_requested'
|
|
3585
|
-
? synthesizeUnresolvableAsk(polledToolName, data.planFile ?? undefined)
|
|
3586
|
-
: undefined;
|
|
3587
|
-
// Read straight from the CLI's durable record so the row attaches to the right branch
|
|
3588
|
-
// card/WIP row immediately, no git probe — mirrors the ended-session push below.
|
|
3589
|
-
const worktreeRoot = worktreeRootFromData(data);
|
|
3590
|
-
|
|
3591
|
-
const subagents: AgentSession[] | undefined = data.subagents?.map(sub => ({
|
|
3592
|
-
id: sub.agentId,
|
|
3593
|
-
providerId: capabilities.providerId,
|
|
3594
|
-
providerName: capabilities.displayName,
|
|
3595
|
-
name: sub.agentType ?? 'Subagent',
|
|
3596
|
-
status: 'thinking',
|
|
3597
|
-
phase: 'working' as const,
|
|
3598
|
-
phaseSince: activityDate,
|
|
3599
|
-
lastActivity: activityDate,
|
|
3600
|
-
isSubagent: true,
|
|
3601
|
-
parentId: data.sessionId,
|
|
3602
|
-
isInWorkspace: isInWorkspace,
|
|
3603
|
-
}));
|
|
3604
|
-
|
|
3605
|
-
this._sessions.push({
|
|
3606
|
-
id: data.sessionId,
|
|
3607
|
-
providerId: capabilities.providerId,
|
|
3608
|
-
providerName: capabilities.displayName,
|
|
3609
|
-
name: data.sessionName || undefined,
|
|
3610
|
-
status: status,
|
|
3611
|
-
phase: phase,
|
|
3612
|
-
phaseSince: phaseSince,
|
|
3613
|
-
pid: data.pid,
|
|
3614
|
-
lastActivity: activityDate,
|
|
3615
|
-
isSubagent: false,
|
|
3616
|
-
workspacePath: workspacePath,
|
|
3617
|
-
cwd: data.cwd,
|
|
3618
|
-
// Prefer the CLI's launch dir; fall back to the snapshot's (possibly drifted) cwd on
|
|
3619
|
-
// older CLIs that don't record it. Without this, a window that cold-starts after the
|
|
3620
|
-
// agent drifted would stamp the drifted cwd as the launch dir.
|
|
3621
|
-
initialCwd: data.initialCwd ?? data.cwd,
|
|
3622
|
-
planFile: data.planFile ?? undefined,
|
|
3623
|
-
isInWorkspace: isInWorkspace,
|
|
3624
|
-
lastPrompt: prepareStoredPrompt(data.prompt ?? undefined),
|
|
3625
|
-
firstPrompt: prepareStoredPrompt(data.firstPrompt ?? undefined),
|
|
3626
|
-
model: data.model ?? undefined,
|
|
3627
|
-
subagents: subagents,
|
|
3628
|
-
pendingPermission: pendingPermission,
|
|
3629
|
-
worktreePath: worktreeRoot != null ? normalizePath(worktreeRoot) : undefined,
|
|
3630
|
-
visitedWorktreePaths: unionVisited(
|
|
3631
|
-
undefined,
|
|
3632
|
-
visitedFromTimeline(data.cwdTimeline),
|
|
3633
|
-
data.worktrees,
|
|
3634
|
-
worktreeRoot != null ? normalizePath(worktreeRoot) : undefined,
|
|
3635
|
-
),
|
|
3636
|
-
});
|
|
3637
|
-
changed = true;
|
|
3638
|
-
noteDiscovered(capabilities.providerId);
|
|
3639
|
-
|
|
3640
|
-
// The record's worktree is the CLI's own resolution — mark it seated so the probe below
|
|
3641
|
-
// (still wanted for `commonPath`) can't clobber it for a nested worktree. Mirrors the
|
|
3642
|
-
// revival branch above.
|
|
3643
|
-
this.getBookkeeping(data.sessionId).cliSeatedWorktree = worktreeRoot != null;
|
|
3644
|
-
if (data.cwd) {
|
|
3645
|
-
void this.resolveGitInfo(data.sessionId, data.cwd);
|
|
3646
|
-
}
|
|
3647
|
-
void this.resolveTranscriptTitles(data.sessionId, data.cwd);
|
|
3648
|
-
}
|
|
3649
|
-
|
|
3650
|
-
// The poll is authoritative for ended-session removal (archived elsewhere or 30-day-purged).
|
|
3651
|
-
// Only remove ones a prior poll confirmed present — never a session the live SessionEnd path just
|
|
3652
|
-
// transitioned, which an already-in-flight poll can legitimately miss (guarded by polledAtLeastOnce).
|
|
3653
|
-
// On a legacy CLI (no `--status`) no poll can ever confirm an ended row, so the guard would
|
|
3654
|
-
// instead pin it forever — there, absence from the poll is the confirmation.
|
|
3655
|
-
const staleEnded = this._sessions.filter(
|
|
3656
|
-
s =>
|
|
3657
|
-
s.status === 'ended' &&
|
|
3658
|
-
!polledIds.has(s.id) &&
|
|
3659
|
-
(!this._statusFilterSupported ||
|
|
3660
|
-
this._sessionBookkeeping.get(s.id)?.polledAtLeastOnce === true ||
|
|
3661
|
-
// Or this poll's snapshot postdates the end, so its absence is authoritative.
|
|
3662
|
-
// `polledAtLeastOnce` alone can never be set for a session whose record disappeared
|
|
3663
|
-
// (archived from another window, or purged) before any poll observed it — and
|
|
3664
|
-
// `endSession` drops the bookkeeping — so without this the row is unremovable for
|
|
3665
|
-
// the window's lifetime. The timestamp answers the same question the flag was proxying:
|
|
3666
|
-
// could this poll have seen it? The grace keeps a just-ended row out of reach of
|
|
3667
|
-
// an absence that's really a not-yet-visible record.
|
|
3668
|
-
s.phaseSince.getTime() < pollStartedAt - endedRemovalGraceMs),
|
|
3669
|
-
);
|
|
3670
|
-
if (staleEnded.length > 0) {
|
|
3671
|
-
const staleIds = new Set(staleEnded.map(s => s.id));
|
|
3672
|
-
this._sessions = this._sessions.filter(s => !staleIds.has(s.id));
|
|
3673
|
-
for (const s of staleEnded) {
|
|
3674
|
-
this._sessionBookkeeping.delete(s.id);
|
|
3675
|
-
this._transcriptReader.forget(s.id);
|
|
3676
|
-
}
|
|
3677
|
-
changed = true;
|
|
3678
|
-
}
|
|
3679
|
-
|
|
3680
|
-
if (liveSessions != null) {
|
|
3681
|
-
Logger.debug(
|
|
3682
|
-
`GkAgentProvider.syncSessions: Claude lists ${liveSessions.size} session(s); kept ${keptAlive.length} alive against a stale ended record${
|
|
3683
|
-
keptAlive.length > 0 ? `: ${keptAlive.join(', ')}` : ''
|
|
3684
|
-
}`,
|
|
3685
|
-
);
|
|
3686
|
-
}
|
|
3687
|
-
|
|
3688
|
-
if (this.pruneDeadSessions()) {
|
|
3689
|
-
changed = true;
|
|
3690
|
-
}
|
|
3691
|
-
|
|
3692
|
-
if (changed) {
|
|
3693
|
-
this._onDidChangeSessions.fire();
|
|
3694
|
-
}
|
|
3695
|
-
|
|
3696
|
-
// Report any drift between the live-synced set and the poll. Only on the recurring (gated)
|
|
3697
|
-
// poll — the ungated bootstrap call runs before the live path has had a chance to receive
|
|
3698
|
-
// any events, so its discoveries are expected cold-start state, not drift. `missing` counts
|
|
3699
|
-
// live sessions we still track that the poll no longer reports alive (a teardown the live path
|
|
3700
|
-
// missed, e.g. an agent killed without firing `SessionEnd`).
|
|
3701
|
-
if (options?.gate) {
|
|
3702
|
-
type Drift = { discovered: number; missing: number; polled: number; tracked: number };
|
|
3703
|
-
const driftByAgent = new Map<string, Drift>();
|
|
3704
|
-
const driftFor = (providerId: string): Drift => {
|
|
3705
|
-
let drift = driftByAgent.get(providerId);
|
|
3706
|
-
if (drift == null) {
|
|
3707
|
-
drift = { discovered: 0, missing: 0, polled: 0, tracked: 0 };
|
|
3708
|
-
driftByAgent.set(providerId, drift);
|
|
3709
|
-
}
|
|
3710
|
-
return drift;
|
|
3711
|
-
};
|
|
3712
|
-
|
|
3713
|
-
for (const [providerId, count] of discoveredByAgent) {
|
|
3714
|
-
driftFor(providerId).discovered += count;
|
|
3715
|
-
}
|
|
3716
|
-
for (const [id, providerId] of trackedLiveBefore) {
|
|
3717
|
-
const drift = driftFor(providerId);
|
|
3718
|
-
drift.tracked++;
|
|
3719
|
-
if (!polledAlive.has(id)) {
|
|
3720
|
-
drift.missing++;
|
|
3721
|
-
}
|
|
3722
|
-
}
|
|
3723
|
-
for (const providerId of polledAlive.values()) {
|
|
3724
|
-
driftFor(providerId).polled++;
|
|
3725
|
-
}
|
|
3726
|
-
|
|
3727
|
-
// One report per agent, not one pooled report: the counts describe how well the live path
|
|
3728
|
-
// and the poll agree, which is a per-agent property. A single agent's numbers are unchanged
|
|
3729
|
-
// from the pre-multi-agent shape.
|
|
3730
|
-
for (const [providerId, drift] of driftByAgent) {
|
|
3731
|
-
if (drift.discovered === 0 && drift.missing === 0) continue;
|
|
3732
|
-
|
|
3733
|
-
this.callbacks.onSyncDiscrepancy?.({ provider: providerId, ...drift });
|
|
3734
|
-
}
|
|
3735
|
-
}
|
|
3736
|
-
}
|
|
3737
|
-
|
|
3738
|
-
protected async querySiblingWindowSessions(): Promise<void> {
|
|
3739
|
-
const discoveryDir = this.callbacks.ipc.agentDiscoveryDir;
|
|
3740
|
-
if (discoveryDir == null) return;
|
|
3741
|
-
|
|
3742
|
-
let files: string[];
|
|
3743
|
-
try {
|
|
3744
|
-
files = await readdir(discoveryDir);
|
|
3745
|
-
} catch {
|
|
3746
|
-
return;
|
|
3747
|
-
}
|
|
3748
|
-
|
|
3749
|
-
const ownPort = this.callbacks.ipc.port;
|
|
3750
|
-
|
|
3751
|
-
const peerBatches = await Promise.all(
|
|
3752
|
-
files
|
|
3753
|
-
.filter(f => f.startsWith('gitlens-ipc-server-') && f.endsWith('.json'))
|
|
3754
|
-
.map(async f => this.fetchPeerSessions(join(discoveryDir, f), ownPort)),
|
|
3755
|
-
);
|
|
3756
|
-
|
|
3757
|
-
let changed = false;
|
|
3758
|
-
// Peer sessions that arrived with `cwd` but no `commonPath` (peer was running pre-
|
|
3759
|
-
// commonPath code, OR the peer's own `resolveGitInfo` failed and we get to retry locally).
|
|
3760
|
-
// Resolved after the merge loop so the local Repository registry is consulted only once
|
|
3761
|
-
// per unique session, off the hot merge path. Keyed by id (value = cwd) so the same
|
|
3762
|
-
// session appearing in multiple peer responses (or re-queued within this loop) only
|
|
3763
|
-
// triggers a single backfill probe.
|
|
3764
|
-
const sessionsNeedingResolve = new Map<string, string>();
|
|
3765
|
-
|
|
3766
|
-
for (const peerSessions of peerBatches) {
|
|
3767
|
-
if (peerSessions == null) continue;
|
|
3768
|
-
|
|
3769
|
-
for (const peerSession of peerSessions) {
|
|
3770
|
-
// Skip terminal rows a peer published: ended sessions live in the machine-global CLI
|
|
3771
|
-
// store, so our own poll surfaces them with proper reconciliation. Importing them from a
|
|
3772
|
-
// peer would add a row with no `polledAtLeastOnce` that our poll can't confirm — a ghost
|
|
3773
|
-
// pinned for this window's lifetime if the store later drops it. (The publish endpoint
|
|
3774
|
-
// filters ended rows out anyway; this also keeps a version-skewed peer's terminal claim
|
|
3775
|
-
// from ending a row we still track live.)
|
|
3776
|
-
if (peerSession.status === 'ended') continue;
|
|
3777
|
-
|
|
3778
|
-
const existing = this._sessions.find(s => s.id === peerSession.id);
|
|
3779
|
-
const peerActivity = new Date(peerSession.lastActivity);
|
|
3780
|
-
const peerPhaseSince = new Date(peerSession.phaseSince);
|
|
3781
|
-
|
|
3782
|
-
if (existing != null) {
|
|
3783
|
-
// If the peer reports a different `cwd` than we last saw, the agent moved
|
|
3784
|
-
// (peer fired a `CwdChanged` event we don't receive locally). Pick up the new
|
|
3785
|
-
// cwd AND reset our local `gitInfoUnresolvable` flag — the new cwd might be a
|
|
3786
|
-
// git repo even if the previous one wasn't.
|
|
3787
|
-
const cwdChanged = peerSession.cwd != null && existing.cwd !== peerSession.cwd;
|
|
3788
|
-
if (cwdChanged && this._sessionBookkeeping.get(peerSession.id)?.gitInfoUnresolvable) {
|
|
3789
|
-
this.getBookkeeping(peerSession.id).gitInfoUnresolvable = false;
|
|
3790
|
-
}
|
|
3791
|
-
|
|
3792
|
-
if (peerActivity > existing.lastActivity) {
|
|
3793
|
-
const idx = this._sessions.indexOf(existing);
|
|
3794
|
-
// A terminal row the peer now reports live IS a resume the peer owns. Mirror the
|
|
3795
|
-
// poll/SessionStart revive paths: take the peer's pid (retaining the terminal
|
|
3796
|
-
// record's dead — and possibly reused — pid would let dispatch focus an unrelated
|
|
3797
|
-
// process) and mark it peer-owned so opens route through the peer's IPC.
|
|
3798
|
-
const revivedFromEnded = existing.status === 'ended';
|
|
3799
|
-
// Peer is the authoritative hook recipient for this session, so wipe any
|
|
3800
|
-
// local bookkeeping that survived from a previous local-ownership window.
|
|
3801
|
-
// Without this, refcounts/timers from a prior local-hosting stretch would
|
|
3802
|
-
// resurface stale paths if ownership ever flips back to us.
|
|
3803
|
-
const bk = this._sessionBookkeeping.get(peerSession.id);
|
|
3804
|
-
if (bk != null) {
|
|
3805
|
-
this.cancelPendingFileClears(bk);
|
|
3806
|
-
this.cancelPendingReadClears(bk);
|
|
3807
|
-
bk.currentFileCounts.clear();
|
|
3808
|
-
bk.currentReadCounts.clear();
|
|
3809
|
-
bk.lastTouchedAt.clear();
|
|
3810
|
-
}
|
|
3811
|
-
this._sessions[idx] = {
|
|
3812
|
-
...existing,
|
|
3813
|
-
status: peerSession.status,
|
|
3814
|
-
phase: peerSession.phase,
|
|
3815
|
-
phaseSince: peerPhaseSince,
|
|
3816
|
-
statusDetail: peerSession.statusDetail,
|
|
3817
|
-
lastActivity: peerActivity,
|
|
3818
|
-
// A locally-routable ask wins — a blocking ask fans out to every window, so we
|
|
3819
|
-
// may hold the hook entry the peer lacks. Otherwise mirror the peer's ask marked
|
|
3820
|
-
// unresolvable (we have no entry to answer it), and clear it when the peer did.
|
|
3821
|
-
pendingPermission:
|
|
3822
|
-
this._sessionBookkeeping.get(peerSession.id)?.pendingPermission ??
|
|
3823
|
-
(peerSession.pendingPermission != null
|
|
3824
|
-
? { ...peerSession.pendingPermission, resolvable: false }
|
|
3825
|
-
: undefined),
|
|
3826
|
-
...(revivedFromEnded ? { pid: peerSession.pid, isPeerOwned: true } : undefined),
|
|
3827
|
-
subagents: rehydrateSubagents(peerSession.subagents),
|
|
3828
|
-
// Carry the peer's published fileActivity across so peer-window WIP
|
|
3829
|
-
// decorations + treemap heatmap follow the agent. Owned by the peer (it's
|
|
3830
|
-
// the hook recipient); we never originate this field for a remote session.
|
|
3831
|
-
fileActivity: peerSession.fileActivity,
|
|
3832
|
-
// Track the peer's resolved repo identity. The peer owns the hook flow and
|
|
3833
|
-
// re-resolves both on `CwdChanged`, so a worktree move (e.g. the agent
|
|
3834
|
-
// `cd`'d into a sibling worktree of the same repo — `commonPath` unchanged
|
|
3835
|
-
// but `worktreePath` changed) only reaches us if we carry BOTH. Carrying
|
|
3836
|
-
// `commonPath` alone froze the displayed worktree at first-discovery.
|
|
3837
|
-
worktreePath: peerSession.worktreePath ?? existing.worktreePath,
|
|
3838
|
-
visitedWorktreePaths: unionVisitedAppendOnly(
|
|
3839
|
-
existing.visitedWorktreePaths,
|
|
3840
|
-
peerSession.visitedWorktreePaths,
|
|
3841
|
-
),
|
|
3842
|
-
commonPath: peerSession.commonPath ?? existing.commonPath,
|
|
3843
|
-
// Pick up the peer's latest cwd so our backfill probe (queued below)
|
|
3844
|
-
// targets the right path. Stale-cwd locally would just re-probe the
|
|
3845
|
-
// non-repo dir and hit the gitInfoUnresolvable retry guard again.
|
|
3846
|
-
cwd: cwdChanged ? peerSession.cwd : existing.cwd,
|
|
3847
|
-
// Launch-state fields are immutable per session: prefer whichever side
|
|
3848
|
-
// already set them (peer that owns the hook flow typically sets first),
|
|
3849
|
-
// and never overwrite a populated local value with the peer's.
|
|
3850
|
-
initialCwd: existing.initialCwd ?? peerSession.initialCwd,
|
|
3851
|
-
initialWorktreePath: existing.initialWorktreePath ?? peerSession.initialWorktreePath,
|
|
3852
|
-
initialCommonPath: existing.initialCommonPath ?? peerSession.initialCommonPath,
|
|
3853
|
-
// The hook path, when present, carries richer un-truncated prompts than what a
|
|
3854
|
-
// peer forwards — prefer our own value over the peer's.
|
|
3855
|
-
lastPrompt: existing.lastPrompt ?? peerSession.lastPrompt,
|
|
3856
|
-
firstPrompt: existing.firstPrompt ?? peerSession.firstPrompt,
|
|
3857
|
-
// Forward peer-discovered transcript titles. Both windows can resolve them
|
|
3858
|
-
// independently against the same on-disk transcript, but only the peer's
|
|
3859
|
-
// hook flow drives its updates — without this, we'd freeze the snapshot
|
|
3860
|
-
// taken at first discovery.
|
|
3861
|
-
transcriptTitles: peerSession.transcriptTitles ?? existing.transcriptTitles,
|
|
3862
|
-
};
|
|
3863
|
-
changed = true;
|
|
3864
|
-
}
|
|
3865
|
-
} else {
|
|
3866
|
-
this._sessions.push({
|
|
3867
|
-
...peerSession,
|
|
3868
|
-
lastActivity: peerActivity,
|
|
3869
|
-
phaseSince: peerPhaseSince,
|
|
3870
|
-
// The peer owns the hook entry, so an imported ask can never be routed from here.
|
|
3871
|
-
pendingPermission:
|
|
3872
|
-
peerSession.pendingPermission != null
|
|
3873
|
-
? { ...peerSession.pendingPermission, resolvable: false }
|
|
3874
|
-
: undefined,
|
|
3875
|
-
workspacePath: normalizeWorkspacePath(peerSession.workspacePath),
|
|
3876
|
-
isInWorkspace: this.matchesWorkspace(peerSession.workspacePath),
|
|
3877
|
-
subagents: rehydrateSubagents(peerSession.subagents),
|
|
3878
|
-
// Override whatever the peer published — this is OUR ownership view: the peer
|
|
3879
|
-
// (or some other window) hosts the live session, not us. Drives the dispatcher
|
|
3880
|
-
// to route opens through the peer's IPC + OS-level window focus instead of
|
|
3881
|
-
// calling `claude-vscode.editor.open` locally (which would just open an inert
|
|
3882
|
-
// view in our window).
|
|
3883
|
-
isPeerOwned: true,
|
|
3884
|
-
});
|
|
3885
|
-
changed = true;
|
|
3886
|
-
}
|
|
3887
|
-
|
|
3888
|
-
// Queue a local resolveGitInfo for any peer session that arrived without a
|
|
3889
|
-
// commonPath but has a cwd — we can fill it in from our own git registry. Skip
|
|
3890
|
-
// if our own bookkeeping already marked this session's cwd unresolvable (we tried
|
|
3891
|
-
// locally and the cwd isn't a git repo from our vantage point either).
|
|
3892
|
-
const merged = this._sessions.find(s => s.id === peerSession.id);
|
|
3893
|
-
if (
|
|
3894
|
-
merged?.commonPath == null &&
|
|
3895
|
-
merged?.cwd != null &&
|
|
3896
|
-
!this.getBookkeeping(merged.id).gitInfoUnresolvable
|
|
3897
|
-
) {
|
|
3898
|
-
sessionsNeedingResolve.set(merged.id, merged.cwd);
|
|
3899
|
-
}
|
|
3900
|
-
}
|
|
3901
|
-
}
|
|
3902
|
-
|
|
3903
|
-
if (changed) {
|
|
3904
|
-
this._onDidChangeSessions.fire();
|
|
3905
|
-
}
|
|
3906
|
-
|
|
3907
|
-
// Backfill commonPath for peer-synced sessions whose owning peer didn't populate it.
|
|
3908
|
-
// `resolveGitInfo` is idempotent — only mutates + fires if it actually finds new info,
|
|
3909
|
-
// so the worst case here is a few wasted git probes for unresolvable cwds.
|
|
3910
|
-
for (const [id, cwd] of sessionsNeedingResolve) {
|
|
3911
|
-
void this.resolveGitInfo(id, cwd);
|
|
3912
|
-
}
|
|
3913
|
-
}
|
|
3914
|
-
|
|
3915
|
-
private async fetchPeerSessions(
|
|
3916
|
-
path: string,
|
|
3917
|
-
ownPort: number | undefined,
|
|
3918
|
-
): Promise<SerializedAgentSession[] | undefined> {
|
|
3919
|
-
let discovery: DiscoveryFile;
|
|
3920
|
-
try {
|
|
3921
|
-
discovery = JSON.parse(await readFile(path, 'utf8')) as DiscoveryFile;
|
|
3922
|
-
} catch {
|
|
3923
|
-
return undefined;
|
|
3924
|
-
}
|
|
3925
|
-
if (ownPort != null && discovery.port === ownPort) return undefined;
|
|
3926
|
-
|
|
3927
|
-
try {
|
|
3928
|
-
const response = await fetch(`${discovery.address}/agents/sessions/list`, {
|
|
3929
|
-
method: 'POST',
|
|
3930
|
-
headers: { Authorization: `Bearer ${discovery.token}`, 'Content-Type': 'application/json' },
|
|
3931
|
-
body: '{}',
|
|
3932
|
-
signal: AbortSignal.timeout(2000),
|
|
3933
|
-
});
|
|
3934
|
-
if (!response.ok) return undefined;
|
|
3935
|
-
return (await response.json()) as SerializedAgentSession[];
|
|
3936
|
-
} catch {
|
|
3937
|
-
return undefined;
|
|
3938
|
-
}
|
|
3939
|
-
}
|
|
3940
|
-
|
|
3941
|
-
/** Find the peer GitLens window whose discovery file claims `workspacePath`, and POST to its
|
|
3942
|
-
* `/agents/sessions/open` route to ask its Claude Code extension to open the session.
|
|
3943
|
-
*
|
|
3944
|
-
* Resolves to `true` when at least one peer claimed the workspace AND was reachable (the POST
|
|
3945
|
-
* completed with any HTTP status). The peer's response shape (`{ opened: boolean }`) is logged
|
|
3946
|
-
* for diagnostics but does NOT affect the return value — an `opened: false` peer is still the
|
|
3947
|
-
* right window for the caller to focus, since it's the window that owns the session. Resolves
|
|
3948
|
-
* to `false` when no peer claimed the workspace OR every claimer was unreachable; the caller
|
|
3949
|
-
* treats that as "no peer to focus" and opens a new window instead of replacing the current
|
|
3950
|
-
* one. Best-effort: swallows scan, JSON-parse errors. */
|
|
3951
|
-
async notifyPeerOpenSession(workspacePath: string, sessionId: string): Promise<boolean> {
|
|
3952
|
-
const discoveryDir = this.callbacks.ipc.agentDiscoveryDir;
|
|
3953
|
-
if (discoveryDir == null) return false;
|
|
3954
|
-
|
|
3955
|
-
const target = normalizePath(workspacePath);
|
|
3956
|
-
const ownPort = this.callbacks.ipc.port;
|
|
3957
|
-
|
|
3958
|
-
let files: string[];
|
|
3959
|
-
try {
|
|
3960
|
-
files = await readdir(discoveryDir);
|
|
3961
|
-
} catch {
|
|
3962
|
-
return false;
|
|
3963
|
-
}
|
|
3964
|
-
|
|
3965
|
-
const results = await Promise.all(
|
|
3966
|
-
files
|
|
3967
|
-
.filter(f => f.startsWith('gitlens-ipc-server-') && f.endsWith('.json'))
|
|
3968
|
-
.map(async f => {
|
|
3969
|
-
let discovery: DiscoveryFile;
|
|
3970
|
-
try {
|
|
3971
|
-
discovery = JSON.parse(await readFile(join(discoveryDir, f), 'utf8')) as DiscoveryFile;
|
|
3972
|
-
} catch {
|
|
3973
|
-
return false;
|
|
3974
|
-
}
|
|
3975
|
-
if (ownPort != null && discovery.port === ownPort) return false;
|
|
3976
|
-
// Symmetric prefix containment — same shape as `matchesWorkspace()` above.
|
|
3977
|
-
// Strict equality misses the common case where the dispatcher passes a `cwd`
|
|
3978
|
-
// inside the peer's workspace folder (or, less commonly, a parent dir that
|
|
3979
|
-
// contains the peer's workspace).
|
|
3980
|
-
if (
|
|
3981
|
-
!discovery.workspacePaths?.some(p => {
|
|
3982
|
-
const normalized = normalizePath(p);
|
|
3983
|
-
return (
|
|
3984
|
-
normalized === target ||
|
|
3985
|
-
target.startsWith(`${normalized}/`) ||
|
|
3986
|
-
normalized.startsWith(`${target}/`)
|
|
3987
|
-
);
|
|
3988
|
-
})
|
|
3989
|
-
) {
|
|
3990
|
-
return false;
|
|
3991
|
-
}
|
|
3992
|
-
|
|
3993
|
-
try {
|
|
3994
|
-
const response = await fetch(`${discovery.address}/agents/sessions/open`, {
|
|
3995
|
-
method: 'POST',
|
|
3996
|
-
headers: {
|
|
3997
|
-
Authorization: `Bearer ${discovery.token}`,
|
|
3998
|
-
'Content-Type': 'application/json',
|
|
3999
|
-
},
|
|
4000
|
-
body: JSON.stringify({ sessionId: sessionId }),
|
|
4001
|
-
signal: AbortSignal.timeout(2000),
|
|
4002
|
-
});
|
|
4003
|
-
if (!response.ok) {
|
|
4004
|
-
Logger.warn(
|
|
4005
|
-
`GkAgentProvider.notifyPeerOpenSession: peer at ${discovery.address} returned ${response.status}`,
|
|
4006
|
-
);
|
|
4007
|
-
} else {
|
|
4008
|
-
// Log a peer that failed to open the specific session, but still treat it
|
|
4009
|
-
// as a reachable claimer of the workspace — focusing that window is still
|
|
4010
|
-
// what the user wants.
|
|
4011
|
-
const body = (await response.json().catch(() => undefined)) as
|
|
4012
|
-
| { opened?: boolean }
|
|
4013
|
-
| undefined;
|
|
4014
|
-
if (body?.opened === false) {
|
|
4015
|
-
Logger.warn(
|
|
4016
|
-
`GkAgentProvider.notifyPeerOpenSession: peer at ${discovery.address} could not open session ${sessionId}`,
|
|
4017
|
-
);
|
|
4018
|
-
}
|
|
4019
|
-
}
|
|
4020
|
-
return true;
|
|
4021
|
-
} catch (ex) {
|
|
4022
|
-
// Peer advertised but unreachable (timeout, RST, etc.). Treat as no match so
|
|
4023
|
-
// the caller opens a new window instead of trying to focus a dead window.
|
|
4024
|
-
Logger.warn(
|
|
4025
|
-
`GkAgentProvider.notifyPeerOpenSession: peer at ${discovery.address} unreachable: ${
|
|
4026
|
-
ex instanceof Error ? ex.message : String(ex)
|
|
4027
|
-
}`,
|
|
4028
|
-
);
|
|
4029
|
-
return false;
|
|
4030
|
-
}
|
|
4031
|
-
}),
|
|
4032
|
-
);
|
|
4033
|
-
|
|
4034
|
-
return results.some(Boolean);
|
|
4035
|
-
}
|
|
4036
|
-
}
|