Compare commits

...

5 Commits

Author SHA1 Message Date
Kit Langton fa4c5b26dc fix(core): stabilize forked message IDs (#44011) 2026-08-21 19:28:51 -04:00
Kit Langton fb703ede73 fix(core): transfer only settled history (#44008) 2026-08-21 19:28:04 -04:00
Kit Langton 2937f0e635 fix(core): omit running shells from forks (#44001) 2026-08-21 19:27:13 -04:00
Kit Langton 3694149135 fix(core): resolve device verification URL (#44021) 2026-08-21 23:26:40 +00:00
Kit Langton c4eeefe0f1 refactor: reuse undefined effect (#44010) 2026-08-21 19:17:29 -04:00
33 changed files with 307 additions and 54 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@opencode-ai/core": patch
---
Fix OpenCode Console device authorization URLs when the server returns an origin-rooted verification path.
+1 -1
View File
@@ -110,7 +110,7 @@ export const model = (input: ModelInput) => {
const multipartImages = yield* Effect.forEach(sourceImages, (image) => {
if (image.type === "bytes") return Effect.succeed({ data: image.data, mediaType: image.mediaType })
if (image.type === "url") return ImageInputs.decodeDataUrl(image.url, ADAPTER)
return Effect.succeed(undefined)
return Effect.undefined
})
const multipartMask =
mask === undefined
@@ -16,7 +16,7 @@ export const decodeDataUrl = (
url: string,
module: string,
): Effect.Effect<{ readonly mediaType: string; readonly data: Uint8Array } | undefined, AIError> => {
if (!url.startsWith("data:")) return Effect.succeed(undefined)
if (!url.startsWith("data:")) return Effect.undefined
const match = /^data:([^;,]+);base64,(.*)$/s.exec(url)
if (!match) return Effect.fail(invalid(module, "Image data URLs must contain a MIME type and base64 data"))
return Effect.fromResult(Encoding.decodeBase64(match[2])).pipe(
+3 -3
View File
@@ -841,17 +841,17 @@ export class Interpreter<R> {
private customIterator(value: unknown, node: AstNode, allowAsync = true) {
if (value instanceof CodeModeGenerator) {
if (value.asynchronous && !allowAsync) return Effect.succeed(undefined)
if (value.asynchronous && !allowAsync) return Effect.undefined
return Effect.succeed({
iterator: value,
next: new GeneratorMethodReference(value, "next"),
asynchronous: value.asynchronous,
})
}
if (!isRecord(value) || isRuntimeReference(value)) return Effect.succeed(undefined)
if (!isRecord(value) || isRuntimeReference(value)) return Effect.undefined
const asyncMethod = allowAsync ? Reflect.get(value, AsyncIteratorSymbol) : undefined
const method = asyncMethod ?? Reflect.get(value, IteratorSymbol)
if (method === undefined || method === null) return Effect.succeed(undefined)
if (method === undefined || method === null) return Effect.undefined
const self = this
return Effect.map(
this.invokeCallable(this.requireIteratorMethod(method, "Iterator method", node), [], node),
+1 -1
View File
@@ -46,7 +46,7 @@ export const Plugin = define({
function firstMissing(target: string): Effect.Effect<string | undefined> {
const parent = path.dirname(target)
if (parent === target) return Effect.succeed(undefined)
if (parent === target) return Effect.undefined
return fs.isDir(parent).pipe(Effect.flatMap((exists) => (exists ? Effect.succeed(target) : firstMissing(parent))))
}
+1 -1
View File
@@ -109,7 +109,7 @@ const layer = Layer.effect(
const next = Bom.split(input.content)
const current = yield* environment.files.read(input.target.absolute, { offset: 0, length: 3 }).pipe(
Effect.map((result) => result.bytes),
Effect.catchTag("Environment.NotFound", () => Effect.succeed(undefined)),
Effect.catchTag("Environment.NotFound", () => Effect.undefined),
)
yield* environment.files.write(
input.target.absolute,
+2 -3
View File
@@ -78,9 +78,8 @@ const layer = Layer.effect(
? "Directory"
: input.kind === "file"
? "File"
: (yield* fs
.stat(absolute)
.pipe(Effect.catchReason("PlatformError", "NotFound", () => Effect.succeed(undefined))))?.type
: (yield* fs.stat(absolute).pipe(Effect.catchReason("PlatformError", "NotFound", () => Effect.undefined)))
?.type
const externalDirectory = type === "Directory" ? absolute : path.dirname(absolute)
const externalResource = slash(path.join(externalDirectory, "*"))
return {
+2 -2
View File
@@ -624,11 +624,11 @@ export const layer = (options?: Options) =>
Effect.map((input) => input as Record<string, SourceProvider>),
Effect.orElseSucceed(() => undefined),
)
: Effect.succeed(undefined)
: Effect.undefined
// The bundled snapshot is the boot-time floor for the catalog; the
// periodic fetch below still refreshes on top.
const loadSnapshot = options?.snapshot === false ? Effect.succeed(undefined) : bundledSnapshot
const loadSnapshot = options?.snapshot === false ? Effect.undefined : bundledSnapshot
const fetchAndWrite = Effect.fn("ModelsDev.fetchAndWrite")(function* () {
const text = yield* fetchApi()
+1 -1
View File
@@ -302,7 +302,7 @@ const layer = Layer.effect(
const rememberedRules = yield* savedRules()
for (const [id, item] of pending) {
const rules = yield* configured(item.request.sessionID, item.agent).pipe(
Effect.catchTag("Session.NotFoundError", () => Effect.succeed(undefined)),
Effect.catchTag("Session.NotFoundError", () => Effect.undefined),
)
if (!rules) continue
if (denied(item.request, rules)) continue
+11 -8
View File
@@ -46,15 +46,18 @@ function oauth(http: HttpClient.HttpClient) {
Effect.gen(function* () {
const server = yield* normalizeServer(answer.server ?? defaultServer)
const device = yield* post(http, `${server}/auth/device/code`, { client_id: clientID }, Device)
const verification = URL.canParse(device.verification_uri_complete)
? new URL(device.verification_uri_complete)
: undefined
if (verification && verification.protocol !== "http:" && verification.protocol !== "https:") {
return yield* Effect.fail(new Error("Invalid device verification URL: expected HTTP(S)"))
}
const verification = yield* Effect.try({
try: () => {
const url = new URL(device.verification_uri_complete, `${server}/`)
if (url.protocol !== "http:" && url.protocol !== "https:") throw new Error("expected HTTP(S)")
return url
},
catch: (cause) =>
new Error(`Invalid device verification URL: ${cause instanceof Error ? cause.message : String(cause)}`),
})
return {
mode: "auto" as const,
url: verification?.href ?? `${server}/${device.verification_uri_complete.replace(/^\/+/, "")}`,
url: verification.href,
instructions: `Enter code: ${device.user_code}`,
callback: poll(http, server, device.device_code, Duration.seconds(device.interval)),
}
@@ -213,7 +216,7 @@ function fetchProviders(http: HttpClient.HttpClient, value: Credential.Value) {
)
.pipe(
Effect.flatMap((response) => {
if (response.status === 404) return Effect.succeed(undefined)
if (response.status === 404) return Effect.undefined
return HttpClientResponse.filterStatusOk(response).pipe(
Effect.flatMap(HttpClientResponse.schemaBodyJson(RemoteResponse)),
Effect.map((remote) => remote.config.provider),
+1 -1
View File
@@ -10,7 +10,7 @@ export const parseResponse = <F extends Schema.Struct.Fields>(body: string, resu
const decode = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Struct({ result })))
const parse = (payload: string) => {
const trimmed = payload.trim()
if (!trimmed.startsWith("{")) return Effect.succeed(undefined)
if (!trimmed.startsWith("{")) return Effect.undefined
return decode(trimmed).pipe(Effect.map((response) => response.result))
}
return Effect.gen(function* () {
+1 -1
View File
@@ -235,7 +235,7 @@ const layer = Layer.effect(
Effect.mapError((cause) => failure("Invalid ripgrep JSON output", cause)),
Effect.flatMap((json) => {
if (!json || typeof json !== "object" || !("type" in json) || json.type !== "match")
return Effect.succeed(undefined)
return Effect.undefined
return Schema.decodeUnknownEffect(RawMatch)(json).pipe(
Effect.map((match) => ({
...match.data,
+2 -1
View File
@@ -183,6 +183,7 @@ const projectFork = Effect.fn("SessionProjector.projectFork")(function* (
lt(SessionMessageTable.seq, copiedSeq + 1),
// Terminal events for active projections stay on the parent, so forks copy only settled history.
sql`${SessionMessageTable.type} != 'assistant' or json_extract(${SessionMessageTable.data}, '$.time.completed') is not null`,
sql`${SessionMessageTable.type} != 'shell' or json_extract(${SessionMessageTable.data}, '$.status') != 'running'`,
sql`${SessionMessageTable.type} != 'compaction' or json_extract(${SessionMessageTable.data}, '$.status') != 'running'`,
),
)
@@ -196,7 +197,7 @@ const projectFork = Effect.fn("SessionProjector.projectFork")(function* (
.insert(SessionMessageTable)
.values(
rows.map((row) => ({
id: SessionMessage.ID.create(),
id: SessionMessage.ID.make(`${SessionMessage.ID.fromEvent(event.id)}_${row.seq}`),
session_id: event.data.sessionID,
type: row.type,
seq: row.seq,
+8 -2
View File
@@ -53,7 +53,7 @@ const layer = Layer.effect(
export: Effect.fn("SessionTransfer.export")(function* (input) {
const data = {
info: yield* sessions.get(input.sessionID),
messages: yield* sessions.messages({ sessionID: input.sessionID, order: "asc" }),
messages: (yield* sessions.messages({ sessionID: input.sessionID, order: "asc" })).filter(isSettled),
}
return input.sanitize ? sanitize(data) : data
}),
@@ -68,7 +68,7 @@ const layer = Layer.effect(
if (recorded) return yield* new ImportConflictError({ sessionID })
const project = yield* projects.resolve(input.location.directory)
yield* upsertProject(db, project).pipe(Effect.orDie)
const messages = input.data.messages.map((message, index) => {
const messages = input.data.messages.filter(isSettled).map((message, index) => {
const encoded = encodeMessage(message)
const { id: _, type, ...data } = encoded
return {
@@ -144,6 +144,12 @@ export const node = makeGlobalNode({
deps: [App.node, Bus.node, Database.node, Project.node, Session.node],
})
function isSettled(message: SessionMessage.Info) {
if (message.type === "assistant") return message.time.completed !== undefined
if (message.type === "shell" || message.type === "compaction") return message.status !== "running"
return true
}
function redact(kind: string, id: string, value: string) {
return value.trim() ? `[redacted:${kind}:${id}]` : value
}
+1 -1
View File
@@ -201,7 +201,7 @@ export const noopLayer = Layer.succeed(
Service.of({
transform: () => Effect.succeed({ dispose: Effect.void }),
reload: () => Effect.void,
capture: () => Effect.succeed(undefined),
capture: () => Effect.undefined,
files: () => Effect.succeed([]),
diff: () => Effect.succeed([]),
restore: () => Effect.void,
+1 -1
View File
@@ -78,7 +78,7 @@ export const Plugin = {
source,
})
const current = yield* FileMutation.readText(environment.files, target.absolute).pipe(
Effect.catchTag("Environment.NotFound", () => Effect.succeed(undefined)),
Effect.catchTag("Environment.NotFound", () => Effect.undefined),
)
const next = Bom.split(input.content)
const preview = fileDiff(target.resource, current?.text ?? "", next.text, current ? "modified" : "added")
+1 -1
View File
@@ -31,7 +31,7 @@ export const make = Effect.gen(function* () {
return yield* Effect.forEach(entries, (entry) =>
canonical(fs, entry.directory).pipe(
Effect.map((directory) => ({ directory, type: entry.kind === "main" ? "root" : "worktree" }) as const),
Effect.catchTag("Worktree.DirectoryUnavailableError", () => Effect.succeed(undefined)),
Effect.catchTag("Worktree.DirectoryUnavailableError", () => Effect.undefined),
),
).pipe(Effect.map((items) => items.filter((item): item is ListEntry => item !== undefined)))
}),
+1 -1
View File
@@ -10,7 +10,7 @@ export const emptyCredentialNode = makeGlobalNode({
Credential.Service.of({
all: () => Effect.succeed([]),
list: () => Effect.succeed([]),
get: () => Effect.succeed(undefined),
get: () => Effect.undefined,
create: () => Effect.die("unused Credential.create"),
update: () => Effect.die("unused Credential.update"),
remove: () => Effect.die("unused Credential.remove"),
+2 -2
View File
@@ -19,9 +19,9 @@ export const emptyMcpLayer = Layer.succeed(
callTool: () => Effect.die("unused mcp.callTool"),
instructions: () => Effect.succeed([]),
prompts: () => Effect.succeed([]),
prompt: () => Effect.succeed(undefined),
prompt: () => Effect.undefined,
resourceCatalog: () => Effect.succeed(MCP.ResourceCatalog.make({ resources: [], templates: [] })),
readResource: () => Effect.succeed(undefined),
readResource: () => Effect.undefined,
}),
)
+2 -2
View File
@@ -21,7 +21,7 @@ const runtime = LanguageModel.make({ id: "gemini", provider: "test-provider", ro
const catalog = Layer.mock(Catalog.Service, {
provider: {
get: () => Effect.succeed(undefined),
get: () => Effect.undefined,
all: () => Effect.die("unused"),
available: () => Effect.die("unused"),
},
@@ -35,7 +35,7 @@ const catalog = Layer.mock(Catalog.Service, {
})
const integrations = Layer.mock(Integration.Service, {
connection: {
active: () => Effect.succeed(undefined),
active: () => Effect.undefined,
resolve: () => Effect.die("unused"),
key: () => Effect.die("unused"),
update: () => Effect.die("unused"),
@@ -405,7 +405,7 @@ describe("ConfigInstructionPlugin.Plugin", () => {
FSUtil.Service.of({
...fs,
up: () => Effect.succeed([discovered]),
readFileStringSafe: () => Effect.succeed(undefined),
readFileStringSafe: () => Effect.undefined,
}),
),
),
+1 -1
View File
@@ -17,7 +17,7 @@ const failingCredentialNode = makeGlobalNode({
Credential.Service.of({
all: () => Effect.succeed([]),
list: () => Effect.succeed([]),
get: () => Effect.succeed(undefined),
get: () => Effect.undefined,
create: () => Effect.die(new Error("credential persistence failed")),
update: () => Effect.void,
remove: () => Effect.void,
+1 -1
View File
@@ -309,7 +309,7 @@ describe("ModelResolver", () => {
})
const integrations = Layer.mock(Integration.Service, {
connection: {
active: () => Effect.succeed(undefined),
active: () => Effect.undefined,
resolve: () => Effect.die("unused"),
key: () => Effect.die("unused"),
update: () => Effect.die("unused"),
+1 -1
View File
@@ -33,7 +33,7 @@ const npmLayer = Layer.succeed(
Npm.Service.of({
add: () => Effect.succeed({ directory: "", entrypoint: undefined }),
resolve: () => Effect.succeed({ directory: "", entrypoint: undefined }),
which: () => Effect.succeed(undefined),
which: () => Effect.undefined,
}),
)
@@ -24,7 +24,7 @@ function npmEntrypoint(entrypoint?: string) {
return Npm.Service.of({
add: () => Effect.succeed({ directory: "", entrypoint }),
resolve: () => Effect.succeed({ directory: "", entrypoint }),
which: () => Effect.succeed(undefined),
which: () => Effect.undefined,
})
}
@@ -105,7 +105,7 @@ describe("OpencodePlugin", () => {
return Response.json({
device_code: "device",
user_code: "user",
verification_uri_complete: `${url.origin}/verify`,
verification_uri_complete: "/console/device?user_code=user&client_id=opencode-cli",
expires_in: 60,
interval: 0,
})
@@ -130,7 +130,7 @@ describe("OpencodePlugin", () => {
methodID: Integration.MethodID.make("device"),
answer: { server: `${server.url.origin}/console///?ignored=true#ignored` },
})
expect(attempt.url).toBe(`${server.url.origin}/verify`)
expect(attempt.url).toBe(`${server.url.origin}/console/device?user_code=user&client_id=opencode-cli`)
yield* eventually(
integrations.oauth.status({ integrationID, attemptID: attempt.attemptID }),
(status) => status.status === "complete",
@@ -148,6 +148,38 @@ describe("OpencodePlugin", () => {
),
)
it.live("rejects malformed device verification URLs", () =>
Effect.acquireUseRelease(
Effect.sync(() =>
Bun.serve({
port: 0,
fetch: () =>
Response.json({
device_code: "device",
user_code: "user",
verification_uri_complete: "http://[::1",
expires_in: 60,
interval: 0,
}),
}),
),
(server) =>
Effect.gen(function* () {
yield* addPlugin()
const error = yield* (yield* Integration.Service).oauth
.connect({
integrationID: Integration.ID.make("opencode"),
methodID: Integration.MethodID.make("device"),
answer: { server: server.url.origin },
})
.pipe(Effect.flip)
expect(error).toBeInstanceOf(Integration.AuthorizationError)
expect(String(error.cause)).toContain("Invalid device verification URL")
}),
(server) => Effect.promise(() => server.stop(true)),
),
)
it.effect("rejects non-HTTP OpenCode servers", () =>
Effect.gen(function* () {
yield* addPlugin()
@@ -15,7 +15,7 @@ const it = testEffect(PluginTestLayer)
const npm = Npm.Service.of({
add: () => Effect.succeed({ directory: "", entrypoint: undefined }),
resolve: () => Effect.succeed({ directory: "", entrypoint: undefined }),
which: () => Effect.succeed(undefined),
which: () => Effect.undefined,
})
const addPlugin = Effect.fn(function* () {
+207
View File
@@ -4,6 +4,7 @@ import fs from "fs/promises"
import path from "path"
import { DateTime, Effect, Layer, Stream } from "effect"
import { Money } from "@opencode-ai/schema/money"
import { Shell } from "@opencode-ai/schema/shell"
import { Agent } from "@opencode-ai/core/agent"
import { asc, eq } from "drizzle-orm"
import { Database } from "@opencode-ai/core/database/database"
@@ -401,6 +402,41 @@ describe("Session.create", () => {
}),
)
it.effect("replays a fork with stable projected identities", () =>
Effect.gen(function* () {
const session = yield* Session.Service
const bus = yield* Bus.Service
const { db } = yield* Database.Service
const parent = yield* session.create({ location, title: "Parent" })
yield* session.prompt({ sessionID: parent.id, text: "First", resume: false })
yield* SessionInbox.promote(db, bus, parent.id, "steer")
yield* session.synthetic({ sessionID: parent.id, text: "Second", resume: false })
yield* SessionInbox.promote(db, bus, parent.id, "steer")
const forked = yield* session.fork({ sessionID: parent.id, boundary: { type: "through" } })
const original = (yield* session.context(forked.id)).map((message) => message.id)
const recorded = yield* db
.select()
.from(EventTable)
.where(eq(EventTable.aggregate_id, forked.id))
.get()
.pipe(Effect.orDie)
if (!recorded) return yield* Effect.die(new Error("Fork event not found"))
yield* bus.remove(forked.id)
yield* db.delete(SessionTable).where(eq(SessionTable.id, forked.id)).run().pipe(Effect.orDie)
yield* bus.replay({
id: recorded.id,
created: recorded.created,
aggregateID: recorded.aggregate_id,
seq: recorded.seq,
type: recorded.type,
data: recorded.data,
})
expect((yield* session.context(forked.id)).map((message) => message.id)).toEqual(original)
}),
)
it.effect("does not copy a running assistant into a fork", () =>
Effect.gen(function* () {
const session = yield* Session.Service
@@ -451,6 +487,49 @@ describe("Session.create", () => {
}),
)
it.effect("copies only settled shell messages into forks", () =>
Effect.gen(function* () {
const session = yield* Session.Service
const bus = yield* Bus.Service
const { db } = yield* Database.Service
const parent = yield* session.create({ location })
yield* session.prompt({ sessionID: parent.id, text: "Run a shell", resume: false })
yield* SessionInbox.promote(db, bus, parent.id, "steer")
const shell = Shell.Info.make({
id: Shell.ID.make("sh_fork_running"),
status: "running",
command: "sleep 10",
cwd: location.directory,
shell: "/bin/sh",
file: "/tmp/sh_fork_running.out",
metadata: {},
time: { started: 0 },
})
yield* bus.publish(SessionEvent.Shell.Started, { sessionID: parent.id, shell })
const running = yield* session.fork({ sessionID: parent.id, boundary: { type: "through" } })
expect(yield* session.context(parent.id)).toMatchObject([
{ type: "user", text: "Run a shell" },
{ type: "shell", command: "sleep 10", status: "running" },
])
expect(yield* session.context(running.id)).toMatchObject([{ type: "user", text: "Run a shell" }])
yield* bus.publish(SessionEvent.Shell.Ended, {
sessionID: parent.id,
shell: { ...shell, status: "exited", exit: 0, time: { started: 0, completed: 1 } },
output: { output: "complete", cursor: 8, size: 8, truncated: false },
})
const completed = yield* session.fork({ sessionID: parent.id, boundary: { type: "through" } })
expect(yield* session.context(running.id)).toMatchObject([{ type: "user", text: "Run a shell" }])
expect(yield* session.context(completed.id)).toMatchObject([
{ type: "user", text: "Run a shell" },
{ type: "shell", command: "sleep 10", status: "exited", output: { output: "complete" } },
])
}),
)
it.effect("rejects forking an empty session", () =>
Effect.gen(function* () {
const session = yield* Session.Service
@@ -887,6 +966,134 @@ describe("Session.create", () => {
})
describe("SessionTransfer", () => {
it.effect("exports only settled projected messages", () =>
Effect.gen(function* () {
const session = yield* Session.Service
const transfer = yield* SessionTransfer.Service
const bus = yield* Bus.Service
const { db } = yield* Database.Service
const source = yield* session.create({ location })
yield* session.prompt({ sessionID: source.id, text: "Settled", resume: false })
yield* SessionInbox.promote(db, bus, source.id, "steer")
yield* bus.publish(SessionEvent.Step.Started, {
sessionID: source.id,
assistantMessageID: SessionMessage.ID.create(),
agent: Agent.ID.make("build"),
model: Model.Ref.make({ id: Model.ID.make("model"), providerID: Provider.ID.make("provider") }),
})
yield* bus.publish(SessionEvent.Shell.Started, {
sessionID: source.id,
shell: Shell.Info.make({
id: Shell.ID.make("sh_transfer_export"),
status: "running",
command: "sleep 10",
cwd: location.directory,
shell: "/bin/sh",
file: "/tmp/sh_transfer_export.out",
metadata: {},
time: { started: 0 },
}),
})
yield* bus.publish(SessionEvent.Compaction.Started, {
sessionID: source.id,
reason: "manual",
recent: "pending",
})
expect((yield* transfer.export({ sessionID: source.id })).messages).toMatchObject([
{ type: "user", text: "Settled" },
])
}),
)
it.effect("imports only settled projected messages", () =>
Effect.gen(function* () {
const session = yield* Session.Service
const transfer = yield* SessionTransfer.Service
const { db } = yield* Database.Service
const template = yield* session.create({ location, title: "Transfer source" })
const sessionID = Session.ID.create()
const userID = SessionMessage.ID.create()
const runningAssistantID = SessionMessage.ID.create()
const completedAssistantID = SessionMessage.ID.create()
const runningShellID = SessionMessage.ID.create()
const completedShellID = SessionMessage.ID.create()
const runningCompactionID = SessionMessage.ID.create()
const completedCompactionID = SessionMessage.ID.create()
const model = Model.Ref.make({ id: Model.ID.make("model"), providerID: Provider.ID.make("provider") })
yield* transfer.import({
data: {
info: { ...template, id: sessionID },
messages: [
{ id: userID, type: "user", text: "Settled", time: { created: DateTime.makeUnsafe(1) } },
{
id: runningAssistantID,
type: "assistant",
agent: Agent.ID.make("build"),
model,
content: [],
time: { created: DateTime.makeUnsafe(2) },
},
{
id: completedAssistantID,
type: "assistant",
agent: Agent.ID.make("build"),
model,
content: [],
time: { created: DateTime.makeUnsafe(3), completed: DateTime.makeUnsafe(4) },
},
{
id: runningShellID,
type: "shell",
shellID: Shell.ID.make("sh_transfer_running"),
command: "sleep 10",
status: "running",
time: { created: DateTime.makeUnsafe(5) },
},
{
id: completedShellID,
type: "shell",
shellID: Shell.ID.make("sh_transfer_completed"),
command: "pwd",
status: "exited",
exit: 0,
output: { output: "/project", cursor: 8, size: 8, truncated: false },
time: { created: DateTime.makeUnsafe(6), completed: DateTime.makeUnsafe(7) },
},
{
id: runningCompactionID,
type: "compaction",
status: "running",
reason: "manual",
summary: "pending",
recent: "pending",
time: { created: DateTime.makeUnsafe(8) },
},
{
id: completedCompactionID,
type: "compaction",
status: "completed",
reason: "manual",
summary: "summary",
recent: "recent",
time: { created: DateTime.makeUnsafe(9) },
},
],
},
location,
})
expect((yield* session.messages({ sessionID, order: "asc" })).map((message) => message.id)).toEqual([
userID,
completedAssistantID,
completedShellID,
completedCompactionID,
])
expect(yield* Bus.latestSequence(db, sessionID)).toBe(4)
}),
)
it.effect("imports projected messages and reserves their aggregate sequence", () =>
Effect.gen(function* () {
const session = yield* Session.Service
+1 -1
View File
@@ -74,7 +74,7 @@ const locations = Layer.effect(
}),
Layer.mock(Snapshot.Service, {
capture: () =>
ready ? Effect.succeed(undefined) : Effect.die(new Error("Snapshot used before plugins were ready")),
ready ? Effect.undefined : Effect.die(new Error("Snapshot used before plugins were ready")),
restore: () =>
ready ? Effect.void : Effect.die(new Error("Snapshot used before plugins were ready")),
}),
@@ -88,16 +88,16 @@ const config = Config.testLayer()
const pluginSupervisor = Layer.succeed(PluginSupervisor.Service, PluginSupervisor.Service.of({ flush: Effect.void }))
const promptCatalog = Layer.mock(Catalog.Service, {
provider: {
get: () => Effect.succeed(undefined),
get: () => Effect.undefined,
all: () => Effect.succeed([]),
available: () => Effect.succeed([]),
},
model: {
get: () => Effect.succeed(undefined),
get: () => Effect.undefined,
all: () => Effect.succeed([]),
available: () => Effect.succeed([]),
default: () => Effect.succeed(undefined),
small: () => Effect.succeed(undefined),
default: () => Effect.undefined,
small: () => Effect.undefined,
},
})
const runnerLayer = (llmClient: Layer.Layer<typeof LLMClient.Service>) =>
+4 -4
View File
@@ -378,16 +378,16 @@ const pluginSupervisor = Layer.succeed(
)
const promptCatalog = Layer.mock(Catalog.Service, {
provider: {
get: () => Effect.succeed(undefined),
get: () => Effect.undefined,
all: () => Effect.succeed([]),
available: () => Effect.succeed([]),
},
model: {
get: () => Effect.succeed(undefined),
get: () => Effect.undefined,
all: () => Effect.succeed([]),
available: () => Effect.succeed([]),
default: () => Effect.succeed(undefined),
small: () => Effect.succeed(undefined),
default: () => Effect.undefined,
small: () => Effect.undefined,
},
})
const runnerLayer = AppNodeBuilder.build(SessionRunnerLLM.node, [
+1 -1
View File
@@ -1426,7 +1426,7 @@ export function write(
output.files,
(file) =>
fs.exists(join(directory, file.path)).pipe(
Effect.flatMap((exists) => (exists ? fs.stat(join(directory, file.path)) : Effect.succeed(undefined))),
Effect.flatMap((exists) => (exists ? fs.stat(join(directory, file.path)) : Effect.undefined)),
Effect.flatMap((info) =>
info?.type === "SymbolicLink"
? new GenerationError({ reason: `Unsafe output path: ${file.path}` })
+2 -2
View File
@@ -70,8 +70,8 @@ export namespace FSUtil {
const readFileStringSafe = Effect.fn("FileSystem.readFileStringSafe")(function* (path: string) {
return yield* fs.readFileString(path).pipe(
Effect.catchReason("PlatformError", "NotFound", () => Effect.succeed(undefined)),
Effect.catchReason("PlatformError", "PermissionDenied", () => Effect.succeed(undefined)),
Effect.catchReason("PlatformError", "NotFound", () => Effect.undefined),
Effect.catchReason("PlatformError", "PermissionDenied", () => Effect.undefined),
)
})