diff --git a/V2_HTTP_API_AUDIT.md b/V2_HTTP_API_AUDIT.md index 03594dfbeb4..ab0c1e48f35 100644 --- a/V2_HTTP_API_AUDIT.md +++ b/V2_HTTP_API_AUDIT.md @@ -162,10 +162,10 @@ Review endpoints in document order. For each endpoint, select one disposition an | [x] 061 | `POST` | `/api/experimental/session/{sessionID}/skill` | `experimental.session.skill` | Experimental-only | Skill ID is now the `id` field; standalone activation remains experimental. | | [x] 062 | `POST` | `/api/session/{sessionID}/synthetic` | `session.synthetic` | Keep | Durable synthetic admission and delivery controls retained. | | [x] 063 | `POST` | `/api/session/{sessionID}/shell` | `session.shell` | Change | Caller ID is now the optimistic shell message ID; server derives its event ID. | -| [ ] 064 | `POST` | `/api/session/{sessionID}/compact` | `session.compact` | | | -| [ ] 065 | `POST` | `/api/session/{sessionID}/wait` | `session.wait` | | | -| [ ] 066 | `POST` | `/api/session/{sessionID}/generate` | `session.generate` | | | -| [ ] 067 | `POST` | `/api/session/{sessionID}/interrupt` | `session.interrupt` | | | +| [x] 064 | `POST` | `/api/session/{sessionID}/compact` | `session.compact` | Keep | Durable compaction admission and delivery controls retained. | +| [x] 065 | `POST` | `/api/experimental/session/{sessionID}/wait` | `experimental.session.wait` | Experimental-only | Race-free idle barrier retained outside the stable API. | +| [x] 066 | `POST` | `/api/session/{sessionID}/generate` | `session.generate` | Keep | Transient generation from session context retained. | +| [x] 067 | `POST` | `/api/session/{sessionID}/interrupt` | `session.interrupt` | Change | Renamed `continue` to `resume` across public and internal interruption APIs. | | [ ] 068 | `PUT` | `/api/session/{sessionID}/environment` | `session.environment` | | | | [ ] 069 | `POST` | `/api/session/{sessionID}/view` | `session.view` | | | diff --git a/packages/app/src/composer/composer.stories.tsx b/packages/app/src/composer/composer.stories.tsx index 73c32cbb5d3..be246d89647 100644 --- a/packages/app/src/composer/composer.stories.tsx +++ b/packages/app/src/composer/composer.stories.tsx @@ -187,7 +187,7 @@ function ComposerStory(props: { ) }, onStop: () => - setStory("activity", props.continueOnStop ? "POST /interrupt · continue: true" : "Stop requested"), + setStory("activity", props.continueOnStop ? "POST /interrupt · resume: true" : "Stop requested"), }, }, }) diff --git a/packages/app/src/session/composer/adapter.ts b/packages/app/src/session/composer/adapter.ts index 28c2ac2054a..98ae8bea87d 100644 --- a/packages/app/src/session/composer/adapter.ts +++ b/packages/app/src/session/composer/adapter.ts @@ -37,7 +37,7 @@ export function createActiveComposerAdapter(input: { }), interrupt: () => server.api.session - .interrupt({ sessionID: id, continue: true }) + .interrupt({ sessionID: id, resume: true }) .then(() => undefined) .catch(() => undefined), } diff --git a/packages/client/src/effect/api/api.ts b/packages/client/src/effect/api/api.ts index b63af9a83a5..20bae58f494 100644 --- a/packages/client/src/effect/api/api.ts +++ b/packages/client/src/effect/api/api.ts @@ -1307,7 +1307,7 @@ export type SessionLogOutput = | EventLog.Synced export type SessionLogOperation = (input: SessionLogInput) => Stream.Stream -export type SessionInterruptInput = { readonly sessionID: Session.ID; readonly continue?: boolean | undefined } +export type SessionInterruptInput = { readonly sessionID: Session.ID; readonly resume?: boolean | undefined } export type SessionInterruptOutput = { readonly interrupted: boolean } export type SessionInterruptOperation = ( input: SessionInterruptInput, diff --git a/packages/client/src/effect/generated/client.ts b/packages/client/src/effect/generated/client.ts index 823cad4ed30..48cb2bd5533 100644 --- a/packages/client/src/effect/generated/client.ts +++ b/packages/client/src/effect/generated/client.ts @@ -668,10 +668,9 @@ const EndpointSessionLog = (raw: RawClient["server.session"]) => (input: Session const EndpointSessionInterrupt = (raw: RawClient["server.session"]) => (input: SessionInterruptInput) => preserveEffect()( - raw["session.interrupt"]({ - params: { sessionID: input["sessionID"] }, - query: { continue: input["continue"] }, - }).pipe(Effect.mapError(mapClientError)), + raw["session.interrupt"]({ params: { sessionID: input["sessionID"] }, query: { resume: input["resume"] } }).pipe( + Effect.mapError(mapClientError), + ), ) const EndpointSessionBackground = (raw: RawClient["server.session"]) => (input: SessionBackgroundInput) => diff --git a/packages/client/src/promise/generated/client.ts b/packages/client/src/promise/generated/client.ts index 99181384818..21de9edba6e 100644 --- a/packages/client/src/promise/generated/client.ts +++ b/packages/client/src/promise/generated/client.ts @@ -767,7 +767,7 @@ export function make(options: ClientOptions) { request( { method: "POST", - path: `/api/session/${encodeURIComponent(input.sessionID)}/wait`, + path: `/api/experimental/session/${encodeURIComponent(input.sessionID)}/wait`, successStatus: 204, declaredStatuses: [400, 401, 404, 503], empty: true, @@ -946,7 +946,7 @@ export function make(options: ClientOptions) { { method: "POST", path: `/api/session/${encodeURIComponent(input.sessionID)}/interrupt`, - query: { continue: input["continue"] }, + query: { resume: input["resume"] }, successStatus: 200, declaredStatuses: [400, 401, 404], empty: false, diff --git a/packages/client/src/promise/generated/types.ts b/packages/client/src/promise/generated/types.ts index afe57578dde..068ca70605c 100644 --- a/packages/client/src/promise/generated/types.ts +++ b/packages/client/src/promise/generated/types.ts @@ -4381,7 +4381,7 @@ export type SessionLogOutput = SessionLogItem export type SessionInterruptInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] - readonly continue?: { readonly continue?: boolean | undefined }["continue"] + readonly resume?: { readonly resume?: boolean | undefined }["resume"] } export type SessionInterruptOutput = SessionInterruptResponse diff --git a/packages/client/test/promise.test.ts b/packages/client/test/promise.test.ts index ff46cee13b3..2ac4f876288 100644 --- a/packages/client/test/promise.test.ts +++ b/packages/client/test/promise.test.ts @@ -883,7 +883,7 @@ test("session methods use the public HTTP contract", async () => { ["POST", "http://localhost:3000/api/session/ses_test/generate"], ["POST", "http://localhost:3000/api/session/ses_test/synthetic"], ["POST", "http://localhost:3000/api/session/ses_test/compact"], - ["POST", "http://localhost:3000/api/session/ses_test/wait"], + ["POST", "http://localhost:3000/api/experimental/session/ses_test/wait"], ["GET", "http://localhost:3000/api/session/ses_test/context"], ["GET", "http://localhost:3000/api/experimental/session/ses_test/log?after=0"], ["POST", "http://localhost:3000/api/session/ses_test/interrupt?continue=true"], diff --git a/packages/core/src/plugin/host.ts b/packages/core/src/plugin/host.ts index d480ee0d4c0..a16a3febe9d 100644 --- a/packages/core/src/plugin/host.ts +++ b/packages/core/src/plugin/host.ts @@ -549,7 +549,7 @@ export const make = Effect.fn("PluginHost.make")(function* ( synthetic: sessions.synthetic, interrupt: (input) => sessions - .interrupt(input.sessionID, { continue: input.continue }) + .interrupt(input.sessionID, { resume: input.resume }) .pipe(Effect.map((interrupted) => ({ interrupted }))), wait: (input) => sessions.wait(input.sessionID), context: (input) => sessions.context(input.sessionID), diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 6c85d0c8287..f2d4286230b 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -205,7 +205,7 @@ export interface Interface { readonly active: Effect.Effect> readonly background: (sessionID: SessionSchema.ID) => Effect.Effect readonly resume: (sessionID: SessionSchema.ID) => Effect.Effect - readonly interrupt: (sessionID: SessionSchema.ID, options?: { readonly continue?: boolean }) => Effect.Effect + readonly interrupt: (sessionID: SessionSchema.ID, options?: { readonly resume?: boolean }) => Effect.Effect readonly synthetic: ( input: Parameters[0] & { sessionID: SessionSchema.ID }, ) => ReturnType diff --git a/packages/core/src/session/execution.ts b/packages/core/src/session/execution.ts index 0932c5f5b5d..39889fed7b9 100644 --- a/packages/core/src/session/execution.ts +++ b/packages/core/src/session/execution.ts @@ -34,7 +34,7 @@ export interface Interface { readonly interrupt: ( sessionID: SessionSchema.ID, options?: { - readonly continue?: boolean + readonly resume?: boolean readonly reason?: "user" | "inactivity" readonly awaitSettlement?: boolean }, @@ -155,7 +155,7 @@ export const layer = Layer.effect( interrupt: (sessionID, options) => Effect.gen(function* () { const interrupted = yield* coordinator.interrupt(sessionID, options?.reason ?? "user", options) - if (!options?.continue) return interrupted + if (!options?.resume) return interrupted // Resume steering input and between-turn control work from the interrupted // intent. Queued next-turn prompts stay parked: a steer-scoped drain never // promotes them, and a control item behind a queued prompt waits its turn. diff --git a/packages/core/src/session/session.ts b/packages/core/src/session/session.ts index 629c647cae6..70bddc6b4e1 100644 --- a/packages/core/src/session/session.ts +++ b/packages/core/src/session/session.ts @@ -307,7 +307,7 @@ export const make = Effect.fn("Session.make")(function* () { ), ) const interrupt = Effect.fn("Session.interrupt")( - (sessionID: SessionSchema.ID, options?: { readonly continue?: boolean }) => + (sessionID: SessionSchema.ID, options?: { readonly resume?: boolean }) => Effect.uninterruptible(execution.interrupt(sessionID, options)), ) const stage = Effect.fn("Session.revert.stage")(function* ( diff --git a/packages/core/test/session-execution.test.ts b/packages/core/test/session-execution.test.ts index 8a1ac80cc56..8cd7324f9e5 100644 --- a/packages/core/test/session-execution.test.ts +++ b/packages/core/test/session-execution.test.ts @@ -1104,7 +1104,7 @@ describe("SessionExecution interrupt continuation", () => { yield* execution.resume(sessionID).pipe(Effect.forkScoped) yield* Deferred.await(draining) - yield* execution.interrupt(sessionID, { continue: true }) + yield* execution.interrupt(sessionID, { resume: true }) yield* execution.awaitIdle(sessionID) // The successor drain is steer-scoped: queued next-turn work stays parked. @@ -1136,7 +1136,7 @@ describe("SessionExecution interrupt continuation", () => { yield* execution.resume(sessionID).pipe(Effect.forkScoped) yield* Deferred.await(draining) - yield* execution.interrupt(sessionID, { continue: true }) + yield* execution.interrupt(sessionID, { resume: true }) yield* execution.awaitIdle(sessionID) expect(drains).toEqual(["input"]) @@ -1159,7 +1159,7 @@ describe("SessionExecution interrupt continuation", () => { ) const execution = Context.get(context, SessionExecution.Service) - yield* execution.interrupt(sessionID, { continue: true }) + yield* execution.interrupt(sessionID, { resume: true }) yield* execution.awaitIdle(sessionID) expect(drains).toEqual([{ force: false, promotable: "steer" }]) @@ -1188,7 +1188,7 @@ describe("SessionExecution interrupt continuation", () => { yield* execution.resume(sessionID).pipe(Effect.forkScoped) yield* Deferred.await(draining) - yield* execution.interrupt(sessionID, { continue: true }) + yield* execution.interrupt(sessionID, { resume: true }) yield* execution.awaitIdle(sessionID) // Control work is housekeeping, not next-turn input: continue runs it. @@ -1214,7 +1214,7 @@ describe("SessionExecution interrupt continuation", () => { ) const execution = Context.get(context, SessionExecution.Service) - yield* execution.interrupt(sessionID, { continue: true }) + yield* execution.interrupt(sessionID, { resume: true }) yield* execution.awaitIdle(sessionID) // The queued prompt is next in line; the compaction behind it waits its turn. diff --git a/packages/core/test/session-owned.test.ts b/packages/core/test/session-owned.test.ts index e0eb0d222bd..3d30a71072d 100644 --- a/packages/core/test/session-owned.test.ts +++ b/packages/core/test/session-owned.test.ts @@ -615,7 +615,7 @@ describe("Session-owned handles", () => { const joining = yield* Deferred.make() const drains: SessionSchema.ID[] = [] const resumes: SessionSchema.ID[] = [] - const interrupts: Array<{ sessionID: SessionSchema.ID; options?: { readonly continue?: boolean } }> = [] + const interrupts: Array<{ sessionID: SessionSchema.ID; options?: { readonly resume?: boolean } }> = [] const coordinator = yield* SessionRunCoordinator.make({ drain: (id) => Effect.sync(() => void drains.push(id)).pipe( @@ -656,10 +656,10 @@ describe("Session-owned handles", () => { yield* fixture.sessions.forSession(sessionID).wait() expect(drains).toEqual([sessionID]) expect(yield* coordinator.active).toEqual(new Set()) - expect(yield* fixture.sessions.forSession(sessionID).interrupt({ continue: true })).toBe(false) + expect(yield* fixture.sessions.forSession(sessionID).interrupt({ resume: true })).toBe(false) expect(yield* fixture.sessions.forSession(sessionID).interrupt()).toBe(false) expect(interrupts).toEqual([ - { sessionID, options: { continue: true } }, + { sessionID, options: { resume: true } }, { sessionID, options: undefined }, ]) expect(fixture.locations).toEqual([]) diff --git a/packages/core/test/session-prompt.test.ts b/packages/core/test/session-prompt.test.ts index adce8aeabc6..16ca1e2c063 100644 --- a/packages/core/test/session-prompt.test.ts +++ b/packages/core/test/session-prompt.test.ts @@ -51,7 +51,7 @@ const execution = Layer.succeed( interrupt: (sessionID, options) => Effect.sync(() => { interruptCalls.push(sessionID) - interruptContinuations.push(options?.continue) + interruptContinuations.push(options?.resume) return activeSessions.delete(sessionID) }), wake: (sessionID) => @@ -211,7 +211,7 @@ describe("Session.prompt", () => { interruptContinuations.length = 0 wakeCalls.length = 0 - yield* session.interrupt(sessionID, { continue: true }) + yield* session.interrupt(sessionID, { resume: true }) expect(interruptCalls).toEqual([sessionID]) expect(interruptContinuations).toEqual([true]) diff --git a/packages/protocol/openapi.json b/packages/protocol/openapi.json index ac824ddf88a..366b20392a8 100644 --- a/packages/protocol/openapi.json +++ b/packages/protocol/openapi.json @@ -2605,10 +2605,10 @@ } } }, - "/api/session/{sessionID}/wait": { + "/api/experimental/session/{sessionID}/wait": { "post": { "tags": ["session"], - "operationId": "session.wait", + "operationId": "experimental.session.wait", "parameters": [ { "name": "sessionID", @@ -4005,7 +4005,7 @@ "required": true }, { - "name": "continue", + "name": "resume", "in": "query", "schema": { "anyOf": [ @@ -4071,7 +4071,7 @@ } } }, - "description": "Interrupt active execution owned by this OpenCode process. Returns interrupted=true when an active execution was interrupted and false for the idle no-op. When continue=true, execution resumes pending steering input and next-in-line control items (manual compaction, moves) while queued prompts remain parked.", + "description": "Interrupt active execution owned by this OpenCode process. Returns interrupted=true when an active execution was interrupted and false for the idle no-op. When resume=true, execution resumes pending steering input and next-in-line control items (manual compaction, moves) while queued prompts remain parked.", "summary": "Interrupt session execution" } }, diff --git a/packages/protocol/src/groups/session.ts b/packages/protocol/src/groups/session.ts index 3bc8ba004c7..79b5e738d01 100644 --- a/packages/protocol/src/groups/session.ts +++ b/packages/protocol/src/groups/session.ts @@ -486,7 +486,7 @@ export const makeSessionGroup = (sessionLo ), ) .add( - HttpApiEndpoint.post("session.wait", "/api/session/:sessionID/wait", { + HttpApiEndpoint.post("session.wait", "/api/experimental/session/:sessionID/wait", { params: { sessionID: Session.ID }, success: HttpApiSchema.NoContent, error: [SessionNotFoundError, ServiceUnavailableError], @@ -494,7 +494,7 @@ export const makeSessionGroup = (sessionLo .middleware(sessionLocationMiddleware) .annotateMerge( OpenApi.annotations({ - identifier: "session.wait", + identifier: "experimental.session.wait", summary: "Wait for session", description: "Wait for a session agent loop to become idle.", }), @@ -716,7 +716,7 @@ export const makeSessionGroup = (sessionLo .add( HttpApiEndpoint.post("session.interrupt", "/api/session/:sessionID/interrupt", { params: { sessionID: Session.ID }, - query: { continue: BooleanFromString.pipe(Schema.optional) }, + query: { resume: BooleanFromString.pipe(Schema.optional) }, success: Schema.Struct({ interrupted: Schema.Boolean.annotate({ description: "Whether an active execution owned by this OpenCode process was interrupted.", @@ -730,7 +730,7 @@ export const makeSessionGroup = (sessionLo identifier: "session.interrupt", summary: "Interrupt session execution", description: - "Interrupt active execution owned by this OpenCode process. Returns interrupted=true when an active execution was interrupted and false for the idle no-op. When continue=true, execution resumes pending steering input and next-in-line control items (manual compaction, moves) while queued prompts remain parked.", + "Interrupt active execution owned by this OpenCode process. Returns interrupted=true when an active execution was interrupted and false for the idle no-op. When resume=true, execution resumes pending steering input and next-in-line control items (manual compaction, moves) while queued prompts remain parked.", }), ), ) diff --git a/packages/server/src/handlers/session.ts b/packages/server/src/handlers/session.ts index 1afed4fc0bf..4001a75f35b 100644 --- a/packages/server/src/handlers/session.ts +++ b/packages/server/src/handlers/session.ts @@ -591,7 +591,7 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl .handle( "session.interrupt", Effect.fn(function* (ctx) { - return { interrupted: yield* session.interrupt(ctx.params.sessionID, { continue: ctx.query.continue }) } + return { interrupted: yield* session.interrupt(ctx.params.sessionID, { resume: ctx.query.resume }) } }), ) .handle( diff --git a/packages/tui/src/component/prompt/index.tsx b/packages/tui/src/component/prompt/index.tsx index 89f56e71288..018ef1a2e4f 100644 --- a/packages/tui/src/component/prompt/index.tsx +++ b/packages/tui/src/component/prompt/index.tsx @@ -521,7 +521,7 @@ export function Prompt(props: PromptProps) { if (store.interrupt >= 2) { void client.api.session.interrupt({ sessionID: props.sessionID, - continue: true, + resume: true, }) setStore("interrupt", 0) } diff --git a/packages/tui/src/mini/runtime.ts b/packages/tui/src/mini/runtime.ts index dbb0cf3fd18..53ae4e2ee50 100644 --- a/packages/tui/src/mini/runtime.ts +++ b/packages/tui/src/mini/runtime.ts @@ -380,7 +380,7 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep void ( state.stream ? state.stream.then((item) => item.handle.interruptActiveTurn()) - : state.sdk.session.interrupt({ sessionID: state.sessionID, continue: true }) + : state.sdk.session.interrupt({ sessionID: state.sessionID, resume: true }) ).catch(() => {}) return true }, diff --git a/packages/tui/src/mini/stream-v2.transport.ts b/packages/tui/src/mini/stream-v2.transport.ts index 85ab5f4f64d..da6a1342c74 100644 --- a/packages/tui/src/mini/stream-v2.transport.ts +++ b/packages/tui/src/mini/stream-v2.transport.ts @@ -1618,7 +1618,7 @@ export async function createSessionTransport(input: StreamInput): Promise { active.interrupted = true - void sdk.session.interrupt({ sessionID: input.sessionID, continue: true }).catch(() => {}) + void sdk.session.interrupt({ sessionID: input.sessionID, resume: true }).catch(() => {}) } next.signal?.addEventListener("abort", interrupt, { once: true }) try { @@ -1864,7 +1864,7 @@ export async function createSessionTransport(input: StreamInput): Promise { if (state.executionEpoch === epoch) paintIdle(blockerStatus(state.view)) }, diff --git a/services/www/openapi.json b/services/www/openapi.json index ac824ddf88a..366b20392a8 100644 --- a/services/www/openapi.json +++ b/services/www/openapi.json @@ -2605,10 +2605,10 @@ } } }, - "/api/session/{sessionID}/wait": { + "/api/experimental/session/{sessionID}/wait": { "post": { "tags": ["session"], - "operationId": "session.wait", + "operationId": "experimental.session.wait", "parameters": [ { "name": "sessionID", @@ -4005,7 +4005,7 @@ "required": true }, { - "name": "continue", + "name": "resume", "in": "query", "schema": { "anyOf": [ @@ -4071,7 +4071,7 @@ } } }, - "description": "Interrupt active execution owned by this OpenCode process. Returns interrupted=true when an active execution was interrupted and false for the idle no-op. When continue=true, execution resumes pending steering input and next-in-line control items (manual compaction, moves) while queued prompts remain parked.", + "description": "Interrupt active execution owned by this OpenCode process. Returns interrupted=true when an active execution was interrupted and false for the idle no-op. When resume=true, execution resumes pending steering input and next-in-line control items (manual compaction, moves) while queued prompts remain parked.", "summary": "Interrupt session execution" } }, diff --git a/services/www/public/openapi.json b/services/www/public/openapi.json index ac824ddf88a..366b20392a8 100644 --- a/services/www/public/openapi.json +++ b/services/www/public/openapi.json @@ -2605,10 +2605,10 @@ } } }, - "/api/session/{sessionID}/wait": { + "/api/experimental/session/{sessionID}/wait": { "post": { "tags": ["session"], - "operationId": "session.wait", + "operationId": "experimental.session.wait", "parameters": [ { "name": "sessionID", @@ -4005,7 +4005,7 @@ "required": true }, { - "name": "continue", + "name": "resume", "in": "query", "schema": { "anyOf": [ @@ -4071,7 +4071,7 @@ } } }, - "description": "Interrupt active execution owned by this OpenCode process. Returns interrupted=true when an active execution was interrupted and false for the idle no-op. When continue=true, execution resumes pending steering input and next-in-line control items (manual compaction, moves) while queued prompts remain parked.", + "description": "Interrupt active execution owned by this OpenCode process. Returns interrupted=true when an active execution was interrupted and false for the idle no-op. When resume=true, execution resumes pending steering input and next-in-line control items (manual compaction, moves) while queued prompts remain parked.", "summary": "Interrupt session execution" } },