github-issue-tower-defence-management 1.149.1 → 1.151.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 +19 -0
- package/README.md +9 -1
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +24 -0
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
- package/bin/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.js +2 -2
- package/bin/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.js.map +1 -1
- package/bin/adapter/entry-points/handlers/OauthTokenSelectHandler.js +1 -1
- package/bin/adapter/entry-points/handlers/OauthTokenSelectHandler.js.map +1 -1
- package/bin/adapter/entry-points/handlers/inTmuxByHumanDataWriter.js +13 -1
- package/bin/adapter/entry-points/handlers/inTmuxByHumanDataWriter.js.map +1 -1
- package/bin/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.js +12 -0
- package/bin/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.js.map +1 -0
- package/bin/adapter/repositories/TranscriptOwnerCallStatusProvider.js +60 -13
- package/bin/adapter/repositories/TranscriptOwnerCallStatusProvider.js.map +1 -1
- package/bin/domain/entities/UnansweredOwnerCall.js +3 -0
- package/bin/domain/entities/UnansweredOwnerCall.js.map +1 -0
- package/bin/domain/usecases/LiveSessionOauthTokenSelectUseCase.js +1 -0
- package/bin/domain/usecases/LiveSessionOauthTokenSelectUseCase.js.map +1 -1
- package/bin/domain/usecases/OauthTokenSelectUseCase.js +15 -7
- package/bin/domain/usecases/OauthTokenSelectUseCase.js.map +1 -1
- package/bin/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.js +21 -2
- package/bin/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +46 -0
- package/src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.test.ts +21 -0
- package/src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.ts +6 -6
- package/src/adapter/entry-points/handlers/OauthTokenSelectHandler.test.ts +27 -0
- package/src/adapter/entry-points/handlers/OauthTokenSelectHandler.ts +5 -5
- package/src/adapter/entry-points/handlers/inTmuxByHumanDataWriter.test.ts +104 -1
- package/src/adapter/entry-points/handlers/inTmuxByHumanDataWriter.ts +19 -0
- package/src/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.test.ts +111 -0
- package/src/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.ts +33 -0
- package/src/adapter/repositories/TranscriptOwnerCallStatusProvider.test.ts +178 -0
- package/src/adapter/repositories/TranscriptOwnerCallStatusProvider.ts +106 -17
- package/src/domain/entities/UnansweredOwnerCall.ts +4 -0
- package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.test.ts +33 -0
- package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.ts +4 -2
- package/src/domain/usecases/OauthTokenSelectUseCase.test.ts +88 -0
- package/src/domain/usecases/OauthTokenSelectUseCase.ts +21 -13
- package/src/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.test.ts +99 -0
- package/src/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.ts +47 -1
- package/src/domain/usecases/intmux/UnansweredOwnerCallSessionKeyContract.test.ts +141 -0
- package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.d.ts +2 -2
- package/types/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/OauthTokenSelectHandler.d.ts +1 -1
- package/types/adapter/entry-points/handlers/OauthTokenSelectHandler.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/inTmuxByHumanDataWriter.d.ts +2 -0
- package/types/adapter/entry-points/handlers/inTmuxByHumanDataWriter.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.d.ts +13 -0
- package/types/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.d.ts.map +1 -0
- package/types/adapter/repositories/TranscriptOwnerCallStatusProvider.d.ts +5 -0
- package/types/adapter/repositories/TranscriptOwnerCallStatusProvider.d.ts.map +1 -1
- package/types/domain/entities/UnansweredOwnerCall.d.ts +5 -0
- package/types/domain/entities/UnansweredOwnerCall.d.ts.map +1 -0
- package/types/domain/usecases/LiveSessionOauthTokenSelectUseCase.d.ts +3 -2
- package/types/domain/usecases/LiveSessionOauthTokenSelectUseCase.d.ts.map +1 -1
- package/types/domain/usecases/OauthTokenSelectUseCase.d.ts +1 -0
- package/types/domain/usecases/OauthTokenSelectUseCase.d.ts.map +1 -1
- package/types/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.d.ts +19 -0
- package/types/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.d.ts.map +1 -1
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.GenerateInTmuxByHumanDataUseCase = void 0;
|
|
4
|
+
const InTmuxByHumanSessionReconcileUseCase_1 = require("./InTmuxByHumanSessionReconcileUseCase");
|
|
4
5
|
const IN_TMUX_BY_HUMAN_STATUS_NAME = 'In Tmux by human';
|
|
5
6
|
const UNKNOWN_STORY_SORT_INDEX = 999999;
|
|
6
7
|
class GenerateInTmuxByHumanDataUseCase {
|
|
7
8
|
constructor() {
|
|
8
9
|
this.run = (input) => {
|
|
9
|
-
const { project, issues, pjcode, assigneeLogin, org, repo, newIssueRepo, consoleBaseUrl, consoleToken, } = input;
|
|
10
|
+
const { project, issues, pjcode, assigneeLogin, org, repo, newIssueRepo, consoleBaseUrl, consoleToken, unansweredCallsByTmuxSessionName, } = input;
|
|
10
11
|
const storyOrder = project.story
|
|
11
12
|
? project.story.stories.map((option) => option.name)
|
|
12
13
|
: [];
|
|
@@ -30,6 +31,15 @@ class GenerateInTmuxByHumanDataUseCase {
|
|
|
30
31
|
description: issue.title,
|
|
31
32
|
})),
|
|
32
33
|
}));
|
|
34
|
+
const v5Groups = groups.map((group) => ({
|
|
35
|
+
story: group.story,
|
|
36
|
+
sessions: group.issues.map((issue) => ({
|
|
37
|
+
name: issue.url,
|
|
38
|
+
description: issue.title,
|
|
39
|
+
unansweredCalls: unansweredCallsByTmuxSessionName.get((0, InTmuxByHumanSessionReconcileUseCase_1.toTmuxSessionName)(issue.url)) ??
|
|
40
|
+
[],
|
|
41
|
+
})),
|
|
42
|
+
}));
|
|
33
43
|
const overviewUrl = project.url;
|
|
34
44
|
const tdpmConsoleUrl = consoleBaseUrl
|
|
35
45
|
? `${consoleBaseUrl}/projects/${pjcode}`
|
|
@@ -51,7 +61,16 @@ class GenerateInTmuxByHumanDataUseCase {
|
|
|
51
61
|
groups: v4Groups,
|
|
52
62
|
}
|
|
53
63
|
: null;
|
|
54
|
-
|
|
64
|
+
const v5 = tdpmConsoleUrl && consoleToken
|
|
65
|
+
? {
|
|
66
|
+
version: 5,
|
|
67
|
+
overviewUrl,
|
|
68
|
+
tdpmConsoleUrl: `${tdpmConsoleUrl}?k=${consoleToken}`,
|
|
69
|
+
newIssueUrl: `https://github.com/${org}/${newIssueRepo ?? repo}/issues/new?assignees=${assigneeLogin}`,
|
|
70
|
+
groups: v5Groups,
|
|
71
|
+
}
|
|
72
|
+
: null;
|
|
73
|
+
return { v1, v2, v3, v4, v5 };
|
|
55
74
|
};
|
|
56
75
|
this.isInTmuxByHuman = (issue, assigneeLogin) => issue.status === IN_TMUX_BY_HUMAN_STATUS_NAME &&
|
|
57
76
|
issue.isClosed === false &&
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GenerateInTmuxByHumanDataUseCase.js","sourceRoot":"","sources":["../../../../src/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"GenerateInTmuxByHumanDataUseCase.js","sourceRoot":"","sources":["../../../../src/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -19,6 +19,12 @@ import {
|
|
|
19
19
|
DEFAULT_NOTIFY_SILENT_TMUX_SESSIONS_PARAMS,
|
|
20
20
|
} from './notifySilentTmuxSessions';
|
|
21
21
|
import { ownerReplyMarkerDirectoryResolve } from './ownerReplyMarkerDirectoryResolve';
|
|
22
|
+
import { resolveUnansweredOwnerCallsByTmuxSessionName } from './resolveUnansweredOwnerCalls';
|
|
23
|
+
import { LocalProcessLiveSessionProcessSnapshotProvider } from '../../repositories/LocalProcessLiveSessionProcessSnapshotProvider';
|
|
24
|
+
import { ProcFsProcessEnvironReader } from '../../repositories/ProcFsProcessEnvironReader';
|
|
25
|
+
import { FileSystemInteractiveLiveSessionTranscriptResolver } from '../../repositories/FileSystemInteractiveLiveSessionTranscriptResolver';
|
|
26
|
+
import { TranscriptOwnerCallStatusProvider } from '../../repositories/TranscriptOwnerCallStatusProvider';
|
|
27
|
+
import { UnansweredOwnerCall } from '../../../domain/entities/UnansweredOwnerCall';
|
|
22
28
|
import {
|
|
23
29
|
resetDegeneratedTmuxSessions,
|
|
24
30
|
DEFAULT_RESET_DEGENERATED_TMUX_SESSIONS_PARAMS,
|
|
@@ -632,6 +638,45 @@ export class HandleScheduledEventUseCaseHandler {
|
|
|
632
638
|
|
|
633
639
|
const inTmuxNow = new Date();
|
|
634
640
|
|
|
641
|
+
let unansweredCallsByTmuxSessionName = new Map<
|
|
642
|
+
string,
|
|
643
|
+
UnansweredOwnerCall[]
|
|
644
|
+
>();
|
|
645
|
+
try {
|
|
646
|
+
const inTmuxOwnerCallMarker =
|
|
647
|
+
mergedInput.ownerCallMarker ??
|
|
648
|
+
process.env.TDPM_SILENT_OWNER_CALL_MARKER ??
|
|
649
|
+
null;
|
|
650
|
+
if (inTmuxOwnerCallMarker !== null) {
|
|
651
|
+
const inTmuxGetuid = process.getuid?.bind(process);
|
|
652
|
+
unansweredCallsByTmuxSessionName =
|
|
653
|
+
await resolveUnansweredOwnerCallsByTmuxSessionName({
|
|
654
|
+
liveSessionProcessSnapshotProvider:
|
|
655
|
+
new LocalProcessLiveSessionProcessSnapshotProvider(
|
|
656
|
+
nodeLocalCommandRunner,
|
|
657
|
+
new ProcFsProcessEnvironReader(),
|
|
658
|
+
),
|
|
659
|
+
interactiveLiveSessionTranscriptResolver:
|
|
660
|
+
new FileSystemInteractiveLiveSessionTranscriptResolver(),
|
|
661
|
+
unansweredOwnerCallListProvider:
|
|
662
|
+
new TranscriptOwnerCallStatusProvider(
|
|
663
|
+
inTmuxOwnerCallMarker,
|
|
664
|
+
ownerReplyMarkerDirectoryResolve(
|
|
665
|
+
mergedInput.ownerReplyMarkerDirectory ?? null,
|
|
666
|
+
process.env,
|
|
667
|
+
inTmuxGetuid === undefined ? null : inTmuxGetuid(),
|
|
668
|
+
),
|
|
669
|
+
),
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
} catch (error) {
|
|
673
|
+
console.error(
|
|
674
|
+
`Failed to resolve unanswered owner calls: ${
|
|
675
|
+
error instanceof Error ? error.message : String(error)
|
|
676
|
+
}`,
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
|
|
635
680
|
try {
|
|
636
681
|
writeInTmuxByHumanData({
|
|
637
682
|
inTmuxDataOutputDir: mergedInput.inTmuxDataOutputDir ?? null,
|
|
@@ -648,6 +693,7 @@ export class HandleScheduledEventUseCaseHandler {
|
|
|
648
693
|
newIssueRepo: mergedInput.newIssueRepo ?? undefined,
|
|
649
694
|
project: result.project,
|
|
650
695
|
issues: result.issues,
|
|
696
|
+
unansweredCallsByTmuxSessionName,
|
|
651
697
|
now: inTmuxNow,
|
|
652
698
|
});
|
|
653
699
|
} catch (error) {
|
|
@@ -347,6 +347,27 @@ describe('LiveSessionOauthTokenSelectHandler', () => {
|
|
|
347
347
|
expect(output.diagnostics.join('\n')).not.toContain('No eligible token');
|
|
348
348
|
});
|
|
349
349
|
|
|
350
|
+
it('includes the selection weight in the diagnostic line for each candidate', () => {
|
|
351
|
+
writeTokenList([{ name: 'foo', token: 'fake-foo' }]);
|
|
352
|
+
writeCache('fake-foo', {
|
|
353
|
+
fiveHourUtilization: 0.1,
|
|
354
|
+
fiveHourReset: NOW + HOUR,
|
|
355
|
+
sevenDayUtilization: 0.1,
|
|
356
|
+
sevenDayReset: NOW + DAY,
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
const handler = buildHandler([]);
|
|
360
|
+
const output = handler.handle({
|
|
361
|
+
tokenListJsonPath: tokenListPath,
|
|
362
|
+
cacheDirectory,
|
|
363
|
+
nowEpochSeconds: NOW,
|
|
364
|
+
selectionSettings: DEFAULT_LIVE_SESSION_OAUTH_TOKEN_SELECTION_SETTINGS,
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
const diagnostics = output.diagnostics.join('\n');
|
|
368
|
+
expect(diagnostics).toMatch(/foo:.*, weight 1 ->/);
|
|
369
|
+
});
|
|
370
|
+
|
|
350
371
|
it('returns a diagnostic when no token list path is resolvable', () => {
|
|
351
372
|
const handler = buildHandler([]);
|
|
352
373
|
const output = handler.handle({
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { ClaudeLiveSessionRepository } from '../../../domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository';
|
|
1
|
+
import type { ClaudeLiveSessionRepository } from '../../../domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository';
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
type LiveSessionOauthTokenSelectionSettings,
|
|
4
|
+
type LiveSessionOauthTokenSelectResult,
|
|
4
5
|
LiveSessionOauthTokenSelectUseCase,
|
|
5
|
-
LiveSessionOauthTokenSelectionSettings,
|
|
6
6
|
} from '../../../domain/usecases/LiveSessionOauthTokenSelectUseCase';
|
|
7
7
|
import {
|
|
8
8
|
DEFAULT_SELECTION_WEIGHT,
|
|
9
9
|
FIVE_HOUR_MIN_FREE_RATIO,
|
|
10
|
-
OauthTokenCandidate,
|
|
10
|
+
type OauthTokenCandidate,
|
|
11
11
|
SEVEN_DAY_MIN_FREE_RATIO,
|
|
12
12
|
} from '../../../domain/usecases/OauthTokenSelectUseCase';
|
|
13
|
-
import { ProcClaudeLiveSessionRepository } from '../../repositories/ProcClaudeLiveSessionRepository';
|
|
14
13
|
import { FABLE_LIMIT_TYPE, readRateLimit } from '../../proxy/RateLimitCache';
|
|
15
14
|
import { loadTokenEntries } from '../../proxy/TokenListLoader';
|
|
15
|
+
import { ProcClaudeLiveSessionRepository } from '../../repositories/ProcClaudeLiveSessionRepository';
|
|
16
16
|
import {
|
|
17
17
|
resolveCacheDirectory,
|
|
18
18
|
resolveTokenListJsonPath,
|
|
@@ -119,7 +119,7 @@ export class LiveSessionOauthTokenSelectHandler {
|
|
|
119
119
|
const status = metric.eligible
|
|
120
120
|
? 'eligible'
|
|
121
121
|
: `excluded (${metric.exclusionReason})`;
|
|
122
|
-
return `${metric.name}: ${metric.liveSessionCount}/${metric.concurrentSessionLimit} live session(s), 5h ${Math.round(metric.fiveHourFreeRatio * 100)}% free, 7d ${Math.round(metric.sevenDayFreeRatio * 100)}% free, 7d-end in ${secondsUntilSevenDayEnd}s -> ${status}`;
|
|
122
|
+
return `${metric.name}: ${metric.liveSessionCount}/${metric.concurrentSessionLimit} live session(s), 5h ${Math.round(metric.fiveHourFreeRatio * 100)}% free, 7d ${Math.round(metric.sevenDayFreeRatio * 100)}% free, 7d-end in ${secondsUntilSevenDayEnd}s, weight ${metric.selectionWeight} -> ${status}`;
|
|
123
123
|
});
|
|
124
124
|
|
|
125
125
|
if (result.selected === null) {
|
|
@@ -367,6 +367,33 @@ describe('OauthTokenSelectHandler', () => {
|
|
|
367
367
|
expect(output.selectedName).toBeNull();
|
|
368
368
|
});
|
|
369
369
|
|
|
370
|
+
it('includes the draw weight in the diagnostic line for each candidate', () => {
|
|
371
|
+
writeTokenList([{ name: 'foo', token: 'fake-foo' }]);
|
|
372
|
+
writeCache('fake-foo', {
|
|
373
|
+
fiveHourUtilization: 0.1,
|
|
374
|
+
fiveHourReset: NOW + HOUR,
|
|
375
|
+
sevenDayUtilization: 0.1,
|
|
376
|
+
sevenDayReset: NOW + 2 * DAY,
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const handler = new OauthTokenSelectHandler(
|
|
380
|
+
new OauthTokenSelectUseCase(),
|
|
381
|
+
() => 0.5,
|
|
382
|
+
);
|
|
383
|
+
const output = handler.handle({
|
|
384
|
+
tokenListJsonPath: tokenListPath,
|
|
385
|
+
cacheDirectory,
|
|
386
|
+
nowEpochSeconds: NOW,
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
const diagnostics = output.diagnostics.join('\n');
|
|
390
|
+
const drawWeightMatch = /foo:.*draw weight (\d+\.\d{2}) ->/.exec(
|
|
391
|
+
diagnostics,
|
|
392
|
+
);
|
|
393
|
+
expect(drawWeightMatch).not.toBeNull();
|
|
394
|
+
expect(Number(drawWeightMatch?.[1])).toBeGreaterThan(0);
|
|
395
|
+
});
|
|
396
|
+
|
|
370
397
|
describe('resolveTokenListJsonPath', () => {
|
|
371
398
|
it('prefers the explicit path over the environment variable', () => {
|
|
372
399
|
process.env.CLAUDE_CODE_OAUTH_TOKEN_LIST_JSON_PATH = '/from/env.json';
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DEFAULT_SELECTION_WEIGHT,
|
|
3
|
-
OauthTokenCandidate,
|
|
4
|
-
OauthTokenSelectResult,
|
|
3
|
+
type OauthTokenCandidate,
|
|
4
|
+
type OauthTokenSelectResult,
|
|
5
5
|
OauthTokenSelectUseCase,
|
|
6
|
-
SelectionRandom,
|
|
6
|
+
type SelectionRandom,
|
|
7
7
|
} from '../../../domain/usecases/OauthTokenSelectUseCase';
|
|
8
8
|
import {
|
|
9
|
-
FABLE_LIMIT_TYPE,
|
|
10
9
|
cacheDir,
|
|
10
|
+
FABLE_LIMIT_TYPE,
|
|
11
11
|
readRateLimit,
|
|
12
12
|
} from '../../proxy/RateLimitCache';
|
|
13
13
|
import { loadTokenEntries } from '../../proxy/TokenListLoader';
|
|
@@ -138,7 +138,7 @@ export class OauthTokenSelectHandler {
|
|
|
138
138
|
const status = metric.eligible
|
|
139
139
|
? 'eligible'
|
|
140
140
|
: `excluded (${metric.exclusionReason})`;
|
|
141
|
-
return `${metric.name}: 5h ${Math.round(metric.fiveHourFreeRatio * 100)}% free, 7d ${Math.round(metric.sevenDayFreeRatio * 100)}% free, 7d-end in ${secondsUntilSevenDayEnd}s -> ${status}`;
|
|
141
|
+
return `${metric.name}: 5h ${Math.round(metric.fiveHourFreeRatio * 100)}% free, 7d ${Math.round(metric.sevenDayFreeRatio * 100)}% free, 7d-end in ${secondsUntilSevenDayEnd}s, draw weight ${metric.drawWeight.toFixed(2)} -> ${status}`;
|
|
142
142
|
});
|
|
143
143
|
|
|
144
144
|
if (result.selected === null) {
|
|
@@ -4,6 +4,7 @@ import path from 'path';
|
|
|
4
4
|
import { Issue } from '../../../domain/entities/Issue';
|
|
5
5
|
import { FieldOption, Project } from '../../../domain/entities/Project';
|
|
6
6
|
import { writeInTmuxByHumanData } from './inTmuxByHumanDataWriter';
|
|
7
|
+
import { toTmuxSessionName } from '../../../domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase';
|
|
7
8
|
|
|
8
9
|
const ASSIGNEE = 'owner-login';
|
|
9
10
|
const CONSOLE_BASE_URL = 'https://console.example.test';
|
|
@@ -98,22 +99,124 @@ describe('writeInTmuxByHumanData', () => {
|
|
|
98
99
|
|
|
99
100
|
const file = (name: string): string => path.join(outDir, name);
|
|
100
101
|
|
|
101
|
-
it('writes the
|
|
102
|
+
it('writes the five per-project files and the five index files', () => {
|
|
102
103
|
writeInTmuxByHumanData(baseParams(outDir));
|
|
103
104
|
for (const name of [
|
|
104
105
|
'demo.json',
|
|
105
106
|
'demo.v2.json',
|
|
106
107
|
'demo.v3.json',
|
|
107
108
|
'demo.v4.json',
|
|
109
|
+
'demo.v5.json',
|
|
108
110
|
'index.json',
|
|
109
111
|
'index.v2.json',
|
|
110
112
|
'index.v3.json',
|
|
111
113
|
'index.v4.json',
|
|
114
|
+
'index.v5.json',
|
|
112
115
|
]) {
|
|
113
116
|
expect(fs.existsSync(file(name))).toBe(true);
|
|
114
117
|
}
|
|
115
118
|
});
|
|
116
119
|
|
|
120
|
+
it('writes the v5 shape carrying the unanswered calls of the session', () => {
|
|
121
|
+
writeInTmuxByHumanData({
|
|
122
|
+
...baseParams(outDir),
|
|
123
|
+
unansweredCallsByTmuxSessionName: new Map([
|
|
124
|
+
[
|
|
125
|
+
toTmuxSessionName('https://github.com/demo/repo/issues/1'),
|
|
126
|
+
[
|
|
127
|
+
{
|
|
128
|
+
calledAt: '2026-08-13T10:14:00.000Z',
|
|
129
|
+
body: 'Please decide whether to merge the release branch',
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
],
|
|
133
|
+
]),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
expect(readJson(file('demo.v5.json'))).toEqual({
|
|
137
|
+
version: 5,
|
|
138
|
+
overviewUrl: 'https://github.com/orgs/demo/projects/1',
|
|
139
|
+
tdpmConsoleUrl: `${CONSOLE_BASE_URL}/projects/demo?k=${CONSOLE_TOKEN}`,
|
|
140
|
+
newIssueUrl: `https://github.com/demo-org/demo-repo/issues/new?assignees=${ASSIGNEE}`,
|
|
141
|
+
groups: [
|
|
142
|
+
{
|
|
143
|
+
story: 'Story Alpha',
|
|
144
|
+
sessions: [
|
|
145
|
+
{
|
|
146
|
+
name: 'https://github.com/demo/repo/issues/1',
|
|
147
|
+
description: 'Issue 1',
|
|
148
|
+
unansweredCalls: [
|
|
149
|
+
{
|
|
150
|
+
calledAt: '2026-08-13T10:14:00.000Z',
|
|
151
|
+
body: 'Please decide whether to merge the release branch',
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('writes an empty unansweredCalls array when no call is supplied', () => {
|
|
162
|
+
writeInTmuxByHumanData(baseParams(outDir));
|
|
163
|
+
expect(readJson(file('demo.v5.json'))).toMatchObject({
|
|
164
|
+
groups: [
|
|
165
|
+
{
|
|
166
|
+
sessions: [
|
|
167
|
+
{
|
|
168
|
+
name: 'https://github.com/demo/repo/issues/1',
|
|
169
|
+
unansweredCalls: [],
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('points the v5 index entry at the v5 document of each project', () => {
|
|
178
|
+
writeInTmuxByHumanData(baseParams(outDir));
|
|
179
|
+
expect(readJson(file('index.v5.json'))).toEqual({
|
|
180
|
+
version: 5,
|
|
181
|
+
projects: [
|
|
182
|
+
{
|
|
183
|
+
name: 'demo',
|
|
184
|
+
path: `/${path.basename(outDir)}/demo.v5.json?k=${CONSOLE_TOKEN}`,
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('keeps the v4 document free of unanswered call data when calls are supplied', () => {
|
|
191
|
+
writeInTmuxByHumanData({
|
|
192
|
+
...baseParams(outDir),
|
|
193
|
+
unansweredCallsByTmuxSessionName: new Map([
|
|
194
|
+
[
|
|
195
|
+
toTmuxSessionName('https://github.com/demo/repo/issues/1'),
|
|
196
|
+
[{ calledAt: '2026-08-13T10:14:00.000Z', body: 'first call body' }],
|
|
197
|
+
],
|
|
198
|
+
]),
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
expect(readJson(file('demo.v4.json'))).toEqual({
|
|
202
|
+
version: 4,
|
|
203
|
+
overviewUrl: 'https://github.com/orgs/demo/projects/1',
|
|
204
|
+
tdpmConsoleUrl: `${CONSOLE_BASE_URL}/projects/demo?k=${CONSOLE_TOKEN}`,
|
|
205
|
+
newIssueUrl: `https://github.com/demo-org/demo-repo/issues/new?assignees=${ASSIGNEE}`,
|
|
206
|
+
groups: [
|
|
207
|
+
{
|
|
208
|
+
story: 'Story Alpha',
|
|
209
|
+
sessions: [
|
|
210
|
+
{
|
|
211
|
+
name: 'https://github.com/demo/repo/issues/1',
|
|
212
|
+
description: 'Issue 1',
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
117
220
|
it('writes pretty-printed JSON ending with a trailing newline', () => {
|
|
118
221
|
writeInTmuxByHumanData(baseParams(outDir));
|
|
119
222
|
const raw = fs.readFileSync(file('demo.json'), 'utf8');
|
|
@@ -2,6 +2,7 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import type { Issue } from '../../../domain/entities/Issue';
|
|
4
4
|
import type { Project } from '../../../domain/entities/Project';
|
|
5
|
+
import type { UnansweredOwnerCall } from '../../../domain/entities/UnansweredOwnerCall';
|
|
5
6
|
import {
|
|
6
7
|
GenerateInTmuxByHumanDataUseCase,
|
|
7
8
|
InTmuxByHumanData,
|
|
@@ -19,6 +20,7 @@ export type InTmuxByHumanDataWriterParams = {
|
|
|
19
20
|
newIssueRepo?: string | null | undefined;
|
|
20
21
|
project: Project;
|
|
21
22
|
issues: Issue[];
|
|
23
|
+
unansweredCallsByTmuxSessionName?: Map<string, UnansweredOwnerCall[]>;
|
|
22
24
|
now: Date;
|
|
23
25
|
};
|
|
24
26
|
|
|
@@ -45,6 +47,7 @@ export const writeInTmuxByHumanData = (
|
|
|
45
47
|
newIssueRepo,
|
|
46
48
|
project,
|
|
47
49
|
issues,
|
|
50
|
+
unansweredCallsByTmuxSessionName,
|
|
48
51
|
now,
|
|
49
52
|
} = params;
|
|
50
53
|
if (!inTmuxDataOutputDir || !pjcode || !assigneeLogin) {
|
|
@@ -61,6 +64,9 @@ export const writeInTmuxByHumanData = (
|
|
|
61
64
|
newIssueRepo: newIssueRepo ?? undefined,
|
|
62
65
|
consoleBaseUrl: inTmuxConsoleBaseUrl ?? null,
|
|
63
66
|
consoleToken: inTmuxConsoleToken ?? null,
|
|
67
|
+
unansweredCallsByTmuxSessionName:
|
|
68
|
+
unansweredCallsByTmuxSessionName ??
|
|
69
|
+
new Map<string, UnansweredOwnerCall[]>(),
|
|
64
70
|
now,
|
|
65
71
|
});
|
|
66
72
|
|
|
@@ -78,6 +84,12 @@ export const writeInTmuxByHumanData = (
|
|
|
78
84
|
data.v4,
|
|
79
85
|
);
|
|
80
86
|
}
|
|
87
|
+
if (data.v5) {
|
|
88
|
+
writeJsonAtomic(
|
|
89
|
+
path.join(inTmuxDataOutputDir, `${pjcode}.v5.json`),
|
|
90
|
+
data.v5,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
81
93
|
|
|
82
94
|
if (!inTmuxProjectOrder || inTmuxProjectOrder.length === 0) {
|
|
83
95
|
return;
|
|
@@ -105,5 +117,12 @@ export const writeInTmuxByHumanData = (
|
|
|
105
117
|
path: `/${outputDirBasename}/${name}.v4.json?k=${inTmuxConsoleToken}`,
|
|
106
118
|
})),
|
|
107
119
|
});
|
|
120
|
+
writeJsonAtomic(path.join(inTmuxDataOutputDir, 'index.v5.json'), {
|
|
121
|
+
version: 5,
|
|
122
|
+
projects: presentProjects.map((name) => ({
|
|
123
|
+
name,
|
|
124
|
+
path: `/${outputDirBasename}/${name}.v5.json?k=${inTmuxConsoleToken}`,
|
|
125
|
+
})),
|
|
126
|
+
});
|
|
108
127
|
}
|
|
109
128
|
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { resolveUnansweredOwnerCallsByTmuxSessionName } from './resolveUnansweredOwnerCalls';
|
|
2
|
+
import { UnansweredOwnerCall } from '../../../domain/entities/UnansweredOwnerCall';
|
|
3
|
+
import { InteractiveLiveSession } from '../../../domain/entities/InteractiveLiveSession';
|
|
4
|
+
import { LiveSessionProcessSnapshot } from '../../../domain/entities/LiveSessionProcessSnapshot';
|
|
5
|
+
import { toTmuxSessionName } from '../../../domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase';
|
|
6
|
+
|
|
7
|
+
const ISSUE_URL = 'https://github.com/demo/repo/issues/1';
|
|
8
|
+
const TMUX_SESSION_NAME = toTmuxSessionName(ISSUE_URL);
|
|
9
|
+
const CONFIG_DIR = '/home/agent/.claude';
|
|
10
|
+
const SESSION_ID = 'session-id-1';
|
|
11
|
+
|
|
12
|
+
const snapshotWithSession = (): LiveSessionProcessSnapshot => ({
|
|
13
|
+
sessions: [{ sessionName: TMUX_SESSION_NAME, panePids: [100] }],
|
|
14
|
+
processes: [
|
|
15
|
+
{
|
|
16
|
+
pid: 100,
|
|
17
|
+
ppid: 1,
|
|
18
|
+
commandLine: `claude ${ISSUE_URL}`,
|
|
19
|
+
sessionId: SESSION_ID,
|
|
20
|
+
currentSessionId: SESSION_ID,
|
|
21
|
+
configDir: CONFIG_DIR,
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('resolveUnansweredOwnerCallsByTmuxSessionName', () => {
|
|
27
|
+
it('asks the call provider for the transcripts of the live interactive sessions and returns what it reports', async () => {
|
|
28
|
+
const call: UnansweredOwnerCall = {
|
|
29
|
+
calledAt: '2026-08-13T10:14:00.000Z',
|
|
30
|
+
body: 'Please decide whether to merge the release branch',
|
|
31
|
+
};
|
|
32
|
+
const requestedTranscriptPaths: Map<string, string>[] = [];
|
|
33
|
+
const resolvedSessions: InteractiveLiveSession[][] = [];
|
|
34
|
+
|
|
35
|
+
const result = await resolveUnansweredOwnerCallsByTmuxSessionName({
|
|
36
|
+
liveSessionProcessSnapshotProvider: {
|
|
37
|
+
getSnapshot: async () => snapshotWithSession(),
|
|
38
|
+
},
|
|
39
|
+
interactiveLiveSessionTranscriptResolver: {
|
|
40
|
+
resolveTranscriptPaths: (sessions) => {
|
|
41
|
+
resolvedSessions.push(sessions);
|
|
42
|
+
return new Map(
|
|
43
|
+
sessions.map((session) => [
|
|
44
|
+
session.sessionName,
|
|
45
|
+
`/transcripts/${session.sessionId}.jsonl`,
|
|
46
|
+
]),
|
|
47
|
+
);
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
unansweredOwnerCallListProvider: {
|
|
51
|
+
listUnansweredOwnerCallsBySessionName: async (
|
|
52
|
+
transcriptPathBySessionName,
|
|
53
|
+
) => {
|
|
54
|
+
requestedTranscriptPaths.push(transcriptPathBySessionName);
|
|
55
|
+
return new Map([[TMUX_SESSION_NAME, [call]]]);
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(resolvedSessions[0]?.map((session) => session.sessionName)).toEqual([
|
|
61
|
+
TMUX_SESSION_NAME,
|
|
62
|
+
]);
|
|
63
|
+
expect(requestedTranscriptPaths[0]?.get(TMUX_SESSION_NAME)).toBe(
|
|
64
|
+
`/transcripts/${SESSION_ID}.jsonl`,
|
|
65
|
+
);
|
|
66
|
+
expect(result.get(TMUX_SESSION_NAME)).toEqual([call]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('keys the returned calls by the tmux session name the reconciler derives from the issue url', async () => {
|
|
70
|
+
const result = await resolveUnansweredOwnerCallsByTmuxSessionName({
|
|
71
|
+
liveSessionProcessSnapshotProvider: {
|
|
72
|
+
getSnapshot: async () => snapshotWithSession(),
|
|
73
|
+
},
|
|
74
|
+
interactiveLiveSessionTranscriptResolver: {
|
|
75
|
+
resolveTranscriptPaths: (sessions) =>
|
|
76
|
+
new Map(
|
|
77
|
+
sessions.map((session) => [session.sessionName, '/transcript']),
|
|
78
|
+
),
|
|
79
|
+
},
|
|
80
|
+
unansweredOwnerCallListProvider: {
|
|
81
|
+
listUnansweredOwnerCallsBySessionName: async (
|
|
82
|
+
transcriptPathBySessionName,
|
|
83
|
+
) =>
|
|
84
|
+
new Map(
|
|
85
|
+
[...transcriptPathBySessionName.keys()].map((sessionName) => [
|
|
86
|
+
sessionName,
|
|
87
|
+
[{ calledAt: '2026-08-13T10:14:00.000Z', body: 'body' }],
|
|
88
|
+
]),
|
|
89
|
+
),
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
expect([...result.keys()]).toEqual([toTmuxSessionName(ISSUE_URL)]);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('returns nothing when no live session exposes a transcript', async () => {
|
|
97
|
+
const result = await resolveUnansweredOwnerCallsByTmuxSessionName({
|
|
98
|
+
liveSessionProcessSnapshotProvider: {
|
|
99
|
+
getSnapshot: async () => ({ sessions: [], processes: [] }),
|
|
100
|
+
},
|
|
101
|
+
interactiveLiveSessionTranscriptResolver: {
|
|
102
|
+
resolveTranscriptPaths: () => new Map(),
|
|
103
|
+
},
|
|
104
|
+
unansweredOwnerCallListProvider: {
|
|
105
|
+
listUnansweredOwnerCallsBySessionName: async () => new Map(),
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
expect(result.size).toBe(0);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { UnansweredOwnerCall } from '../../../domain/entities/UnansweredOwnerCall';
|
|
2
|
+
import { InteractiveLiveSession } from '../../../domain/entities/InteractiveLiveSession';
|
|
3
|
+
import { ResolveInteractiveLiveSessionsUseCase } from '../../../domain/usecases/ResolveInteractiveLiveSessionsUseCase';
|
|
4
|
+
import { LiveSessionProcessSnapshotProvider } from '../../../domain/usecases/adapter-interfaces/LiveSessionProcessSnapshotProvider';
|
|
5
|
+
import { InteractiveLiveSessionTranscriptResolver } from '../../../domain/usecases/adapter-interfaces/InteractiveLiveSessionTranscriptResolver';
|
|
6
|
+
|
|
7
|
+
export type UnansweredOwnerCallListProvider = {
|
|
8
|
+
listUnansweredOwnerCallsBySessionName: (
|
|
9
|
+
transcriptPathBySessionName: Map<string, string>,
|
|
10
|
+
) => Promise<Map<string, UnansweredOwnerCall[]>>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type ResolveUnansweredOwnerCallsParams = {
|
|
14
|
+
liveSessionProcessSnapshotProvider: LiveSessionProcessSnapshotProvider;
|
|
15
|
+
interactiveLiveSessionTranscriptResolver: InteractiveLiveSessionTranscriptResolver;
|
|
16
|
+
unansweredOwnerCallListProvider: UnansweredOwnerCallListProvider;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const resolveUnansweredOwnerCallsByTmuxSessionName = async (
|
|
20
|
+
params: ResolveUnansweredOwnerCallsParams,
|
|
21
|
+
): Promise<Map<string, UnansweredOwnerCall[]>> => {
|
|
22
|
+
const snapshot =
|
|
23
|
+
await params.liveSessionProcessSnapshotProvider.getSnapshot();
|
|
24
|
+
const interactiveSessions: InteractiveLiveSession[] =
|
|
25
|
+
new ResolveInteractiveLiveSessionsUseCase().resolve(snapshot);
|
|
26
|
+
const transcriptPathBySessionName =
|
|
27
|
+
params.interactiveLiveSessionTranscriptResolver.resolveTranscriptPaths(
|
|
28
|
+
interactiveSessions,
|
|
29
|
+
);
|
|
30
|
+
return params.unansweredOwnerCallListProvider.listUnansweredOwnerCallsBySessionName(
|
|
31
|
+
transcriptPathBySessionName,
|
|
32
|
+
);
|
|
33
|
+
};
|