github-issue-tower-defence-management 1.146.5 → 1.146.7

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 (54) hide show
  1. package/.github/dependabot.yml +2 -0
  2. package/.github/workflows/commit-lint.yml +20 -0
  3. package/.github/workflows/umino-project.yml +0 -1
  4. package/CHANGELOG.md +14 -0
  5. package/bin/adapter/entry-points/cli/index.js +2 -2
  6. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  7. package/bin/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.js +4 -3
  8. package/bin/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.js.map +1 -1
  9. package/bin/adapter/entry-points/handlers/OauthTokenSelectHandler.js +4 -3
  10. package/bin/adapter/entry-points/handlers/OauthTokenSelectHandler.js.map +1 -1
  11. package/bin/adapter/entry-points/handlers/notifySilentTmuxSessions.js +3 -2
  12. package/bin/adapter/entry-points/handlers/notifySilentTmuxSessions.js.map +1 -1
  13. package/bin/adapter/entry-points/handlers/resetDegeneratedTmuxSessions.js +1 -1
  14. package/bin/adapter/entry-points/handlers/resetDegeneratedTmuxSessions.js.map +1 -1
  15. package/bin/adapter/repositories/FileSystemSubAgentLivenessResolver.js +19 -1
  16. package/bin/adapter/repositories/FileSystemSubAgentLivenessResolver.js.map +1 -1
  17. package/bin/adapter/repositories/TmuxSilentSessionNotificationRepository.js +73 -2
  18. package/bin/adapter/repositories/TmuxSilentSessionNotificationRepository.js.map +1 -1
  19. package/bin/domain/usecases/LiveSessionOauthTokenSelectUseCase.js +3 -1
  20. package/bin/domain/usecases/LiveSessionOauthTokenSelectUseCase.js.map +1 -1
  21. package/bin/domain/usecases/OauthTokenSelectUseCase.js +13 -4
  22. package/bin/domain/usecases/OauthTokenSelectUseCase.js.map +1 -1
  23. package/package.json +1 -1
  24. package/renovate.json +5 -0
  25. package/src/adapter/entry-points/cli/index.ts +2 -2
  26. package/src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.test.ts +7 -4
  27. package/src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.ts +4 -1
  28. package/src/adapter/entry-points/handlers/OauthTokenSelectHandler.test.ts +6 -2
  29. package/src/adapter/entry-points/handlers/OauthTokenSelectHandler.ts +8 -2
  30. package/src/adapter/entry-points/handlers/notifySilentTmuxSessions.test.ts +1 -0
  31. package/src/adapter/entry-points/handlers/notifySilentTmuxSessions.ts +14 -2
  32. package/src/adapter/entry-points/handlers/resetDegeneratedTmuxSessions.ts +4 -1
  33. package/src/adapter/repositories/FileSystemSubAgentLivenessResolver.test.ts +49 -1
  34. package/src/adapter/repositories/FileSystemSubAgentLivenessResolver.ts +21 -1
  35. package/src/adapter/repositories/TmuxSilentSessionNotificationRepository.test.ts +157 -15
  36. package/src/adapter/repositories/TmuxSilentSessionNotificationRepository.ts +115 -1
  37. package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.test.ts +77 -5
  38. package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.ts +10 -1
  39. package/src/domain/usecases/OauthTokenSelectUseCase.test.ts +39 -8
  40. package/src/domain/usecases/OauthTokenSelectUseCase.ts +25 -5
  41. package/types/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.d.ts +3 -1
  42. package/types/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.d.ts.map +1 -1
  43. package/types/adapter/entry-points/handlers/OauthTokenSelectHandler.d.ts +3 -2
  44. package/types/adapter/entry-points/handlers/OauthTokenSelectHandler.d.ts.map +1 -1
  45. package/types/adapter/entry-points/handlers/notifySilentTmuxSessions.d.ts +1 -0
  46. package/types/adapter/entry-points/handlers/notifySilentTmuxSessions.d.ts.map +1 -1
  47. package/types/adapter/entry-points/handlers/resetDegeneratedTmuxSessions.d.ts.map +1 -1
  48. package/types/adapter/repositories/FileSystemSubAgentLivenessResolver.d.ts +1 -0
  49. package/types/adapter/repositories/FileSystemSubAgentLivenessResolver.d.ts.map +1 -1
  50. package/types/adapter/repositories/TmuxSilentSessionNotificationRepository.d.ts +14 -1
  51. package/types/adapter/repositories/TmuxSilentSessionNotificationRepository.d.ts.map +1 -1
  52. package/types/domain/usecases/LiveSessionOauthTokenSelectUseCase.d.ts.map +1 -1
  53. package/types/domain/usecases/OauthTokenSelectUseCase.d.ts +4 -1
  54. package/types/domain/usecases/OauthTokenSelectUseCase.d.ts.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"OauthTokenSelectUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/OauthTokenSelectUseCase.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"OauthTokenSelectUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/OauthTokenSelectUseCase.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.146.5",
