chore: generate
This commit is contained in:
@@ -277,8 +277,7 @@ function ProviderConnection(props: {
|
||||
})
|
||||
const provider = createMemo(() => ({
|
||||
id: props.provider,
|
||||
name:
|
||||
providers.all().get(props.provider)?.name ?? controller.integration()?.name ?? props.provider,
|
||||
name: providers.all().get(props.provider)?.name ?? controller.integration()?.name ?? props.provider,
|
||||
}))
|
||||
const methodLabel = (value?: { type?: string; label?: string }) => {
|
||||
if (!value) return ""
|
||||
|
||||
@@ -50,7 +50,8 @@ export const DialogSelectMcp: Component = () => {
|
||||
>
|
||||
{(i) => {
|
||||
const mcpStatus = () =>
|
||||
data.location.mcp.server.list({ directory: sdk().directory })?.find((server) => server.name === i.name)?.status
|
||||
data.location.mcp.server.list({ directory: sdk().directory })?.find((server) => server.name === i.name)
|
||||
?.status
|
||||
const status = () => mcpStatus()?.status
|
||||
const statusLabel = () => {
|
||||
const key = status() ? statusLabels[status() as keyof typeof statusLabels] : undefined
|
||||
|
||||
@@ -90,8 +90,8 @@ export const ProjectSettingsExtensions: Component = () => {
|
||||
.sort()
|
||||
})
|
||||
const mcpEnabled = (name: string) =>
|
||||
data.location.mcp.server.list({ directory: directorySDK().directory })?.find((server) => server.name === name)?.status
|
||||
.status === "connected"
|
||||
data.location.mcp.server.list({ directory: directorySDK().directory })?.find((server) => server.name === name)
|
||||
?.status.status === "connected"
|
||||
|
||||
const [globalPluginList] = createResource(
|
||||
() => serverSDK.connection.status() === "connected",
|
||||
@@ -196,7 +196,6 @@ export const ProjectSettingsExtensions: Component = () => {
|
||||
<SharedSection count={serverSkills().length}>{skillRows(serverSkills())}</SharedSection>
|
||||
</div>
|
||||
</TabsV2.Content>
|
||||
|
||||
</TabsV2>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -63,7 +63,10 @@ export async function sendFollowupDraft(input: FollowupSendInput) {
|
||||
|
||||
const [head, ...tail] = text.split(" ")
|
||||
const cmd = head?.startsWith("/") ? head.slice(1) : undefined
|
||||
if (cmd && input.data.location.command.list({ directory: input.draft.sessionDirectory })?.some((item) => item.name === cmd)) {
|
||||
if (
|
||||
cmd &&
|
||||
input.data.location.command.list({ directory: input.draft.sessionDirectory })?.some((item) => item.name === cmd)
|
||||
) {
|
||||
setBusy()
|
||||
try {
|
||||
const messageID = Identifier.ascending("message")
|
||||
@@ -197,8 +200,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
||||
if (!sessionID) return Promise.resolve()
|
||||
input.onAbort?.()
|
||||
|
||||
return serverSDK.api.session.interrupt({ sessionID })
|
||||
.catch(() => {})
|
||||
return serverSDK.api.session.interrupt({ sessionID }).catch(() => {})
|
||||
}
|
||||
|
||||
const restoreCommentItems = (
|
||||
|
||||
@@ -181,8 +181,7 @@ export function SessionContextTab() {
|
||||
{ label: "context.stats.reasoningTokens", value: () => formatter().number(ctx()?.tokens.reasoning) },
|
||||
{
|
||||
label: "context.stats.cacheTokens",
|
||||
value: () =>
|
||||
`${formatter().number(ctx()?.tokens.cache.read)} / ${formatter().number(ctx()?.tokens.cache.write)}`,
|
||||
value: () => `${formatter().number(ctx()?.tokens.cache.read)} / ${formatter().number(ctx()?.tokens.cache.write)}`,
|
||||
},
|
||||
{ label: "context.stats.userMessages", value: () => counts().user.toLocaleString(language.intl()) },
|
||||
{ label: "context.stats.assistantMessages", value: () => counts().assistant.toLocaleString(language.intl()) },
|
||||
@@ -305,9 +304,7 @@ export function SessionContextTab() {
|
||||
</div>
|
||||
<Accordion multiple>
|
||||
<For each={messages()}>
|
||||
{(message) => (
|
||||
<RawMessage message={message} onRendered={restoreScroll} time={formatter().time} />
|
||||
)}
|
||||
{(message) => <RawMessage message={message} onRendered={restoreScroll} time={formatter().time} />}
|
||||
</For>
|
||||
</Accordion>
|
||||
</div>
|
||||
|
||||
@@ -27,15 +27,16 @@ export function StatusPopoverBody(props: { shown: boolean }) {
|
||||
|
||||
const toggleMcp = useMcpToggle(() => sdk().directory)
|
||||
const mcp = () => data.location.mcp.server.list({ directory: sdk().directory }) ?? []
|
||||
const mcpNames = createMemo(() => mcp().map((server) => server.name).sort((a, b) => a.localeCompare(b)))
|
||||
const mcpNames = createMemo(() =>
|
||||
mcp()
|
||||
.map((server) => server.name)
|
||||
.sort((a, b) => a.localeCompare(b)),
|
||||
)
|
||||
const mcpStatus = (name: string) => mcp().find((server) => server.name === name)?.status.status
|
||||
const mcpConnected = createMemo(() => mcpNames().filter((name) => mcpStatus(name) === "connected").length)
|
||||
const [pluginList] = createResource(
|
||||
() => (props.shown ? sdk().directory : undefined),
|
||||
(directory) =>
|
||||
serverSDK.api.plugin
|
||||
.list({ location: { directory } })
|
||||
.then((result) => result.data),
|
||||
(directory) => serverSDK.api.plugin.list({ location: { directory } }).then((result) => result.data),
|
||||
)
|
||||
const plugins = createMemo(() => (pluginList.latest ?? []).map((item) => item.id))
|
||||
const pluginCount = createMemo(() => plugins().length)
|
||||
|
||||
@@ -78,16 +78,14 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
|
||||
scope,
|
||||
normalizeDir: path.normalizeDir,
|
||||
list: (dir) =>
|
||||
serverSDK.api.file
|
||||
.list({ path: dir, location: { directory: scope() } })
|
||||
.then((x) =>
|
||||
x.data.map((entry) => ({
|
||||
...entry,
|
||||
name: entry.path.split("/").at(-1) ?? entry.path,
|
||||
absolute: `${scope()}/${entry.path}`,
|
||||
ignored: false,
|
||||
})),
|
||||
),
|
||||
serverSDK.api.file.list({ path: dir, location: { directory: scope() } }).then((x) =>
|
||||
x.data.map((entry) => ({
|
||||
...entry,
|
||||
name: entry.path.split("/").at(-1) ?? entry.path,
|
||||
absolute: `${scope()}/${entry.path}`,
|
||||
ignored: false,
|
||||
})),
|
||||
),
|
||||
onError: (message) => {
|
||||
showToast({
|
||||
variant: "error",
|
||||
|
||||
@@ -86,5 +86,4 @@ describe("query keys", () => {
|
||||
{ id: "b", sandboxes: [] },
|
||||
])
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
@@ -233,7 +233,10 @@ export function createChildStoreManager(input: {
|
||||
},
|
||||
get mcp() {
|
||||
return Object.fromEntries(
|
||||
(input.data.location.mcp.server.list({ directory }) ?? []).map((server) => [server.name, server.status]),
|
||||
(input.data.location.mcp.server.list({ directory }) ?? []).map((server) => [
|
||||
server.name,
|
||||
server.status,
|
||||
]),
|
||||
)
|
||||
},
|
||||
get mcp_resource() {
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import type {
|
||||
AgentListOutput,
|
||||
ModelListOutput,
|
||||
ProviderListOutput,
|
||||
} from "@opencode-ai/client/promise"
|
||||
import type { AgentListOutput, ModelListOutput, ProviderListOutput } from "@opencode-ai/client/promise"
|
||||
import { directoryKey, normalizeAgentList, normalizeProviderList } from "./utils"
|
||||
|
||||
describe("normalizeAgentList", () => {
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import type {
|
||||
AgentListOutput,
|
||||
ModelListOutput,
|
||||
ProviderListOutput,
|
||||
} from "@opencode-ai/client/promise"
|
||||
import type { AgentListOutput, ModelListOutput, ProviderListOutput } from "@opencode-ai/client/promise"
|
||||
import type { Agent, Project, Provider, ProviderListResponse } from "@/types"
|
||||
import type { Project as CurrentProject } from "@opencode-ai/client/promise"
|
||||
import { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context"
|
||||
|
||||
@@ -13,10 +13,7 @@ export type WorkspaceLocation = LocationContext & {
|
||||
|
||||
const context = createSimpleContext({
|
||||
name: "Location",
|
||||
init: (props: {
|
||||
directory: string | Accessor<string>
|
||||
workspaceID?: string | Accessor<string | undefined>
|
||||
}) => {
|
||||
init: (props: { directory: string | Accessor<string>; workspaceID?: string | Accessor<string | undefined> }) => {
|
||||
const serverSDK = useServerSDK()
|
||||
const data = useData()
|
||||
const ref = createMemo(() => ({
|
||||
@@ -44,9 +41,7 @@ const context = createSimpleContext({
|
||||
})
|
||||
})
|
||||
|
||||
const location = createMemo(() =>
|
||||
serverSDK.ensureDirSdkContext(current()?.directory ?? ref().directory),
|
||||
)
|
||||
const location = createMemo(() => serverSDK.ensureDirSdkContext(current()?.directory ?? ref().directory))
|
||||
return createMemo<WorkspaceLocation>(() => ({
|
||||
...location(),
|
||||
ref: ref(),
|
||||
|
||||
@@ -26,7 +26,8 @@ export function useMcpToggle(directory?: Accessor<string | undefined>, onSuccess
|
||||
} else if (server.status.status === "needs_auth" && server.integrationID) {
|
||||
const integration = await serverSDK.api.integration.get({ integrationID: server.integrationID, location: ref })
|
||||
const method = integration.data?.methods.find((item) => item.type === "oauth" && !item.form?.length)
|
||||
if (!method || method.type !== "oauth") throw new Error(`MCP server ${name} requires an interactive authentication form`)
|
||||
if (!method || method.type !== "oauth")
|
||||
throw new Error(`MCP server ${name} requires an interactive authentication form`)
|
||||
const attempt = await serverSDK.api.integration.oauth.connect({
|
||||
integrationID: server.integrationID,
|
||||
methodID: method.id,
|
||||
|
||||
@@ -5,12 +5,7 @@ import { getOwner, onCleanup, untrack } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { type ServerSDK } from "./server-sdk"
|
||||
import {
|
||||
bootstrapDirectory,
|
||||
bootstrapGlobal,
|
||||
loadGlobalConfigQuery,
|
||||
loadPathQuery,
|
||||
} from "./global-sync/bootstrap"
|
||||
import { bootstrapDirectory, bootstrapGlobal, loadGlobalConfigQuery, loadPathQuery } from "./global-sync/bootstrap"
|
||||
import { createChildStoreManager } from "./global-sync/child-store"
|
||||
import { applyDirectoryEvent, applyGlobalEvent } from "./global-sync/event-reducer"
|
||||
import type { ProjectMeta } from "./global-sync/types"
|
||||
@@ -247,11 +242,7 @@ export function createServerSyncContextInner(serverSDK: ServerSDK, data: Data) {
|
||||
const existing = children.children[key]
|
||||
if (!existing) return
|
||||
children.mark(key)
|
||||
if (
|
||||
eventType === "config.updated" ||
|
||||
eventType === "agent.updated"
|
||||
)
|
||||
queue.push(key)
|
||||
if (eventType === "config.updated" || eventType === "agent.updated") queue.push(key)
|
||||
const [store, setStore] = existing
|
||||
if (eventType === "worktree.updated") void bootstrap.refetch()
|
||||
if (eventType !== "vcs.branch.updated")
|
||||
|
||||
@@ -57,17 +57,16 @@ export function createHomeSessionsController(home: HomeController) {
|
||||
? homeSessionIndexKey(ServerConnection.key(conn))
|
||||
: (["home", "session-index", "unselected"] as const),
|
||||
enabled: !!ctx && ctx.sdk.connection.status() === "connected",
|
||||
queryFn:
|
||||
ctx
|
||||
? async ({ signal }) => {
|
||||
const index = await loadHomeSessionIndex(
|
||||
(input, options) => ctx.sdk.api.session.list(input, options),
|
||||
signal,
|
||||
)
|
||||
index.forEach(ctx.data.session.remember)
|
||||
return Date.now()
|
||||
}
|
||||
: skipToken,
|
||||
queryFn: ctx
|
||||
? async ({ signal }) => {
|
||||
const index = await loadHomeSessionIndex(
|
||||
(input, options) => ctx.sdk.api.session.list(input, options),
|
||||
signal,
|
||||
)
|
||||
index.forEach(ctx.data.session.remember)
|
||||
return Date.now()
|
||||
}
|
||||
: skipToken,
|
||||
retry: false,
|
||||
staleTime: 30_000,
|
||||
refetchOnMount: true,
|
||||
|
||||
@@ -15,9 +15,14 @@ export function useSessionTabAvatarState(
|
||||
const ctx = serverCtx()
|
||||
if (!ctx) return false
|
||||
const permission = ctx.permission
|
||||
return !!sessionPermissionRequest(ctx.data.session.list(), ctx.data.session.permission.list, sessionId(), (item) => {
|
||||
return !permission.autoResponds(item, directory())
|
||||
})
|
||||
return !!sessionPermissionRequest(
|
||||
ctx.data.session.list(),
|
||||
ctx.data.session.permission.list,
|
||||
sessionId(),
|
||||
(item) => {
|
||||
return !permission.autoResponds(item, directory())
|
||||
},
|
||||
)
|
||||
})
|
||||
const hasQuestions = createMemo(() => {
|
||||
const data = serverCtx()?.data
|
||||
|
||||
@@ -88,7 +88,9 @@ export function createNewSessionWorkspaceController(input: {
|
||||
)
|
||||
const projectRoot = createMemo(() => currentProject()?.worktree ?? sdk().directory)
|
||||
createEffect(() => {
|
||||
void Promise.all([data.location.syncInfo({ directory: sdk().directory }), data.project.sync()]).catch(() => undefined)
|
||||
void Promise.all([data.location.syncInfo({ directory: sdk().directory }), data.project.sync()]).catch(
|
||||
() => undefined,
|
||||
)
|
||||
const project = currentProject()
|
||||
const directories = project ? [project.worktree, ...workspaceDirectories(project)] : [sdk().directory]
|
||||
directories.forEach((directory) => void data.location.vcs.sync({ directory }).catch(() => undefined))
|
||||
|
||||
@@ -40,7 +40,9 @@ export function createPromptInputController(input: {
|
||||
model: {
|
||||
selection: input.model ?? local.model,
|
||||
paid: providers.paid().length > 0,
|
||||
loading: (local.agent.visible() && data.location.agent.list({ directory: sdk().directory }) === undefined) || !providers.ready(),
|
||||
loading:
|
||||
(local.agent.visible() && data.location.agent.list({ directory: sdk().directory }) === undefined) ||
|
||||
!providers.ready(),
|
||||
},
|
||||
session: {
|
||||
id: input.sessionID(),
|
||||
|
||||
@@ -72,9 +72,7 @@ export function SessionComposerRegion(props: {
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
<div
|
||||
class="w-full min-h-32 md:min-h-40 rounded-md border border-border-weak-base bg-background-base/50 px-4 py-3 text-text-weak whitespace-pre-wrap pointer-events-none"
|
||||
>
|
||||
<div class="w-full min-h-32 md:min-h-40 rounded-md border border-border-weak-base bg-background-base/50 px-4 py-3 text-text-weak whitespace-pre-wrap pointer-events-none">
|
||||
{controller.handoffPrompt() || language.t("prompt.loading")}
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -53,9 +53,9 @@ export function createSessionComposerController() {
|
||||
if (!primary()) return []
|
||||
const id = params.id
|
||||
if (!id) return []
|
||||
const assistant = data.session.message.list(id).findLast(
|
||||
(message) => message.type === "assistant" && message.time.completed === undefined,
|
||||
)
|
||||
const assistant = data.session.message
|
||||
.list(id)
|
||||
.findLast((message) => message.type === "assistant" && message.time.completed === undefined)
|
||||
if (assistant?.type !== "assistant") return []
|
||||
return assistant.content.flatMap((part) => {
|
||||
if (part.type !== "tool" || part.state.status !== "running") return []
|
||||
@@ -149,14 +149,12 @@ export function createSessionComposerController() {
|
||||
if (!primary()) return
|
||||
const sessionID = params.id
|
||||
if (!sessionID) return
|
||||
await serverSDK.api.session
|
||||
.background({ sessionID })
|
||||
.catch((error) => {
|
||||
showToast({
|
||||
title: language.t("common.requestFailed"),
|
||||
description: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
await serverSDK.api.session.background({ sessionID }).catch((error) => {
|
||||
showToast({
|
||||
title: language.t("common.requestFailed"),
|
||||
description: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const [store, setStore] = createStore({
|
||||
|
||||
@@ -44,7 +44,6 @@ export function createTimelineModel(input: { session: Pick<SessionController, "i
|
||||
userMessages: input.session.history.userMessages,
|
||||
visibleUserMessages: input.session.history.visibleUserMessages,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function isTimelineReady(messages: Message[] | undefined, loading: boolean) {
|
||||
|
||||
@@ -220,129 +220,133 @@ const setup = Effect.gen(function* () {
|
||||
return { db, bus, instructions: yield* instructionBuiltIns.load(sessionID) }
|
||||
})
|
||||
|
||||
it.effect("generates from fresh settled Session context without durable mutation", () =>
|
||||
Effect.gen(function* () {
|
||||
requests.length = 0
|
||||
hasHttpMiddleware = false
|
||||
instruction = "Initial context"
|
||||
const { db, bus, instructions } = yield* setup
|
||||
yield* InstructionState.prepare(db, bus, instructions, sessionID)
|
||||
const existing = SessionMessage.ID.create()
|
||||
yield* bus.publish(SessionEvent.InboxEnqueued, {
|
||||
sessionID,
|
||||
inboxID: existing,
|
||||
item: { type: "user", payload: { text: "Existing durable context" }, delivery: "steer" },
|
||||
})
|
||||
yield* bus.publish(SessionEvent.InboxDelivered, {
|
||||
sessionID,
|
||||
inboxID: existing,
|
||||
})
|
||||
const settledAssistant = SessionMessage.ID.create()
|
||||
yield* bus.publish(SessionEvent.Step.Started, {
|
||||
sessionID,
|
||||
assistantMessageID: settledAssistant,
|
||||
agent: Agent.ID.make("build"),
|
||||
model: { id: ID.make("generate-model"), providerID: Provider.ID.make("test") },
|
||||
})
|
||||
yield* bus.publish(SessionEvent.Text.Started, {
|
||||
sessionID,
|
||||
assistantMessageID: settledAssistant,
|
||||
ordinal: 0,
|
||||
})
|
||||
yield* bus.publish(SessionEvent.Text.Ended, {
|
||||
sessionID,
|
||||
assistantMessageID: settledAssistant,
|
||||
ordinal: 0,
|
||||
text: "Settled partial answer",
|
||||
})
|
||||
const activeAssistant = SessionMessage.ID.create()
|
||||
yield* bus.publish(SessionEvent.Step.Started, {
|
||||
sessionID,
|
||||
assistantMessageID: activeAssistant,
|
||||
agent: Agent.ID.make("build"),
|
||||
model: { id: ID.make("generate-model"), providerID: Provider.ID.make("test") },
|
||||
})
|
||||
yield* bus.publish(SessionEvent.Tool.Input.Started, {
|
||||
sessionID,
|
||||
assistantMessageID: activeAssistant,
|
||||
id: "active-call",
|
||||
name: "echo",
|
||||
})
|
||||
yield* bus.publish(SessionEvent.Tool.Input.Ended, {
|
||||
sessionID,
|
||||
assistantMessageID: activeAssistant,
|
||||
id: "active-call",
|
||||
text: "{}",
|
||||
})
|
||||
yield* bus.publish(SessionEvent.Tool.Called, {
|
||||
sessionID,
|
||||
assistantMessageID: activeAssistant,
|
||||
id: "active-call",
|
||||
input: {},
|
||||
executed: false,
|
||||
})
|
||||
yield* bus.publish(SessionEvent.InboxEnqueued, {
|
||||
sessionID,
|
||||
inboxID: SessionMessage.ID.create(),
|
||||
item: { type: "user", payload: { text: "Queued input must remain invisible" }, delivery: "queue" },
|
||||
})
|
||||
instruction = "Changed context"
|
||||
const before = yield* durableState(db, sessionID)
|
||||
const hooks = yield* PluginHooks.Service
|
||||
yield* hooks.register("session", "context", (event) =>
|
||||
Effect.sync(() => {
|
||||
event.system = [SystemPart.make("Hooked system"), ...event.system]
|
||||
if (event.tools.lookup) event.tools.lookup.description = "Hooked lookup"
|
||||
}),
|
||||
)
|
||||
it.effect(
|
||||
"generates from fresh settled Session context without durable mutation",
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
requests.length = 0
|
||||
hasHttpMiddleware = false
|
||||
instruction = "Initial context"
|
||||
const { db, bus, instructions } = yield* setup
|
||||
yield* InstructionState.prepare(db, bus, instructions, sessionID)
|
||||
const existing = SessionMessage.ID.create()
|
||||
yield* bus.publish(SessionEvent.InboxEnqueued, {
|
||||
sessionID,
|
||||
inboxID: existing,
|
||||
item: { type: "user", payload: { text: "Existing durable context" }, delivery: "steer" },
|
||||
})
|
||||
yield* bus.publish(SessionEvent.InboxDelivered, {
|
||||
sessionID,
|
||||
inboxID: existing,
|
||||
})
|
||||
const settledAssistant = SessionMessage.ID.create()
|
||||
yield* bus.publish(SessionEvent.Step.Started, {
|
||||
sessionID,
|
||||
assistantMessageID: settledAssistant,
|
||||
agent: Agent.ID.make("build"),
|
||||
model: { id: ID.make("generate-model"), providerID: Provider.ID.make("test") },
|
||||
})
|
||||
yield* bus.publish(SessionEvent.Text.Started, {
|
||||
sessionID,
|
||||
assistantMessageID: settledAssistant,
|
||||
ordinal: 0,
|
||||
})
|
||||
yield* bus.publish(SessionEvent.Text.Ended, {
|
||||
sessionID,
|
||||
assistantMessageID: settledAssistant,
|
||||
ordinal: 0,
|
||||
text: "Settled partial answer",
|
||||
})
|
||||
const activeAssistant = SessionMessage.ID.create()
|
||||
yield* bus.publish(SessionEvent.Step.Started, {
|
||||
sessionID,
|
||||
assistantMessageID: activeAssistant,
|
||||
agent: Agent.ID.make("build"),
|
||||
model: { id: ID.make("generate-model"), providerID: Provider.ID.make("test") },
|
||||
})
|
||||
yield* bus.publish(SessionEvent.Tool.Input.Started, {
|
||||
sessionID,
|
||||
assistantMessageID: activeAssistant,
|
||||
id: "active-call",
|
||||
name: "echo",
|
||||
})
|
||||
yield* bus.publish(SessionEvent.Tool.Input.Ended, {
|
||||
sessionID,
|
||||
assistantMessageID: activeAssistant,
|
||||
id: "active-call",
|
||||
text: "{}",
|
||||
})
|
||||
yield* bus.publish(SessionEvent.Tool.Called, {
|
||||
sessionID,
|
||||
assistantMessageID: activeAssistant,
|
||||
id: "active-call",
|
||||
input: {},
|
||||
executed: false,
|
||||
})
|
||||
yield* bus.publish(SessionEvent.InboxEnqueued, {
|
||||
sessionID,
|
||||
inboxID: SessionMessage.ID.create(),
|
||||
item: { type: "user", payload: { text: "Queued input must remain invisible" }, delivery: "queue" },
|
||||
})
|
||||
instruction = "Changed context"
|
||||
const before = yield* durableState(db, sessionID)
|
||||
const hooks = yield* PluginHooks.Service
|
||||
yield* hooks.register("session", "context", (event) =>
|
||||
Effect.sync(() => {
|
||||
event.system = [SystemPart.make("Hooked system"), ...event.system]
|
||||
if (event.tools.lookup) event.tools.lookup.description = "Hooked lookup"
|
||||
}),
|
||||
)
|
||||
|
||||
const generate = yield* SessionGenerate.Service
|
||||
const result = yield* generate.generate({ sessionID, prompt: "Summarize privately" })
|
||||
const generate = yield* SessionGenerate.Service
|
||||
const result = yield* generate.generate({ sessionID, prompt: "Summarize privately" })
|
||||
|
||||
expect(result).toBe("Transient answer")
|
||||
expect(requests).toHaveLength(1)
|
||||
expect(hasHttpMiddleware).toBe(true)
|
||||
expect(requests[0]?.model).toBe(model)
|
||||
expect(requests[0]?.system[0]?.text).toBe("Hooked system")
|
||||
expect(requests[0]?.system.map((part) => part.text)).toContain("Initial context")
|
||||
expect(requests[0]?.http?.headers).toMatchObject({ "X-Session-Id": sessionID })
|
||||
expect(requests[0]?.promptCacheKey).toBe(sessionID)
|
||||
const instructionUpdates = requests[0]?.messages.flatMap((message) =>
|
||||
message.role === "system"
|
||||
? message.content.flatMap((content) => (content.type === "text" ? [content.text] : []))
|
||||
: [],
|
||||
)
|
||||
expect(instructionUpdates).toHaveLength(1)
|
||||
expect(instructionUpdates?.[0]).toContain("Changed context")
|
||||
expect(instructionUpdates?.[0]).toContain("tools.captured.lookup(input: {}): Promise<string>")
|
||||
expect(userTexts(requests[0])).toEqual(["Existing durable context", "Summarize privately"])
|
||||
expect(
|
||||
requests[0]?.messages.flatMap((message) =>
|
||||
message.role === "assistant"
|
||||
expect(result).toBe("Transient answer")
|
||||
expect(requests).toHaveLength(1)
|
||||
expect(hasHttpMiddleware).toBe(true)
|
||||
expect(requests[0]?.model).toBe(model)
|
||||
expect(requests[0]?.system[0]?.text).toBe("Hooked system")
|
||||
expect(requests[0]?.system.map((part) => part.text)).toContain("Initial context")
|
||||
expect(requests[0]?.http?.headers).toMatchObject({ "X-Session-Id": sessionID })
|
||||
expect(requests[0]?.promptCacheKey).toBe(sessionID)
|
||||
const instructionUpdates = requests[0]?.messages.flatMap((message) =>
|
||||
message.role === "system"
|
||||
? message.content.flatMap((content) => (content.type === "text" ? [content.text] : []))
|
||||
: [],
|
||||
),
|
||||
).toEqual(["Settled partial answer"])
|
||||
expect(requests[0]?.tools).toMatchObject([{ name: "lookup", description: "Hooked lookup" }])
|
||||
expect(requests[0]?.toolChoice).toBeUndefined()
|
||||
expect(yield* durableState(db, sessionID)).toEqual(before)
|
||||
}),
|
||||
)
|
||||
expect(instructionUpdates).toHaveLength(1)
|
||||
expect(instructionUpdates?.[0]).toContain("Changed context")
|
||||
expect(instructionUpdates?.[0]).toContain("tools.captured.lookup(input: {}): Promise<string>")
|
||||
expect(userTexts(requests[0])).toEqual(["Existing durable context", "Summarize privately"])
|
||||
expect(
|
||||
requests[0]?.messages.flatMap((message) =>
|
||||
message.role === "assistant"
|
||||
? message.content.flatMap((content) => (content.type === "text" ? [content.text] : []))
|
||||
: [],
|
||||
),
|
||||
).toEqual(["Settled partial answer"])
|
||||
expect(requests[0]?.tools).toMatchObject([{ name: "lookup", description: "Hooked lookup" }])
|
||||
expect(requests[0]?.toolChoice).toBeUndefined()
|
||||
expect(yield* durableState(db, sessionID)).toEqual(before)
|
||||
}),
|
||||
{ timeout: 15_000 },
|
||||
)
|
||||
|
||||
it.effect("blocks unavailable initial instructions before generation", () =>
|
||||
Effect.gen(function* () {
|
||||
requests.length = 0
|
||||
instruction = Instructions.unavailable
|
||||
const { db } = yield* setup
|
||||
const before = yield* durableState(db, sessionID)
|
||||
const generate = yield* SessionGenerate.Service
|
||||
it.effect(
|
||||
"blocks unavailable initial instructions before generation",
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
requests.length = 0
|
||||
instruction = Instructions.unavailable
|
||||
const { db } = yield* setup
|
||||
const before = yield* durableState(db, sessionID)
|
||||
const generate = yield* SessionGenerate.Service
|
||||
|
||||
const error = yield* generate.generate({ sessionID, prompt: "Summarize privately" }).pipe(Effect.flip)
|
||||
const error = yield* generate.generate({ sessionID, prompt: "Summarize privately" }).pipe(Effect.flip)
|
||||
|
||||
expect(error).toBeInstanceOf(Instructions.InitializationBlocked)
|
||||
expect(requests).toEqual([])
|
||||
expect(yield* durableState(db, sessionID)).toEqual(before)
|
||||
}),
|
||||
expect(error).toBeInstanceOf(Instructions.InitializationBlocked)
|
||||
expect(requests).toEqual([])
|
||||
expect(yield* durableState(db, sessionID)).toEqual(before)
|
||||
}),
|
||||
{ timeout: 15_000 },
|
||||
)
|
||||
|
||||
@@ -180,28 +180,30 @@ describeHg("Vcs mercurial", () => {
|
||||
),
|
||||
)
|
||||
|
||||
it.live("diffs a named branch against the default branch", () =>
|
||||
withHg((directory) =>
|
||||
Effect.gen(function* () {
|
||||
yield* Effect.promise(async () => {
|
||||
await fs.writeFile(path.join(directory, "file.txt"), "one\n")
|
||||
await commitAll(directory, "initial")
|
||||
})
|
||||
const vcs = yield* Vcs.Service
|
||||
expect(yield* vcs.diff("branch")).toEqual([])
|
||||
it.live(
|
||||
"diffs a named branch against the default branch",
|
||||
() =>
|
||||
withHg((directory) =>
|
||||
Effect.gen(function* () {
|
||||
yield* Effect.promise(async () => {
|
||||
await fs.writeFile(path.join(directory, "file.txt"), "one\n")
|
||||
await commitAll(directory, "initial")
|
||||
})
|
||||
const vcs = yield* Vcs.Service
|
||||
expect(yield* vcs.diff("branch")).toEqual([])
|
||||
|
||||
yield* Effect.promise(async () => {
|
||||
await hg(directory, "branch", "-q", "feature")
|
||||
await fs.writeFile(path.join(directory, "file.txt"), "one\ntwo\n")
|
||||
await commitAll(directory, "feature change")
|
||||
})
|
||||
const diff = yield* vcs.diff("branch")
|
||||
expect(diff.map((item) => ({ file: item.file, status: item.status }))).toEqual([
|
||||
{ file: "file.txt", status: "modified" },
|
||||
])
|
||||
expect(diff[0].patch).toContain("+two")
|
||||
}),
|
||||
),
|
||||
yield* Effect.promise(async () => {
|
||||
await hg(directory, "branch", "-q", "feature")
|
||||
await fs.writeFile(path.join(directory, "file.txt"), "one\ntwo\n")
|
||||
await commitAll(directory, "feature change")
|
||||
})
|
||||
const diff = yield* vcs.diff("branch")
|
||||
expect(diff.map((item) => ({ file: item.file, status: item.status }))).toEqual([
|
||||
{ file: "file.txt", status: "modified" },
|
||||
])
|
||||
expect(diff[0].patch).toContain("+two")
|
||||
}),
|
||||
),
|
||||
15_000,
|
||||
)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user