| ... | @@ -341,6 +341,19 @@ describe("EventNdjsonLogger", () => { | ... | @@ -341,6 +341,19 @@ describe("EventNdjsonLogger", () => { |
| 341 | }, | 341 | }, |
| 342 | threadId, | 342 | threadId, |
| 343 | ); | 343 | ); |
| | 344 | yield* native.write( |
| | 345 | { |
| | 346 | event: { |
| | 347 | type: "message.part.updated", |
| | 348 | payload: { |
| | 349 | properties: { |
| | 350 | part: { type: "tool", state: { status: "running", output: "progress" } }, |
| | 351 | }, |
| | 352 | }, |
| | 353 | }, |
| | 354 | }, |
| | 355 | threadId, |
| | 356 | ); |
| 344 | yield* native.write({ type: "turn.completed", id: "native-final" }, threadId); | 357 | yield* native.write({ type: "turn.completed", id: "native-final" }, threadId); |
| 345 | yield* store.close(); | 358 | yield* store.close(); |
| 346 | | 359 | |
| ... | @@ -362,6 +375,51 @@ describe("EventNdjsonLogger", () => { | ... | @@ -362,6 +375,51 @@ describe("EventNdjsonLogger", () => { |
| 362 | }), | 375 | }), |
| 363 | ); | 376 | ); |
| 364 | | 377 | |
| | 378 | it.effect("keeps OpenCode tool input, final output, and errors in native logs", () => |
| | 379 | Effect.gen(function* () { |
| | 380 | const tempDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-provider-log-")); |
| | 381 | const basePath = NodePath.join(tempDir, "events.log"); |
| | 382 | const threadId = ThreadId.make("thread-tool-lifecycle"); |
| | 383 | |
| | 384 | try { |
| | 385 | const store = yield* makeEventNdjsonLogStore(basePath, { batchWindowMs: 0 }); |
| | 386 | const native = store.logger("native"); |
| | 387 | const input = { command: "run-build" }; |
| | 388 | const states = [ |
| | 389 | { status: "pending", input }, |
| | 390 | { status: "completed", input, output: "Build complete", time: { start: 1, end: 2 } }, |
| | 391 | { status: "error", input, error: "Build failed", time: { start: 3, end: 4 } }, |
| | 392 | { status: "unknown", input }, |
| | 393 | null, |
| | 394 | ]; |
| | 395 | const events = states.map((state) => ({ |
| | 396 | event: { |
| | 397 | type: "message.part.updated", |
| | 398 | payload: { properties: { part: { type: "tool", state } } }, |
| | 399 | }, |
| | 400 | })); |
| | 401 | for (const event of events) { |
| | 402 | yield* native.write(event, threadId); |
| | 403 | } |
| | 404 | yield* store.close(); |
| | 405 | |
| | 406 | const payloads = NodeFS.readFileSync( |
| | 407 | ownedLogPath(basePath, "thread-tool-lifecycle"), |
| | 408 | "utf8", |
| | 409 | ) |
| | 410 | .trim() |
| | 411 | .split("\n") |
| | 412 | .map((line) => parseLogLine(line).payload); |
| | 413 | assert.deepEqual( |
| | 414 | payloads, |
| | 415 | events.map((event) => encodeUnknownJson(event)), |
| | 416 | ); |
| | 417 | } finally { |
| | 418 | NodeFS.rmSync(tempDir, { recursive: true, force: true }); |
| | 419 | } |
| | 420 | }), |
| | 421 | ); |
| | 422 | |
| 365 | it.effect("contains hostile event accessors inside guarded serialization", () => | 423 | it.effect("contains hostile event accessors inside guarded serialization", () => |
| 366 | Effect.gen(function* () { | 424 | Effect.gen(function* () { |
| 367 | const tempDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-provider-log-")); | 425 | const tempDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-provider-log-")); |