Skip to content

Commit c427266

Browse files
artacartac
authored andcommitted
fix(session): allow clearing archived timestamp via HTTP API
The internal session schema (UpdatedTime) supports null for the archived field, but the HTTP API UpdatePayload schema rejects it. This means you can archive a session via the API but cannot unarchive it. This aligns the HTTP API schema with the internal domain model by accepting null for the archived field in: - UpdatePayload (httpapi/groups/session.ts) - SetArchivedInput (session/session.ts) - setArchived function signature (session/session.ts) Unblocks #16030
1 parent 27ac53a commit c427266

4 files changed

Lines changed: 49 additions & 5 deletions

File tree

packages/opencode/src/server/routes/instance/httpapi/groups/session.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export const UpdatePayload = Schema.Struct({
4848
permission: Schema.optional(Permission.Ruleset),
4949
time: Schema.optional(
5050
Schema.Struct({
51-
archived: Schema.optional(Session.ArchivedTimestamp),
51+
archived: Schema.optional(Schema.NullOr(Session.ArchivedTimestamp)),
5252
}),
5353
),
5454
})

packages/opencode/src/session/session.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ export const RemoveInput = SessionID
260260
export const SetTitleInput = Schema.Struct({ sessionID: SessionID, title: Schema.String })
261261
export const SetArchivedInput = Schema.Struct({
262262
sessionID: SessionID,
263-
time: Schema.optional(ArchivedTimestamp),
263+
time: Schema.optional(Schema.NullOr(ArchivedTimestamp)),
264264
})
265265
export const SetPermissionInput = Schema.Struct({
266266
sessionID: SessionID,
@@ -462,7 +462,7 @@ export interface Interface {
462462
readonly touch: (sessionID: SessionID) => Effect.Effect<void>
463463
readonly get: (id: SessionID) => Effect.Effect<Info, NotFound>
464464
readonly setTitle: (input: { sessionID: SessionID; title: string }) => Effect.Effect<void>
465-
readonly setArchived: (input: { sessionID: SessionID; time?: number }) => Effect.Effect<void>
465+
readonly setArchived: (input: { sessionID: SessionID; time?: number | null }) => Effect.Effect<void>
466466
readonly setPermission: (input: { sessionID: SessionID; permission: Permission.Ruleset }) => Effect.Effect<void>
467467
readonly setRevert: (input: {
468468
sessionID: SessionID
@@ -726,8 +726,8 @@ export const layer: Layer.Layer<
726726
yield* patch(input.sessionID, { title: input.title })
727727
})
728728

729-
const setArchived = Effect.fn("Session.setArchived")(function* (input: { sessionID: SessionID; time?: number }) {
730-
yield* patch(input.sessionID, { time: { archived: input.time } })
729+
const setArchived = Effect.fn("Session.setArchived")(function* (input: { sessionID: SessionID; time?: number | null }) {
730+
yield* patch(input.sessionID, { time: { archived: input.time ?? null } })
731731
})
732732

733733
const setPermission = Effect.fn("Session.setPermission")(function* (input: {

packages/opencode/test/server/global-session-list.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,25 @@ describe("session.listGlobal", () => {
7070
{ git: true },
7171
)
7272

73+
it.instance(
74+
"includes sessions after unarchiving",
75+
() =>
76+
Effect.gen(function* () {
77+
const session = yield* withSession({ title: "unarchive-me" })
78+
79+
yield* SessionNs.Service.use((svc) => svc.setArchived({ sessionID: session.id, time: Date.now() }))
80+
81+
const archived = yield* Effect.sync(() => [...SessionNs.listGlobal({ limit: 200 })])
82+
expect(archived.map((s) => s.id)).not.toContain(session.id)
83+
84+
yield* SessionNs.Service.use((svc) => svc.setArchived({ sessionID: session.id }))
85+
86+
const active = yield* Effect.sync(() => [...SessionNs.listGlobal({ limit: 200 })])
87+
expect(active.map((s) => s.id)).toContain(session.id)
88+
}),
89+
{ git: true },
90+
)
91+
7392
it.instance(
7493
"supports cursor pagination",
7594
() =>

packages/opencode/test/server/httpapi-session.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,31 @@ describe("session HttpApi", () => {
453453
{ git: true, config: { formatter: false, lsp: false } },
454454
)
455455

456+
it.instance(
457+
"clears archived timestamp when null is passed",
458+
() =>
459+
Effect.gen(function* () {
460+
const test = yield* TestInstance
461+
const headers = { "x-opencode-directory": test.directory, "content-type": "application/json" }
462+
const session = yield* createSession({ title: "to-unarchive" })
463+
464+
yield* request(pathFor(SessionPaths.update, { sessionID: session.id }), {
465+
method: "PATCH",
466+
headers,
467+
body: JSON.stringify({ time: { archived: Date.now() } }),
468+
})
469+
470+
const response = yield* request(pathFor(SessionPaths.update, { sessionID: session.id }), {
471+
method: "PATCH",
472+
headers,
473+
body: JSON.stringify({ time: { archived: null } }),
474+
})
475+
expect(response.status).toBe(200)
476+
expect((yield* json<Session.Info>(response)).time.archived).toBeUndefined()
477+
}),
478+
{ git: true, config: { formatter: false, lsp: false } },
479+
)
480+
456481
it.instance(
457482
"uses project-scoped path and directory precedence",
458483
() =>

0 commit comments

Comments
 (0)