github-issue-tower-defence-management 1.116.6 → 1.116.8

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 (21) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +1 -1
  3. package/bin/adapter/entry-points/console/consoleOperationApi.js +7 -1
  4. package/bin/adapter/entry-points/console/consoleOperationApi.js.map +1 -1
  5. package/bin/adapter/entry-points/console/ui-dist/assets/{index-CORMFbfE.js → index-Bm0wt3Mn.js} +1 -1
  6. package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
  7. package/bin/adapter/repositories/FileSystemInteractiveLiveSessionTranscriptResolver.js +19 -12
  8. package/bin/adapter/repositories/FileSystemInteractiveLiveSessionTranscriptResolver.js.map +1 -1
  9. package/package.json +1 -1
  10. package/src/adapter/entry-points/console/consoleOperationApi.test.ts +30 -0
  11. package/src/adapter/entry-points/console/consoleOperationApi.ts +11 -4
  12. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOperationMenu.test.tsx +4 -2
  13. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOperationMenu.tsx +1 -3
  14. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.test.ts +19 -0
  15. package/src/adapter/entry-points/console/ui-dist/assets/{index-CORMFbfE.js → index-Bm0wt3Mn.js} +1 -1
  16. package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
  17. package/src/adapter/repositories/FileSystemInteractiveLiveSessionTranscriptResolver.test.ts +240 -0
  18. package/src/adapter/repositories/FileSystemInteractiveLiveSessionTranscriptResolver.ts +29 -15
  19. package/types/adapter/entry-points/console/consoleOperationApi.d.ts.map +1 -1
  20. package/types/adapter/repositories/FileSystemInteractiveLiveSessionTranscriptResolver.d.ts +1 -0
  21. package/types/adapter/repositories/FileSystemInteractiveLiveSessionTranscriptResolver.d.ts.map +1 -1
@@ -2,6 +2,7 @@ import * as fs from 'fs';
2
2
  import * as os from 'os';
3
3
  import * as path from 'path';
4
4
  import { FileSystemInteractiveLiveSessionTranscriptResolver } from './FileSystemInteractiveLiveSessionTranscriptResolver';
5
+ import { TranscriptOwnerCallStatusProvider } from './TranscriptOwnerCallStatusProvider';
5
6
 
