tmux-team 2.2.0 → 3.0.0-alpha.2
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/README.md +42 -193
- package/package.json +3 -2
- package/skills/README.md +101 -0
- package/skills/claude/team.md +46 -0
- package/skills/codex/SKILL.md +46 -0
- package/src/cli.ts +19 -5
- package/src/commands/config.ts +2 -44
- package/src/commands/help.ts +1 -2
- package/src/commands/install-skill.ts +148 -0
- package/src/commands/talk.test.ts +458 -63
- package/src/commands/talk.ts +158 -69
- package/src/config.test.ts +0 -1
- package/src/config.ts +0 -1
- package/src/identity.ts +89 -0
- package/src/types.ts +2 -2
- package/src/version.ts +1 -1
- package/src/pm/commands.test.ts +0 -1462
- package/src/pm/commands.ts +0 -1011
- package/src/pm/manager.test.ts +0 -377
- package/src/pm/manager.ts +0 -146
- package/src/pm/permissions.test.ts +0 -444
- package/src/pm/permissions.ts +0 -293
- package/src/pm/storage/adapter.ts +0 -57
- package/src/pm/storage/fs.test.ts +0 -512
- package/src/pm/storage/fs.ts +0 -290
- package/src/pm/storage/github.ts +0 -842
- package/src/pm/types.ts +0 -91
package/src/commands/talk.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
setActiveRequest,
|
|
14
14
|
incrementPreambleCounter,
|
|
15
15
|
} from '../state.js';
|
|
16
|
-
import { resolveActor } from '../
|
|
16
|
+
import { resolveActor } from '../identity.js';
|
|
17
17
|
|
|
18
18
|
function sleepMs(ms: number): Promise<void> {
|
|
19
19
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -32,6 +32,41 @@ function renderWaitLine(agent: string, elapsedSeconds: number): string {
|
|
|
32
32
|
return `⏳ Waiting for ${agent}... (${s}s)`;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Extract partial response from output when end marker is not found.
|
|
37
|
+
* Used to capture whatever the agent wrote before timeout.
|
|
38
|
+
*/
|
|
39
|
+
function extractPartialResponse(
|
|
40
|
+
output: string,
|
|
41
|
+
startMarker: string,
|
|
42
|
+
_endMarker: string
|
|
43
|
+
): string | null {
|
|
44
|
+
// Look for instruction end pattern `}]` (the instruction ends with `{end}]`)
|
|
45
|
+
const instructionEndPattern = '}]';
|
|
46
|
+
const instructionEndIndex = output.lastIndexOf(instructionEndPattern);
|
|
47
|
+
|
|
48
|
+
let responseStart = 0;
|
|
49
|
+
if (instructionEndIndex !== -1) {
|
|
50
|
+
// Find the first newline after the instruction's closing `}]`
|
|
51
|
+
responseStart = output.indexOf('\n', instructionEndIndex + 2);
|
|
52
|
+
if (responseStart !== -1) responseStart += 1;
|
|
53
|
+
else responseStart = instructionEndIndex + 2;
|
|
54
|
+
} else {
|
|
55
|
+
// Fallback: try to find newline after start marker
|
|
56
|
+
const startMarkerIndex = output.lastIndexOf(startMarker);
|
|
57
|
+
if (startMarkerIndex !== -1) {
|
|
58
|
+
responseStart = output.indexOf('\n', startMarkerIndex);
|
|
59
|
+
if (responseStart !== -1) responseStart += 1;
|
|
60
|
+
else return null; // Can't find response start
|
|
61
|
+
} else {
|
|
62
|
+
return null; // Start marker not found
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const partial = output.slice(responseStart).trim();
|
|
67
|
+
return partial || null;
|
|
68
|
+
}
|
|
69
|
+
|
|
35
70
|
// ─────────────────────────────────────────────────────────────
|
|
36
71
|
// Types for broadcast wait mode
|
|
37
72
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -41,10 +76,11 @@ interface AgentWaitState {
|
|
|
41
76
|
pane: string;
|
|
42
77
|
requestId: string;
|
|
43
78
|
nonce: string;
|
|
44
|
-
|
|
45
|
-
|
|
79
|
+
startMarker: string;
|
|
80
|
+
endMarker: string;
|
|
46
81
|
status: 'pending' | 'completed' | 'timeout' | 'error';
|
|
47
82
|
response?: string;
|
|
83
|
+
partialResponse?: string | null;
|
|
48
84
|
error?: string;
|
|
49
85
|
elapsedMs?: number;
|
|
50
86
|
}
|
|
@@ -212,11 +248,12 @@ export async function cmdTalk(ctx: Context, target: string, message: string): Pr
|
|
|
212
248
|
|
|
213
249
|
const requestId = makeRequestId();
|
|
214
250
|
const nonce = makeNonce();
|
|
215
|
-
const
|
|
251
|
+
const startMarker = `{tmux-team-start:${nonce}}`;
|
|
252
|
+
const endMarker = `{tmux-team-end:${nonce}}`;
|
|
216
253
|
|
|
217
|
-
// Build message with preamble, then
|
|
254
|
+
// Build message with preamble, then wrap with start/end markers
|
|
218
255
|
const messageWithPreamble = buildMessage(message, target, ctx);
|
|
219
|
-
const fullMessage = `${messageWithPreamble}\n\n[IMPORTANT: When your response is complete, print exactly: ${
|
|
256
|
+
const fullMessage = `${startMarker}\n${messageWithPreamble}\n\n[IMPORTANT: When your response is complete, print exactly: ${endMarker}]`;
|
|
220
257
|
|
|
221
258
|
// Best-effort cleanup and soft-lock warning
|
|
222
259
|
const state = cleanupState(ctx.paths, 60 * 60); // 1 hour TTL
|
|
@@ -227,14 +264,6 @@ export async function cmdTalk(ctx: Context, target: string, message: string): Pr
|
|
|
227
264
|
);
|
|
228
265
|
}
|
|
229
266
|
|
|
230
|
-
let baseline = '';
|
|
231
|
-
try {
|
|
232
|
-
baseline = tmux.capture(pane, captureLines);
|
|
233
|
-
} catch {
|
|
234
|
-
ui.error(`Failed to capture pane ${pane}. Is tmux running?`);
|
|
235
|
-
exit(ExitCodes.ERROR);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
267
|
setActiveRequest(ctx.paths, target, { id: requestId, nonce, pane, startedAtMs: Date.now() });
|
|
239
268
|
|
|
240
269
|
const startedAt = Date.now();
|
|
@@ -258,8 +287,22 @@ export async function cmdTalk(ctx: Context, target: string, message: string): Pr
|
|
|
258
287
|
const elapsedSeconds = (Date.now() - startedAt) / 1000;
|
|
259
288
|
if (elapsedSeconds >= timeoutSeconds) {
|
|
260
289
|
clearActiveRequest(ctx.paths, target, requestId);
|
|
290
|
+
|
|
291
|
+
// Capture partial response on timeout
|
|
292
|
+
let partialResponse: string | null = null;
|
|
293
|
+
try {
|
|
294
|
+
const output = tmux.capture(pane, captureLines);
|
|
295
|
+
const extracted = extractPartialResponse(output, startMarker, endMarker);
|
|
296
|
+
if (extracted) partialResponse = extracted;
|
|
297
|
+
} catch {
|
|
298
|
+
// Ignore capture errors on timeout
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (isTTY) {
|
|
302
|
+
process.stdout.write('\r' + ' '.repeat(80) + '\r');
|
|
303
|
+
}
|
|
304
|
+
|
|
261
305
|
if (flags.json) {
|
|
262
|
-
// Single JSON output with error field (don't call ui.error separately)
|
|
263
306
|
ui.json({
|
|
264
307
|
target,
|
|
265
308
|
pane,
|
|
@@ -267,14 +310,19 @@ export async function cmdTalk(ctx: Context, target: string, message: string): Pr
|
|
|
267
310
|
error: `Timed out waiting for ${target} after ${Math.floor(timeoutSeconds)}s`,
|
|
268
311
|
requestId,
|
|
269
312
|
nonce,
|
|
270
|
-
|
|
313
|
+
startMarker,
|
|
314
|
+
endMarker,
|
|
315
|
+
partialResponse,
|
|
271
316
|
});
|
|
272
317
|
exit(ExitCodes.TIMEOUT);
|
|
273
318
|
}
|
|
274
|
-
|
|
275
|
-
process.stdout.write('\r' + ' '.repeat(80) + '\r');
|
|
276
|
-
}
|
|
319
|
+
|
|
277
320
|
ui.error(`Timed out waiting for ${target} after ${Math.floor(timeoutSeconds)}s.`);
|
|
321
|
+
if (partialResponse) {
|
|
322
|
+
console.log();
|
|
323
|
+
console.log(colors.yellow(`─── Partial response from ${target} (${pane}) ───`));
|
|
324
|
+
console.log(partialResponse);
|
|
325
|
+
}
|
|
278
326
|
exit(ExitCodes.TIMEOUT);
|
|
279
327
|
}
|
|
280
328
|
|
|
@@ -283,7 +331,7 @@ export async function cmdTalk(ctx: Context, target: string, message: string): Pr
|
|
|
283
331
|
process.stdout.write('\r' + renderWaitLine(target, elapsedSeconds));
|
|
284
332
|
} else {
|
|
285
333
|
const now = Date.now();
|
|
286
|
-
if (now - lastNonTtyLogAt >=
|
|
334
|
+
if (now - lastNonTtyLogAt >= 30000) {
|
|
287
335
|
lastNonTtyLogAt = now;
|
|
288
336
|
console.error(
|
|
289
337
|
`[tmux-team] Waiting for ${target} (${Math.floor(elapsedSeconds)}s elapsed)`
|
|
@@ -303,16 +351,39 @@ export async function cmdTalk(ctx: Context, target: string, message: string): Pr
|
|
|
303
351
|
exit(ExitCodes.ERROR);
|
|
304
352
|
}
|
|
305
353
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const
|
|
311
|
-
if (
|
|
312
|
-
|
|
354
|
+
// Find end marker - it appears once in our instruction and again when agent prints it
|
|
355
|
+
// We need TWO occurrences: one in instruction + one from agent = complete
|
|
356
|
+
// Only ONE occurrence means it's just in instruction = still waiting
|
|
357
|
+
const firstEndMarkerIndex = output.indexOf(endMarker);
|
|
358
|
+
const lastEndMarkerIndex = output.lastIndexOf(endMarker);
|
|
359
|
+
if (firstEndMarkerIndex === -1 || firstEndMarkerIndex === lastEndMarkerIndex) {
|
|
360
|
+
// No marker or only one (in instruction) - still waiting
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
const endMarkerIndex = lastEndMarkerIndex;
|
|
364
|
+
|
|
365
|
+
// Find the end of our instruction by looking for `}]` pattern (the instruction ends with `{end}]`)
|
|
366
|
+
// This is more reliable than looking for newline after start marker because
|
|
367
|
+
// the message may be word-wrapped across multiple visual lines
|
|
368
|
+
let responseStart = 0;
|
|
369
|
+
const instructionEndPattern = '}]';
|
|
370
|
+
const instructionEndIndex = output.lastIndexOf(instructionEndPattern, endMarkerIndex);
|
|
371
|
+
if (instructionEndIndex !== -1) {
|
|
372
|
+
// Find the first newline after the instruction's closing `}]`
|
|
373
|
+
responseStart = output.indexOf('\n', instructionEndIndex + 2);
|
|
374
|
+
if (responseStart !== -1) responseStart += 1;
|
|
375
|
+
else responseStart = instructionEndIndex + 2;
|
|
376
|
+
} else {
|
|
377
|
+
// Fallback: if no `}]` found, try to find newline after start marker
|
|
378
|
+
const startMarkerIndex = output.lastIndexOf(startMarker);
|
|
379
|
+
if (startMarkerIndex !== -1) {
|
|
380
|
+
responseStart = output.indexOf('\n', startMarkerIndex);
|
|
381
|
+
if (responseStart !== -1) responseStart += 1;
|
|
382
|
+
else responseStart = startMarkerIndex + startMarker.length;
|
|
383
|
+
}
|
|
313
384
|
}
|
|
314
385
|
|
|
315
|
-
const response = output.slice(
|
|
386
|
+
const response = output.slice(responseStart, endMarkerIndex).trim();
|
|
316
387
|
|
|
317
388
|
if (!flags.json && isTTY) {
|
|
318
389
|
process.stdout.write('\r' + ' '.repeat(80) + '\r');
|
|
@@ -323,7 +394,7 @@ export async function cmdTalk(ctx: Context, target: string, message: string): Pr
|
|
|
323
394
|
|
|
324
395
|
clearActiveRequest(ctx.paths, target, requestId);
|
|
325
396
|
|
|
326
|
-
const result: WaitResult = { requestId, nonce,
|
|
397
|
+
const result: WaitResult = { requestId, nonce, startMarker, endMarker, response };
|
|
327
398
|
if (flags.json) {
|
|
328
399
|
ui.json({ target, pane, status: 'completed', ...result });
|
|
329
400
|
} else {
|
|
@@ -367,35 +438,16 @@ async function cmdTalkAllWait(
|
|
|
367
438
|
);
|
|
368
439
|
}
|
|
369
440
|
|
|
370
|
-
// Phase 1: Send messages to all agents
|
|
441
|
+
// Phase 1: Send messages to all agents with start/end markers
|
|
371
442
|
for (const [name, data] of targetAgents) {
|
|
372
443
|
const requestId = makeRequestId();
|
|
373
444
|
const nonce = makeNonce(); // Unique nonce per agent (#19)
|
|
374
|
-
const
|
|
445
|
+
const startMarker = `{tmux-team-start:${nonce}}`;
|
|
446
|
+
const endMarker = `{tmux-team-end:${nonce}}`;
|
|
375
447
|
|
|
376
|
-
|
|
377
|
-
try {
|
|
378
|
-
baseline = tmux.capture(data.pane, captureLines);
|
|
379
|
-
} catch {
|
|
380
|
-
agentStates.push({
|
|
381
|
-
agent: name,
|
|
382
|
-
pane: data.pane,
|
|
383
|
-
requestId,
|
|
384
|
-
nonce,
|
|
385
|
-
marker,
|
|
386
|
-
baseline: '',
|
|
387
|
-
status: 'error',
|
|
388
|
-
error: `Failed to capture pane ${data.pane}`,
|
|
389
|
-
});
|
|
390
|
-
if (!flags.json) {
|
|
391
|
-
ui.warn(`Failed to capture ${name} (${data.pane})`);
|
|
392
|
-
}
|
|
393
|
-
continue;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// Build and send message
|
|
448
|
+
// Build and send message with start/end markers
|
|
397
449
|
const messageWithPreamble = buildMessage(message, name, ctx);
|
|
398
|
-
const fullMessage = `${messageWithPreamble}\n\n[IMPORTANT: When your response is complete, print exactly: ${
|
|
450
|
+
const fullMessage = `${startMarker}\n${messageWithPreamble}\n\n[IMPORTANT: When your response is complete, print exactly: ${endMarker}]`;
|
|
399
451
|
const msg = name === 'gemini' ? fullMessage.replace(/!/g, '') : fullMessage;
|
|
400
452
|
|
|
401
453
|
try {
|
|
@@ -411,8 +463,8 @@ async function cmdTalkAllWait(
|
|
|
411
463
|
pane: data.pane,
|
|
412
464
|
requestId,
|
|
413
465
|
nonce,
|
|
414
|
-
|
|
415
|
-
|
|
466
|
+
startMarker,
|
|
467
|
+
endMarker,
|
|
416
468
|
status: 'pending',
|
|
417
469
|
});
|
|
418
470
|
if (!flags.json) {
|
|
@@ -424,8 +476,8 @@ async function cmdTalkAllWait(
|
|
|
424
476
|
pane: data.pane,
|
|
425
477
|
requestId,
|
|
426
478
|
nonce,
|
|
427
|
-
|
|
428
|
-
|
|
479
|
+
startMarker,
|
|
480
|
+
endMarker,
|
|
429
481
|
status: 'error',
|
|
430
482
|
error: `Failed to send to pane ${data.pane}`,
|
|
431
483
|
});
|
|
@@ -476,6 +528,16 @@ async function cmdTalkAllWait(
|
|
|
476
528
|
state.status = 'timeout';
|
|
477
529
|
state.error = `Timed out after ${Math.floor(timeoutSeconds)}s`;
|
|
478
530
|
state.elapsedMs = Math.floor(elapsedSeconds * 1000);
|
|
531
|
+
|
|
532
|
+
// Capture partial response on timeout
|
|
533
|
+
try {
|
|
534
|
+
const output = tmux.capture(state.pane, captureLines);
|
|
535
|
+
const extracted = extractPartialResponse(output, state.startMarker, state.endMarker);
|
|
536
|
+
if (extracted) state.partialResponse = extracted;
|
|
537
|
+
} catch {
|
|
538
|
+
// Ignore capture errors on timeout
|
|
539
|
+
}
|
|
540
|
+
|
|
479
541
|
clearActiveRequest(paths, state.agent, state.requestId);
|
|
480
542
|
if (!flags.json) {
|
|
481
543
|
console.log(
|
|
@@ -491,7 +553,7 @@ async function cmdTalkAllWait(
|
|
|
491
553
|
// Progress logging (non-TTY)
|
|
492
554
|
if (!flags.json && !isTTY) {
|
|
493
555
|
const now = Date.now();
|
|
494
|
-
if (now - lastLogAt >=
|
|
556
|
+
if (now - lastLogAt >= 30000) {
|
|
495
557
|
lastLogAt = now;
|
|
496
558
|
const pending = pendingAgents()
|
|
497
559
|
.map((s) => s.agent)
|
|
@@ -520,17 +582,39 @@ async function cmdTalkAllWait(
|
|
|
520
582
|
continue;
|
|
521
583
|
}
|
|
522
584
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
585
|
+
// Find end marker - it appears once in our instruction and again when agent prints it
|
|
586
|
+
// We need TWO occurrences: one in instruction + one from agent = complete
|
|
587
|
+
// Only ONE occurrence means it's just in instruction = still waiting
|
|
588
|
+
const firstEndMarkerIndex = output.indexOf(state.endMarker);
|
|
589
|
+
const lastEndMarkerIndex = output.lastIndexOf(state.endMarker);
|
|
590
|
+
if (firstEndMarkerIndex === -1 || firstEndMarkerIndex === lastEndMarkerIndex) {
|
|
591
|
+
// No marker or only one (in instruction) - still waiting
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
const endMarkerIndex = lastEndMarkerIndex;
|
|
595
|
+
|
|
596
|
+
// Find the end of our instruction by looking for `}]` pattern (the instruction ends with `{end}]`)
|
|
597
|
+
// This is more reliable than looking for newline after start marker because
|
|
598
|
+
// the message may be word-wrapped across multiple visual lines
|
|
599
|
+
let responseStart = 0;
|
|
600
|
+
const instructionEndPattern = '}]';
|
|
601
|
+
const instructionEndIndex = output.lastIndexOf(instructionEndPattern, endMarkerIndex);
|
|
602
|
+
if (instructionEndIndex !== -1) {
|
|
603
|
+
// Find the first newline after the instruction's closing `}]`
|
|
604
|
+
responseStart = output.indexOf('\n', instructionEndIndex + 2);
|
|
605
|
+
if (responseStart !== -1) responseStart += 1;
|
|
606
|
+
else responseStart = instructionEndIndex + 2;
|
|
607
|
+
} else {
|
|
608
|
+
// Fallback: if no `}]` found, try to find newline after start marker
|
|
609
|
+
const startMarkerIndex = output.lastIndexOf(state.startMarker);
|
|
610
|
+
if (startMarkerIndex !== -1) {
|
|
611
|
+
responseStart = output.indexOf('\n', startMarkerIndex);
|
|
612
|
+
if (responseStart !== -1) responseStart += 1;
|
|
613
|
+
else responseStart = startMarkerIndex + state.startMarker.length;
|
|
614
|
+
}
|
|
531
615
|
}
|
|
532
616
|
|
|
533
|
-
state.response = output.slice(
|
|
617
|
+
state.response = output.slice(responseStart, endMarkerIndex).trim();
|
|
534
618
|
state.status = 'completed';
|
|
535
619
|
state.elapsedMs = Date.now() - startedAt;
|
|
536
620
|
clearActiveRequest(paths, state.agent, state.requestId);
|
|
@@ -592,10 +676,11 @@ function outputBroadcastResults(
|
|
|
592
676
|
pane: s.pane,
|
|
593
677
|
requestId: s.requestId,
|
|
594
678
|
nonce: s.nonce,
|
|
595
|
-
|
|
596
|
-
|
|
679
|
+
startMarker: s.startMarker,
|
|
680
|
+
endMarker: s.endMarker,
|
|
597
681
|
status: s.status,
|
|
598
682
|
response: s.response,
|
|
683
|
+
partialResponse: s.partialResponse,
|
|
599
684
|
error: s.error,
|
|
600
685
|
elapsedMs: s.elapsedMs,
|
|
601
686
|
})),
|
|
@@ -617,6 +702,10 @@ function outputBroadcastResults(
|
|
|
617
702
|
console.log(colors.cyan(`─── Response from ${state.agent} (${state.pane}) ───`));
|
|
618
703
|
console.log(state.response);
|
|
619
704
|
console.log();
|
|
705
|
+
} else if (state.status === 'timeout' && state.partialResponse) {
|
|
706
|
+
console.log(colors.yellow(`─── Partial response from ${state.agent} (${state.pane}) ───`));
|
|
707
|
+
console.log(state.partialResponse);
|
|
708
|
+
console.log();
|
|
620
709
|
}
|
|
621
710
|
}
|
|
622
711
|
}
|
package/src/config.test.ts
CHANGED
|
@@ -161,7 +161,6 @@ describe('loadConfig', () => {
|
|
|
161
161
|
expect(config.defaults.timeout).toBe(180);
|
|
162
162
|
expect(config.defaults.pollInterval).toBe(1);
|
|
163
163
|
expect(config.defaults.captureLines).toBe(100);
|
|
164
|
-
expect(config.defaults.hideOrphanTasks).toBe(false);
|
|
165
164
|
expect(config.agents).toEqual({});
|
|
166
165
|
expect(config.paneRegistry).toEqual({});
|
|
167
166
|
});
|
package/src/config.ts
CHANGED
package/src/identity.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// Identity resolution - determine current agent from tmux pane
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
import { execSync } from 'child_process';
|
|
6
|
+
import type { PaneEntry } from './types.js';
|
|
7
|
+
|
|
8
|
+
export interface ActorResolution {
|
|
9
|
+
actor: string;
|
|
10
|
+
source: 'pane' | 'env' | 'default';
|
|
11
|
+
warning?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get current tmux pane ID (e.g., "1.0").
|
|
16
|
+
*/
|
|
17
|
+
function getCurrentPane(): string | null {
|
|
18
|
+
if (!process.env.TMUX) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const tmuxPane = process.env.TMUX_PANE;
|
|
23
|
+
if (!tmuxPane) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const result = execSync(
|
|
29
|
+
`tmux display-message -p -t "${tmuxPane}" '#{window_index}.#{pane_index}'`,
|
|
30
|
+
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
31
|
+
);
|
|
32
|
+
return result.trim();
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Find agent name by pane ID.
|
|
40
|
+
*/
|
|
41
|
+
function findAgentByPane(paneRegistry: Record<string, PaneEntry>, paneId: string): string | null {
|
|
42
|
+
for (const [agentName, entry] of Object.entries(paneRegistry)) {
|
|
43
|
+
if (entry.pane === paneId) {
|
|
44
|
+
return agentName;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Resolve current actor using pane registry as primary source.
|
|
52
|
+
*/
|
|
53
|
+
export function resolveActor(paneRegistry: Record<string, PaneEntry>): ActorResolution {
|
|
54
|
+
const envActor = process.env.TMT_AGENT_NAME || process.env.TMUX_TEAM_ACTOR;
|
|
55
|
+
const currentPane = getCurrentPane();
|
|
56
|
+
|
|
57
|
+
// Not in tmux - use env var or default to human
|
|
58
|
+
if (!currentPane) {
|
|
59
|
+
if (envActor) {
|
|
60
|
+
return { actor: envActor, source: 'env' };
|
|
61
|
+
}
|
|
62
|
+
return { actor: 'human', source: 'default' };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// In tmux - look up pane in registry
|
|
66
|
+
const paneAgent = findAgentByPane(paneRegistry, currentPane);
|
|
67
|
+
|
|
68
|
+
if (paneAgent) {
|
|
69
|
+
if (envActor && envActor !== paneAgent) {
|
|
70
|
+
return {
|
|
71
|
+
actor: paneAgent,
|
|
72
|
+
source: 'pane',
|
|
73
|
+
warning: `⚠️ Identity mismatch: TMT_AGENT_NAME="${envActor}" but pane ${currentPane} is registered to "${paneAgent}". Using pane identity.`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return { actor: paneAgent, source: 'pane' };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Pane not in registry
|
|
80
|
+
if (envActor) {
|
|
81
|
+
return {
|
|
82
|
+
actor: envActor,
|
|
83
|
+
source: 'env',
|
|
84
|
+
warning: `⚠️ Unregistered pane: pane ${currentPane} is not in registry. Using TMT_AGENT_NAME="${envActor}".`,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return { actor: 'human', source: 'default' };
|
|
89
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -19,7 +19,6 @@ export interface ConfigDefaults {
|
|
|
19
19
|
pollInterval: number; // seconds
|
|
20
20
|
captureLines: number;
|
|
21
21
|
preambleEvery: number; // inject preamble every N messages (default: 3)
|
|
22
|
-
hideOrphanTasks: boolean; // hide tasks without milestone in list (default: false)
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
export interface GlobalConfig {
|
|
@@ -86,7 +85,8 @@ export interface Tmux {
|
|
|
86
85
|
export interface WaitResult {
|
|
87
86
|
requestId: string;
|
|
88
87
|
nonce: string;
|
|
89
|
-
|
|
88
|
+
startMarker: string;
|
|
89
|
+
endMarker: string;
|
|
90
90
|
response: string;
|
|
91
91
|
}
|
|
92
92
|
|