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.
- package/CHANGELOG.md +14 -0
- package/README.md +2 -2
- package/bin/adapter/entry-points/console/ui-dist/assets/{index-CJJsRF8i.js → index-BXcfEKkX.js} +1 -1
- package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/bin/adapter/repositories/TranscriptSessionSubAgentActivityRepository.js +32 -10
- package/bin/adapter/repositories/TranscriptSessionSubAgentActivityRepository.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleDetailPrefetch.test.ts +12 -21
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleDetailPrefetch.ts +1 -1
- package/src/adapter/entry-points/console/ui-dist/assets/{index-CJJsRF8i.js → index-BXcfEKkX.js} +1 -1
- package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/src/adapter/repositories/TranscriptSessionSubAgentActivityRepository.test.ts +140 -0
- package/src/adapter/repositories/TranscriptSessionSubAgentActivityRepository.ts +39 -13
- package/types/adapter/repositories/TranscriptSessionSubAgentActivityRepository.d.ts.map +1 -1
|
@@ -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-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
53
|
-
|
|
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,
|
|
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;
|
|
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"}
|