42fd3db9df8e7c53ae02a27ff12613aec0622f9b packages/effect-acp/src/client.test.ts:1310-1340 full-source-sha256=6c88ee1e32839bdb74e377115cc83205ec24178e0fbfa5e5d92ed133e919b3d1 1310: it.effect("returns the ACP v2 idle error as the prompt result", () => 1311: Effect.gen(function* () { 1312: const { stdio, input, output } = yield* makeInMemoryStdio(); 1313: const acp = yield* AcpClient.make(stdio); 1314: const error = { code: -32000, message: "Missing Authentication header" }; 1315: const prompt = yield* acp.agent 1316: .prompt({ sessionId: "idle-error", prompt: [{ type: "text", text: "Hello" }] }) 1317: .pipe(Effect.forkScoped); 1318: const request = yield* decodePromptRequestLine(yield* Queue.take(output)); 1319: yield* Queue.offer( 1320: input, 1321: concatBytes([ 1322: yield* encodeJsonl(jsonRpcResponse(Schema.Unknown), { 1323: jsonrpc: "2.0", 1324: id: request.id, 1325: result: { messageId: `user-${request.id}` }, 1326: }), 1327: yield* encodeJsonl(SessionUpdateNotification, { 1328: jsonrpc: "2.0", 1329: method: "session/update", 1330: params: { 1331: sessionId: "idle-error", 1332: update: { sessionUpdate: "state_update", state: "idle", stopReason: "error", error }, 1333: }, 1334: }), 1335: ]), 1336: ); 1337: assert.deepEqual(yield* Fiber.join(prompt), { stopReason: "error", error }); 1338: }).pipe(Effect.scoped), 1339: ); 1340: 42fd3db9df8e7c53ae02a27ff12613aec0622f9b apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts:400-546 full-source-sha256=7fb58d8f2edfb6957360b51bb2866f9123702a7b575a345b47f0d4ebd9518f7d 400: 401: describe("AcpAdapterV2", () => { 402: it.live.each(["details", "none"] as const)( 403: "fails the turn on an ACP v2 idle error with %s, settles its open tool, and accepts the next prompt", 404: (details) => 405: Effect.gen(function* () { 406: const path = yield* Path.Path; 407: const instanceId = ProviderInstanceId.make(`idle-error-${details}`); 408: const threadId = ThreadId.make(`thread-idle-error-${details}`); 409: const childSessionId = "mock-child-session-idle-error"; 410: const adapter = yield* makeAcpAdapterV2({ 411: instanceId, 412: selfInvocation: yield* resolveSelfInvocation(), 413: flavor: { 414: driver: ACP_TEST_DRIVER, 415: capabilities: AcpProviderCapabilitiesV2, 416: extractBackgroundTaskId: (toolCall) => 417: toolCall.toolCallId === "tool-call-idle-error-monitor" 418: ? "idle-error-monitor" 419: : undefined, 420: extractSubagentUpdate: (toolCall) => 421: toolCall.toolCallId === "tool-call-idle-error-subagent" 422: ? { 423: nativeTaskId: "task-idle-error", 424: prompt: "child work", 425: title: "child work", 426: model: null, 427: status: toolCall.status === "completed" ? "completed" : "running", 428: childSessionId, 429: result: null, 430: suppressNormalTool: true, 431: } 432: : undefined, 433: makeRuntime: makeMockRuntime({ 434: childProcessSpawner: yield* ChildProcessSpawner.ChildProcessSpawner, 435: mockAgentPath: yield* path.fromFileUrl( 436: new URL("../../../scripts/acp-mock-agent.ts", import.meta.url), 437: ), 438: environment: { T3_ACP_IDLE_ERROR: details }, 439: }), 440: }, 441: }); 442: const runtimePolicy = ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({ 443: runtimeMode: "full-access", 444: interactionMode: "default", 445: cwd: process.cwd(), 446: }); 447: const modelSelection = { instanceId, model: "default" } as const; 448: const runtime = yield* adapter.openSession({ 449: threadId, 450: providerSessionId: ProviderSessionId.make(`session-idle-error-${details}`), 451: modelSelection, 452: runtimePolicy, 453: }); 454: const providerThread = yield* runtime.ensureThread({ 455: threadId, 456: modelSelection, 457: runtimePolicy, 458: }); 459: const runTurn = (ordinal: number) => 460: Effect.gen(function* () { 461: yield* runtime.startTurn( 462: makeTurnInput({ 463: threadId, 464: providerThread, 465: instanceId, 466: runtimePolicy, 467: now: yield* DateTime.now, 468: ordinal, 469: }), 470: ); 471: return yield* runtime.events.pipe( 472: Stream.takeUntil((event) => event.type === "turn.terminal"), 473: Stream.runCollect, 474: ); 475: }); 476: 477: const failedEvents = yield* runTurn(1); 478: const failed = failedEvents.at(-1); 479: assert.equal(failed?.type, "turn.terminal"); 480: if (failed?.type !== "turn.terminal" || failed.status !== "failed") { 481: return assert.fail("expected a failed terminal"); 482: } 483: assert.isTrue( 484: failed.childTurnsContinue, 485: "the run keeps ingesting work that outlives the failed prompt", 486: ); 487: const lateSubagent = yield* runtime.events.pipe( 488: Stream.filter( 489: (event) => event.type === "subagent.updated" && event.subagent.status === "completed", 490: ), 491: Stream.runHead, 492: Effect.timeoutOption("2 seconds"), 493: ); 494: assert.isTrue( 495: Option.isSome(lateSubagent) && Option.isSome(lateSubagent.value), 496: "a subagent finishing after the failed prompt still reports to the run", 497: ); 498: assert.deepEqual( 499: failed.failure, 500: details === "details" 501: ? { 502: class: "provider_error", 503: message: "Missing Authentication header", 504: code: "-32000", 505: retryable: null, 506: } 507: : { 508: class: "provider_error", 509: message: "Provider turn failed.", 510: code: null, 511: retryable: null, 512: }, 513: ); 514: const toolItems = (nativeId: string) => 515: failedEvents.flatMap((event) => 516: event.type === "turn_item.updated" && 517: event.turnItem.nativeItemRef?.nativeId === nativeId 518: ? [event.turnItem] 519: : [], 520: ); 521: const toolStatuses = toolItems("tool-call-idle-error-1").map((item) => item.status); 522: assert.include(toolStatuses, "running"); 523: assert.equal(toolStatuses.at(-1), "failed"); 524: assert.deepEqual( 525: toolItems("tool-call-idle-error-monitor").map((item) => item.status), 526: ["running"], 527: "a background monitor outlives the failed turn and keeps its running row", 528: ); 529: const childItems = toolItems("task-idle-error:tool:tool-call-idle-error-child"); 530: assert.deepEqual( 531: childItems.map((item) => item.status), 532: ["running"], 533: "a child-session tool keeps running in its own thread", 534: ); 535: assert.isTrue(childItems.every((item) => item.threadId !== threadId)); 536: assert.isEmpty( 537: toolItems("tool-call-idle-error-child"), 538: "the failed turn must not reproject the child tool as a root tool", 539: ); 540: 541: const nextEvents = yield* runTurn(2); 542: const next = nextEvents.at(-1); 543: assert.equal(next?.type === "turn.terminal" ? next.status : undefined, "completed"); 544: }).pipe(Effect.provide(layerTest), Effect.scoped), 545: ); 546: 42fd3db9df8e7c53ae02a27ff12613aec0622f9b apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts:3229-3293 full-source-sha256=7fb58d8f2edfb6957360b51bb2866f9123702a7b575a345b47f0d4ebd9518f7d 3229: 3230: it.live("settles a Grok background command that completed before an ACP v2 idle error", () => 3231: Effect.gen(function* () { 3232: const childProcessSpawner = yield* ChildProcessSpawner.ChildProcessSpawner; 3233: const path = yield* Path.Path; 3234: const mockAgentPath = yield* path.fromFileUrl( 3235: new URL("../../../scripts/acp-mock-agent.ts", import.meta.url), 3236: ); 3237: const instanceId = ProviderInstanceId.make("grok-idle-error-background"); 3238: const adapter = yield* makeGrokAdapterV2({ 3239: instanceId, 3240: settings: DEFAULT_GROK_SETTINGS, 3241: environment: {}, 3242: hostPlatform: yield* HostProcess.Platform, 3243: selfInvocation: yield* resolveSelfInvocation(), 3244: makeRuntime: (input) => 3245: makeMockRuntime({ 3246: childProcessSpawner, 3247: mockAgentPath, 3248: environment: { T3_ACP_IDLE_ERROR: "grok-background" }, 3249: })(input).pipe(Effect.flatMap(makeXAiPromptCompletionRuntime)), 3250: }); 3251: const threadId = ThreadId.make("grok-idle-error-background"); 3252: const modelSelection = { instanceId, model: "default" } as const; 3253: const runtimePolicy = ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({ 3254: runtimeMode: "full-access", 3255: interactionMode: "default", 3256: cwd: process.cwd(), 3257: }); 3258: const runtime = yield* adapter.openSession({ 3259: threadId, 3260: providerSessionId: ProviderSessionId.make("grok-idle-error-background"), 3261: modelSelection, 3262: runtimePolicy, 3263: }); 3264: const providerThread = yield* runtime.ensureThread({ 3265: threadId, 3266: modelSelection, 3267: runtimePolicy, 3268: }); 3269: yield* runtime.startTurn( 3270: makeTurnInput({ 3271: threadId, 3272: providerThread, 3273: instanceId, 3274: runtimePolicy, 3275: now: yield* DateTime.now, 3276: }), 3277: ); 3278: const events = yield* runtime.events.pipe( 3279: Stream.takeUntil((event) => event.type === "turn.terminal"), 3280: Stream.runCollect, 3281: ); 3282: const terminal = events.at(-1); 3283: assert.equal(terminal?.type === "turn.terminal" ? terminal.status : undefined, "failed"); 3284: const command = events.findLast( 3285: (event) => 3286: event.type === "turn_item.updated" && 3287: event.turnItem.nativeItemRef?.nativeId === "call-idle-error-background", 3288: ); 3289: assert.equal( 3290: command?.type === "turn_item.updated" ? command.turnItem.status : undefined, 3291: "completed", 3292: ); 3293: }).pipe(Effect.provide(layerTest), Effect.scoped), 42fd3db9df8e7c53ae02a27ff12613aec0622f9b apps/server/src/orchestration-v2/RunExecutionService.test.ts:1829-1839 full-source-sha256=586af06dd69b59515cde0642cfaf0e912d36c215c7fe62ae4d1dff54a6aa5df9 1829: it.effect("keeps ingesting a failed root's background item when its work continues", () => 1830: Effect.gen(function* () { 1831: const observed = yield* runBackgroundItemScenario("bg-failed-continues", (ids) => [ 1832: backgroundTurnItemEvent(ids, "command_execution", "running", 1), 1833: { ...rootTerminalEvent(ids, "failed"), childTurnsContinue: true }, 1834: backgroundTurnItemEvent(ids, "command_execution", "completed", 2), 1835: ]); 1836: assert.deepEqual(observed, ["turn_item:running", "root-finalized", "turn_item:completed"]); 1837: }), 1838: ); 1839: 42fd3db9df8e7c53ae02a27ff12613aec0622f9b apps/server/src/orchestration-v2/RunExecutionService.test.ts:3400-3455 full-source-sha256=586af06dd69b59515cde0642cfaf0e912d36c215c7fe62ae4d1dff54a6aa5df9 3400: it.effect("keeps a failed root's still-running native children live while the run settles", () => 3401: Effect.gen(function* () { 3402: const finalized = yield* Ref.make>([]); 3403: const childTurn = ( 3404: ids: BackgroundScenarioIds, 3405: status: "running" | "completed", 3406: ): ProviderAdapter.ProviderAdapterV2Event => 3407: ({ 3408: type: "provider_turn.updated", 3409: driver, 3410: threadId: ids.childThreadId, 3411: providerTurn: { 3412: id: ProviderTurnId.make(`${ids.rootProviderTurnId}:child`), 3413: providerThreadId: ProviderThreadId.make(`${ids.providerThreadId}:child`), 3414: nodeId: NodeId.make(`${ids.subagentNodeId}:child-root`), 3415: runAttemptId: null, 3416: status, 3417: }, 3418: }) as ProviderAdapter.ProviderAdapterV2Event; 3419: const observed = yield* runBackgroundItemScenario( 3420: "failed-root-live-child", 3421: (ids) => [ 3422: childThreadCreatedEvent(ids), 3423: subagentEvent(ids, "running"), 3424: childTurn(ids, "running"), 3425: { ...rootTerminalEvent(ids, "failed"), childTurnsContinue: true }, 3426: subagentEvent(ids, "completed"), 3427: childTurn(ids, "completed"), 3428: ], 3429: { 3430: keepEventStreamOpen: true, 3431: onFinalized: (events) => Ref.set(finalized, events), 3432: }, 3433: ); 3434: 3435: assert.deepEqual(observed, ["subagent:running", "root-finalized", "subagent:completed"]); 3436: const written = yield* Ref.get(finalized); 3437: assert.deepEqual( 3438: written.flatMap((event) => (event.type === "run.updated" ? [event.payload.status] : [])), 3439: ["failed"], 3440: ); 3441: assert.isFalse( 3442: written.some((event) => event.type === "subagent.updated"), 3443: "a child that outlives the failed root must not be settled with it", 3444: ); 3445: }), 3446: );