6
7
  describe('FileSystemInteractiveLiveSessionTranscriptResolver', () => {
7
8
  let configRoot: string;
@@ -41,6 +42,35 @@ describe('FileSystemInteractiveLiveSessionTranscriptResolver', () => {
41
42
  return filePath;
42
43
  };
43
44
 
45
+ const writeSubagentTranscript = (params: {
46
+ projectsDirectory: string;
47
+ cwdSlug: string;
48
+ parentSessionId: string;
49
+ agentId: string;
50
+ mtimeEpochSeconds?: number;
51
+ }): string => {
52
+ const subagentDirectory = path.join(
53
+ params.projectsDirectory,
54
+ params.cwdSlug,
55
+ params.parentSessionId,
56
+ 'subagents',
57
+ );
58
+ fs.mkdirSync(subagentDirectory, { recursive: true });
59
+ const filePath = path.join(
60
+ subagentDirectory,
61
+ `agent-${params.agentId}.jsonl`,
62
+ );
63
+ fs.writeFileSync(filePath, '{"type":"custom-title"}', 'utf8');
64
+ if (params.mtimeEpochSeconds !== undefined) {
65
+ fs.utimesSync(
66
+ filePath,
67
+ params.mtimeEpochSeconds,
68
+ params.mtimeEpochSeconds,
69
+ );
70
+ }
71
+ return filePath;
72
+ };
73
+
44
74
  it('resolves a resume-case transcript by config dir and session id', () => {
45
75
  const configDir = path.join(configRoot, 'workbench');
46
76
  const filePath = writeTranscript({
@@ -186,6 +216,123 @@ describe('FileSystemInteractiveLiveSessionTranscriptResolver', () => {
186
216
  expect(result.get('workbench')).toBe(newerPath);
187
217
  });
188
218
 
219
+ it('selects the parent transcript over a newer descendant id transcript', () => {
220
+ const configDir = path.join(configRoot, 'with-subagent');
221
+ const parentPath = writeTranscript({
222
+ projectsDirectory: path.join(configDir, 'projects'),
223
+ cwdSlug: '-home-user',
224
+ sessionId: 'parent-id',
225
+ mtimeEpochSeconds: 1700000000,
226
+ });
227
+ writeTranscript({
228
+ projectsDirectory: path.join(configDir, 'projects'),
229
+ cwdSlug: '-home-user',
230
+ sessionId: 'subagent-id',
231
+ mtimeEpochSeconds: 1700000500,
232
+ });
233
+ const resolver = new FileSystemInteractiveLiveSessionTranscriptResolver(
234
+ sharedProjectsDirectory,
235
+ );
236
+
237
+ const result = resolver.resolveTranscriptPaths([
238
+ {
239
+ sessionName: 'with-subagent',
240
+ sessionId: 'parent-id',
241
+ candidateSessionIds: ['parent-id', 'subagent-id'],
242
+ configDir,
243
+ },
244
+ ]);
245
+
246
+ expect(result.get('with-subagent')).toBe(parentPath);
247
+ });
248
+
249
+ it('selects the rotated parent transcript over a newer descendant id transcript', () => {
250
+ const configDir = path.join(configRoot, 'rotated-with-subagent');
251
+ const rotatedParentPath = writeTranscript({
252
+ projectsDirectory: path.join(configDir, 'projects'),
253
+ cwdSlug: '-home-user',
254
+ sessionId: 'rotated-id',
255
+ mtimeEpochSeconds: 1700000000,
256
+ });
257
+ writeTranscript({
258
+ projectsDirectory: path.join(configDir, 'projects'),
259
+ cwdSlug: '-home-user',
260
+ sessionId: 'subagent-id',
261
+ mtimeEpochSeconds: 1700000500,
262
+ });
263
+ const resolver = new FileSystemInteractiveLiveSessionTranscriptResolver(
264
+ sharedProjectsDirectory,
265
+ );
266
+
267
+ const result = resolver.resolveTranscriptPaths([
268
+ {
269
+ sessionName: 'rotated-with-subagent',
270
+ sessionId: 'launch-id',
271
+ candidateSessionIds: ['rotated-id', 'launch-id', 'subagent-id'],
272
+ configDir,
273
+ },
274
+ ]);
275
+
276
+ expect(result.get('rotated-with-subagent')).toBe(rotatedParentPath);
277
+ });
278
+
279
+ it('selects the parent transcript and never a newer subagent transcript nested under subagents', () => {
280
+ const configDir = path.join(configRoot, 'subagent-nested');
281
+ const parentPath = writeTranscript({
282
+ projectsDirectory: path.join(configDir, 'projects'),
283
+ cwdSlug: '-home-user',
284
+ sessionId: 'parent-id',
285
+ mtimeEpochSeconds: 1700000000,
286
+ });
287
+ const subagentPath = writeSubagentTranscript({
288
+ projectsDirectory: path.join(configDir, 'projects'),
289
+ cwdSlug: '-home-user',
290
+ parentSessionId: 'parent-id',
291
+ agentId: 'worker',
292
+ mtimeEpochSeconds: 1700000500,
293
+ });
294
+ const resolver = new FileSystemInteractiveLiveSessionTranscriptResolver(
295
+ sharedProjectsDirectory,
296
+ );
297
+
298
+ const result = resolver.resolveTranscriptPaths([
299
+ {
300
+ sessionName: 'subagent-nested',
301
+ sessionId: 'parent-id',
302
+ candidateSessionIds: ['parent-id', 'subagent-id'],
303
+ configDir,
304
+ },
305
+ ]);
306
+
307
+ expect(result.get('subagent-nested')).toBe(parentPath);
308
+ expect(result.get('subagent-nested')).not.toBe(subagentPath);
309
+ });
310
+
311
+ it('omits a session whose only transcript is nested under a subagents directory', () => {
312
+ const configDir = path.join(configRoot, 'subagent-only');
313
+ writeSubagentTranscript({
314
+ projectsDirectory: path.join(configDir, 'projects'),
315
+ cwdSlug: '-home-user',
316
+ parentSessionId: 'parent-id',
317
+ agentId: 'worker',
318
+ mtimeEpochSeconds: 1700000500,
319
+ });
320
+ const resolver = new FileSystemInteractiveLiveSessionTranscriptResolver(
321
+ sharedProjectsDirectory,
322
+ );
323
+
324
+ const result = resolver.resolveTranscriptPaths([
325
+ {
326
+ sessionName: 'subagent-only',
327
+ sessionId: 'parent-id',
328
+ candidateSessionIds: ['parent-id', 'subagent-id'],
329
+ configDir,
330
+ },
331
+ ]);
332
+
333
+ expect(result.has('subagent-only')).toBe(false);
334
+ });
335
+
189
336
  it('resolves several sessions in one call', () => {
190
337
  const workbenchConfig = path.join(configRoot, 'workbench');
191
338
  const controlRoomConfig = path.join(configRoot, 'control-room');
@@ -272,3 +419,96 @@ describe('FileSystemInteractiveLiveSessionTranscriptResolver', () => {
272
419
  expect(result.size).toBe(0);
273
420
  });
274
421
  });
422
+
423
+ describe('parent transcript resolution feeding owner-call detection', () => {
424
+ let configRoot: string;
425
+ let sharedProjectsDirectory: string;
426
+
427
+ beforeEach(() => {
428
+ configRoot = fs.mkdtempSync(
429
+ path.join(os.tmpdir(), 'interactive-transcript-ownercall-'),
430
+ );
431
+ sharedProjectsDirectory = path.join(configRoot, 'shared', 'projects');
432
+ });
433
+
434
+ afterEach(() => {
435
+ fs.rmSync(configRoot, { force: true, recursive: true });
436
+ });
437
+
438
+ const ownerCallMarker = '<<OWNER_CALL>>';
439
+
440
+ const writeJsonlTranscript = (
441
+ filePath: string,
442
+ lines: object[],
443
+ mtimeEpochSeconds: number,
444
+ ): void => {
445
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
446
+ fs.writeFileSync(
447
+ filePath,
448
+ lines.map((line) => JSON.stringify(line)).join('\n'),
449
+ 'utf8',
450
+ );
451
+ fs.utimesSync(filePath, mtimeEpochSeconds, mtimeEpochSeconds);
452
+ };
453
+
454
+ it('identifies a session running a subagent as waiting on the owner', () => {
455
+ const configDir = path.join(configRoot, 'waiting');
456
+ const projectDirectory = path.join(configDir, 'projects', '-home-user');
457
+ writeJsonlTranscript(
458
+ path.join(projectDirectory, 'parent-id.jsonl'),
459
+ [
460
+ {
461
+ type: 'assistant',
462
+ timestamp: '2026-01-01T00:00:00.000Z',
463
+ message: {
464
+ role: 'assistant',
465
+ content: [
466
+ { type: 'text', text: `${ownerCallMarker} please decide` },
467
+ ],
468
+ },
469
+ },
470
+ ],
471
+ 1700000000,
472
+ );
473
+ writeJsonlTranscript(
474
+ path.join(
475
+ projectDirectory,
476
+ 'parent-id',
477
+ 'subagents',
478
+ 'agent-worker.jsonl',
479
+ ),
480
+ [
481
+ {
482
+ type: 'assistant',
483
+ timestamp: '2026-01-01T00:05:00.000Z',
484
+ message: {
485
+ role: 'assistant',
486
+ content: [{ type: 'text', text: 'subagent progress' }],
487
+ },
488
+ },
489
+ ],
490
+ 1700000500,
491
+ );
492
+ const resolver = new FileSystemInteractiveLiveSessionTranscriptResolver(
493
+ sharedProjectsDirectory,
494
+ );
495
+ const ownerCallStatusProvider = new TranscriptOwnerCallStatusProvider(
496
+ ownerCallMarker,
497
+ );
498
+
499
+ const transcriptPaths = resolver.resolveTranscriptPaths([
500
+ {
501
+ sessionName: 'waiting',
502
+ sessionId: 'parent-id',
503
+ candidateSessionIds: ['parent-id', 'subagent-id'],
504
+ configDir,
505
+ },
506
+ ]);
507
+
508
+ return ownerCallStatusProvider
509
+ .listSessionNamesWithUnansweredOwnerCall(transcriptPaths)
510
+ .then((waiting) => {
511
+ expect(waiting.has('waiting')).toBe(true);
512
+ });
513
+ });
514
+ });
@@ -38,23 +38,37 @@ export class FileSystemInteractiveLiveSessionTranscriptResolver implements Inter
38
38
  session: InteractiveLiveSession,
39
39
  ): string | null => {
40
40
  const projectsDirectories = this.listProjectsDirectories(session.configDir);
41
+ for (const candidateSessionId of session.candidateSessionIds) {
42
+ const transcriptPath = this.resolveCandidateTranscriptPath(
43
+ candidateSessionId,
44
+ projectsDirectories,
45
+ );
46
+ if (transcriptPath !== null) {
47
+ return transcriptPath;
48
+ }
49
+ }
50
+ return null;
51
+ };
52
+
53
+ private resolveCandidateTranscriptPath = (
54
+ candidateSessionId: string,
55
+ projectsDirectories: string[],
56
+ ): string | null => {
57
+ const fileName = `${candidateSessionId}.jsonl`;
41
58
  let latestPath: string | null = null;
42
59
  let latestEpochMs = -Infinity;
43
- for (const candidateSessionId of session.candidateSessionIds) {
44
- const fileName = `${candidateSessionId}.jsonl`;
45
- for (const projectsDirectory of projectsDirectories) {
46
- for (const candidate of this.listCandidatePaths(
47
- projectsDirectory,
48
- fileName,
49
- )) {
50
- const epochMs = modifiedEpochMs(candidate);
51
- if (epochMs === null) {
52
- continue;
53
- }
54
- if (epochMs > latestEpochMs) {
55
- latestEpochMs = epochMs;
56
- latestPath = candidate;
57
- }
60
+ for (const projectsDirectory of projectsDirectories) {
61
+ for (const candidate of this.listCandidatePaths(
62
+ projectsDirectory,
63
+ fileName,
64
+ )) {
65
+ const epochMs = modifiedEpochMs(candidate);
66
+ if (epochMs === null) {
67
+ continue;
68
+ }
69
+ if (epochMs > latestEpochMs) {
70
+ latestEpochMs = epochMs;
71
+ latestPath = candidate;
58
72
  }
59
73
  }
60
74
  }
@@ -1 +1 @@
1
- {"version":3,"file":"consoleOperationApi.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/console/consoleOperationApi.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EAEhB,MAAM,6DAA6D,CAAC;AACrE,OAAO,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AAI3D,eAAO,MAAM,8BAA8B,uBAAuB,CAAC;AACnE,eAAO,MAAM,4BAA4B,qBAAqB,CAAC;AAE/D,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,CACnC,MAAM,EAAE,MAAM,KACX,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;AAE3C,MAAM,MAAM,uBAAuB,GAAG;IACpC,eAAe,EAAE,eAAe,CAAC;IACjC,cAAc,EAAE,sBAAsB,CAAC;IACvC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,OAAO,CAAC;CACf,CAAC;AA+GF,eAAO,MAAM,YAAY,GACvB,SAAS,uBAAuB,EAChC,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,wBAAwB,CAwElC,CAAC;AAEF,eAAO,MAAM,YAAY,GACvB,SAAS,uBAAuB,EAChC,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,wBAAwB,CA4FlC,CAAC;AAEF,eAAO,MAAM,aAAa,GACxB,SAAS,uBAAuB,EAChC,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,wBAAwB,CA2BlC,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC9B,SAAS,uBAAuB,EAChC,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,wBAAwB,CAiClC,CAAC;AAEF,eAAO,MAAM,YAAY,GACvB,SAAS,uBAAuB,EAChC,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,wBAAwB,CAiClC,CAAC"}
1
+ {"version":3,"file":"consoleOperationApi.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/console/consoleOperationApi.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EAEhB,MAAM,6DAA6D,CAAC;AACrE,OAAO,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AAI3D,eAAO,MAAM,8BAA8B,uBAAuB,CAAC;AACnE,eAAO,MAAM,4BAA4B,qBAAqB,CAAC;AAE/D,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,CACnC,MAAM,EAAE,MAAM,KACX,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;AAE3C,MAAM,MAAM,uBAAuB,GAAG;IACpC,eAAe,EAAE,eAAe,CAAC;IACjC,cAAc,EAAE,sBAAsB,CAAC;IACvC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,OAAO,CAAC;CACf,CAAC;AAkHF,eAAO,MAAM,YAAY,GACvB,SAAS,uBAAuB,EAChC,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,wBAAwB,CAwElC,CAAC;AAEF,eAAO,MAAM,YAAY,GACvB,SAAS,uBAAuB,EAChC,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,wBAAwB,CAgGlC,CAAC;AAEF,eAAO,MAAM,aAAa,GACxB,SAAS,uBAAuB,EAChC,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,wBAAwB,CA2BlC,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC9B,SAAS,uBAAuB,EAChC,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,wBAAwB,CAiClC,CAAC;AAEF,eAAO,MAAM,YAAY,GACvB,SAAS,uBAAuB,EAChC,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,wBAAwB,CAiClC,CAAC"}
@@ -5,6 +5,7 @@ export declare class FileSystemInteractiveLiveSessionTranscriptResolver implemen
5
5
  constructor(sharedProjectsDirectory?: string);
6
6
  resolveTranscriptPaths: (sessions: InteractiveLiveSession[]) => Map<string, string>;
7
7
  private resolveTranscriptPath;
8
+ private resolveCandidateTranscriptPath;
8
9
  private listProjectsDirectories;
9
10
  private listCandidatePaths;
10
11
  }
@@ -1 +1 @@
1
- {"version":3,"file":"FileSystemInteractiveLiveSessionTranscriptResolver.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/FileSystemInteractiveLiveSessionTranscriptResolver.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,sBAAsB,EAAE,MAAM,8CAA8C,CAAC;AACtF,OAAO,EAAE,wCAAwC,EAAE,MAAM,mFAAmF,CAAC;AAc7I,qBAAa,kDAAmD,YAAW,wCAAwC;IAE/G,OAAO,CAAC,QAAQ,CAAC,uBAAuB;gBAAvB,uBAAuB,GAAE,MAAyC;IAGrF,sBAAsB,GACpB,UAAU,sBAAsB,EAAE,KACjC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CASpB;IAEF,OAAO,CAAC,qBAAqB,CAyB3B;IAEF,OAAO,CAAC,uBAAuB,CAM7B;IAEF,OAAO,CAAC,kBAAkB,CAsBxB;CACH"}
1
+ {"version":3,"file":"FileSystemInteractiveLiveSessionTranscriptResolver.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/FileSystemInteractiveLiveSessionTranscriptResolver.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,sBAAsB,EAAE,MAAM,8CAA8C,CAAC;AACtF,OAAO,EAAE,wCAAwC,EAAE,MAAM,mFAAmF,CAAC;AAc7I,qBAAa,kDAAmD,YAAW,wCAAwC;IAE/G,OAAO,CAAC,QAAQ,CAAC,uBAAuB;gBAAvB,uBAAuB,GAAE,MAAyC;IAGrF,sBAAsB,GACpB,UAAU,sBAAsB,EAAE,KACjC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CASpB;IAEF,OAAO,CAAC,qBAAqB,CAc3B;IAEF,OAAO,CAAC,8BAA8B,CAuBpC;IAEF,OAAO,CAAC,uBAAuB,CAM7B;IAEF,OAAO,CAAC,kBAAkB,CAsBxB;CACH"}