




















@@ -165,24 +165,7 @@ async function waitForPath(filePath: string, timeoutMs = 60_000): Promise<void>
165165throw new Error(`timed out waiting for ${filePath}`);
166166}
167167168-function extractAssistantTexts(messages: unknown[]): string[] {
169-const texts: string[] = [];
170-for (const entry of messages) {
171-if (!entry || typeof entry !== "object") {
172-continue;
173-}
174-if ((entry as { role?: unknown }).role !== "assistant") {
175-continue;
176-}
177-const text = extractFirstTextBlock(entry);
178-if (typeof text === "string" && text.trim().length > 0) {
179-texts.push(text);
180-}
181-}
182-return texts;
183-}
184-185-function formatAssistantTextPreview(texts: string[], maxChars = 800): string {
168+function formatTextPreview(texts: string[], maxChars = 800): string {
186169const combined = texts.join("\n\n").trim();
187170if (!combined) {
188171return "<none>";
@@ -191,48 +174,28 @@ function formatAssistantTextPreview(texts: string[], maxChars = 800): string {
191174}
192175193176async function waitForTrajectoryExportInstructionText(params: {
194-client: GatewayClient;
195177events: EventFrame[];
196178expectedText: string;
197179runId: string;
198-sessionKey: string;
199180timeoutMs: number;
200181}): Promise<string> {
201182const deadline = Date.now() + params.timeoutMs;
202-let lastAssistantTexts: string[] = [];
183+let finalTexts: string[] = [];
203184while (Date.now() < deadline) {
204-const eventText = params.events
185+finalTexts = params.events
205186.map((event) => extractChatFinalText(event, params.runId))
206-.find(Boolean);
207-if (eventText) {
208-return eventText;
209-}
210-const sessionEventText = params.events
211-.map((event) => extractChatFinalTextForSession(event, params.sessionKey))
212-.find((text) => text?.includes(params.expectedText));
213-if (sessionEventText) {
214-return sessionEventText;
215-}
216-const history: { messages?: unknown[] } = await params.client.request(
217-"chat.history",
218-{
219-sessionKey: params.sessionKey,
220-limit: 24,
221-},
222-{ timeoutMs: 10_000 },
223-);
224-lastAssistantTexts = extractAssistantTexts(history.messages ?? []);
225-const historyText = lastAssistantTexts.find((text) => text.includes(params.expectedText));
226-if (historyText) {
227-return historyText;
187+.filter((text): text is string => typeof text === "string" && text.trim().length > 0);
188+const matchedText = finalTexts.find((text) => text.includes(params.expectedText));
189+if (matchedText) {
190+return matchedText;
228191}
229192await new Promise((resolve) => {
230193setTimeout(resolve, 500);
231194});
232195}
233196throw new Error(
234197`timed out waiting for trajectory export instruction text for ${params.runId}; ` +
235-`events=${params.events.length}; assistantTexts=${formatAssistantTextPreview(lastAssistantTexts)}`,
198+`events=${params.events.length}; finalTexts=${formatTextPreview(finalTexts)}`,
236199);
237200}
238201@@ -248,41 +211,14 @@ function extractChatFinalText(event: EventFrame, runId: string): string | undefi
248211if (record.runId !== runId || record.state !== "final") {
249212return undefined;
250213}
251-return extractChatFinalRecordText(record);
252-}
253-254-function extractChatFinalTextForSession(event: EventFrame, sessionKey: string): string | undefined {
255-if (event.event !== "chat") {
256-return undefined;
257-}
258-const payload = event.payload;
259-if (!payload || typeof payload !== "object") {
260-return undefined;
261-}
262-const record = payload as Record<string, unknown>;
263-if (record.sessionKey !== sessionKey || record.state !== "final") {
264-return undefined;
265-}
266-return extractChatFinalRecordText(record);
267-}
268-269-function extractChatFinalRecordText(record: Record<string, unknown>): string | undefined {
270214const message = record.message;
271215if (!message || typeof message !== "object") {
272216return undefined;
273217}
274-const messageRecord = message as Record<string, unknown>;
275-if (typeof messageRecord.text === "string" && messageRecord.text.trim()) {
276-return messageRecord.text;
277-}
278-const content = Array.isArray(messageRecord.content) ? messageRecord.content : [];
279-return content
280-.map((entry) =>
281-entry && typeof entry === "object" ? (entry as Record<string, unknown>).text : undefined,
282-)
283-.filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0)
284-.join("\n")
285-.trim();
218+const messageRecord = message as { text?: unknown };
219+return typeof messageRecord.text === "string"
220+ ? messageRecord.text
221+ : extractFirstTextBlock(message);
286222}
287223288224async function approveTrajectoryExport(client: GatewayClient): Promise<string> {
@@ -295,6 +231,7 @@ async function approveTrajectoryExport(client: GatewayClient): Promise<string> {
295231};
296232}
297233| undefined;
234+let lastApprovalCommands: string[] = [];
298235while (Date.now() - startedAt < 60_000) {
299236const approvals = (await client.request(
300237"exec.approval.list",
@@ -306,6 +243,9 @@ async function approveTrajectoryExport(client: GatewayClient): Promise<string> {
306243command?: string;
307244};
308245}>;
246+lastApprovalCommands = approvals
247+.map((entry) => entry.request?.command)
248+.filter((command): command is string => typeof command === "string");
309249approval = approvals.find((entry) =>
310250entry.request?.command?.includes("sessions export-trajectory"),
311251);
@@ -316,11 +256,12 @@ async function approveTrajectoryExport(client: GatewayClient): Promise<string> {
316256setTimeout(resolve, 500);
317257});
318258}
319-expect(typeof approval?.id).toBe("string");
320-expect(approval?.request?.command).toContain("sessions export-trajectory");
321259if (!approval?.id) {
322-throw new Error("expected trajectory export approval id");
260+throw new Error(
261+`expected trajectory export approval id; approvals=${JSON.stringify(lastApprovalCommands)}`,
262+);
323263}
264+expect(approval.request?.command).toContain("sessions export-trajectory");
324265await client.request(
325266"exec.approval.resolve",
326267{ id: approval.id, decision: "allow-once" },
@@ -448,20 +389,18 @@ describeLive("gateway live trajectory export", () => {
448389typeof exportResponse?.message === "object"
449390 ? extractFirstTextBlock(exportResponse.message)
450391 : await waitForTrajectoryExportInstructionText({
451- client,
452392events: gatewayEvents,
453393expectedText: "Trajectory exports can include",
454394runId: exportRunId,
455- sessionKey,
456395timeoutMs: 60_000,
457396});
458397expect(finalText).toContain("Trajectory exports can include");
459398expect(finalText).toContain("through exec approval");
399+expect(finalText).toContain("Approve once");
460400const approvalId = await approveTrajectoryExport(client);
461401logLiveStep("export:approved", { approvalId });
462402await waitForPath(path.join(bundleDir, "events.jsonl"), 60_000);
463-logLiveStep("export:done", { finalText });
464-expect(finalText).toContain("Approve once");
403+logLiveStep("export:done", { approvalId, finalText });
465404const bundleNames = await listDirectoryNames(bundleDir);
466405for (const expectedName of [
467406"artifacts.json",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。