github-issue-tower-defence-management 1.117.2 → 1.117.4

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.
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>TDPM Console</title>
7
- <script type="module" crossorigin src="/assets/index-CJJsRF8i.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-BXcfEKkX.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-DGVKvwux.css">
9
9
  </head>
10
10
  <body>
@@ -98,6 +98,78 @@ describe('TranscriptSessionSubAgentActivityRepository', () => {
98
98
  },
99
99
  ];
100
100
 
101
+ const finalTextAnswerEntries = (startTimestamp: string): object[] => [
102
+ { type: 'user', timestamp: startTimestamp, message: { role: 'user' } },
103
+ {
104
+ type: 'assistant',
105
+ timestamp: startTimestamp,
106
+ message: {
107
+ role: 'assistant',
108
+ stop_reason: 'tool_use',
109
+ content: [{ type: 'tool_use', name: 'Bash', input: {} }],
110
+ },
111
+ },
112
+ {
113
+ type: 'user',
114
+ timestamp: startTimestamp,
115
+ message: {
116
+ role: 'user',
117
+ content: [{ type: 'tool_result', content: '' }],
118
+ },
119
+ },
120
+ {
121
+ type: 'assistant',
122
+ timestamp: startTimestamp,
123
+ message: {
124
+ role: 'assistant',
125
+ stop_reason: null,
126
+ content: [{ type: 'text', text: 'All verification complete.' }],
127
+ },
128
+ },
129
+ ];
130
+
131
+ const interruptedEntries = (startTimestamp: string): object[] => [
132
+ { type: 'user', timestamp: startTimestamp, message: { role: 'user' } },
133
+ {
134
+ type: 'assistant',
135
+ timestamp: startTimestamp,
136
+ message: {
137
+ role: 'assistant',
138
+ stop_reason: 'tool_use',
139
+ content: [{ type: 'tool_use', name: 'Bash', input: {} }],
140
+ },
141
+ },
142
+ {
143
+ type: 'user',
144
+ timestamp: startTimestamp,
145
+ message: {
146
+ role: 'user',
147
+ content: [{ type: 'text', text: '[Request interrupted by user]' }],
148
+ },
149
+ },
150
+ ];
151
+
152
+ const pendingToolUseEntries = (startTimestamp: string): object[] => [
153
+ { type: 'user', timestamp: startTimestamp, message: { role: 'user' } },
154
+ {
155
+ type: 'user',
156
+ timestamp: startTimestamp,
157
+ message: {
158
+ role: 'user',
159
+ content: [{ type: 'tool_result', content: '' }],
160
+ },
161
+ },
162
+ {
163
+ type: 'assistant',
164
+ timestamp: startTimestamp,
165
+ message: {
166
+ role: 'assistant',
167
+ stop_reason: 'tool_use',
168
+ content: [{ type: 'tool_use', name: 'Bash', input: {} }],
169
+ },
170
+ },
171
+ ];
172
+
101
173
  it('reports a running sub-agent with silent seconds from the file mtime and running seconds from the first entry', async () => {
102
174
  const sessionName = 'https_//github_com/owner/repo/issues/9';
103
175
  const startTimestamp = '2026-06-27T11:45:00.000Z';
@@ -167,6 +239,74 @@ describe('TranscriptSessionSubAgentActivityRepository', () => {
167
239
  expect(result.size).toBe(0);
168
240
  });
169
241
 
242
+ it('excludes a finished sub-agent whose last entry is a final assistant text answer with a null stop_reason', async () => {
243
+ const sessionName = 'https_//github_com/owner/repo/issues/9';
244
+ writeAgentTranscript(
245
+ sessionName,
246
+ 'finaltext',
247
+ finalTextAnswerEntries('2026-06-27T11:00:00.000Z'),
248
+ nowEpochSeconds - 600,
249
+ );
250
+ const repository = new TranscriptSessionSubAgentActivityRepository(
251
+ createResolver(),
252
+ now,
253
+ noOpCeilingSeconds,
254
+ );
255
+
256
+ const result = await repository.listSubAgentActivitiesBySessionName(
257
+ [sessionName],
258
+ transcriptMapFor([sessionName]),
259
+ );
260
+
261
+ expect(result.size).toBe(0);
262
+ });
263
+
264
+ it('excludes a sub-agent whose transcript ends with a user interruption text entry', async () => {
265
+ const sessionName = 'https_//github_com/owner/repo/issues/9';
266
+ writeAgentTranscript(
267
+ sessionName,
268
+ 'interrupted',
269
+ interruptedEntries('2026-06-27T11:00:00.000Z'),
270
+ nowEpochSeconds - 600,
271
+ );
272
+ const repository = new TranscriptSessionSubAgentActivityRepository(
273
+ createResolver(),
274
+ now,
275
+ noOpCeilingSeconds,
276
+ );
277
+
278
+ const result = await repository.listSubAgentActivitiesBySessionName(
279
+ [sessionName],
280
+ transcriptMapFor([sessionName]),
281
+ );
282
+
283
+ expect(result.size).toBe(0);
284
+ });
285
+
286
+ it('flags a genuinely-active sub-agent whose last entry is a pending tool call', async () => {
287
+ const sessionName = 'https_//github_com/owner/repo/issues/9';
288
+ writeAgentTranscript(
289
+ sessionName,
290
+ 'pending',
291
+ pendingToolUseEntries('2026-06-27T11:45:00.000Z'),
292
+ nowEpochSeconds - 600,
293
+ );
294
+ const repository = new TranscriptSessionSubAgentActivityRepository(
295
+ createResolver(),
296
+ now,
297
+ noOpCeilingSeconds,
298
+ );
299
+
300
+ const result = await repository.listSubAgentActivitiesBySessionName(
301
+ [sessionName],
302
+ transcriptMapFor([sessionName]),
303
+ );
304
+
305
+ expect(result.get(sessionName)).toEqual([
306
+ { label: 'agent-pending', silentSeconds: 600, runningSeconds: 900 },
307
+ ]);
308
+ });
309
+
170
310
  it('follows a symlinked transcript when computing silent seconds', async () => {
171
311
  const sessionName = 'https_//github_com/owner/repo/issues/9';
172
312
  const targetDir = fs.mkdtempSync(
@@ -25,12 +25,45 @@ const parseEpochSeconds = (timestamp: string | null): number | null => {
25
25
 
26
26
  type ParsedTranscript = {
27
27
  firstEntryEpochSeconds: number | null;
28
- lastStopReason: string | null;
28
+ lastEntryIndicatesCompletion: boolean;
29
+ };
30
+
31
+ const readContentBlocks = (
32
+ message: Record<string, unknown>,
33
+ ): Record<string, unknown>[] => {
34
+ const content = message.content;
35
+ if (!Array.isArray(content)) {
36
+ return [];
37
+ }
38
+ return content.filter(isRecord);
39
+ };
40
+
41
+ const entryIndicatesCompletion = (entry: Record<string, unknown>): boolean => {
42
+ const type = readString(entry, 'type');
43
+ const message = entry.message;
44
+ if (!isRecord(message)) {
45
+ return false;
46
+ }
47
+ if (type === 'assistant') {
48
+ const stopReason = readString(message, 'stop_reason');
49
+ if (stopReason === 'end_turn' || stopReason === 'stop_sequence') {
50
+ return true;
51
+ }
52
+ const blocks = readContentBlocks(message);
53
+ const lastBlock = blocks[blocks.length - 1];
54
+ return lastBlock !== undefined && readString(lastBlock, 'type') === 'text';
55
+ }
56
+ if (type === 'user') {
57
+ return readContentBlocks(message).some(
58
+ (block) => readString(block, 'type') === 'text',
59
+ );
60
+ }
61
+ return false;
29
62
  };
30
63
 
31
64
  const parseTranscript = (content: string): ParsedTranscript => {
32
65
  let firstEntryEpochSeconds: number | null = null;
33
- let lastStopReason: string | null = null;
66
+ let lastEntryIndicatesCompletion = false;
34
67
  for (const line of content.split('\n')) {
35
68
  const trimmed = line.trim();
36
69
  if (trimmed.length === 0) {
@@ -49,15 +82,11 @@ const parseTranscript = (content: string): ParsedTranscript => {
49
82
  if (firstEntryEpochSeconds === null && epochSeconds !== null) {
50
83
  firstEntryEpochSeconds = epochSeconds;
51
84
  }
52
- const message = parsed.message;
53
- if (isRecord(message)) {
54
- const stopReason = readString(message, 'stop_reason');
55
- if (stopReason !== null) {
56
- lastStopReason = stopReason;
57
- }
85
+ if (isRecord(parsed.message)) {
86
+ lastEntryIndicatesCompletion = entryIndicatesCompletion(parsed);
58
87
  }
59
88
  }
60
- return { firstEntryEpochSeconds, lastStopReason };
89
+ return { firstEntryEpochSeconds, lastEntryIndicatesCompletion };
61
90
  };
62
91
 
63
92
  const clampToZero = (value: number): number => (value > 0 ? value : 0);
@@ -132,10 +161,7 @@ export class TranscriptSessionSubAgentActivityRepository implements SessionSubAg
132
161
  return null;
133
162
  }
134
163
  const transcript = parseTranscript(content);
135
- if (
136
- transcript.lastStopReason === 'end_turn' ||
137
- transcript.lastStopReason === 'stop_sequence'
138
- ) {
164
+ if (transcript.lastEntryIndicatesCompletion) {
139
165
  return null;
140
166
  }
141
167
  const silentSeconds = clampToZero(
@@ -1 +1 @@
1
- {"version":3,"file":"TranscriptSessionSubAgentActivityRepository.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/TranscriptSessionSubAgentActivityRepository.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mDAAmD,CAAC;AACrF,OAAO,EAAE,iCAAiC,EAAE,MAAM,4EAA4E,CAAC;AAC/H,OAAO,EAAE,mCAAmC,EAAE,MAAM,8EAA8E,CAAC;AA4DnI,qBAAa,2CAA4C,YAAW,iCAAiC;IAEjG,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,oBAAoB;gBAFpB,iBAAiB,EAAE,mCAAmC,EACtD,GAAG,EAAE,IAAI,EACT,oBAAoB,EAAE,MAAM;IAG/C,mCAAmC,GACjC,cAAc,MAAM,EAAE,EACtB,6BAA6B,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,KAC/C,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAmBzC;IAEF,OAAO,CAAC,iBAAiB,CAuBvB;IAEF,OAAO,CAAC,UAAU,CAmChB;CACH"}
1
+ {"version":3,"file":"TranscriptSessionSubAgentActivityRepository.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/TranscriptSessionSubAgentActivityRepository.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mDAAmD,CAAC;AACrF,OAAO,EAAE,iCAAiC,EAAE,MAAM,4EAA4E,CAAC;AAC/H,OAAO,EAAE,mCAAmC,EAAE,MAAM,8EAA8E,CAAC;AAyFnI,qBAAa,2CAA4C,YAAW,iCAAiC;IAEjG,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,oBAAoB;gBAFpB,iBAAiB,EAAE,mCAAmC,EACtD,GAAG,EAAE,IAAI,EACT,oBAAoB,EAAE,MAAM;IAG/C,mCAAmC,GACjC,cAAc,MAAM,EAAE,EACtB,6BAA6B,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,KAC/C,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAmBzC;IAEF,OAAO,CAAC,iBAAiB,CAuBvB;IAEF,OAAO,CAAC,UAAU,CAgChB;CACH"}