refactor(schema): extract public event definitions (#33579)
This commit is contained in:
parent
858f35f1b3
commit
24b0132bc5
93 changed files with 2743 additions and 2127 deletions
|
|
@ -86,6 +86,7 @@
|
|||
"@openauthjs/openauth": "catalog:",
|
||||
"@opencode-ai/llm": "workspace:*",
|
||||
"@opencode-ai/plugin": "workspace:*",
|
||||
"@opencode-ai/schema": "workspace:*",
|
||||
"@opencode-ai/script": "workspace:*",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/server": "workspace:*",
|
||||
|
|
|
|||
|
|
@ -2,29 +2,20 @@ import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
|||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { EffectBridge } from "@/effect/bridge"
|
||||
import type { InstanceContext } from "@/project/instance-context"
|
||||
import { SessionID, MessageID } from "@/session/schema"
|
||||
import { Effect, Layer, Context, Schema } from "effect"
|
||||
import { Config } from "@/config/config"
|
||||
import { MCP } from "../mcp"
|
||||
import { Skill } from "../skill"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import PROMPT_INITIALIZE from "./template/initialize.txt"
|
||||
import PROMPT_REVIEW from "./template/review.txt"
|
||||
import { LegacyEvent } from "@opencode-ai/schema/legacy-event"
|
||||
|
||||
type State = {
|
||||
commands: Record<string, Info>
|
||||
}
|
||||
|
||||
export const Event = {
|
||||
Executed: EventV2.define({
|
||||
type: "command.executed",
|
||||
schema: {
|
||||
name: Schema.String,
|
||||
sessionID: SessionID,
|
||||
arguments: Schema.String,
|
||||
messageID: MessageID,
|
||||
},
|
||||
}),
|
||||
Executed: LegacyEvent.CommandExecuted,
|
||||
}
|
||||
|
||||
export const Info = Schema.Struct({
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import { Vcs } from "@/project/vcs"
|
|||
import { InstanceStore } from "@/project/instance-store"
|
||||
import { InstanceBootstrap } from "@/project/bootstrap"
|
||||
import { WorkspaceAdapterRuntime } from "./workspace-adapter-runtime"
|
||||
import { WorkspaceEvent } from "@opencode-ai/schema/workspace-event"
|
||||
|
||||
export const Info = Schema.Struct({
|
||||
...WorkspaceInfoSchema.fields,
|
||||
|
|
@ -40,27 +41,10 @@ export const Info = Schema.Struct({
|
|||
}).annotate({ identifier: "Workspace" })
|
||||
export type Info = WorkspaceInfo & { timeUsed: number }
|
||||
|
||||
export const ConnectionStatus = Schema.Struct({
|
||||
workspaceID: WorkspaceV2.ID,
|
||||
status: Schema.Literals(["connected", "connecting", "disconnected", "error"]),
|
||||
})
|
||||
export type ConnectionStatus = Schema.Schema.Type<typeof ConnectionStatus>
|
||||
export const ConnectionStatus = WorkspaceEvent.ConnectionStatus
|
||||
export type ConnectionStatus = WorkspaceEvent.ConnectionStatus
|
||||
|
||||
export const Event = {
|
||||
Ready: EventV2.define({
|
||||
type: "workspace.ready",
|
||||
schema: {
|
||||
name: Schema.String,
|
||||
},
|
||||
}),
|
||||
Failed: EventV2.define({
|
||||
type: "workspace.failed",
|
||||
schema: {
|
||||
message: Schema.String,
|
||||
},
|
||||
}),
|
||||
Status: EventV2.define({ type: "workspace.status", schema: ConnectionStatus.fields }),
|
||||
}
|
||||
export const Event = WorkspaceEvent
|
||||
|
||||
function fromRow(row: typeof WorkspaceTable.$inferSelect): Info {
|
||||
return {
|
||||
|
|
|
|||
3
packages/opencode/src/event-manifest.ts
Normal file
3
packages/opencode/src/event-manifest.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export * as EventManifest from "./event-manifest"
|
||||
|
||||
export { Definitions, Durable, Latest } from "@opencode-ai/schema/event-manifest"
|
||||
|
|
@ -7,9 +7,6 @@ import { EventV2 } from "@opencode-ai/core/event"
|
|||
import { Location } from "@opencode-ai/core/location"
|
||||
import { Project } from "@opencode-ai/core/project"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
import "@opencode-ai/core/account"
|
||||
import "@opencode-ai/core/catalog"
|
||||
import "@opencode-ai/core/session/event"
|
||||
import { Context, Effect, Layer } from "effect"
|
||||
|
||||
export class Service extends Context.Service<Service, EventV2.Interface>()("@opencode/EventV2Bridge") {}
|
||||
|
|
@ -45,10 +42,7 @@ export const layer = Layer.effect(
|
|||
workspace: workspaceID,
|
||||
payload: { id: event.id, type: event.type, properties: event.data },
|
||||
})
|
||||
const durable = EventV2.registry.get(event.type)?.durable
|
||||
if (durable === undefined || event.durable === undefined) return
|
||||
const aggregateID = (event.data as Record<string, unknown>)[durable.aggregate]
|
||||
if (typeof aggregateID !== "string") return
|
||||
if (event.durable === undefined) return
|
||||
GlobalBus.emit("event", {
|
||||
directory: event.location?.directory ?? ctx?.directory,
|
||||
project: ctx?.project.id,
|
||||
|
|
@ -59,7 +53,7 @@ export const layer = Layer.effect(
|
|||
id: event.id,
|
||||
type: EventV2.versionedType(event.type, event.durable.version),
|
||||
seq: event.durable.seq,
|
||||
aggregateID,
|
||||
aggregateID: event.durable.aggregateID,
|
||||
data: event.data,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { Schema } from "effect"
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
import { Process } from "@/util/process"
|
||||
import { IdeEvent } from "@opencode-ai/schema/ide-event"
|
||||
|
||||
const SUPPORTED_IDES = [
|
||||
{ name: "Windsurf" as const, cmd: "windsurf" },
|
||||
|
|
@ -11,14 +11,7 @@ const SUPPORTED_IDES = [
|
|||
{ name: "VSCodium" as const, cmd: "codium" },
|
||||
]
|
||||
|
||||
export const Event = {
|
||||
Installed: EventV2.define({
|
||||
type: "ide.installed",
|
||||
schema: {
|
||||
ide: Schema.String,
|
||||
},
|
||||
}),
|
||||
}
|
||||
export const Event = IdeEvent
|
||||
|
||||
export const AlreadyInstalledError = NamedError.create("AlreadyInstalledError", {})
|
||||
|
||||
|
|
|
|||
|
|
@ -8,30 +8,17 @@ import { errorMessage } from "@/util/error"
|
|||
import { ChildProcess } from "effect/unstable/process"
|
||||
import { AppProcess } from "@opencode-ai/core/process"
|
||||
import path from "path"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { makeRuntime } from "@opencode-ai/core/effect/runtime"
|
||||
import semver from "semver"
|
||||
import { InstallationChannel, InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import { NpmConfig } from "@opencode-ai/core/npm-config"
|
||||
import { InstallationEvent } from "@opencode-ai/schema/installation-event"
|
||||
|
||||
export type Method = "curl" | "npm" | "yarn" | "pnpm" | "bun" | "brew" | "scoop" | "choco" | "unknown"
|
||||
|
||||
export type ReleaseType = "patch" | "minor" | "major"
|
||||
|
||||
export const Event = {
|
||||
Updated: EventV2.define({
|
||||
type: "installation.updated",
|
||||
schema: {
|
||||
version: Schema.String,
|
||||
},
|
||||
}),
|
||||
UpdateAvailable: EventV2.define({
|
||||
type: "installation.update-available",
|
||||
schema: {
|
||||
version: Schema.String,
|
||||
},
|
||||
}),
|
||||
}
|
||||
export const Event = InstallationEvent
|
||||
|
||||
export function getReleaseType(current: string, latest: string): ReleaseType {
|
||||
const currMajor = semver.major(current)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import * as LSPClient from "./client"
|
||||
import path from "path"
|
||||
import { pathToFileURL, fileURLToPath } from "url"
|
||||
|
|
@ -14,10 +13,9 @@ import { InstanceState } from "@/effect/instance-state"
|
|||
import { containsPath } from "@/project/instance-context"
|
||||
import { NonNegativeInt } from "@opencode-ai/core/schema"
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { LspEvent } from "@opencode-ai/schema/lsp-event"
|
||||
|
||||
export const Event = {
|
||||
Updated: EventV2.define({ type: "lsp.updated", schema: {} }),
|
||||
}
|
||||
export const Event = LspEvent
|
||||
|
||||
const Position = Schema.Struct({
|
||||
line: NonNegativeInt,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import { McpOAuthProvider, OAUTH_CALLBACK_PATH } from "./oauth-provider"
|
|||
import { McpOAuthCallback } from "./oauth-callback"
|
||||
import { McpAuth } from "./auth"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { TuiEvent } from "@/server/tui-event"
|
||||
import open from "open"
|
||||
import { Cause, Effect, Exit, Layer, Option, Context, Schema, Stream } from "effect"
|
||||
|
|
@ -35,6 +34,7 @@ import { InstanceState } from "@/effect/instance-state"
|
|||
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { McpCatalog } from "./catalog"
|
||||
import { McpEvent } from "@opencode-ai/schema/mcp-event"
|
||||
|
||||
const DEFAULT_TIMEOUT = 30_000
|
||||
const CLIENT_OPTIONS = {
|
||||
|
|
@ -59,20 +59,9 @@ export const Resource = Schema.Struct({
|
|||
}).annotate({ identifier: "McpResource" })
|
||||
export type Resource = Schema.Schema.Type<typeof Resource>
|
||||
|
||||
export const ToolsChanged = EventV2.define({
|
||||
type: "mcp.tools.changed",
|
||||
schema: {
|
||||
server: Schema.String,
|
||||
},
|
||||
})
|
||||
export const ToolsChanged = McpEvent.ToolsChanged
|
||||
|
||||
export const BrowserOpenFailed = EventV2.define({
|
||||
type: "mcp.browser.open.failed",
|
||||
schema: {
|
||||
mcpName: Schema.String,
|
||||
url: Schema.String,
|
||||
},
|
||||
})
|
||||
export const BrowserOpenFailed = McpEvent.BrowserOpenFailed
|
||||
|
||||
export const Failed = NamedError.create("MCPFailed", {
|
||||
name: Schema.String,
|
||||
|
|
|
|||
|
|
@ -6,19 +6,9 @@ import { Deferred, Effect, Layer, Context } from "effect"
|
|||
import os from "os"
|
||||
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { PermissionV1Event } from "@opencode-ai/schema/permission-v1"
|
||||
|
||||
export const Event = {
|
||||
Asked: EventV2.define({ type: "permission.asked", schema: PermissionV1.Request.fields }),
|
||||
Replied: EventV2.define({
|
||||
type: "permission.replied",
|
||||
schema: {
|
||||
sessionID: PermissionV1.Request.fields.sessionID,
|
||||
requestID: PermissionV1.ID,
|
||||
reply: PermissionV1.Reply,
|
||||
},
|
||||
}),
|
||||
}
|
||||
export const Event = PermissionV1Event
|
||||
|
||||
export interface Interface {
|
||||
readonly ask: (input: PermissionV1.AskInput) => Effect.Effect<void, PermissionV1.Error>
|
||||
|
|
|
|||
|
|
@ -16,46 +16,18 @@ import { FSUtil } from "@opencode-ai/core/fs-util"
|
|||
import { AppProcess } from "@opencode-ai/core/process"
|
||||
import { ProjectV2 } from "@opencode-ai/core/project"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { AbsolutePath, NonNegativeInt, optionalOmitUndefined } from "@opencode-ai/core/schema"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { Project } from "@opencode-ai/schema/project"
|
||||
|
||||
const ProjectVcs = Schema.Literal("git")
|
||||
|
||||
const ProjectIcon = Schema.Struct({
|
||||
url: optionalOmitUndefined(Schema.String),
|
||||
override: optionalOmitUndefined(Schema.String),
|
||||
color: optionalOmitUndefined(Schema.String),
|
||||
})
|
||||
|
||||
const ProjectCommands = Schema.Struct({
|
||||
start: optionalOmitUndefined(
|
||||
Schema.String.annotate({ description: "Startup script to run when creating a new workspace (worktree)" }),
|
||||
),
|
||||
})
|
||||
|
||||
const ProjectTime = Schema.Struct({
|
||||
created: NonNegativeInt,
|
||||
updated: NonNegativeInt,
|
||||
initialized: optionalOmitUndefined(NonNegativeInt),
|
||||
})
|
||||
|
||||
export const Info = Schema.Struct({
|
||||
id: ProjectV2.ID,
|
||||
worktree: Schema.String,
|
||||
vcs: optionalOmitUndefined(ProjectVcs),
|
||||
name: optionalOmitUndefined(Schema.String),
|
||||
icon: optionalOmitUndefined(ProjectIcon),
|
||||
commands: optionalOmitUndefined(ProjectCommands),
|
||||
time: ProjectTime,
|
||||
sandboxes: Schema.Array(Schema.String),
|
||||
}).annotate({ identifier: "Project" })
|
||||
export const Info = Project.Info
|
||||
export type Info = Types.DeepMutable<Schema.Schema.Type<typeof Info>>
|
||||
|
||||
export const Event = {
|
||||
Updated: EventV2.define({ type: "project.updated", schema: Info.fields }),
|
||||
Updated: Project.Event.Updated,
|
||||
}
|
||||
|
||||
type Row = typeof ProjectTable.$inferSelect
|
||||
|
|
@ -72,7 +44,7 @@ export function fromRow(row: Row): Info {
|
|||
return {
|
||||
id: row.id,
|
||||
worktree: row.worktree,
|
||||
vcs: row.vcs ? Schema.decodeUnknownSync(ProjectVcs)(row.vcs) : undefined,
|
||||
vcs: row.vcs ? Schema.decodeUnknownSync(Project.Vcs)(row.vcs) : undefined,
|
||||
name: row.name ?? undefined,
|
||||
icon,
|
||||
time: {
|
||||
|
|
@ -88,15 +60,15 @@ export function fromRow(row: Row): Info {
|
|||
export const UpdateInput = Schema.Struct({
|
||||
projectID: ProjectV2.ID,
|
||||
name: Schema.optional(Schema.String),
|
||||
icon: Schema.optional(ProjectIcon),
|
||||
commands: Schema.optional(ProjectCommands),
|
||||
icon: Schema.optional(Project.Icon),
|
||||
commands: Schema.optional(Project.Commands),
|
||||
})
|
||||
export type UpdateInput = Types.DeepMutable<Schema.Schema.Type<typeof UpdateInput>>
|
||||
|
||||
export const UpdatePayload = Schema.Struct({
|
||||
name: Schema.optional(Schema.String),
|
||||
icon: Schema.optional(ProjectIcon),
|
||||
commands: Schema.optional(ProjectCommands),
|
||||
icon: Schema.optional(Project.Icon),
|
||||
commands: Schema.optional(Project.Commands),
|
||||
}).annotate({ identifier: "ProjectUpdateInput" })
|
||||
export type UpdatePayload = Types.DeepMutable<Schema.Schema.Type<typeof UpdatePayload>>
|
||||
|
||||
|
|
@ -135,7 +107,6 @@ export const layer = Layer.effect(
|
|||
Service,
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FSUtil.Service
|
||||
const proc = yield* AppProcess.Service
|
||||
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
|
||||
const projectV2 = yield* ProjectV2.Service
|
||||
const projectDirectories = yield* ProjectDirectories.Service
|
||||
|
|
@ -168,7 +139,7 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
const fakeVcs = Schema.decodeUnknownSync(Schema.optional(ProjectVcs))(Flag.OPENCODE_FAKE_VCS)
|
||||
const fakeVcs = Schema.decodeUnknownSync(Schema.optional(Project.Vcs))(Flag.OPENCODE_FAKE_VCS)
|
||||
|
||||
const scope = yield* Scope.Scope
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { Effect, Layer, Context, Schema, Stream, Scope } from "effect"
|
||||
import { Effect, Layer, Context, Schema, Scope } from "effect"
|
||||
import { formatPatch, structuredPatch } from "diff"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { Watcher } from "@opencode-ai/core/filesystem/watcher"
|
||||
import { Git } from "@/git"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { VcsEvent } from "@opencode-ai/schema/vcs-event"
|
||||
|
||||
const PATCH_CONTEXT_LINES = 2_147_483_647
|
||||
const MAX_PATCH_BYTES = 10_000_000
|
||||
|
|
@ -234,14 +235,7 @@ const track = Effect.fnUntraced(function* (
|
|||
export const Mode = Schema.Literals(["git", "branch"])
|
||||
export type Mode = Schema.Schema.Type<typeof Mode>
|
||||
|
||||
export const Event = {
|
||||
BranchUpdated: EventV2.define({
|
||||
type: "vcs.branch.updated",
|
||||
schema: {
|
||||
branch: Schema.optional(Schema.String),
|
||||
},
|
||||
}),
|
||||
}
|
||||
export const Event = VcsEvent
|
||||
|
||||
export const Info = Schema.Struct({
|
||||
branch: Schema.optional(Schema.String),
|
||||
|
|
|
|||
|
|
@ -1,94 +1,28 @@
|
|||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { Deferred, Effect, Layer, Schema, Context } from "effect"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { SessionID, MessageID } from "@/session/schema"
|
||||
import { SessionID } from "@/session/schema"
|
||||
import { QuestionID } from "./schema"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { QuestionV1 } from "@opencode-ai/schema/question-v1"
|
||||
|
||||
// Schemas — these are pure data; nothing checks class identity (see PR
|
||||
// description) so they're plain `Schema.Struct` + type alias. That lets
|
||||
// `Question.ask` and other internal sites trust the type contract without a
|
||||
// re-decode to coerce nested class instances.
|
||||
|
||||
export const Option = Schema.Struct({
|
||||
label: Schema.String.annotate({
|
||||
description: "Display text (1-5 words, concise)",
|
||||
}),
|
||||
description: Schema.String.annotate({
|
||||
description: "Explanation of choice",
|
||||
}),
|
||||
}).annotate({ identifier: "QuestionOption" })
|
||||
export type Option = Schema.Schema.Type<typeof Option>
|
||||
|
||||
const base = {
|
||||
question: Schema.String.annotate({
|
||||
description: "Complete question",
|
||||
}),
|
||||
header: Schema.String.annotate({
|
||||
description: "Very short label (max 30 chars)",
|
||||
}),
|
||||
options: Schema.Array(Option).annotate({
|
||||
description: "Available choices",
|
||||
}),
|
||||
multiple: Schema.optional(Schema.Boolean).annotate({
|
||||
description: "Allow selecting multiple choices",
|
||||
}),
|
||||
}
|
||||
|
||||
export const Info = Schema.Struct({
|
||||
...base,
|
||||
custom: Schema.optional(Schema.Boolean).annotate({
|
||||
description: "Allow typing a custom answer (default: true)",
|
||||
}),
|
||||
}).annotate({ identifier: "QuestionInfo" })
|
||||
export type Info = Schema.Schema.Type<typeof Info>
|
||||
|
||||
export const Prompt = Schema.Struct(base).annotate({ identifier: "QuestionPrompt" })
|
||||
export type Prompt = Schema.Schema.Type<typeof Prompt>
|
||||
|
||||
export const Tool = Schema.Struct({
|
||||
messageID: MessageID,
|
||||
callID: Schema.String,
|
||||
}).annotate({ identifier: "QuestionTool" })
|
||||
export type Tool = Schema.Schema.Type<typeof Tool>
|
||||
|
||||
export const Request = Schema.Struct({
|
||||
id: QuestionID,
|
||||
sessionID: SessionID,
|
||||
questions: Schema.Array(Info).annotate({
|
||||
description: "Questions to ask",
|
||||
}),
|
||||
tool: Schema.optional(Tool),
|
||||
}).annotate({ identifier: "QuestionRequest" })
|
||||
export type Request = Schema.Schema.Type<typeof Request>
|
||||
|
||||
export const Answer = Schema.Array(Schema.String).annotate({ identifier: "QuestionAnswer" })
|
||||
export type Answer = Schema.Schema.Type<typeof Answer>
|
||||
|
||||
export const Reply = Schema.Struct({
|
||||
answers: Schema.Array(Answer).annotate({
|
||||
description: "User answers in order of questions (each answer is an array of selected labels)",
|
||||
}),
|
||||
}).annotate({ identifier: "QuestionReply" })
|
||||
export type Reply = Schema.Schema.Type<typeof Reply>
|
||||
|
||||
export const Replied = Schema.Struct({
|
||||
sessionID: SessionID,
|
||||
requestID: QuestionID,
|
||||
answers: Schema.Array(Answer),
|
||||
}).annotate({ identifier: "QuestionReplied" })
|
||||
|
||||
export const Rejected = Schema.Struct({
|
||||
sessionID: SessionID,
|
||||
requestID: QuestionID,
|
||||
}).annotate({ identifier: "QuestionRejected" })
|
||||
|
||||
export const Event = {
|
||||
Asked: EventV2.define({ type: "question.asked", schema: Request.fields }),
|
||||
Replied: EventV2.define({ type: "question.replied", schema: Replied.fields }),
|
||||
Rejected: EventV2.define({ type: "question.rejected", schema: Rejected.fields }),
|
||||
}
|
||||
export const Option = QuestionV1.Option
|
||||
export type Option = typeof Option.Type
|
||||
export const Info = QuestionV1.Info
|
||||
export type Info = typeof Info.Type
|
||||
export const Prompt = QuestionV1.Prompt
|
||||
export type Prompt = typeof Prompt.Type
|
||||
export const Tool = QuestionV1.Tool
|
||||
export type Tool = typeof Tool.Type
|
||||
export const Request = QuestionV1.Request
|
||||
export type Request = typeof Request.Type
|
||||
export const Answer = QuestionV1.Answer
|
||||
export type Answer = typeof Answer.Type
|
||||
export const Reply = QuestionV1.Reply
|
||||
export type Reply = typeof Reply.Type
|
||||
export const Replied = QuestionV1.Replied
|
||||
export const Rejected = QuestionV1.Rejected
|
||||
export const Event = QuestionV1.Event
|
||||
|
||||
export class RejectedError extends Schema.TaggedErrorClass<RejectedError>()("QuestionRejectedError", {}) {
|
||||
override get message() {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,4 @@
|
|||
import { Schema } from "effect"
|
||||
import { QuestionV1 } from "@opencode-ai/schema/question-v1"
|
||||
|
||||
import { Identifier } from "@/id/id"
|
||||
import { Newtype } from "@opencode-ai/core/schema"
|
||||
|
||||
export class QuestionID extends Newtype<QuestionID>()("QuestionID", Schema.String.check(Schema.isStartsWith("que"))) {
|
||||
static ascending(id?: string): QuestionID {
|
||||
return this.make(Identifier.ascending("question", id))
|
||||
}
|
||||
}
|
||||
export const QuestionID = QuestionV1.ID
|
||||
export type QuestionID = typeof QuestionID.Type
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { Schema } from "effect"
|
||||
import { ServerEvent } from "@opencode-ai/schema/server-event"
|
||||
|
||||
export const Event = {
|
||||
Connected: EventV2.define({ type: "server.connected", schema: {} }),
|
||||
Disposed: EventV2.define({ type: "global.disposed", schema: {} }),
|
||||
}
|
||||
export const Event = ServerEvent
|
||||
|
||||
export const InstanceDisposed = Schema.Struct({
|
||||
id: Schema.String,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Schema } from "effect"
|
||||
import { HttpApi } from "effect/unstable/httpapi"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { EventManifest } from "@/event-manifest"
|
||||
import { Credential } from "@opencode-ai/core/credential"
|
||||
import { Integration } from "@opencode-ai/core/integration"
|
||||
import { SkillV2 } from "@opencode-ai/core/skill"
|
||||
|
|
@ -24,15 +25,13 @@ import { SessionApi } from "./groups/session"
|
|||
import { SyncApi } from "./groups/sync"
|
||||
import { TuiApi } from "./groups/tui"
|
||||
import { WorkspaceApi } from "./groups/workspace"
|
||||
import { Api } from "@opencode-ai/server/api"
|
||||
// GlobalEventSchema snapshots the registry after event-producing groups register their variants.
|
||||
import { makeApi } from "@opencode-ai/server/api"
|
||||
import { GlobalApi } from "./groups/global"
|
||||
import { Authorization } from "./middleware/authorization"
|
||||
import { SchemaErrorMiddleware } from "./middleware/schema-error"
|
||||
|
||||
const EventSchema = Schema.Union([
|
||||
...EventV2.registry
|
||||
.values()
|
||||
...EventManifest.Latest.values()
|
||||
.map((definition) =>
|
||||
Schema.Struct({
|
||||
id: EventV2.ID,
|
||||
|
|
@ -44,6 +43,8 @@ const EventSchema = Schema.Union([
|
|||
InstanceDisposed,
|
||||
]).annotate({ identifier: "Event" })
|
||||
|
||||
export const ServerApi = makeApi(EventManifest.Latest.values().toArray())
|
||||
|
||||
export const RootHttpApi = HttpApi.make("opencode-root")
|
||||
.addHttpApi(ControlApi)
|
||||
.addHttpApi(ControlPlaneApi)
|
||||
|
|
@ -73,7 +74,7 @@ export const OpenCodeHttpApi = HttpApi.make("opencode")
|
|||
.addHttpApi(RootHttpApi)
|
||||
.addHttpApi(EventApi)
|
||||
.addHttpApi(InstanceHttpApi)
|
||||
.addHttpApi(Api)
|
||||
.addHttpApi(ServerApi)
|
||||
.addHttpApi(PtyConnectApi)
|
||||
.annotate(HttpApi.AdditionalSchemas, [
|
||||
EventSchema,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Config } from "@/config/config"
|
||||
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { EventManifest } from "@/event-manifest"
|
||||
import { InstanceDisposed } from "@/server/event"
|
||||
import "@opencode-ai/core/account"
|
||||
import "@/server/event"
|
||||
|
|
@ -13,8 +13,7 @@ const GlobalHealth = Schema.Struct({
|
|||
version: Schema.String,
|
||||
})
|
||||
|
||||
const SyncEventSchemas = EventV2.registry
|
||||
.values()
|
||||
const SyncEventSchemas = EventManifest.Latest.values()
|
||||
.flatMap((definition) => {
|
||||
if (!definition.durable) return []
|
||||
return [
|
||||
|
|
@ -38,8 +37,7 @@ const GlobalEventSchema = Schema.Struct({
|
|||
project: Schema.optional(Schema.String),
|
||||
workspace: Schema.optional(Schema.String),
|
||||
payload: Schema.Union([
|
||||
...EventV2.registry
|
||||
.values()
|
||||
...EventManifest.Latest.values()
|
||||
.map((definition) =>
|
||||
Schema.Struct({ id: EventV2.ID, type: Schema.Literal(definition.type), properties: definition.data }),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,53 +1 @@
|
|||
import { SessionID } from "@/session/schema"
|
||||
import { PositiveInt } from "@opencode-ai/core/schema"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { Effect, Schema } from "effect"
|
||||
|
||||
const DEFAULT_TOAST_DURATION = 5000
|
||||
|
||||
export const TuiEvent = {
|
||||
PromptAppend: EventV2.define({ type: "tui.prompt.append", schema: { text: Schema.String } }),
|
||||
CommandExecute: EventV2.define({
|
||||
type: "tui.command.execute",
|
||||
schema: {
|
||||
command: Schema.Union([
|
||||
Schema.Literals([
|
||||
"session.list",
|
||||
"session.new",
|
||||
"session.share",
|
||||
"session.interrupt",
|
||||
"session.compact",
|
||||
"session.page.up",
|
||||
"session.page.down",
|
||||
"session.line.up",
|
||||
"session.line.down",
|
||||
"session.half.page.up",
|
||||
"session.half.page.down",
|
||||
"session.first",
|
||||
"session.last",
|
||||
"prompt.clear",
|
||||
"prompt.submit",
|
||||
"agent.cycle",
|
||||
]),
|
||||
Schema.String,
|
||||
]),
|
||||
},
|
||||
}),
|
||||
ToastShow: EventV2.define({
|
||||
type: "tui.toast.show",
|
||||
schema: {
|
||||
title: Schema.optional(Schema.String),
|
||||
message: Schema.String,
|
||||
variant: Schema.Literals(["info", "success", "warning", "error"]),
|
||||
duration: PositiveInt.pipe(Schema.withDecodingDefault(Effect.succeed(DEFAULT_TOAST_DURATION))).annotate({
|
||||
description: "Duration in milliseconds",
|
||||
}),
|
||||
},
|
||||
}),
|
||||
SessionSelect: EventV2.define({
|
||||
type: "tui.session.select",
|
||||
schema: {
|
||||
sessionID: SessionID.annotate({ description: "Session ID to navigate to" }),
|
||||
},
|
||||
}),
|
||||
}
|
||||
export { TuiEvent } from "@opencode-ai/schema/tui-event"
|
||||
|
|
|
|||
|
|
@ -23,17 +23,10 @@ import { SessionEvent } from "@opencode-ai/core/session/event"
|
|||
import { SessionMessage } from "@opencode-ai/core/session/message"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { buildPrompt } from "@opencode-ai/core/session/compaction"
|
||||
import { SessionCompactionEvent } from "@opencode-ai/schema/session-compaction-event"
|
||||
|
||||
export const Event = {
|
||||
Compacted: EventV2.define({
|
||||
type: "session.compacted",
|
||||
schema: {
|
||||
sessionID: SessionID,
|
||||
},
|
||||
}),
|
||||
}
|
||||
export const Event = SessionCompactionEvent
|
||||
|
||||
export const PRUNE_MINIMUM = 20_000
|
||||
export const PRUNE_PROTECT = 40_000
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { SessionID, MessageID, PartID } from "./schema"
|
||||
import { SessionID, MessageID } from "./schema"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import {
|
||||
|
|
@ -12,11 +11,9 @@ import {
|
|||
Info,
|
||||
OutputLengthError,
|
||||
Part,
|
||||
StructuredOutputError,
|
||||
SubtaskPart,
|
||||
User,
|
||||
WithParts,
|
||||
type ToolPart,
|
||||
} from "@opencode-ai/core/v1/session"
|
||||
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
|
|
@ -61,16 +58,7 @@ export const Event = {
|
|||
Updated: SessionV1.Event.MessageUpdated,
|
||||
Removed: SessionV1.Event.MessageRemoved,
|
||||
PartUpdated: SessionV1.Event.PartUpdated,
|
||||
PartDelta: EventV2.define({
|
||||
type: "message.part.delta",
|
||||
schema: {
|
||||
sessionID: SessionID,
|
||||
messageID: MessageID,
|
||||
partID: PartID,
|
||||
field: Schema.String,
|
||||
delta: Schema.String,
|
||||
},
|
||||
}),
|
||||
PartDelta: SessionV1.Event.PartDelta,
|
||||
PartRemoved: SessionV1.Event.PartRemoved,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
|||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { makeRuntime } from "@opencode-ai/core/effect/runtime"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { SessionV2 } from "@opencode-ai/core/session"
|
||||
import { SessionExecution } from "@opencode-ai/core/session/execution"
|
||||
|
||||
|
|
@ -38,7 +37,6 @@ import { WorkspaceV2 } from "@opencode-ai/core/workspace"
|
|||
import { SessionID, MessageID, PartID } from "./schema"
|
||||
|
||||
import type { Provider } from "@/provider/provider"
|
||||
import { Permission } from "@/permission"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Effect, Layer, Option, Context, Schema, Types } from "effect"
|
||||
import { NonNegativeInt, optionalOmitUndefined } from "@opencode-ai/core/schema"
|
||||
|
|
@ -309,69 +307,12 @@ export type GlobalListInput = {
|
|||
archived?: boolean
|
||||
}
|
||||
|
||||
const CreatedEventSchema = Schema.Struct({
|
||||
sessionID: SessionID,
|
||||
info: Info,
|
||||
})
|
||||
|
||||
const UpdatedShare = Schema.Struct({
|
||||
url: Schema.optional(Schema.NullOr(Schema.String)),
|
||||
})
|
||||
|
||||
const UpdatedTime = Schema.Struct({
|
||||
created: Schema.optional(Schema.NullOr(NonNegativeInt)),
|
||||
updated: Schema.optional(Schema.NullOr(NonNegativeInt)),
|
||||
compacting: Schema.optional(Schema.NullOr(NonNegativeInt)),
|
||||
archived: Schema.optional(Schema.NullOr(ArchivedTimestamp)),
|
||||
})
|
||||
|
||||
const UpdatedInfo = Schema.Struct({
|
||||
id: Schema.optional(Schema.NullOr(SessionID)),
|
||||
slug: Schema.optional(Schema.NullOr(Schema.String)),
|
||||
projectID: Schema.optional(Schema.NullOr(ProjectV2.ID)),
|
||||
workspaceID: Schema.optional(Schema.NullOr(WorkspaceV2.ID)),
|
||||
directory: Schema.optional(Schema.NullOr(Schema.String)),
|
||||
path: Schema.optional(Schema.NullOr(Schema.String)),
|
||||
parentID: Schema.optional(Schema.NullOr(SessionID)),
|
||||
summary: Schema.optional(Schema.NullOr(Summary)),
|
||||
cost: Schema.optional(Schema.Finite),
|
||||
tokens: Schema.optional(Tokens),
|
||||
share: Schema.optional(UpdatedShare),
|
||||
title: Schema.optional(Schema.NullOr(Schema.String)),
|
||||
agent: Schema.optional(Schema.NullOr(Schema.String)),
|
||||
model: Schema.optional(Schema.NullOr(Model)),
|
||||
version: Schema.optional(Schema.NullOr(Schema.String)),
|
||||
metadata: Schema.optional(Schema.NullOr(Metadata)),
|
||||
time: Schema.optional(UpdatedTime),
|
||||
permission: Schema.optional(Schema.NullOr(PermissionV1.Ruleset)),
|
||||
revert: Schema.optional(Schema.NullOr(Revert)),
|
||||
})
|
||||
|
||||
const UpdatedEventSchema = Schema.Struct({
|
||||
sessionID: SessionID,
|
||||
info: UpdatedInfo,
|
||||
})
|
||||
|
||||
export const Event = {
|
||||
Created: SessionV1.Event.Created,
|
||||
Updated: SessionV1.Event.Updated,
|
||||
Deleted: SessionV1.Event.Deleted,
|
||||
Diff: EventV2.define({
|
||||
type: "session.diff",
|
||||
schema: {
|
||||
sessionID: SessionID,
|
||||
diff: Schema.Array(Snapshot.FileDiff),
|
||||
},
|
||||
}),
|
||||
Error: EventV2.define({
|
||||
type: "session.error",
|
||||
schema: {
|
||||
sessionID: Schema.optional(SessionID),
|
||||
// Reuses SessionV1.Assistant.fields.error (already Schema.optional) so
|
||||
// the derived schema keeps the same discriminated-union shape on the event stream.
|
||||
error: SessionV1.Assistant.fields.error,
|
||||
},
|
||||
}),
|
||||
Diff: SessionV1.Event.Diff,
|
||||
Error: SessionV1.Event.Error,
|
||||
}
|
||||
|
||||
export function plan(input: { slug: string; time: { created: number } }, instance: InstanceContext) {
|
||||
|
|
|
|||
|
|
@ -1,53 +1,14 @@
|
|||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { SessionID } from "./schema"
|
||||
import { NonNegativeInt } from "@opencode-ai/core/schema"
|
||||
import { Effect, Layer, Context, Schema } from "effect"
|
||||
import { Effect, Layer, Context } from "effect"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { SessionStatusEvent } from "@opencode-ai/schema/session-status-event"
|
||||
|
||||
export const Info = Schema.Union([
|
||||
Schema.Struct({
|
||||
type: Schema.Literal("idle"),
|
||||
}),
|
||||
Schema.Struct({
|
||||
type: Schema.Literal("retry"),
|
||||
attempt: NonNegativeInt,
|
||||
message: Schema.String,
|
||||
action: Schema.optional(
|
||||
Schema.Struct({
|
||||
reason: Schema.String,
|
||||
provider: Schema.String,
|
||||
title: Schema.String,
|
||||
message: Schema.String,
|
||||
label: Schema.String,
|
||||
link: Schema.optional(Schema.String),
|
||||
}),
|
||||
),
|
||||
next: NonNegativeInt,
|
||||
}),
|
||||
Schema.Struct({
|
||||
type: Schema.Literal("busy"),
|
||||
}),
|
||||
]).annotate({ identifier: "SessionStatus" })
|
||||
export type Info = Schema.Schema.Type<typeof Info>
|
||||
export const Info = SessionStatusEvent.Info
|
||||
export type Info = SessionStatusEvent.Info
|
||||
|
||||
export const Event = {
|
||||
Status: EventV2.define({
|
||||
type: "session.status",
|
||||
schema: {
|
||||
sessionID: SessionID,
|
||||
status: Info,
|
||||
},
|
||||
}),
|
||||
// deprecated
|
||||
Idle: EventV2.define({
|
||||
type: "session.idle",
|
||||
schema: {
|
||||
sessionID: SessionID,
|
||||
},
|
||||
}),
|
||||
}
|
||||
export const Event = SessionStatusEvent
|
||||
|
||||
export interface Interface {
|
||||
readonly get: (sessionID: SessionID) => Effect.Effect<Info>
|
||||
|
|
|
|||
|
|
@ -1,31 +1,17 @@
|
|||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { SessionID } from "./schema"
|
||||
import { Effect, Layer, Context, Schema } from "effect"
|
||||
import { Effect, Layer, Context } from "effect"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { eq } from "drizzle-orm"
|
||||
import { asc } from "drizzle-orm"
|
||||
import { TodoTable } from "@opencode-ai/core/session/sql"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { SessionTodo } from "@opencode-ai/schema/session-todo"
|
||||
|
||||
export const Info = Schema.Struct({
|
||||
content: Schema.String.annotate({ description: "Brief description of the task" }),
|
||||
status: Schema.String.annotate({
|
||||
description: "Current status of the task: pending, in_progress, completed, cancelled",
|
||||
}),
|
||||
priority: Schema.String.annotate({ description: "Priority level of the task: high, medium, low" }),
|
||||
}).annotate({ identifier: "Todo" })
|
||||
export type Info = Schema.Schema.Type<typeof Info>
|
||||
export const Info = SessionTodo.Info
|
||||
export type Info = SessionTodo.Info
|
||||
|
||||
export const Event = {
|
||||
Updated: EventV2.define({
|
||||
type: "todo.updated",
|
||||
schema: {
|
||||
sessionID: SessionID,
|
||||
todos: Schema.Array(Info),
|
||||
},
|
||||
}),
|
||||
}
|
||||
export const Event = SessionTodo.Event
|
||||
|
||||
export interface Interface {
|
||||
readonly update: (input: { sessionID: SessionID; todos: Info[] }) => Effect.Effect<void>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { FSUtil } from "@opencode-ai/core/fs-util"
|
|||
import { Hash } from "@opencode-ai/core/util/hash"
|
||||
import { Config } from "@/config/config"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Info } from "@opencode-ai/schema/file-diff"
|
||||
|
||||
export const Patch = Schema.Struct({
|
||||
hash: Schema.String,
|
||||
|
|
@ -16,16 +17,7 @@ export const Patch = Schema.Struct({
|
|||
})
|
||||
export type Patch = typeof Patch.Type
|
||||
|
||||
export const FileDiff = Schema.Struct({
|
||||
// Optional because legacy/imported `summary_diffs` on disk may omit
|
||||
// file details and patch text. Required Schema rejected the whole
|
||||
// session response and broke session loading on Desktop.
|
||||
file: Schema.optional(Schema.String),
|
||||
patch: Schema.optional(Schema.String),
|
||||
additions: Schema.Finite,
|
||||
deletions: Schema.Finite,
|
||||
status: Schema.optional(Schema.Literals(["added", "deleted", "modified"])),
|
||||
}).annotate({ identifier: "SnapshotFileDiff" })
|
||||
export const FileDiff = Info
|
||||
export type FileDiff = typeof FileDiff.Type
|
||||
|
||||
const prune = "7.days"
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import { ProjectTable } from "@opencode-ai/core/project/sql"
|
|||
import type { ProjectV2 } from "@opencode-ai/core/project"
|
||||
import { Slug } from "@opencode-ai/core/util/slug"
|
||||
import { errorMessage } from "../util/error"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { GlobalBus } from "@/bus/global"
|
||||
import { Git } from "@/git"
|
||||
import { Effect, Layer, Path, Schema, Scope, Context } from "effect"
|
||||
|
|
@ -19,22 +18,9 @@ import { NodePath } from "@effect/platform-node"
|
|||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { AppProcess } from "@opencode-ai/core/process"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { WorktreeEvent } from "@opencode-ai/schema/worktree-event"
|
||||
|
||||
export const Event = {
|
||||
Ready: EventV2.define({
|
||||
type: "worktree.ready",
|
||||
schema: {
|
||||
name: Schema.String,
|
||||
branch: Schema.optional(Schema.String),
|
||||
},
|
||||
}),
|
||||
Failed: EventV2.define({
|
||||
type: "worktree.failed",
|
||||
schema: {
|
||||
message: Schema.String,
|
||||
},
|
||||
}),
|
||||
}
|
||||
export const Event = WorktreeEvent
|
||||
|
||||
export const Info = Schema.Struct({
|
||||
name: Schema.String,
|
||||
|
|
|
|||
24
packages/opencode/test/event-manifest.test.ts
Normal file
24
packages/opencode/test/event-manifest.test.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import { SessionEvent } from "@opencode-ai/core/session/event"
|
||||
import { EventManifest as SchemaEventManifest } from "@opencode-ai/schema/event-manifest"
|
||||
import { Todo } from "@/session/todo"
|
||||
import { EventManifest } from "@/event-manifest"
|
||||
|
||||
describe("public event manifest", () => {
|
||||
test("contains every latest public wire type once", () => {
|
||||
expect(EventManifest.Definitions).toBe(SchemaEventManifest.Definitions)
|
||||
expect(EventManifest.Latest).toBe(SchemaEventManifest.Latest)
|
||||
expect(EventManifest.Durable).toBe(SchemaEventManifest.Durable)
|
||||
expect(EventManifest.Latest.size).toBe(85)
|
||||
expect(EventManifest.Latest.get("session.next.step.ended")).toBe(SessionEvent.Step.Ended)
|
||||
expect(EventManifest.Latest.get("todo.updated")).toBe(Todo.Event.Updated)
|
||||
expect(EventManifest.Latest.has("ide.installed")).toBe(false)
|
||||
expect(EventManifest.Latest.has("server.connected")).toBe(true)
|
||||
expect(EventManifest.Latest.has("global.disposed")).toBe(true)
|
||||
})
|
||||
|
||||
test("contains only the current step settlement versions", () => {
|
||||
expect(EventManifest.Durable.has("session.next.step.ended.1")).toBe(false)
|
||||
expect(EventManifest.Durable.get("session.next.step.ended.2")).toBe(SessionEvent.Step.Ended)
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue