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.
Files changed (61) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +9 -1
  3. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +24 -0
  4. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
  5. package/bin/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.js +2 -2
  6. package/bin/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.js.map +1 -1
  7. package/bin/adapter/entry-points/handlers/OauthTokenSelectHandler.js +1 -1
  8. package/bin/adapter/entry-points/handlers/OauthTokenSelectHandler.js.map +1 -1
  9. package/bin/adapter/entry-points/handlers/inTmuxByHumanDataWriter.js +13 -1
  10. package/bin/adapter/entry-points/handlers/inTmuxByHumanDataWriter.js.map +1 -1
  11. package/bin/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.js +12 -0
  12. package/bin/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.js.map +1 -0
  13. package/bin/adapter/repositories/TranscriptOwnerCallStatusProvider.js +60 -13
  14. package/bin/adapter/repositories/TranscriptOwnerCallStatusProvider.js.map +1 -1
  15. package/bin/domain/entities/UnansweredOwnerCall.js +3 -0
  16. package/bin/domain/entities/UnansweredOwnerCall.js.map +1 -0
  17. package/bin/domain/usecases/LiveSessionOauthTokenSelectUseCase.js +1 -0
  18. package/bin/domain/usecases/LiveSessionOauthTokenSelectUseCase.js.map +1 -1
  19. package/bin/domain/usecases/OauthTokenSelectUseCase.js +15 -7
  20. package/bin/domain/usecases/OauthTokenSelectUseCase.js.map +1 -1
  21. package/bin/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.js +21 -2
  22. package/bin/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.js.map +1 -1
  23. package/package.json +1 -1
  24. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +46 -0
  25. package/src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.test.ts +21 -0
  26. package/src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.ts +6 -6
  27. package/src/adapter/entry-points/handlers/OauthTokenSelectHandler.test.ts +27 -0
  28. package/src/adapter/entry-points/handlers/OauthTokenSelectHandler.ts +5 -5
  29. package/src/adapter/entry-points/handlers/inTmuxByHumanDataWriter.test.ts +104 -1
  30. package/src/adapter/entry-points/handlers/inTmuxByHumanDataWriter.ts +19 -0
  31. package/src/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.test.ts +111 -0
  32. package/src/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.ts +33 -0
  33. package/src/adapter/repositories/TranscriptOwnerCallStatusProvider.test.ts +178 -0
  34. package/src/adapter/repositories/TranscriptOwnerCallStatusProvider.ts +106 -17
  35. package/src/domain/entities/UnansweredOwnerCall.ts +4 -0
  36. package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.test.ts +33 -0
  37. package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.ts +4 -2
  38. package/src/domain/usecases/OauthTokenSelectUseCase.test.ts +88 -0
  39. package/src/domain/usecases/OauthTokenSelectUseCase.ts +21 -13
  40. package/src/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.test.ts +99 -0
  41. package/src/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.ts +47 -1
  42. package/src/domain/usecases/intmux/UnansweredOwnerCallSessionKeyContract.test.ts +141 -0
  43. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
  44. package/types/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.d.ts +2 -2
  45. package/types/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.d.ts.map +1 -1
  46. package/types/adapter/entry-points/handlers/OauthTokenSelectHandler.d.ts +1 -1
  47. package/types/adapter/entry-points/handlers/OauthTokenSelectHandler.d.ts.map +1 -1
  48. package/types/adapter/entry-points/handlers/inTmuxByHumanDataWriter.d.ts +2 -0
  49. package/types/adapter/entry-points/handlers/inTmuxByHumanDataWriter.d.ts.map +1 -1
  50. package/types/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.d.ts +13 -0
  51. package/types/adapter/entry-points/handlers/resolveUnansweredOwnerCalls.d.ts.map +1 -0
  52. package/types/adapter/repositories/TranscriptOwnerCallStatusProvider.d.ts +5 -0
  53. package/types/adapter/repositories/TranscriptOwnerCallStatusProvider.d.ts.map +1 -1
  54. package/types/domain/entities/UnansweredOwnerCall.d.ts +5 -0
  55. package/types/domain/entities/UnansweredOwnerCall.d.ts.map +1 -0
  56. package/types/domain/usecases/LiveSessionOauthTokenSelectUseCase.d.ts +3 -2
  57. package/types/domain/usecases/LiveSessionOauthTokenSelectUseCase.d.ts.map +1 -1
  58. package/types/domain/usecases/OauthTokenSelectUseCase.d.ts +1 -0
  59. package/types/domain/usecases/OauthTokenSelectUseCase.d.ts.map +1 -1
  60. package/types/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.d.ts +19 -0
  61. 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
- return { v1, v2, v3, v4 };
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":"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"}
1
+ {"version":3,"file":"GenerateInTmuxByHumanDataUseCase.js","sourceRoot":"","sources":["../../../../src/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-issue-tower-defence-management",
3
- "version": "1.149.1",
3
+ "version": "1.151.0",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "scripts": {
@@ -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
- LiveSessionOauthTokenSelectResult,
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 four per-project files and the four index files', () => {
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
+ };