| author | |
| committer | |
| log | e1ad595e0f10cc16d66fe54b4ee195435dd3cc0d |
| tree | 55c1f0d12ba1002566eccd953a1587dbe2709c40 |
| parent | 4606f9d6f213952124d93792854b0c4e48658e60 |
| signature | Signed by PGP key B5690EEEBB952194 |
7 files changed, 121 insertions(+), 14 deletions(-)
apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.test.ts+65-1| ... | ... | @@ -79,8 +79,11 @@ const mcpRules = [ |
| 79 | 79 | { action: "t3-code-thread_opencode2-adapter_*", resource: "*", effect: "allow" }, |
| 80 | 80 | ]; |
| 81 | 81 | const t3Rules = [{ action: "*", resource: "*", effect: "allow" }, ...mcpRules]; |
| 82 | /** The thread's title, which the recorded session already carries. */ | |
| 83 | const TITLE = "Prime check"; | |
| 82 | 84 | const sessionInfo = (overrides: Record<string, unknown> = {}) => ({ |
| 83 | 85 | id: SESSION, |
| 86 | title: TITLE, | |
| 84 | 87 | permissions: t3Rules, |
| 85 | 88 | projectID: "global", |
| 86 | 89 | model: { id: "big-pickle", providerID: "opencode", variant: "default" }, |
| ... | ... | @@ -226,7 +229,7 @@ const turnInput = ( |
| 226 | 229 | modelSelection: ModelSelection = bigPickle, |
| 227 | 230 | runtimeMode: "full-access" | "approval-required" = "full-access", |
| 228 | 231 | ) => ({ |
| 229 | appThread: {} as OrchestrationV2AppThread, | |
| 232 | appThread: { title: TITLE } as OrchestrationV2AppThread, | |
| 230 | 233 | threadId, |
| 231 | 234 | runId: RunId.make("run:opencode2-adapter"), |
| 232 | 235 | runOrdinal: 1, |
| ... | ... | @@ -433,6 +436,66 @@ it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => { |
| 433 | 436 | }).pipe(Effect.scoped), |
| 434 | 437 | ); |
| 435 | 438 | |
| 439 | it.effect("creates the session with the thread's title, so OpenCode generates none", () => | |
| 440 | Effect.gen(function* () { | |
| 441 | const runtime = yield* openCode2ReplayRuntimeWithInstructions([ | |
| 442 | ...opening, | |
| 443 | out("session.create", { | |
| 444 | location: { directory: WORK }, | |
| 445 | model: { providerID: "opencode", id: "big-pickle" }, | |
| 446 | permissions: t3Rules, | |
| 447 | title: "Check whether 391 is prime", | |
| 448 | }), | |
| 449 | replyData("session.create", sessionInfo({ title: "Check whether 391 is prime" })), | |
| 450 | out("session.prompt", { sessionID: SESSION, text: "<any>" }), | |
| 451 | promptAccepted, | |
| 452 | event("session.execution.succeeded", { sessionID: SESSION }), | |
| 453 | ]); | |
| 454 | const thread = yield* runtime.ensureThread({ | |
| 455 | threadId, | |
| 456 | title: " Check whether 391 is prime ", | |
| 457 | modelSelection: bigPickle, | |
| 458 | runtimePolicy: policy(), | |
| 459 | }); | |
| 460 | const terminal = yield* terminalOf(runtime).pipe(Effect.forkScoped); | |
| 461 | yield* runtime.startTurn({ | |
| 462 | ...turnInput(thread), | |
| 463 | appThread: { title: "Check whether 391 is prime" } as OrchestrationV2AppThread, | |
| 464 | }); | |
| 465 | assert.equal((yield* Fiber.join(terminal))?.status, "completed"); | |
| 466 | }).pipe(Effect.scoped), | |
| 467 | ); | |
| 468 | ||
| 469 | it.effect("renames the session before the first turn after the thread was renamed", () => | |
| 470 | Effect.gen(function* () { | |
| 471 | const { runtime, thread } = yield* resumed([ | |
| 472 | out("session.update", { sessionID: SESSION, title: "Prime factors of 391" }), | |
| 473 | reply("session.update", null), | |
| 474 | out("session.prompt", { sessionID: SESSION, text: "<any>" }), | |
| 475 | promptAccepted, | |
| 476 | event("session.execution.succeeded", { sessionID: SESSION }), | |
| 477 | // Renamed once: the next turn under the same title sends no update. | |
| 478 | out("session.prompt", { sessionID: SESSION, text: "<any>" }), | |
| 479 | promptAccepted, | |
| 480 | event("session.execution.succeeded", { sessionID: SESSION }), | |
| 481 | ]); | |
| 482 | const renamed = { | |
| 483 | ...turnInput(thread), | |
| 484 | appThread: { title: "Prime factors of 391" } as OrchestrationV2AppThread, | |
| 485 | }; | |
| 486 | const first = yield* terminalOf(runtime).pipe(Effect.forkScoped); | |
| 487 | yield* runtime.startTurn(renamed); | |
| 488 | assert.equal((yield* Fiber.join(first))?.status, "completed"); | |
| 489 | const second = yield* terminalOf(runtime).pipe(Effect.forkScoped); | |
| 490 | yield* runtime.startTurn({ | |
| 491 | ...renamed, | |
| 492 | runId: RunId.make("run:opencode2-adapter:2"), | |
| 493 | attemptId: RunAttemptId.make("attempt:opencode2-adapter:2"), | |
| 494 | }); | |
| 495 | assert.equal((yield* Fiber.join(second))?.status, "completed"); | |
| 496 | }).pipe(Effect.scoped), | |
| 497 | ); | |
| 498 | ||
| 436 | 499 | it.effect("ends a turn on the provider thread it started on", () => |
| 437 | 500 | Effect.gen(function* () { |
| 438 | 501 | const { runtime, thread } = yield* resumed([ |
| ... | ... | @@ -631,6 +694,7 @@ it.layer(McpProviderSessions.layer)("OpenCode2 adapter", (it) => { |
| 631 | 694 | ...turnInput(thread), |
| 632 | 695 | appThread: { |
| 633 | 696 | id: threadId, |
| 697 | title: TITLE, | |
| 634 | 698 | lineage: { parentThreadId: null, relationshipToParent: null, rootThreadId: threadId }, |
| 635 | 699 | } as OrchestrationV2AppThread, |
| 636 | 700 | }); |
apps/server/src/orchestration-v2/OpenCode2OrchestratorV2.integration.test.ts+18-12| ... | ... | @@ -151,9 +151,10 @@ const planRules = (name: string) => [ |
| 151 | 151 | ...mcpRules(name), |
| 152 | 152 | ]; |
| 153 | 153 | |
| 154 | const sessionInfo = (directory: string, permissions: ReadonlyArray<unknown>) => ({ | |
| 154 | const sessionInfo = (directory: string, permissions: ReadonlyArray<unknown>, title: string) => ({ | |
| 155 | 155 | data: { |
| 156 | 156 | id: SESSION, |
| 157 | title, | |
| 157 | 158 | projectID: "global", |
| 158 | 159 | model: { id: "big-pickle", providerID: "opencode", variant: "default" }, |
| 159 | 160 | cost: 0, |
| ... | ... | @@ -235,8 +236,9 @@ const createdSession = ( |
| 235 | 236 | ], |
| 236 | 237 | }), |
| 237 | 238 | ...(narrows ? [out("agent.list", "<any>"), reply("agent.list", agentList(directory))] : []), |
| 238 | out("session.create", { location: { directory }, model: "<any>", permissions }), | |
| 239 | reply("session.create", sessionInfo(directory, permissions)), | |
| 239 | // The thread's title, which is its name here. | |
| 240 | out("session.create", { location: { directory }, model: "<any>", permissions, title: name }), | |
| 241 | reply("session.create", sessionInfo(directory, permissions, name)), | |
| 240 | 242 | ]; |
| 241 | 243 | |
| 242 | 244 | /** A recording with its scrubbed `<work>` directory replaced by `directory`. */ |
| ... | ... | @@ -348,7 +350,7 @@ describe("OpenCode 2 through the orchestrator", () => { |
| 348 | 350 | ...answeredPrompt("FIRST"), |
| 349 | 351 | // The next turn resumes the session at its new selection. |
| 350 | 352 | out("session.get", { sessionID: SESSION }), |
| 351 | reply("session.get", sessionInfo(cwd, t3Rules(name))), | |
| 353 | reply("session.get", sessionInfo(cwd, t3Rules(name), name)), | |
| 352 | 354 | out("session.switchModel", { |
| 353 | 355 | sessionID: SESSION, |
| 354 | 356 | model: { providerID: "opencode", id: "mimo-v2.6-flash-free" }, |
| ... | ... | @@ -401,7 +403,7 @@ describe("OpenCode 2 through the orchestrator", () => { |
| 401 | 403 | ...answeredPrompt("FIRST"), |
| 402 | 404 | ...directoryModels(after), |
| 403 | 405 | out("session.get", { sessionID: SESSION }), |
| 404 | reply("session.get", sessionInfo(before, t3Rules(name))), | |
| 406 | reply("session.get", sessionInfo(before, t3Rules(name), name)), | |
| 405 | 407 | // The worktree change detached the thread, so its session is loaded afresh. |
| 406 | 408 | ...noOpenRequests, |
| 407 | 409 | out("session.move", { sessionID: SESSION, directory: after }), |
| ... | ... | @@ -450,10 +452,14 @@ describe("OpenCode 2 through the orchestrator", () => { |
| 450 | 452 | out("session.get", { sessionID: SESSION }), |
| 451 | 453 | reply( |
| 452 | 454 | "session.get", |
| 453 | sessionInfo(before, [ | |
| 454 | { action: "*", resource: "*", effect: "allow" }, | |
| 455 | { action: "subagent", resource: "*", effect: "deny" }, | |
| 456 | ]), | |
| 455 | sessionInfo( | |
| 456 | before, | |
| 457 | [ | |
| 458 | { action: "*", resource: "*", effect: "allow" }, | |
| 459 | { action: "subagent", resource: "*", effect: "deny" }, | |
| 460 | ], | |
| 461 | name, | |
| 462 | ), | |
| 457 | 463 | ), |
| 458 | 464 | ...noOpenRequests, |
| 459 | 465 | out("session.update", { sessionID: SESSION, permissions: t3Rules(name) }), |
| ... | ... | @@ -531,7 +537,7 @@ describe("OpenCode 2 through the orchestrator", () => { |
| 531 | 537 | ...answeredPrompt("FIRST"), |
| 532 | 538 | // A mode change detaches nothing: the same session is resumed with the new rules. |
| 533 | 539 | out("session.get", { sessionID: SESSION }), |
| 534 | reply("session.get", sessionInfo(cwd, t3Rules(name))), | |
| 540 | reply("session.get", sessionInfo(cwd, t3Rules(name), name)), | |
| 535 | 541 | out("agent.list", "<any>"), |
| 536 | 542 | reply("agent.list", agentList(cwd)), |
| 537 | 543 | out("session.update", { sessionID: SESSION, permissions: autoEditRules(name) }), |
| ... | ... | @@ -539,7 +545,7 @@ describe("OpenCode 2 through the orchestrator", () => { |
| 539 | 545 | ...answeredPrompt("SECOND"), |
| 540 | 546 | // Back to Full access: the narrowing rules go. |
| 541 | 547 | out("session.get", { sessionID: SESSION }), |
| 542 | reply("session.get", sessionInfo(cwd, autoEditRules(name))), | |
| 548 | reply("session.get", sessionInfo(cwd, autoEditRules(name), name)), | |
| 543 | 549 | out("session.update", { sessionID: SESSION, permissions: t3Rules(name) }), |
| 544 | 550 | reply("session.update", null), |
| 545 | 551 | ...answeredPrompt("THIRD"), |
| ... | ... | @@ -577,7 +583,7 @@ describe("OpenCode 2 through the orchestrator", () => { |
| 577 | 583 | ...instructionsWritten, |
| 578 | 584 | ...answeredPrompt("PLANNED"), |
| 579 | 585 | out("session.get", { sessionID: SESSION }), |
| 580 | reply("session.get", sessionInfo(cwd, planRules(name))), | |
| 586 | reply("session.get", sessionInfo(cwd, planRules(name), name)), | |
| 581 | 587 | out("session.update", { sessionID: SESSION, permissions: t3Rules(name) }), |
| 582 | 588 | reply("session.update", null), |
| 583 | 589 | out("session.switchAgent", { sessionID: SESSION, agent: "build" }), |
apps/server/src/orchestration-v2/ProviderTurnStartService.ts+2| ... | ... | @@ -651,6 +651,7 @@ export const layer: Layer.Layer< |
| 651 | 651 | return yield* loadFromProvider( |
| 652 | 652 | session.ensureThread({ |
| 653 | 653 | threadId: projection.thread.id, |
| 654 | title: projection.thread.title, | |
| 654 | 655 | modelSelection: run.modelSelection, |
| 655 | 656 | runtimePolicy: resolvedRuntimePolicy, |
| 656 | 657 | providerSessionId, |
| ... | ... | @@ -696,6 +697,7 @@ export const layer: Layer.Layer< |
| 696 | 697 | const replacement = yield* loadFromProvider( |
| 697 | 698 | session.ensureThread({ |
| 698 | 699 | threadId: projection.thread.id, |
| 700 | title: projection.thread.title, | |
| 699 | 701 | modelSelection: run.modelSelection, |
| 700 | 702 | runtimePolicy: resolvedRuntimePolicy, |
| 701 | 703 | providerSessionId, |
apps/server/src/orchestration-v2/testkit/fixtures/opencode2_fork/opencode_transcript.ndjson+2| ... | ... | @@ -50,6 +50,8 @@ |
| 50 | 50 | {"type": "expect_outbound", "label": "session.update", "frame": {"type": "session.update", "input": {"sessionID": "ses_f1484db83ffeLGtrRCFimo1H0e", "permissions": [{"action": "*", "resource": "*", "effect": "allow"}, {"action": "t3-code-*", "resource": "*", "effect": "deny"}, {"action": "t3-code-thread_opencode2-fork_target_*", "resource": "*", "effect": "allow"}]}}} |
| 51 | 51 | {"type": "emit_inbound", "label": "session.update.response", "frame": {"type": "sdk.response", "operation": "session.update", "data": null}} |
| 52 | 52 | {"type": "emit_inbound", "label": "session.forked", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb7b247d001fXzFroPg5P0bps", "created": 1790657111165, "type": "session.forked", "location": {"directory": "<work>"}, "data": {"sessionID": "ses_f1484db83ffeLGtrRCFimo1H0e", "parentID": "ses_f1484ea8affe5aTo8CcgbGIszj", "boundary": {"type": "before", "messageID": "msg_0eb7b1f9f001g95Bduh3bi8syS"}, "instructions": {"core/environment": "db4a793a05fec32fdfa985a3432125a86d6cb42debc8b9ee8f6a99fbfcf2f93b", "core/date": "bf08b8486572ab3eef677a3c9ee12468e411ae41a8f4d2fb6cf02a6ca73f1986", "core/codemode": "616a7ad8b8e40e8a71eab238dfbbe9eaf0f4a9e6fdad853226d075bfe602e1e6", "core/skill-guidance": "0d001919496102654717308c72635523bf7c3cd99c81e058cc79f2947104f9a5"}, "instructionEntries": []}, "durable": {"aggregateID": "ses_f1484db83ffeLGtrRCFimo1H0e", "seq": 0, "version": 2}}}} |
| 53 | {"type": "expect_outbound", "label": "session.update.title", "frame": {"type": "session.update", "input": {"sessionID": "ses_f1484db83ffeLGtrRCFimo1H0e", "title": "<any>"}}} | |
| 54 | {"type": "emit_inbound", "label": "session.update.title.response", "frame": {"type": "sdk.response", "operation": "session.update", "data": null}} | |
| 53 | 55 | {"type": "expect_outbound", "label": "session.instructions.entry.put.2", "frame": {"type": "session.instructions.entry.put", "input": {"sessionID": "ses_f1484db83ffeLGtrRCFimo1H0e", "key": "t3-code", "value": "<any>"}}} |
| 54 | 56 | {"type": "emit_inbound", "label": "session.instructions.entry.put.response.2", "frame": {"type": "sdk.response", "operation": "session.instructions.entry.put", "data": null}} |
| 55 | 57 | {"type": "expect_outbound", "label": "session.prompt.3", "frame": {"type": "session.prompt", "input": {"sessionID": "ses_f1484db83ffeLGtrRCFimo1H0e", "id": "msg_0eb7b2493001nZTYCniIjU5Y65", "text": "<any>"}}} |
apps/server/src/orchestration-v2/testkit/fixtures/opencode2_switch/opencode_transcript.ndjson+2| ... | ... | @@ -59,6 +59,8 @@ |
| 59 | 59 | {"type": "emit_inbound", "label": "session.switchAgent.response.2", "frame": {"type": "sdk.response", "operation": "session.switchAgent", "data": null}} |
| 60 | 60 | {"type": "emit_inbound", "label": "session.agent.selected.2", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb7eef80001lOHUbFIwlg7iap", "created": 1790657359744, "type": "session.agent.selected", "location": {"directory": "<work>"}, "data": {"sessionID": "ses_f14812197ffeCbw641wM2r3ZmP", "agent": "build", "previous": "plan"}, "durable": {"aggregateID": "ses_f14812197ffeCbw641wM2r3ZmP", "seq": 16, "version": 1}}}} |
| 61 | 61 | {"type": "emit_inbound", "label": "session.inbox.enqueued.3", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb7eef830020JKd2UmFNPZ8vu", "created": 1790657359747, "type": "session.inbox.enqueued", "location": {"directory": "<work>"}, "data": {"inboxID": "msg_0eb7eef83001M1o8rpSkbKzlf5", "sessionID": "ses_f14812197ffeCbw641wM2r3ZmP", "item": {"type": "synthetic", "payload": {"text": "<system-reminder>\nYou are NO LONGER in Plan mode. The previous Plan restrictions no longer apply. Any Plan mode instructions from earlier in this conversation are no longer active.\n</system-reminder>"}, "delivery": "steer"}}, "durable": {"aggregateID": "ses_f14812197ffeCbw641wM2r3ZmP", "seq": 17, "version": 1}}}} |
| 62 | {"type": "expect_outbound", "label": "session.update.title", "frame": {"type": "session.update", "input": {"sessionID": "ses_f14812197ffeCbw641wM2r3ZmP", "title": "<any>"}}} | |
| 63 | {"type": "emit_inbound", "label": "session.update.title.response", "frame": {"type": "sdk.response", "operation": "session.update", "data": null}} | |
| 62 | 64 | {"type": "expect_outbound", "label": "session.switchModel", "frame": {"type": "session.switchModel", "input": {"sessionID": "ses_f14812197ffeCbw641wM2r3ZmP", "model": {"providerID": "opencode", "id": "nemotron-3.5-lightning-free"}}}} |
| 63 | 65 | {"type": "emit_inbound", "label": "session.switchModel.response", "frame": {"type": "sdk.response", "operation": "session.switchModel", "data": null}} |
| 64 | 66 | {"type": "emit_inbound", "label": "session.model.selected", "frame": {"type": "sdk.event", "event": {"id": "evt_0eb7eef8d001Yy5fmSDr6nQ8OS", "created": 1790657359757, "type": "session.model.selected", "location": {"directory": "<work>"}, "data": {"sessionID": "ses_f14812197ffeCbw641wM2r3ZmP", "model": {"id": "nemotron-3.5-lightning-free", "providerID": "opencode"}, "previous": {"id": "big-pickle", "providerID": "opencode", "variant": "default"}}, "durable": {"aggregateID": "ses_f14812197ffeCbw641wM2r3ZmP", "seq": 18, "version": 1}}}} |
packages/provider-core/src/server/ProviderAdapter.ts+2| ... | ... | @@ -394,6 +394,8 @@ export interface ProviderAdapterV2OpenSessionInput { |
| 394 | 394 | |
| 395 | 395 | export interface ProviderAdapterV2EnsureThreadInput { |
| 396 | 396 | readonly threadId: ThreadId; |
| 397 | /** The app thread's title, for providers that name their native sessions. */ | |
| 398 | readonly title?: string; | |
| 397 | 399 | readonly modelSelection: ModelSelection; |
| 398 | 400 | readonly runtimePolicy: ProviderAdapterV2RuntimePolicy; |
| 399 | 401 | readonly providerSessionId?: ProviderSessionId; |
packages/provider-opencode/src/server/v2/adapter.ts+30-1| ... | ... | @@ -345,6 +345,8 @@ interface ThreadState { |
| 345 | 345 | * rules stay in force, and a changed mode switches it before prompting. |
| 346 | 346 | */ |
| 347 | 347 | agent: string; |
| 348 | /** The native session's title as T3 last read or wrote it. */ | |
| 349 | title: string | undefined; | |
| 348 | 350 | /** The native session's rules as T3 last read or wrote them, and the policy they are for. */ |
| 349 | 351 | rules: ReadonlyArray<Rule> | undefined; |
| 350 | 352 | policy: RulesPolicy; |
| ... | ... | @@ -934,6 +936,7 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr |
| 934 | 936 | unsettled: false, |
| 935 | 937 | directory, |
| 936 | 938 | agent: "build", |
| 939 | title: undefined, | |
| 937 | 940 | rules: undefined, |
| 938 | 941 | policy: input.runtimePolicy, |
| 939 | 942 | grants: [], |
| ... | ... | @@ -3012,12 +3015,22 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr |
| 3012 | 3015 | state.policy = policy; |
| 3013 | 3016 | }); |
| 3014 | 3017 | |
| 3018 | /** Names the session after its thread when they differ. */ | |
| 3019 | const writeTitle = Effect.fnUntraced(function* (state: ThreadState, title: string) { | |
| 3020 | const next = title.trim(); | |
| 3021 | // An empty title asks OpenCode to generate one. | |
| 3022 | if (next === "" || next === state.title) return; | |
| 3023 | yield* client.session.update({ sessionID: Session.ID.make(state.sessionId), title: next }); | |
| 3024 | state.title = next; | |
| 3025 | }); | |
| 3026 | ||
| 3015 | 3027 | const register = ( |
| 3016 | 3028 | providerThread: OrchestrationV2ProviderThread, |
| 3017 | 3029 | native: { |
| 3018 | 3030 | readonly id: string; |
| 3019 | 3031 | readonly model?: ModelRef | undefined; |
| 3020 | 3032 | readonly agent?: string | undefined; |
| 3033 | readonly title?: string | undefined; | |
| 3021 | 3034 | readonly permissions?: ReadonlyArray<Rule> | undefined; |
| 3022 | 3035 | }, |
| 3023 | 3036 | directory: string, |
| ... | ... | @@ -3029,12 +3042,14 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr |
| 3029 | 3042 | existing.model = native.model; |
| 3030 | 3043 | existing.directory = directory; |
| 3031 | 3044 | existing.agent = native.agent ?? existing.agent; |
| 3045 | existing.title = native.title; | |
| 3032 | 3046 | existing.rules = native.permissions; |
| 3033 | 3047 | return existing; |
| 3034 | 3048 | } |
| 3035 | 3049 | const state = newThreadState(native.id, providerThread, directory, undefined); |
| 3036 | 3050 | state.model = native.model; |
| 3037 | 3051 | state.agent = native.agent ?? state.agent; |
| 3052 | state.title = native.title; | |
| 3038 | 3053 | state.rules = native.permissions; |
| 3039 | 3054 | threads.set(native.id, state); |
| 3040 | 3055 | return state; |
| ... | ... | @@ -3615,10 +3630,13 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr |
| 3615 | 3630 | policy, |
| 3616 | 3631 | threadInput.threadId, |
| 3617 | 3632 | ); |
| 3633 | const title = threadInput.title?.trim() || undefined; | |
| 3634 | // A session created with a title is never titled by OpenCode's own model. | |
| 3618 | 3635 | const created = yield* client.session.create({ |
| 3619 | 3636 | location: Location.PublicRef.make({ directory: AbsolutePath.make(directory) }), |
| 3620 | 3637 | model, |
| 3621 | 3638 | permissions, |
| 3639 | ...(title === undefined ? {} : { title }), | |
| 3622 | 3640 | }); |
| 3623 | 3641 | const createdAt = yield* DateTime.now; |
| 3624 | 3642 | const providerThread: OrchestrationV2ProviderThread = { |
| ... | ... | @@ -3642,7 +3660,13 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr |
| 3642 | 3660 | }; |
| 3643 | 3661 | const state = register( |
| 3644 | 3662 | providerThread, |
| 3645 | { id: created.id, model: created.model, agent: created.agent, permissions }, | |
| 3663 | { | |
| 3664 | id: created.id, | |
| 3665 | model: created.model, | |
| 3666 | agent: created.agent, | |
| 3667 | title: title ?? created.title, | |
| 3668 | permissions, | |
| 3669 | }, | |
| 3646 | 3670 | directory, |
| 3647 | 3671 | ); |
| 3648 | 3672 | state.policy = policy; |
| ... | ... | @@ -3801,6 +3825,11 @@ export const make = Effect.fn("OpenCode2Adapter.make")(function* (instanceId: Pr |
| 3801 | 3825 | // and its subagents still running hold the rules they started with. |
| 3802 | 3826 | // Those run on whether or not this turn starts, so theirs are best effort. |
| 3803 | 3827 | yield* writeRules(state, turnInput.runtimePolicy); |
| 3828 | // A thread renamed since the last turn, or titled after its first prompt. | |
| 3829 | yield* writeTitle(state, turnInput.appThread.title).pipe( | |
| 3830 | Effect.timeout(REQUEST_REPLY_TIMEOUT), | |
| 3831 | Effect.ignore({ log: true }), | |
| 3832 | ); | |
| 3804 | 3833 | for (const call of runningCalls(state)) { |
| 3805 | 3834 | if (call.child === undefined) continue; |
| 3806 | 3835 | yield* writeRules(call.child, turnInput.runtimePolicy).pipe( |