tau-all-agent 0.2.0 → 0.3.0
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/extensions/answer.ts +112 -85
- package/extensions/branch-term/index.ts +9 -5
- package/extensions/btw.ts +67 -36
- package/extensions/insights.ts +67 -56
- package/extensions/loop.ts +14 -52
- package/extensions/memory.ts +65 -33
- package/extensions/notify.ts +5 -3
- package/extensions/review/fix.ts +10 -16
- package/extensions/review/git.ts +7 -1
- package/extensions/review/index.ts +2 -2
- package/extensions/review/models.ts +12 -2
- package/extensions/review/review.ts +3 -5
- package/extensions/review/runtime.ts +9 -8
- package/extensions/review/triage.ts +2 -4
- package/extensions/sandbox/README.md +5 -0
- package/extensions/sandbox/bash.ts +83 -85
- package/extensions/sandbox/index.ts +21 -11
- package/extensions/sandbox/permissions/network.ts +8 -3
- package/extensions/sandbox/permissions/unsandboxed.ts +1 -240
- package/extensions/sandbox/runtime.ts +53 -19
- package/extensions/subagent/README.md +58 -1
- package/extensions/subagent/config.ts +118 -0
- package/extensions/subagent/index.ts +54 -22
- package/extensions/telegram/README.md +2 -0
- package/extensions/telegram/assistant-result.d.mts +4 -1
- package/extensions/telegram/assistant-result.mjs +31 -16
- package/extensions/telegram/daemon.mjs +146 -194
- package/extensions/telegram/index.ts +11 -7
- package/extensions/tool-display-mode.ts +29 -50
- package/extensions/usage/README.md +5 -0
- package/extensions/usage/index.ts +139 -80
- package/extensions/websearch/README.md +8 -1
- package/extensions/websearch/config.ts +2 -1
- package/extensions/websearch/index.ts +18 -9
- package/extensions/websearch/providers/{openai-codex.pi.ts → openai.pi.ts} +51 -16
- package/extensions/websearch/providers/shared.ts +10 -3
- package/extensions/websearch/types.ts +2 -1
- package/extensions/worktree.ts +33 -89
- package/package.json +1 -1
- package/skills/oracle/SKILL.md +14 -8
- package/skills/oracle/scripts/oracle +43 -19
- package/skills/update-changelog/SKILL.md +8 -4
|
@@ -42,6 +42,7 @@ const POLLING_STOP_TIMEOUT_MS = 4_000;
|
|
|
42
42
|
const ACTIVITY_NOTICE_COOLDOWN_MS = 60 * 60 * 1000;
|
|
43
43
|
const UNPAIRED_IDLE_SHUTDOWN_MS = 60_000;
|
|
44
44
|
const HEADLESS_START_TIMEOUT_MS = 10_000;
|
|
45
|
+
const HEADLESS_PROMPT_ACCEPT_TIMEOUT_MS = 30_000;
|
|
45
46
|
const HEADLESS_STOP_TIMEOUT_MS = 5_000;
|
|
46
47
|
const HEADLESS_ABORT_TIMEOUT_MS = 60_000;
|
|
47
48
|
const POST_COMPACTION_RETRY_GRACE_MS = 500;
|
|
@@ -441,18 +442,20 @@ function createHeadlessRpcClient(cwd) {
|
|
|
441
442
|
stdio: ["pipe", "pipe", "pipe"],
|
|
442
443
|
});
|
|
443
444
|
|
|
444
|
-
const write =
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
445
|
+
const write = (payload) => {
|
|
446
|
+
if (stopping || transportClosed || !child.stdin?.writable) {
|
|
447
|
+
throw new Error("Headless pi session is closed.");
|
|
448
|
+
}
|
|
449
|
+
child.stdin.write(JSON.stringify(payload) + "\n");
|
|
450
|
+
};
|
|
449
451
|
const eventHandlers = new Set();
|
|
450
|
-
const responseHandlers = new Set();
|
|
451
452
|
const closeHandlers = new Set();
|
|
452
453
|
const pending = new Map();
|
|
453
454
|
const closed = Promise.withResolvers();
|
|
454
455
|
let nextRequestId = 1;
|
|
455
456
|
let stopPromise = null;
|
|
457
|
+
let stopping = false;
|
|
458
|
+
let stopError;
|
|
456
459
|
let transportClosed = false;
|
|
457
460
|
|
|
458
461
|
function createExitError(code, signal) {
|
|
@@ -483,27 +486,22 @@ function createHeadlessRpcClient(cwd) {
|
|
|
483
486
|
}
|
|
484
487
|
}
|
|
485
488
|
|
|
486
|
-
child.stdin
|
|
487
|
-
|
|
488
|
-
|
|
489
|
+
for (const stream of [child.stdin, child.stdout, child.stderr]) {
|
|
490
|
+
stream?.on("error", (error) => {
|
|
491
|
+
void stop({ force: true, error });
|
|
492
|
+
});
|
|
493
|
+
}
|
|
489
494
|
child.on("error", (error) => {
|
|
490
|
-
|
|
495
|
+
void stop({ force: true, error });
|
|
491
496
|
});
|
|
492
497
|
|
|
493
498
|
if (child.stdout) {
|
|
494
499
|
attachJsonlReader(child.stdout, (line) => {
|
|
500
|
+
if (stopping || transportClosed) return;
|
|
495
501
|
const msg = safeJsonParse(line);
|
|
496
502
|
if (!msg || typeof msg.type !== "string") return;
|
|
497
503
|
|
|
498
504
|
if (msg.type === "response") {
|
|
499
|
-
for (const handler of Array.from(responseHandlers)) {
|
|
500
|
-
try {
|
|
501
|
-
handler(msg);
|
|
502
|
-
} catch {
|
|
503
|
-
// ignore
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
|
|
507
505
|
const id = typeof msg.id === "string" ? msg.id : undefined;
|
|
508
506
|
if (!id) return;
|
|
509
507
|
const pendingRequest = pending.get(id);
|
|
@@ -538,183 +536,109 @@ function createHeadlessRpcClient(cwd) {
|
|
|
538
536
|
});
|
|
539
537
|
}
|
|
540
538
|
|
|
539
|
+
child.once("exit", closeExitedTransport);
|
|
541
540
|
child.once("close", (code, signal) => {
|
|
542
|
-
finishTransportClose(createExitError(code, signal), { code, signal });
|
|
541
|
+
finishTransportClose(stopError ?? createExitError(code, signal), { code, signal });
|
|
543
542
|
});
|
|
544
543
|
|
|
545
544
|
async function call(command, { timeoutMs = HEADLESS_START_TIMEOUT_MS } = {}) {
|
|
546
|
-
if (child.exitCode !== null) {
|
|
547
|
-
return {
|
|
548
|
-
type: "response",
|
|
549
|
-
success: false,
|
|
550
|
-
error: `Headless pi session already exited with code ${child.exitCode}`,
|
|
551
|
-
};
|
|
545
|
+
if (stopping || transportClosed || child.exitCode !== null || child.signalCode !== null) {
|
|
546
|
+
return { type: "response", success: false, error: "Headless pi session is closed." };
|
|
552
547
|
}
|
|
553
548
|
|
|
554
549
|
const id = `telegram-rpc-${nextRequestId++}`;
|
|
555
|
-
const
|
|
550
|
+
const isPrompt = command.type === "prompt";
|
|
556
551
|
|
|
557
552
|
return await new Promise((resolve) => {
|
|
558
|
-
const
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
553
|
+
const fail = (error) => {
|
|
554
|
+
if (isPrompt) {
|
|
555
|
+
// A deadline or write failure cannot retract RPC input. Resolve only after close.
|
|
556
|
+
void stop({ force: true, error });
|
|
557
|
+
} else {
|
|
558
|
+
pending.delete(id);
|
|
559
|
+
clearTimeout(timeout);
|
|
560
|
+
resolve({ type: "response", success: false, error: errorMessage(error) });
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
const timeout = setTimeout(
|
|
564
|
+
() =>
|
|
565
|
+
fail(
|
|
566
|
+
new Error(
|
|
567
|
+
isPrompt
|
|
568
|
+
? "Timed out waiting for prompt acceptance. Session closed; saved history was kept. The prompt was not retried."
|
|
569
|
+
: `Timed out waiting for ${command.type} response`,
|
|
570
|
+
),
|
|
571
|
+
),
|
|
572
|
+
timeoutMs,
|
|
573
|
+
);
|
|
574
|
+
pending.set(id, { resolve, timeout, isPrompt });
|
|
568
575
|
|
|
569
576
|
try {
|
|
570
|
-
write(
|
|
577
|
+
write({ ...command, id });
|
|
571
578
|
} catch (error) {
|
|
572
|
-
|
|
573
|
-
clearTimeout(timeout);
|
|
574
|
-
resolve({ type: "response", success: false, error: errorMessage(error) });
|
|
579
|
+
fail(error);
|
|
575
580
|
}
|
|
576
581
|
});
|
|
577
582
|
}
|
|
578
583
|
|
|
579
|
-
async function prompt(
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
onStarted,
|
|
587
|
-
onFailed,
|
|
588
|
-
} = {},
|
|
589
|
-
) {
|
|
590
|
-
if (child.exitCode !== null) {
|
|
591
|
-
throw new Error(`Headless pi session already exited with code ${child.exitCode}`);
|
|
584
|
+
async function prompt(message) {
|
|
585
|
+
const response = await call(
|
|
586
|
+
{ type: "prompt", message, streamingBehavior: "followUp" },
|
|
587
|
+
{ timeoutMs: HEADLESS_PROMPT_ACCEPT_TIMEOUT_MS },
|
|
588
|
+
);
|
|
589
|
+
if (!response.success) {
|
|
590
|
+
throw new Error(getCommandError(response, "Failed to send prompt to headless session"));
|
|
592
591
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
let trackingActive = true;
|
|
601
|
-
const timeout = setTimeout(() => {
|
|
602
|
-
cleanup();
|
|
603
|
-
onFailed?.({
|
|
604
|
-
type: "response",
|
|
605
|
-
id,
|
|
606
|
-
success: false,
|
|
607
|
-
error: "Timed out waiting for prompt response",
|
|
608
|
-
});
|
|
609
|
-
reject(
|
|
610
|
-
new Error(
|
|
611
|
-
waitForStart
|
|
612
|
-
? "Timed out waiting for prompt to start"
|
|
613
|
-
: "Timed out waiting for prompt response",
|
|
614
|
-
),
|
|
615
|
-
);
|
|
616
|
-
}, timeoutMs);
|
|
617
|
-
|
|
618
|
-
const cleanup = () => {
|
|
619
|
-
if (!trackingActive) return;
|
|
620
|
-
trackingActive = false;
|
|
621
|
-
clearTimeout(timeout);
|
|
622
|
-
responseHandlers.delete(handleResponse);
|
|
623
|
-
eventHandlers.delete(handleEvent);
|
|
624
|
-
closeHandlers.delete(handleClose);
|
|
625
|
-
};
|
|
626
|
-
|
|
627
|
-
const resolveOnce = () => {
|
|
628
|
-
if (resolved) return;
|
|
629
|
-
resolved = true;
|
|
630
|
-
resolve({ id, success: true });
|
|
631
|
-
};
|
|
632
|
-
|
|
633
|
-
const rejectOnce = (error) => {
|
|
634
|
-
if (resolved) return;
|
|
635
|
-
resolved = true;
|
|
636
|
-
reject(error instanceof Error ? error : new Error(String(error)));
|
|
637
|
-
};
|
|
638
|
-
|
|
639
|
-
const handleResponse = (response) => {
|
|
640
|
-
if (response.id !== id) return;
|
|
641
|
-
|
|
642
|
-
if (response.success === false) {
|
|
643
|
-
cleanup();
|
|
644
|
-
onFailed?.(response);
|
|
645
|
-
rejectOnce(
|
|
646
|
-
new Error(getCommandError(response, "Failed to send prompt to headless session")),
|
|
647
|
-
);
|
|
648
|
-
return;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
if (accepted) return;
|
|
652
|
-
accepted = true;
|
|
653
|
-
onAccepted?.(id);
|
|
654
|
-
|
|
655
|
-
if (!waitForStart) {
|
|
656
|
-
cleanup();
|
|
657
|
-
resolveOnce();
|
|
658
|
-
}
|
|
659
|
-
};
|
|
660
|
-
|
|
661
|
-
const handleEvent = (event) => {
|
|
662
|
-
if (!accepted || event.type !== "agent_start") return;
|
|
663
|
-
onStarted?.(id);
|
|
664
|
-
cleanup();
|
|
665
|
-
resolveOnce();
|
|
666
|
-
};
|
|
667
|
-
|
|
668
|
-
const handleClose = ({ error }) => {
|
|
669
|
-
cleanup();
|
|
670
|
-
onFailed?.({ type: "response", id, success: false, error });
|
|
671
|
-
rejectOnce(new Error(error));
|
|
672
|
-
};
|
|
673
|
-
|
|
674
|
-
responseHandlers.add(handleResponse);
|
|
675
|
-
eventHandlers.add(handleEvent);
|
|
676
|
-
closeHandlers.add(handleClose);
|
|
677
|
-
|
|
678
|
-
try {
|
|
679
|
-
write(payload);
|
|
680
|
-
} catch (error) {
|
|
681
|
-
cleanup();
|
|
682
|
-
onFailed?.({ type: "response", id, success: false, error: errorMessage(error) });
|
|
683
|
-
rejectOnce(error);
|
|
684
|
-
}
|
|
685
|
-
});
|
|
592
|
+
const disposition = response.data?.disposition;
|
|
593
|
+
if (!["started", "queued", "handled"].includes(disposition)) {
|
|
594
|
+
const error = new Error("Headless pi returned an invalid prompt disposition.");
|
|
595
|
+
await stop({ force: true, error });
|
|
596
|
+
throw error;
|
|
597
|
+
}
|
|
598
|
+
return disposition;
|
|
686
599
|
}
|
|
687
600
|
|
|
688
|
-
async function stop() {
|
|
601
|
+
async function stop({ force = hasPendingPrompt(), error } = {}) {
|
|
689
602
|
if (stopPromise) return await stopPromise;
|
|
603
|
+
stopping = true;
|
|
604
|
+
stopError = error;
|
|
690
605
|
|
|
691
|
-
stopPromise =
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
try {
|
|
695
|
-
child.kill("SIGTERM");
|
|
696
|
-
} catch {
|
|
697
|
-
// ignore
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
const result = await Promise.race([
|
|
701
|
-
closed.promise,
|
|
702
|
-
sleep(HEADLESS_STOP_TIMEOUT_MS).then(() => null),
|
|
703
|
-
]);
|
|
606
|
+
stopPromise = closed.promise;
|
|
607
|
+
if (transportClosed) return;
|
|
704
608
|
|
|
705
|
-
|
|
706
|
-
|
|
609
|
+
// Native abort/shutdown cannot retract a suspended before_agent_start hook.
|
|
610
|
+
// Force retirement when acceptance is uncertain, rather than letting it resume.
|
|
611
|
+
const timeout = setTimeout(() => signalChild("SIGKILL"), HEADLESS_STOP_TIMEOUT_MS);
|
|
612
|
+
try {
|
|
613
|
+
closeExitedTransport();
|
|
614
|
+
signalChild(force ? "SIGKILL" : "SIGTERM");
|
|
615
|
+
// `exit` is not enough: stdout/stderr must be closed before callers clean up.
|
|
616
|
+
await stopPromise;
|
|
617
|
+
} finally {
|
|
618
|
+
clearTimeout(timeout);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
707
621
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
// ignore
|
|
712
|
-
}
|
|
622
|
+
function hasPendingPrompt() {
|
|
623
|
+
return Array.from(pending.values()).some((request) => request.isPrompt);
|
|
624
|
+
}
|
|
713
625
|
|
|
714
|
-
|
|
715
|
-
|
|
626
|
+
function closeExitedTransport() {
|
|
627
|
+
if (!stopping || (child.exitCode === null && child.signalCode === null)) return;
|
|
628
|
+
// Descendants can retain inherited writers after Pi exits. Close only our endpoints,
|
|
629
|
+
// then let Node emit the real `close` event once these streams have finished closing.
|
|
630
|
+
child.stdin?.destroy();
|
|
631
|
+
child.stdout?.destroy();
|
|
632
|
+
child.stderr?.destroy();
|
|
633
|
+
}
|
|
716
634
|
|
|
717
|
-
|
|
635
|
+
function signalChild(signal) {
|
|
636
|
+
try {
|
|
637
|
+
child.kill(signal);
|
|
638
|
+
} catch (error) {
|
|
639
|
+
stopError ??= error;
|
|
640
|
+
console.error(`[telegram] Failed to send ${signal} to headless pi: ${errorMessage(error)}`);
|
|
641
|
+
}
|
|
718
642
|
}
|
|
719
643
|
|
|
720
644
|
return {
|
|
@@ -722,14 +646,16 @@ function createHeadlessRpcClient(cwd) {
|
|
|
722
646
|
call,
|
|
723
647
|
prompt,
|
|
724
648
|
stop,
|
|
649
|
+
get closing() {
|
|
650
|
+
return stopping || transportClosed;
|
|
651
|
+
},
|
|
652
|
+
get awaitingPromptAcceptance() {
|
|
653
|
+
return hasPendingPrompt();
|
|
654
|
+
},
|
|
725
655
|
onEvent(handler) {
|
|
726
656
|
eventHandlers.add(handler);
|
|
727
657
|
return () => eventHandlers.delete(handler);
|
|
728
658
|
},
|
|
729
|
-
onResponse(handler) {
|
|
730
|
-
responseHandlers.add(handler);
|
|
731
|
-
return () => responseHandlers.delete(handler);
|
|
732
|
-
},
|
|
733
659
|
onClose(handler) {
|
|
734
660
|
closeHandlers.add(handler);
|
|
735
661
|
return () => closeHandlers.delete(handler);
|
|
@@ -990,8 +916,9 @@ function setSessionCompacting(session, compacting) {
|
|
|
990
916
|
}
|
|
991
917
|
|
|
992
918
|
async function waitForHeadlessSessionPromptWindow(session) {
|
|
993
|
-
while (sessions.has(session.key)) {
|
|
919
|
+
while (sessions.has(session.key) && !session.closing && !session.rpc.closing) {
|
|
994
920
|
await refreshHeadlessSessionState(session).catch(() => {});
|
|
921
|
+
if (session.closing || session.rpc.closing) break;
|
|
995
922
|
|
|
996
923
|
const pendingMessageCount =
|
|
997
924
|
typeof session.pendingMessageCount === "number" ? session.pendingMessageCount : 0;
|
|
@@ -1559,7 +1486,7 @@ async function createHeadlessSession(cwd) {
|
|
|
1559
1486
|
: 0,
|
|
1560
1487
|
retryAfterCompactionUntil: 0,
|
|
1561
1488
|
awaitingRetry: false,
|
|
1562
|
-
|
|
1489
|
+
runAssistantMessages: undefined,
|
|
1563
1490
|
lastTurnResult: undefined,
|
|
1564
1491
|
lastTurnSeq: 0,
|
|
1565
1492
|
unreadTurns: [],
|
|
@@ -1572,6 +1499,9 @@ async function createHeadlessSession(cwd) {
|
|
|
1572
1499
|
compactionWaiters: new Set(),
|
|
1573
1500
|
sendQueue: Promise.resolve(),
|
|
1574
1501
|
async sendText(text) {
|
|
1502
|
+
if (session.closing || rpc.closing || !sessions.has(session.key)) {
|
|
1503
|
+
throw new Error("Session is no longer available.");
|
|
1504
|
+
}
|
|
1575
1505
|
const backlogSize = session.pendingSendCount + session.queuedPromptCount;
|
|
1576
1506
|
if (backlogSize >= MAX_QUEUED_HEADLESS_PROMPTS) {
|
|
1577
1507
|
throw new Error("Session is busy. Too many queued prompts. Wait for it to catch up.");
|
|
@@ -1582,17 +1512,15 @@ async function createHeadlessSession(cwd) {
|
|
|
1582
1512
|
.then(async () => {
|
|
1583
1513
|
await waitForHeadlessSessionPromptWindow(session);
|
|
1584
1514
|
|
|
1585
|
-
const waitForStart = !session.busy;
|
|
1586
1515
|
session.queuedPromptCount += 1;
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
streamingBehavior: "followUp",
|
|
1590
|
-
waitForStart,
|
|
1591
|
-
timeoutMs: HEADLESS_START_TIMEOUT_MS,
|
|
1592
|
-
onFailed: () => {
|
|
1516
|
+
try {
|
|
1517
|
+
if ((await rpc.prompt(text)) === "handled") {
|
|
1593
1518
|
session.queuedPromptCount = Math.max(0, session.queuedPromptCount - 1);
|
|
1594
|
-
}
|
|
1595
|
-
})
|
|
1519
|
+
}
|
|
1520
|
+
} catch (error) {
|
|
1521
|
+
session.queuedPromptCount = Math.max(0, session.queuedPromptCount - 1);
|
|
1522
|
+
throw error;
|
|
1523
|
+
}
|
|
1596
1524
|
})
|
|
1597
1525
|
.finally(() => {
|
|
1598
1526
|
session.pendingSendCount = Math.max(0, session.pendingSendCount - 1);
|
|
@@ -1602,20 +1530,35 @@ async function createHeadlessSession(cwd) {
|
|
|
1602
1530
|
await sendOperation;
|
|
1603
1531
|
},
|
|
1604
1532
|
async abort() {
|
|
1533
|
+
if (
|
|
1534
|
+
rpc.awaitingPromptAcceptance ||
|
|
1535
|
+
rpc.closing ||
|
|
1536
|
+
(!session.busy && session.pendingSendCount > 0)
|
|
1537
|
+
) {
|
|
1538
|
+
session.closing = true;
|
|
1539
|
+
await rpc.stop({
|
|
1540
|
+
force: true,
|
|
1541
|
+
error: new Error("Prompt cancelled. Session closed; saved history was kept."),
|
|
1542
|
+
});
|
|
1543
|
+
await session.sendQueue;
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
1605
1546
|
const response = await rpc.call({ type: "abort" }, { timeoutMs: HEADLESS_ABORT_TIMEOUT_MS });
|
|
1606
1547
|
if (!response.success) {
|
|
1607
1548
|
throw new Error(getCommandError(response, "Failed to abort headless session"));
|
|
1608
1549
|
}
|
|
1609
1550
|
},
|
|
1610
1551
|
async quit() {
|
|
1611
|
-
if (session.closing)
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1552
|
+
if (!session.closing) {
|
|
1553
|
+
session.closing = true;
|
|
1554
|
+
try {
|
|
1555
|
+
await session.abort();
|
|
1556
|
+
} catch {
|
|
1557
|
+
// ignore
|
|
1558
|
+
}
|
|
1617
1559
|
}
|
|
1618
1560
|
await rpc.stop();
|
|
1561
|
+
await session.sendQueue;
|
|
1619
1562
|
},
|
|
1620
1563
|
};
|
|
1621
1564
|
|
|
@@ -1645,6 +1588,8 @@ async function createHeadlessSession(cwd) {
|
|
|
1645
1588
|
}
|
|
1646
1589
|
|
|
1647
1590
|
if (event.type === "agent_start") {
|
|
1591
|
+
// Keep same-run partial text through native retries, never across settlement.
|
|
1592
|
+
session.runAssistantMessages ??= [];
|
|
1648
1593
|
session.retryAfterCompactionUntil = 0;
|
|
1649
1594
|
session.queuedPromptCount = Math.max(0, session.queuedPromptCount - 1);
|
|
1650
1595
|
session.busy = true;
|
|
@@ -1652,8 +1597,12 @@ async function createHeadlessSession(cwd) {
|
|
|
1652
1597
|
return;
|
|
1653
1598
|
}
|
|
1654
1599
|
|
|
1600
|
+
if (event.type === "message_end" && event.message.role === "assistant") {
|
|
1601
|
+
session.runAssistantMessages?.push(event.message);
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1655
1605
|
if (event.type === "agent_end") {
|
|
1656
|
-
session.lastAgentEndMessages = event.messages;
|
|
1657
1606
|
session.awaitingRetry = Boolean(event.willRetry);
|
|
1658
1607
|
session.busy = true;
|
|
1659
1608
|
updateTypingIndicator();
|
|
@@ -1661,8 +1610,11 @@ async function createHeadlessSession(cwd) {
|
|
|
1661
1610
|
}
|
|
1662
1611
|
|
|
1663
1612
|
if (event.type === "agent_settled") {
|
|
1664
|
-
const result = formatTelegramAssistantResultFromMessages(
|
|
1665
|
-
|
|
1613
|
+
const result = formatTelegramAssistantResultFromMessages(
|
|
1614
|
+
session.runAssistantMessages,
|
|
1615
|
+
event.aborted,
|
|
1616
|
+
);
|
|
1617
|
+
session.runAssistantMessages = undefined;
|
|
1666
1618
|
session.awaitingRetry = false;
|
|
1667
1619
|
session.retryAfterCompactionUntil = 0;
|
|
1668
1620
|
session.busy = false;
|
|
@@ -500,7 +500,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
500
500
|
sendFileToolRegistered: false,
|
|
501
501
|
};
|
|
502
502
|
|
|
503
|
-
let
|
|
503
|
+
let runAssistantMessages: AgentMessage[] | undefined;
|
|
504
504
|
const daemonMessageHandlers = new Set<(msg: DaemonToClientMessage) => void>();
|
|
505
505
|
|
|
506
506
|
function clearPendingInjectedFlushTimer() {
|
|
@@ -1070,6 +1070,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1070
1070
|
}
|
|
1071
1071
|
|
|
1072
1072
|
pi.on("session_start", async (_event, ctx) => {
|
|
1073
|
+
runAssistantMessages = undefined;
|
|
1073
1074
|
state.lastCtx = ctx;
|
|
1074
1075
|
startAutoConnectLoop();
|
|
1075
1076
|
if (isSocketConnected()) {
|
|
@@ -1090,6 +1091,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
1090
1091
|
});
|
|
1091
1092
|
|
|
1092
1093
|
pi.on("agent_start", async (_event, ctx) => {
|
|
1094
|
+
// Native retries start another low-level agent run before session-level settlement.
|
|
1095
|
+
runAssistantMessages ??= [];
|
|
1093
1096
|
if (state.compacting) {
|
|
1094
1097
|
applyCompactingState(false, ctx);
|
|
1095
1098
|
}
|
|
@@ -1108,13 +1111,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
1108
1111
|
}
|
|
1109
1112
|
});
|
|
1110
1113
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1114
|
+
// turn_end observes the final message after every message_end replacement handler.
|
|
1115
|
+
pi.on("turn_end", (event) => {
|
|
1116
|
+
if (event.message.role === "assistant") runAssistantMessages?.push(event.message);
|
|
1113
1117
|
});
|
|
1114
1118
|
|
|
1115
|
-
pi.on("agent_settled", async (
|
|
1116
|
-
const result = formatTelegramAssistantResultFromMessages(
|
|
1117
|
-
|
|
1119
|
+
pi.on("agent_settled", async (event, ctx) => {
|
|
1120
|
+
const result = formatTelegramAssistantResultFromMessages(runAssistantMessages, event.aborted);
|
|
1121
|
+
runAssistantMessages = undefined;
|
|
1118
1122
|
state.awaitingRetry = false;
|
|
1119
1123
|
state.busy = false;
|
|
1120
1124
|
if (isSocketConnected()) {
|
|
@@ -1161,7 +1165,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1161
1165
|
state.busy = false;
|
|
1162
1166
|
state.compacting = false;
|
|
1163
1167
|
state.awaitingRetry = false;
|
|
1164
|
-
|
|
1168
|
+
runAssistantMessages = undefined;
|
|
1165
1169
|
state.pendingInjectedTexts = [];
|
|
1166
1170
|
disconnect(false);
|
|
1167
1171
|
state.lastCtx = null;
|
|
@@ -5,19 +5,13 @@ import {
|
|
|
5
5
|
getAgentDir,
|
|
6
6
|
type AppKeybinding,
|
|
7
7
|
CustomEditor,
|
|
8
|
-
createBashToolDefinition,
|
|
9
|
-
createFindToolDefinition,
|
|
10
|
-
createGrepToolDefinition,
|
|
11
|
-
createLsToolDefinition,
|
|
12
|
-
createReadToolDefinition,
|
|
13
8
|
type AgentToolResult,
|
|
14
9
|
type BashToolDetails,
|
|
15
10
|
type ExtensionAPI,
|
|
16
11
|
type ExtensionContext,
|
|
17
12
|
type KeybindingsManager,
|
|
18
13
|
type Theme,
|
|
19
|
-
type
|
|
20
|
-
type ToolInfo,
|
|
14
|
+
type ToolRenderers,
|
|
21
15
|
type ToolRenderResultOptions,
|
|
22
16
|
} from "@earendil-works/pi-coding-agent";
|
|
23
17
|
import {
|
|
@@ -48,20 +42,13 @@ const FIND_NO_MATCHES_OUTPUT = "No files found matching pattern"; // core/tools/
|
|
|
48
42
|
const LS_EMPTY_DIRECTORY_OUTPUT = "(empty directory)"; // core/tools/ls.ts
|
|
49
43
|
|
|
50
44
|
// PowerShell is intentionally omitted because Tau does not officially support Windows.
|
|
51
|
-
const
|
|
52
|
-
read: createReadToolDefinition,
|
|
53
|
-
bash: createBashToolDefinition,
|
|
54
|
-
grep: createGrepToolDefinition,
|
|
55
|
-
find: createFindToolDefinition,
|
|
56
|
-
ls: createLsToolDefinition,
|
|
57
|
-
};
|
|
45
|
+
const TOOL_NAMES = ["read", "bash", "grep", "find", "ls"] as const;
|
|
58
46
|
|
|
59
47
|
// --- Types ---
|
|
60
48
|
|
|
61
49
|
type Mode = (typeof MODES)[number];
|
|
62
|
-
type ToolName =
|
|
63
|
-
type
|
|
64
|
-
type AnyToolRenderContext = Parameters<NonNullable<AnyToolDefinition["renderResult"]>>[3];
|
|
50
|
+
type ToolName = (typeof TOOL_NAMES)[number];
|
|
51
|
+
type AnyToolRenderContext = Parameters<NonNullable<ToolRenderers["renderResult"]>>[3];
|
|
65
52
|
type JsonObject = Record<string, unknown>;
|
|
66
53
|
type EditorFactory = NonNullable<ReturnType<ExtensionContext["ui"]["getEditorComponent"]>>;
|
|
67
54
|
type WorkingIndicator = NonNullable<Parameters<CustomEditor["setWorkingStatusIndicator"]>[0]>;
|
|
@@ -137,20 +124,13 @@ async function saveConfig(config: ToolDisplayModeConfig): Promise<void> {
|
|
|
137
124
|
await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
138
125
|
}
|
|
139
126
|
|
|
140
|
-
// --- Tool
|
|
141
|
-
|
|
142
|
-
function shouldRegisterToolRenderer(tools: ToolInfo[], name: ToolName): boolean {
|
|
143
|
-
const existingTool = tools.find((tool) => tool.name === name);
|
|
144
|
-
return existingTool?.sourceInfo.source === "builtin";
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function createToolDisplayDefinition(options: {
|
|
148
|
-
name: ToolName;
|
|
149
|
-
getMode: () => Mode;
|
|
150
|
-
}): AnyToolDefinition {
|
|
151
|
-
const { name, getMode } = options;
|
|
152
|
-
const base: AnyToolDefinition = TOOL_FACTORIES[name](process.cwd());
|
|
127
|
+
// --- Tool rendering ---
|
|
153
128
|
|
|
129
|
+
function createToolDisplayRenderers(
|
|
130
|
+
name: ToolName,
|
|
131
|
+
base: ToolRenderers,
|
|
132
|
+
getMode: () => Mode | undefined,
|
|
133
|
+
): ToolRenderers {
|
|
154
134
|
return {
|
|
155
135
|
...base,
|
|
156
136
|
|
|
@@ -159,22 +139,21 @@ function createToolDisplayDefinition(options: {
|
|
|
159
139
|
const renderer = base.renderResult;
|
|
160
140
|
const previous = context.lastComponent;
|
|
161
141
|
|
|
162
|
-
|
|
142
|
+
// Explicit expansion (including HTML export) always reveals the delegated output.
|
|
143
|
+
if (mode === "minimal" && !options.expanded) {
|
|
163
144
|
const component =
|
|
164
145
|
previous instanceof MinimalResultComponent
|
|
165
146
|
? previous
|
|
166
147
|
: new MinimalResultComponent(previous);
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
});
|
|
172
|
-
}
|
|
148
|
+
component.nativeComponent = renderer?.(result, options, theme, {
|
|
149
|
+
...context,
|
|
150
|
+
lastComponent: component.nativeComponent,
|
|
151
|
+
});
|
|
173
152
|
component.setText(formatMinimalResult(name, result, options, theme, context));
|
|
174
153
|
return component;
|
|
175
154
|
}
|
|
176
155
|
|
|
177
|
-
const expanded = mode === "expanded";
|
|
156
|
+
const expanded = mode === "expanded" || options.expanded;
|
|
178
157
|
return (
|
|
179
158
|
renderer?.(result, { ...options, expanded }, theme, {
|
|
180
159
|
...context,
|
|
@@ -576,7 +555,6 @@ class ToolDisplayEditor implements EditorComponent, Focusable {
|
|
|
576
555
|
|
|
577
556
|
export default function toolDisplayModeExtension(pi: ExtensionAPI): void {
|
|
578
557
|
let mode: Mode = INITIAL_MODE;
|
|
579
|
-
let registeredToolRenderers = false;
|
|
580
558
|
let installedEditorFactory: EditorFactory | undefined;
|
|
581
559
|
let previousEditorFactory: EditorFactory | undefined;
|
|
582
560
|
let editor: ToolDisplayEditor | undefined;
|
|
@@ -588,6 +566,17 @@ export default function toolDisplayModeExtension(pi: ExtensionAPI): void {
|
|
|
588
566
|
let compacting = false;
|
|
589
567
|
const activities = new Map<ActivitySource, string>();
|
|
590
568
|
|
|
569
|
+
pi.registerToolRenderer((name, next) => {
|
|
570
|
+
const renderers = next();
|
|
571
|
+
if (!renderers?.renderResult || !(TOOL_NAMES as readonly string[]).includes(name))
|
|
572
|
+
return renderers;
|
|
573
|
+
|
|
574
|
+
// Restored rows resolve before session_start. Read the mode when rendering.
|
|
575
|
+
return createToolDisplayRenderers(name as ToolName, renderers, () =>
|
|
576
|
+
context ? mode : undefined,
|
|
577
|
+
);
|
|
578
|
+
});
|
|
579
|
+
|
|
591
580
|
const getActivity = (): ActivityState | undefined => {
|
|
592
581
|
if (
|
|
593
582
|
!context ||
|
|
@@ -662,22 +651,12 @@ export default function toolDisplayModeExtension(pi: ExtensionAPI): void {
|
|
|
662
651
|
pi.on("session_start", async (_event, ctx) => {
|
|
663
652
|
if (!ctx.hasUI) return;
|
|
664
653
|
mode = (await loadConfig()).mode;
|
|
665
|
-
|
|
666
|
-
if (!registeredToolRenderers) {
|
|
667
|
-
const tools = pi.getAllTools();
|
|
668
|
-
for (const name of Object.keys(TOOL_FACTORIES) as ToolName[]) {
|
|
669
|
-
if (!shouldRegisterToolRenderer(tools, name)) continue;
|
|
670
|
-
|
|
671
|
-
pi.registerTool(createToolDisplayDefinition({ name, getMode: () => mode }));
|
|
672
|
-
}
|
|
673
|
-
registeredToolRenderers = true;
|
|
674
|
-
}
|
|
654
|
+
context = ctx.mode === "tui" ? ctx : undefined;
|
|
675
655
|
|
|
676
656
|
applyMode(ctx, mode);
|
|
677
657
|
|
|
678
658
|
if (installedEditorFactory && ctx.ui.getEditorComponent() === installedEditorFactory) return;
|
|
679
659
|
|
|
680
|
-
context = ctx.mode === "tui" ? ctx : undefined;
|
|
681
660
|
sessionKey =
|
|
682
661
|
ctx.sessionManager.getSessionFile() ?? `session:${ctx.sessionManager.getSessionId()}`;
|
|
683
662
|
previousEditorFactory = ctx.ui.getEditorComponent();
|