3
+ "version": "1.146.7",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "scripts": {
package/renovate.json CHANGED
@@ -17,6 +17,11 @@
17
17
  "ignorePaths": ["**/generated/*", "**/_gen/*"],
18
18
  "osvVulnerabilityAlerts": true,
19
19
  "packageRules": [
20
+ {
21
+ "matchUpdateTypes": ["minor", "patch", "pin", "digest"],
22
+ "groupName": "all non-major dependencies",
23
+ "groupSlug": "all-non-major"
24
+ },
20
25
  {
21
26
  "matchPackageNames": ["eslint"],
22
27
  "allowedVersions": "<9.0.0"
@@ -919,7 +919,7 @@ addServeWebOptions(program.command('serveConsole'))
919
919
  program
920
920
  .command('selectOauthToken')
921
921
  .description(
922
- 'Print exactly one Claude Code OAuth token chosen by a rate-limit-aware filter. When tokens carry differing per-token selectionWeight values, the choice among rate-limit-eligible tokens is weighted-random by that weight (default 1), so a lower-weight token is chosen proportionally less often; uniform weights preserve the deterministic soonest-7d-reset choice. The token string is written to stdout (pipeable); the per-candidate decision trace is written to stderr. Exits non-zero when no token passes the filter.',
922
+ 'Print exactly one Claude Code OAuth token chosen by a rate-limit-aware filter. Among rate-limit-eligible tokens the choice is weighted-random, with each token weighted by its per-token selectionWeight (default 1) multiplied by how urgent its 7d window is (the free share of that window times 168 divided by the hours left before it resets, with those hours floored at 1), so a token holding unused allowance that resets soon is chosen more often. When every eligible weight is identical, the choice stays deterministic and no random draw is made. The token string is written to stdout (pipeable); the per-candidate decision trace is written to stderr. Exits non-zero when no token passes the filter.',
923
923
  )
924
924
  .option(
925
925
  '--tokenListJsonPath <path>',
@@ -951,7 +951,7 @@ program
951
951
  program
952
952
  .command('selectLiveSessionOauthToken')
953
953
  .description(
954
- 'Print exactly one Claude Code OAuth token chosen for a new live interactive session. Among rate-limit-eligible tokens it prefers the one with the fewest current live sessions (by distinct CLAUDE_CODE_SESSION_ID found in running Claude Code processes), tiebreaking on the soonest 7d reset. When tokens carry differing per-token selectionWeight values, the choice among eligible tokens is weighted-random by that weight (default 1), so a lower-weight token is chosen proportionally less often; uniform weights preserve the fewest-live-sessions-then-soonest-7d-reset choice. The token string is written to stdout (pipeable); the per-candidate decision trace is written to stderr. Exits non-zero when no token passes the filter.',
954
+ 'Print exactly one Claude Code OAuth token chosen for a new live interactive session. Among rate-limit-eligible tokens the choice is weighted-random, with each token weighted by its per-token selectionWeight (default 1), multiplied by how urgent its 7d window is (the free share of that window times 168 divided by the hours left before it resets, with those hours floored at 1), and divided by one plus its current live session count (by distinct CLAUDE_CODE_SESSION_ID found in running Claude Code processes). A token whose 7d window still holds unused allowance and resets soon is therefore chosen more often, while occupancy keeps sessions spread across tokens. When every eligible weight is identical, the choice stays deterministic and no random draw is made. The token string is written to stdout (pipeable); the per-candidate decision trace is written to stderr. Exits non-zero when no token passes the filter.',
955
955
  )
956
956
  .option(
957
957
  '--tokenListJsonPath <path>',
@@ -109,10 +109,12 @@ describe('LiveSessionOauthTokenSelectHandler', () => {
109
109
 
110
110
  const buildHandler = (
111
111
  sessions: ClaudeLiveSession[],
112
+ random: () => number = () => 0.5,
112
113
  ): LiveSessionOauthTokenSelectHandler =>
113
114
  new LiveSessionOauthTokenSelectHandler(
114
115
  new LiveSessionOauthTokenSelectUseCase(),
115
116
  new FakeClaudeLiveSessionRepository(sessions),
117
+ random,
116
118
  );
117
119
 
118
120
  const writeFableRejectionCache = (token: string, resetsAt: number): void => {
@@ -163,7 +165,7 @@ describe('LiveSessionOauthTokenSelectHandler', () => {
163
165
  );
164
166
  });
165
167
 
166
- it('selects the eligible token with the fewest live sessions', () => {
168
+ it('selects the eligible token with fewer live sessions for a draw inside its weight slice', () => {
167
169
  writeTokenList([
168
170
  { name: 'busy', token: 'fake-busy' },
169
171
  { name: 'idle', token: 'fake-idle' },
@@ -181,9 +183,10 @@ describe('LiveSessionOauthTokenSelectHandler', () => {
181
183
  sevenDayReset: NOW + 6 * DAY,
182
184
  });
183
185
 
184
- const handler = buildHandler([
185
- { token: 'fake-busy', sessionKey: 'session-a' },
186
- ]);
186
+ const handler = buildHandler(
187
+ [{ token: 'fake-busy', sessionKey: 'session-a' }],
188
+ () => 0.9,
189
+ );
187
190
  const output = handler.handle({
188
191
  tokenListJsonPath: tokenListPath,
189
192
  cacheDirectory,
@@ -8,6 +8,7 @@ import {
8
8
  FIVE_HOUR_MIN_FREE_RATIO,
9
9
  OauthTokenCandidate,
10
10
  SEVEN_DAY_MIN_FREE_RATIO,
11
+ SelectionRandom,
11
12
  } from '../../../domain/usecases/OauthTokenSelectUseCase';
12
13
  import { ProcClaudeLiveSessionRepository } from '../../repositories/ProcClaudeLiveSessionRepository';
13
14
  import { FABLE_LIMIT_TYPE, readRateLimit } from '../../proxy/RateLimitCache';
@@ -33,6 +34,7 @@ export class LiveSessionOauthTokenSelectHandler {
33
34
  constructor(
34
35
  private readonly useCase: LiveSessionOauthTokenSelectUseCase = new LiveSessionOauthTokenSelectUseCase(),
35
36
  private readonly liveSessionRepository: ClaudeLiveSessionRepository = new ProcClaudeLiveSessionRepository(),
37
+ private readonly random: SelectionRandom = Math.random,
36
38
  ) {}
37
39
 
38
40
  handle = (
@@ -96,6 +98,7 @@ export class LiveSessionOauthTokenSelectHandler {
96
98
  candidates,
97
99
  liveSessions,
98
100
  input.nowEpochSeconds,
101
+ this.random,
99
102
  );
100
103
 
101
104
  return {
@@ -125,7 +128,7 @@ export class LiveSessionOauthTokenSelectHandler {
125
128
  );
126
129
  } else {
127
130
  lines.push(
128
- `Selected ${result.selected.name} (fewest live sessions, then soonest 7d reset among eligible tokens).`,
131
+ `Selected ${result.selected.name} (weighted by how soon the 7d window resets, how much of it is free, and how few live sessions the token carries).`,
129
132
  );
130
133
  }
131
134
 
@@ -1,6 +1,7 @@
1
1
  import * as fs from 'fs';
2
2
  import * as os from 'os';
3
3
  import * as path from 'path';
4
+ import { OauthTokenSelectUseCase } from '../../../domain/usecases/OauthTokenSelectUseCase';
4
5
  import { FABLE_LIMIT_TYPE, hashToken } from '../../proxy/RateLimitCache';
5
6
  import {
6
7
  OauthTokenSelectHandler,
@@ -122,7 +123,7 @@ describe('OauthTokenSelectHandler', () => {
122
123
  );
123
124
  };
124
125
 
125
- it('selects the eligible token with the soonest 7d reset', () => {
126
+ it('selects the token whose 7d window resets soonest for a draw inside its weight slice', () => {
126
127
  writeTokenList([
127
128
  { name: 'far', token: 'fake-far' },
128
129
  { name: 'soon', token: 'fake-soon' },
@@ -140,7 +141,10 @@ describe('OauthTokenSelectHandler', () => {
140
141
  sevenDayReset: NOW + 2 * DAY,
141
142
  });
142
143
 
143
- const handler = new OauthTokenSelectHandler();
144
+ const handler = new OauthTokenSelectHandler(
145
+ new OauthTokenSelectUseCase(),
146
+ () => 0.5,
147
+ );
144
148
  const output = handler.handle({
145
149
  tokenListJsonPath: tokenListPath,
146
150
  cacheDirectory,
@@ -3,6 +3,7 @@ import {
3
3
  OauthTokenCandidate,
4
4
  OauthTokenSelectResult,
5
5
  OauthTokenSelectUseCase,
6
+ SelectionRandom,
6
7
  } from '../../../domain/usecases/OauthTokenSelectUseCase';
7
8
  import {
8
9
  FABLE_LIMIT_TYPE,
@@ -55,6 +56,7 @@ export const resolveCacheDirectory = (
55
56
  export class OauthTokenSelectHandler {
56
57
  constructor(
57
58
  private readonly useCase: OauthTokenSelectUseCase = new OauthTokenSelectUseCase(),
59
+ private readonly random: SelectionRandom = Math.random,
58
60
  ) {}
59
61
 
60
62
  handle = (
@@ -112,7 +114,11 @@ export class OauthTokenSelectHandler {
112
114
  },
113
115
  );
114
116
 
115
- const result = this.useCase.run(candidates, input.nowEpochSeconds);
117
+ const result = this.useCase.run(
118
+ candidates,
119
+ input.nowEpochSeconds,
120
+ this.random,
121
+ );
116
122
 
117
123
  return {
118
124
  selectedToken: result.selected?.token ?? null,
@@ -139,7 +145,7 @@ export class OauthTokenSelectHandler {
139
145
  lines.push('No eligible token passed the rate-limit filter.');
140
146
  } else {
141
147
  lines.push(
142
- `Selected ${result.selected.name} (soonest 7d reset among eligible tokens).`,
148
+ `Selected ${result.selected.name} (weighted by how soon the 7d window resets and how much of it is free among eligible tokens).`,
143
149
  );
144
150
  }
145
151
 
@@ -147,6 +147,7 @@ describe('notifySilentTmuxSessions', () => {
147
147
  messageTemplates: EMPTY_TEMPLATES,
148
148
  now: NOW,
149
149
  ...DEFAULT_NOTIFY_SILENT_TMUX_SESSIONS_PARAMS,
150
+ submitPushOutWaitMilliseconds: 0,
150
151
  });
151
152
 
152
153
  it('sends a main stalled notification to a silent github-named live session that was already a candidate in the previous cycle', async () => {
@@ -18,7 +18,11 @@ import { LocalProcessLiveSessionProcessSnapshotProvider } from '../../repositori
18
18
  import { ProcFsProcessEnvironReader } from '../../repositories/ProcFsProcessEnvironReader';
19
19
  import { FileSystemInteractiveLiveSessionTranscriptResolver } from '../../repositories/FileSystemInteractiveLiveSessionTranscriptResolver';
20
20
  import { FileSystemSessionOutputActivityRepository } from '../../repositories/FileSystemSessionOutputActivityRepository';
21
- import { TmuxSilentSessionNotificationRepository } from '../../repositories/TmuxSilentSessionNotificationRepository';
21
+ import {
22
+ DEFAULT_SUBMIT_PUSH_OUT_ATTEMPT_LIMIT,
23
+ DEFAULT_SUBMIT_PUSH_OUT_WAIT_MILLISECONDS,
24
+ TmuxSilentSessionNotificationRepository,
25
+ } from '../../repositories/TmuxSilentSessionNotificationRepository';
22
26
  import { NoUnansweredOwnerCallStatusProvider } from '../../repositories/NoUnansweredOwnerCallStatusProvider';
23
27
  import { TranscriptOwnerCallStatusProvider } from '../../repositories/TranscriptOwnerCallStatusProvider';
24
28
  import { TranscriptRefusalTailStatusProvider } from '../../repositories/TranscriptRefusalTailStatusProvider';
@@ -57,6 +61,7 @@ export type NotifySilentTmuxSessionsParams = {
57
61
  hubTaskStatusCacheStateFilePath: string | null;
58
62
  hubTaskStatusCacheTtlSeconds: number;
59
63
  messageTemplates: SilentSessionMessageTemplates;
64
+ submitPushOutWaitMilliseconds?: number;
60
65
  now: Date;
61
66
  };
62
67
 
@@ -121,6 +126,7 @@ export const notifySilentTmuxSessions = async (
121
126
  hubTaskStatusCacheStateFilePath,
122
127
  hubTaskStatusCacheTtlSeconds,
123
128
  messageTemplates,
129
+ submitPushOutWaitMilliseconds,
124
130
  now,
125
131
  } = params;
126
132
  if (!enabled) {
@@ -149,7 +155,13 @@ export const notifySilentTmuxSessions = async (
149
155
  now,
150
156
  ),
151
157
  createOwnerCallStatusProvider(ownerCallMarker),
152
- new TmuxSilentSessionNotificationRepository(localCommandRunner),
158
+ new TmuxSilentSessionNotificationRepository(
159
+ localCommandRunner,
160
+ new RealSleeper(),
161
+ DEFAULT_SUBMIT_PUSH_OUT_ATTEMPT_LIMIT,
162
+ submitPushOutWaitMilliseconds ??
163
+ DEFAULT_SUBMIT_PUSH_OUT_WAIT_MILLISECONDS,
164
+ ),
153
165
  candidateDebounceStateFilePath !== null
154
166
  ? new FileSystemSilentSessionCandidateStateRepository(
155
167
  candidateDebounceStateFilePath,
@@ -46,7 +46,10 @@ export const resetDegeneratedTmuxSessions = async (
46
46
  ),
47
47
  new FileSystemInteractiveLiveSessionTranscriptResolver(),
48
48
  new FileSystemSessionAssistantTurnsRepository(),
49
- new TmuxSilentSessionNotificationRepository(localCommandRunner),
49
+ new TmuxSilentSessionNotificationRepository(
50
+ localCommandRunner,
51
+ new RealSleeper(),
52
+ ),
50
53
  new NodeTmuxSessionRepository(localCommandRunner),
51
54
  cooldownStateFilePath !== null
52
55
  ? new FileSystemSessionDegenerationCooldownStateRepository(
@@ -69,7 +69,7 @@ describe('FileSystemSubAgentLivenessResolver', () => {
69
69
  expect(result).toEqual(new Set());
70
70
  });
71
71
 
72
- it('returns null when the running file does not exist', async () => {
72
+ it('returns an empty set when the runtime root exists and the session has no running file', async () => {
73
73
  const resolver = new FileSystemSubAgentLivenessResolver(runtimeRoot);
74
74
 
75
75
  const result = await resolver.resolveLiveSubAgentIds({
@@ -77,6 +77,19 @@ describe('FileSystemSubAgentLivenessResolver', () => {
77
77
  mainTranscriptPath,
78
78
  });
79
79
 
80
+ expect(result).toEqual(new Set());
81
+ });
82
+
83
+ it('returns null when the configured runtime root directory does not exist', async () => {
84
+ const resolver = new FileSystemSubAgentLivenessResolver(
85
+ path.join(runtimeRoot, 'absent-runtime-root'),
86
+ );
87
+
88
+ const result = await resolver.resolveLiveSubAgentIds({
89
+ sessionName,
90
+ mainTranscriptPath,
91
+ });
92
+
80
93
  expect(result).toBeNull();
81
94
  });
82
95
 
@@ -178,4 +191,39 @@ describe('running-subagents record agent id contract with TranscriptSessionSubAg
178
191
  `agent-${liveAgentId}`,
179
192
  ]);
180
193
  });
194
+
195
+ it('reports no sub-agent when the runtime root exists and the session has no running-subagents record', async () => {
196
+ const subAgentsDirectory = path.join(
197
+ transcriptRoot,
198
+ cwdSlug,
199
+ sessionUuid,
200
+ 'subagents',
201
+ );
202
+ fs.mkdirSync(subAgentsDirectory, { recursive: true });
203
+ fs.writeFileSync(
204
+ path.join(subAgentsDirectory, `agent-${liveAgentId}.jsonl`),
205
+ JSON.stringify({
206
+ type: 'assistant',
207
+ timestamp: '2026-07-20T11:00:00.000Z',
208
+ message: {
209
+ stop_reason: 'tool_use',
210
+ content: [{ type: 'tool_use', name: 'Read', input: {} }],
211
+ },
212
+ }),
213
+ 'utf8',
214
+ );
215
+ const repository = new TranscriptSessionSubAgentActivityRepository(
216
+ new FileSystemSubAgentTranscriptDirectoryResolver(transcriptRoot),
217
+ emptyProcessLister,
218
+ now,
219
+ new FileSystemSubAgentLivenessResolver(runtimeRoot),
220
+ );
221
+
222
+ const result = await repository.listSubAgentActivitiesBySessionName(
223
+ [sessionName],
224
+ new Map([[sessionName, mainTranscriptPath]]),
225
+ );
226
+
227
+ expect(result.get(sessionName)).toBeUndefined();
228
+ });
181
229
  });
@@ -4,6 +4,12 @@ import { SubAgentLivenessResolver } from '../../domain/usecases/adapter-interfac
4
4
 
5
5
  const RUNNING_SUBAGENTS_FILE_NAME = 'running-subagents.txt';
6
6
 
7
+ const isMissingFileError = (error: unknown): boolean =>
8
+ typeof error === 'object' &&
9
+ error !== null &&
10
+ 'code' in error &&
11
+ error.code === 'ENOENT';
12
+
7
13
  export class FileSystemSubAgentLivenessResolver implements SubAgentLivenessResolver {
8
14
  constructor(private readonly runtimeRootDirectory: string | null) {}
9
15
 
@@ -20,7 +26,10 @@ export class FileSystemSubAgentLivenessResolver implements SubAgentLivenessResol
20
26
  let content: string;
21
27
  try {
22
28
  content = fs.readFileSync(runningFilePath, 'utf8');
23
- } catch {
29
+ } catch (error) {
30
+ if (isMissingFileError(error) && this.hasReadableRuntimeRootDirectory()) {
31
+ return new Set();
32
+ }
24
33
  return null;
25
34
  }
26
35
  const liveIds = new Set<string>();
@@ -33,6 +42,17 @@ export class FileSystemSubAgentLivenessResolver implements SubAgentLivenessResol
33
42
  return liveIds;
34
43
  };
35
44
 
45
+ private hasReadableRuntimeRootDirectory = (): boolean => {
46
+ if (this.runtimeRootDirectory === null) {
47
+ return false;
48
+ }
49
+ try {
50
+ return fs.statSync(this.runtimeRootDirectory).isDirectory();
51
+ } catch {
52
+ return false;
53
+ }
54
+ };
55
+
36
56
  private resolveRunningSubAgentsFilePath = (
37
57
  mainTranscriptPath: string | null,
38
58
  ): string | null => {
@@ -1,4 +1,5 @@
1
1
  import { LocalCommandRunner } from '../../domain/usecases/adapter-interfaces/LocalCommandRunner';
2
+ import { Sleeper } from '../../domain/usecases/adapter-interfaces/Sleeper';
2
3
  import { TmuxSilentSessionNotificationRepository } from './TmuxSilentSessionNotificationRepository';
3
4
 
4
5
  type Mocked<T> = jest.Mocked<T> & jest.MockedObject<T>;
@@ -11,14 +12,55 @@ const createMockRunner = (): Mocked<LocalCommandRunner> => ({
11
12
  }),
12
13
  });
13
14
 
15
+ const createMockSleeper = (): Mocked<Sleeper> => ({
16
+ sleep: jest.fn().mockResolvedValue(undefined),
17
+ });
18
+
19
+ const sessionName = 'https_//github_com/owner/repo/issues/9';
20
+
21
+ const paneWithInputBoxContent = (inputBoxContent: string): string =>
22
+ [
23
+ '● The previous assistant output line',
24
+ '',
25
+ '────────────────────────────────────────',
26
+ inputBoxContent,
27
+ '────────────────────────────────────────',
28
+ ' {Opus 4.5} (dev1) CTX[██░░░░░░░░]16%',
29
+ ].join('\n');
30
+
31
+ const createPaneAwareRunner = (
32
+ paneTexts: string[],
33
+ ): Mocked<LocalCommandRunner> => {
34
+ let capturedPaneIndex = 0;
35
+ return {
36
+ runCommand: jest.fn().mockImplementation((_program, args: string[]) => {
37
+ if (args[0] !== 'capture-pane') {
38
+ return Promise.resolve({ stdout: '', stderr: '', exitCode: 0 });
39
+ }
40
+ const paneText =
41
+ paneTexts[Math.min(capturedPaneIndex, paneTexts.length - 1)];
42
+ capturedPaneIndex += 1;
43
+ return Promise.resolve({ stdout: paneText, stderr: '', exitCode: 0 });
44
+ }),
45
+ };
46
+ };
47
+
48
+ const submitKeyCallCount = (runner: Mocked<LocalCommandRunner>): number =>
49
+ runner.runCommand.mock.calls.filter(
50
+ (call) => call[1][0] === 'send-keys' && call[1][3] === 'Enter',
51
+ ).length;
52
+
14
53
  describe('TmuxSilentSessionNotificationRepository', () => {
15
54
  describe('sendSelfCheckNotification', () => {
16
55
  it('wraps the message in bracketed-paste framing, sends it literally, then submits it with Enter', async () => {
17
56
  const runner = createMockRunner();
18
- const repository = new TmuxSilentSessionNotificationRepository(runner);
57
+ const repository = new TmuxSilentSessionNotificationRepository(
58
+ runner,
59
+ createMockSleeper(),
60
+ );
19
61
 
20
62
  await repository.sendSelfCheckNotification(
21
- 'https_//github_com/owner/repo/issues/9',
63
+ sessionName,
22
64
  'self check message',
23
65
  );
24
66
 
@@ -27,26 +69,26 @@ describe('TmuxSilentSessionNotificationRepository', () => {
27
69
  [
28
70
  'send-keys',
29
71
  '-t',
30
- 'https_//github_com/owner/repo/issues/9',
72
+ sessionName,
31
73
  '-l',
32
74
  '\x1b[200~self check message\x1b[201~',
33
75
  ],
34
76
  ]);
35
77
  expect(runner.runCommand.mock.calls[1]).toEqual([
36
78
  'tmux',
37
- ['send-keys', '-t', 'https_//github_com/owner/repo/issues/9', 'Enter'],
79
+ ['send-keys', '-t', sessionName, 'Enter'],
38
80
  ]);
39
81
  });
40
82
 
41
83
  it('flattens a multi-line message into a single line so no newline reaches the input box', async () => {
42
84
  const runner = createMockRunner();
43
- const repository = new TmuxSilentSessionNotificationRepository(runner);
85
+ const repository = new TmuxSilentSessionNotificationRepository(
86
+ runner,
87
+ createMockSleeper(),
88
+ );
44
89
  const multiLineMessage = 'first line\nsecond line\n\nthird line';
45
90
 
46
- await repository.sendSelfCheckNotification(
47
- 'https_//github_com/owner/repo/issues/9',
48
- multiLineMessage,
49
- );
91
+ await repository.sendSelfCheckNotification(sessionName, multiLineMessage);
50
92
 
51
93
  const literalArgument = runner.runCommand.mock.calls[0][1][4];
52
94
  expect(literalArgument).not.toContain('\n');
@@ -58,10 +100,13 @@ describe('TmuxSilentSessionNotificationRepository', () => {
58
100
 
59
101
  it('keeps the bracketed-paste framing around the flattened message', async () => {
60
102
  const runner = createMockRunner();
61
- const repository = new TmuxSilentSessionNotificationRepository(runner);
103
+ const repository = new TmuxSilentSessionNotificationRepository(
104
+ runner,
105
+ createMockSleeper(),
106
+ );
62
107
 
63
108
  await repository.sendSelfCheckNotification(
64
- 'https_//github_com/owner/repo/issues/9',
109
+ sessionName,
65
110
  'first line\nsecond line',
66
111
  );
67
112
 
@@ -71,16 +116,19 @@ describe('TmuxSilentSessionNotificationRepository', () => {
71
116
  expect(literalBytes.endsWith('1b5b3230317e')).toBe(true);
72
117
  expect(runner.runCommand.mock.calls[1]).toEqual([
73
118
  'tmux',
74
- ['send-keys', '-t', 'https_//github_com/owner/repo/issues/9', 'Enter'],
119
+ ['send-keys', '-t', sessionName, 'Enter'],
75
120
  ]);
76
121
  });
77
122
 
78
123
  it('collapses the run of spaces left by consecutive line breaks', async () => {
79
124
  const runner = createMockRunner();
80
- const repository = new TmuxSilentSessionNotificationRepository(runner);
125
+ const repository = new TmuxSilentSessionNotificationRepository(
126
+ runner,
127
+ createMockSleeper(),
128
+ );
81
129
 
82
130
  await repository.sendSelfCheckNotification(
83
- 'https_//github_com/owner/repo/issues/9',
131
+ sessionName,
84
132
  'section one\n\n\nsection two',
85
133
  );
86
134
 
@@ -97,7 +145,10 @@ describe('TmuxSilentSessionNotificationRepository', () => {
97
145
  stderr: "can't find session",
98
146
  exitCode: 1,
99
147
  });
100
- const repository = new TmuxSilentSessionNotificationRepository(runner);
148
+ const repository = new TmuxSilentSessionNotificationRepository(
149
+ runner,
150
+ createMockSleeper(),
151
+ );
101
152
 
102
153
  await expect(
103
154
  repository.sendSelfCheckNotification('missing_session', 'message'),
@@ -105,5 +156,96 @@ describe('TmuxSilentSessionNotificationRepository', () => {
105
156
  'Failed to send notification to tmux session "missing_session"',
106
157
  );
107
158
  });
159
+
160
+ it('pushes the message out with another Enter while it is still sitting in the input box', async () => {
161
+ const runner = createPaneAwareRunner([
162
+ paneWithInputBoxContent(
163
+ '❯ [TDPM_SILENT_SESSION_SELF_CHECK_REMINDER] report your state',
164
+ ),
165
+ paneWithInputBoxContent('❯ '),
166
+ ]);
167
+ const repository = new TmuxSilentSessionNotificationRepository(
168
+ runner,
169
+ createMockSleeper(),
170
+ );
171
+
172
+ await repository.sendSelfCheckNotification(
173
+ sessionName,
174
+ '[TDPM_SILENT_SESSION_SELF_CHECK_REMINDER] report your state',
175
+ );
176
+
177
+ expect(submitKeyCallCount(runner)).toBe(2);
178
+ expect(runner.runCommand.mock.calls[2]).toEqual([
179
+ 'tmux',
180
+ ['capture-pane', '-p', '-t', sessionName],
181
+ ]);
182
+ });
183
+
184
+ it('sends no further Enter once the input box no longer holds the message', async () => {
185
+ const runner = createPaneAwareRunner([paneWithInputBoxContent('❯ ')]);
186
+ const repository = new TmuxSilentSessionNotificationRepository(
187
+ runner,
188
+ createMockSleeper(),
189
+ );
190
+
191
+ await repository.sendSelfCheckNotification(
192
+ sessionName,
193
+ '[TDPM_SILENT_SESSION_SELF_CHECK_REMINDER] report your state',
194
+ );
195
+
196
+ expect(submitKeyCallCount(runner)).toBe(1);
197
+ });
198
+
199
+ it('sends no further Enter while the message is held as a queued message', async () => {
200
+ const runner = createPaneAwareRunner([
201
+ paneWithInputBoxContent('❯ Press up to edit queued messages'),
202
+ ]);
203
+ const repository = new TmuxSilentSessionNotificationRepository(
204
+ runner,
205
+ createMockSleeper(),
206
+ );
207
+
208
+ await repository.sendSelfCheckNotification(
209
+ sessionName,
210
+ '[TDPM_SILENT_SESSION_SELF_CHECK_REMINDER] report your state',
211
+ );
212
+
213
+ expect(submitKeyCallCount(runner)).toBe(1);
214
+ });
215
+
216
+ it('stops pushing after the configured attempt limit so newlines cannot pile up', async () => {
217
+ const runner = createPaneAwareRunner([
218
+ paneWithInputBoxContent(
219
+ '❯ [TDPM_SILENT_SESSION_SELF_CHECK_REMINDER] report your state',
220
+ ),
221
+ ]);
222
+ const repository = new TmuxSilentSessionNotificationRepository(
223
+ runner,
224
+ createMockSleeper(),
225
+ 2,
226
+ );
227
+
228
+ await repository.sendSelfCheckNotification(
229
+ sessionName,
230
+ '[TDPM_SILENT_SESSION_SELF_CHECK_REMINDER] report your state',
231
+ );
232
+
233
+ expect(submitKeyCallCount(runner)).toBe(3);
234
+ });
235
+
236
+ it('waits for the bracketed-paste parsing and rendering window before reading the input box back', async () => {
237
+ const runner = createPaneAwareRunner([paneWithInputBoxContent('❯ ')]);
238
+ const sleeper = createMockSleeper();
239
+ const repository = new TmuxSilentSessionNotificationRepository(
240
+ runner,
241
+ sleeper,
242
+ 3,
243
+ 2500,
244
+ );
245
+
246
+ await repository.sendSelfCheckNotification(sessionName, 'self check');
247
+
248
+ expect(sleeper.sleep).toHaveBeenCalledWith(2500);
249
+ });
108
250
  });
109
251
  });