(args: string)
| 831 | } |
| 832 | |
| 833 | private async runCompactCommand(args: string): Promise<void> { |
| 834 | const instruction = args.trim() || undefined; |
| 835 | let started = false; |
| 836 | let settled = false; |
| 837 | let unsubscribe: (() => void) | undefined; |
| 838 | // The agent-core compaction worker emits events in this order on |
| 839 | // failure: `compaction.cancelled` (from `markCanceled`) followed by |
| 840 | // `error` (unless the failure happened while blocked-by-turn, in |
| 841 | // which case `compact()` itself rejects). We resolve on whichever |
| 842 | // terminal event arrives first and ignore the rest, so a follow-up |
| 843 | // `error` after a cancelled never causes a double-settle. |
| 844 | const completion = new Promise<CompactionOutcome>((resolve, reject) => { |
| 845 | const settle = (action: () => void): void => { |
| 846 | if (settled) return; |
| 847 | settled = true; |
| 848 | action(); |
| 849 | }; |
| 850 | unsubscribe = this.session.onEvent((event: Event) => { |
| 851 | if (event.agentId !== undefined && event.agentId !== MAIN_AGENT_ID) return; |
| 852 | if (event.type === 'compaction.started') { |
| 853 | started = true; |
| 854 | void this.emitLocalCommandMessage( |
| 855 | instruction === undefined |
| 856 | ? 'Compacting conversation context…' |
| 857 | : `Compacting conversation context with instruction: ${instruction}`, |
| 858 | ); |
| 859 | return; |
| 860 | } |
| 861 | if (event.type === 'compaction.completed') { |
| 862 | settle(() => resolve({ kind: 'completed', result: event.result })); |
| 863 | return; |
| 864 | } |
| 865 | if (event.type === 'compaction.cancelled') { |
| 866 | settle(() => resolve({ kind: 'cancelled' })); |
| 867 | return; |
| 868 | } |
| 869 | if (event.type === 'compaction.blocked') { |
| 870 | void this.emitLocalCommandMessage('Compaction is blocked by the current turn; retry when the turn is idle.'); |
| 871 | return; |
| 872 | } |
| 873 | // Surface any error event the worker emits, even if it lands |
| 874 | // before `compaction.started` — that path is currently empty |
| 875 | // (begin() throws synchronously and rejects compact()), but |
| 876 | // dropping pre-start errors would silently hang the prompt if |
| 877 | // the worker is ever restructured. |
| 878 | if (event.type === 'error') { |
| 879 | settle(() => reject(new Error(event.message))); |
| 880 | } |
| 881 | }); |
| 882 | }); |
| 883 | try { |
| 884 | await this.session.compact({ instruction }); |
| 885 | if (!started && !settled) { |
| 886 | await this.emitLocalCommandMessage('Compaction was not started.'); |
| 887 | return; |
| 888 | } |
| 889 | const outcome = await completion; |
| 890 | if (outcome.kind === 'completed') { |
no test coverage detected