mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
refactor(apiproxy): delete the goal unary domain
The goal domain has been served by GoalService's @Remote namespace since it shipped; the API Proxy copy was a second implementation of the same six mutations. Remove the goals contract, schemas, route rows, IApiClient stub, host implementation, and the fixture's compatibility face, leaving ctx.remote.goals as the only path. The fixture's goal fold keeps its coverage through the Goal Remotes: its lifecycle case moves out of the unary-dispatch test, which no longer has goal rows to cover.
This commit is contained in:
@@ -255,15 +255,6 @@ export class FakeApiClient implements IApiClient {
|
||||
list: (payload: unknown) => this.record('skill.list', payload, this.onSkillList(payload)),
|
||||
}
|
||||
|
||||
readonly goals: IApiClient['goals'] = {
|
||||
create: payload => this.record('goal.create', payload, Promise.resolve(ok({ ref: { id: 'fake-goal' as never, revision: 1 } }))),
|
||||
edit: payload => this.record('goal.edit', payload, Promise.resolve(ok({ ref: { id: 'fake-goal' as never, revision: 1 } }))),
|
||||
pause: payload => this.record('goal.pause', payload, Promise.resolve(ok({ ref: { id: 'fake-goal' as never, revision: 1 } }))),
|
||||
resume: payload => this.record('goal.resume', payload, Promise.resolve(ok({ ref: { id: 'fake-goal' as never, revision: 1 } }))),
|
||||
complete: payload => this.record('goal.complete', payload, Promise.resolve(ok({ ref: { id: 'fake-goal' as never, revision: 1 } }))),
|
||||
clear: payload => this.record('goal.clear', payload, Promise.resolve(ok({ cleared: true as const }))),
|
||||
}
|
||||
|
||||
readonly settings: IApiClient['settings'] = {
|
||||
describe: payload => this.record('settings.describe', payload, Promise.resolve(ok({ writable: true, hasDocument: false, namespaces: [] }))),
|
||||
openDocument: payload => this.record('settings.openDocument', payload, Promise.resolve(ok({ opened: true as const }))),
|
||||
|
||||
@@ -12,7 +12,6 @@ export type {
|
||||
SkillsApi, SkillEntry,
|
||||
ModelCatalog, ModelCatalogFailure, ModelCatalogModel, ModelProviderGroup, ModelReasoning,
|
||||
ModelReasoningEffort, ModelSelection,
|
||||
GoalsApi, GoalRef,
|
||||
SettingsApi, SettingsNamespaceView, SettingsPathOpView, SettingsSecretView,
|
||||
CredentialsApi, CredentialView, ConfigurableProviderView, DiscoveredModelView, LlmApi,
|
||||
SubagentsApi, SubagentAddress, SubagentCatalog, SubagentListEntry, SubagentPromptReceipt,
|
||||
|
||||
@@ -2235,18 +2235,6 @@ function createFixtureWorld(options: FixtureOptions): FixtureWorld {
|
||||
return { ok: true, value: goalView(projection) }
|
||||
}
|
||||
|
||||
const mapGoalResult = <T, U>(result: RpcResult<T>, map: (value: T) => U): RpcResult<U> => (
|
||||
result.ok ? { ok: true, value: map(result.value) } : result
|
||||
)
|
||||
|
||||
const goalRefResult = (result: RpcResult<FxGoalView>): RpcResult<{ ref: { id: never; revision: number } }> => (
|
||||
mapGoalResult(result, view => ({ ref: { id: view.id as never, revision: view.revision } }))
|
||||
)
|
||||
|
||||
const legacyGoalResponse = <P, T>(request: RpcRequest<P>, result: RpcResult<T>): Promise<RpcResponse<T>> => (
|
||||
Promise.resolve({ rpcId: request.rpcId, result })
|
||||
)
|
||||
|
||||
/** At most one in-flight replay per session; cancel clears it. */
|
||||
const replays = new Map<SessionId, { timer: ReturnType<typeof setTimeout>; finish(aborted: boolean): void }>()
|
||||
|
||||
@@ -3301,46 +3289,6 @@ function createFixtureWorld(options: FixtureOptions): FixtureWorld {
|
||||
})
|
||||
},
|
||||
},
|
||||
goals: {
|
||||
// Compatibility face only: old API Proxy payloads and acknowledgements
|
||||
// adapt to the canonical fixture Remote implementation above.
|
||||
create: request => legacyGoalResponse(
|
||||
request,
|
||||
mapGoalResult(
|
||||
goalRemotes.create(request.payload.sessionId, {
|
||||
objective: request.payload.objective,
|
||||
...request.payload.maxGoalRounds === undefined ? {} : { maxGoalRounds: request.payload.maxGoalRounds },
|
||||
}),
|
||||
value => ({ ref: { id: value.ref.id as never, revision: value.ref.revision } }),
|
||||
),
|
||||
),
|
||||
edit: request => legacyGoalResponse(
|
||||
request,
|
||||
goalRefResult(goalRemotes.edit(request.payload.sessionId, request.payload.ref, {
|
||||
...request.payload.objective === undefined ? {} : { objective: request.payload.objective },
|
||||
...request.payload.maxGoalRounds === undefined ? {} : { maxGoalRounds: request.payload.maxGoalRounds },
|
||||
})),
|
||||
),
|
||||
pause: request => legacyGoalResponse(
|
||||
request,
|
||||
goalRefResult(goalRemotes.pause(request.payload.sessionId, request.payload.ref)),
|
||||
),
|
||||
resume: request => legacyGoalResponse(
|
||||
request,
|
||||
goalRefResult(goalRemotes.resume(request.payload.sessionId, request.payload.ref)),
|
||||
),
|
||||
complete: request => legacyGoalResponse(
|
||||
request,
|
||||
goalRefResult(goalRemotes.complete(request.payload.sessionId, request.payload.ref)),
|
||||
),
|
||||
clear: request => legacyGoalResponse(
|
||||
request,
|
||||
mapGoalResult(
|
||||
goalRemotes.clear(request.payload.sessionId, request.payload.ref),
|
||||
() => ({ cleared: true as const }),
|
||||
),
|
||||
),
|
||||
},
|
||||
settings: {
|
||||
// Only the resolved DeepSeek address needed by first-run readiness is
|
||||
// represented here. Fixture-backed journeys do not open its Models
|
||||
@@ -3600,12 +3548,6 @@ export class FixtureApiClient extends AbstractApiClient {
|
||||
case 'agentPreset.copy': return this.api.agentPresets.copy(request)
|
||||
case 'agentPreset.openDocument': return this.api.agentPresets.openDocument(request, new AbortController().signal)
|
||||
case 'agentPreset.remove': return this.api.agentPresets.remove(request)
|
||||
case 'goal.create': return this.api.goals.create(request)
|
||||
case 'goal.edit': return this.api.goals.edit(request)
|
||||
case 'goal.pause': return this.api.goals.pause(request)
|
||||
case 'goal.resume': return this.api.goals.resume(request)
|
||||
case 'goal.complete': return this.api.goals.complete(request)
|
||||
case 'goal.clear': return this.api.goals.clear(request)
|
||||
case 'settings.describe': return this.api.settings.describe(request)
|
||||
case 'settings.openDocument': return this.api.settings.openDocument(request, signal)
|
||||
case 'settings.update': return this.api.settings.update(request)
|
||||
|
||||
@@ -39,7 +39,6 @@ export type {
|
||||
RpcRequest, RpcResponse, RpcResult, RpcError, RpcErrorCode,
|
||||
ClientRequest, ServerResponse, RpcMessage,
|
||||
HostDescription, IApiClient, SessionId, SessionEvent, ContentBlock, StreamChunk,
|
||||
GoalsApi, GoalRef,
|
||||
SettingsApi, SettingsNamespaceView, SettingsPathOpView, SettingsSecretView,
|
||||
CredentialsApi, CredentialView, ConfigurableProviderView, DiscoveredModelView, LlmApi,
|
||||
} from './api.ts'
|
||||
|
||||
@@ -116,15 +116,6 @@ export class FakeApiClient implements IApiClient {
|
||||
list: (payload: unknown) => this.record('skill.list', payload, this.onSkillList(payload)),
|
||||
}
|
||||
|
||||
readonly goals: IApiClient['goals'] = {
|
||||
create: payload => this.record('goal.create', payload, Promise.resolve(ok({ ref: { id: 'fake-goal' as never, revision: 1 } }))),
|
||||
edit: payload => this.record('goal.edit', payload, Promise.resolve(ok({ ref: { id: 'fake-goal' as never, revision: 1 } }))),
|
||||
pause: payload => this.record('goal.pause', payload, Promise.resolve(ok({ ref: { id: 'fake-goal' as never, revision: 1 } }))),
|
||||
resume: payload => this.record('goal.resume', payload, Promise.resolve(ok({ ref: { id: 'fake-goal' as never, revision: 1 } }))),
|
||||
complete: payload => this.record('goal.complete', payload, Promise.resolve(ok({ ref: { id: 'fake-goal' as never, revision: 1 } }))),
|
||||
clear: payload => this.record('goal.clear', payload, Promise.resolve(ok({ cleared: true as const }))),
|
||||
}
|
||||
|
||||
readonly settings: IApiClient['settings'] = {
|
||||
describe: payload => this.record('settings.describe', payload, Promise.resolve(ok({ writable: true, hasDocument: false, namespaces: [] }))),
|
||||
openDocument: payload => this.record('settings.openDocument', payload, Promise.resolve(ok({ opened: true as const }))),
|
||||
|
||||
@@ -1567,29 +1567,33 @@ describe('FixtureApiClient (protocol-level fake carrier)', () => {
|
||||
const moved = await workspaces.insertSessionBefore({ workspaceId: wsid, sessionId: attached.result.value.sessionId })
|
||||
if (!moved.result.ok) throw new Error('workspace move failed')
|
||||
expect(moved.result.value.workspace.sessionIds).toEqual([attached.result.value.sessionId])
|
||||
// Goal lifecycle over the fixture fold: create → edit → pause → resume → complete → clear;
|
||||
// every mutation acknowledges with the NEW CAS ref (state rides the projection frames).
|
||||
const goalCreated = await client.goals.create({ sessionId: id, objective: 'ship it' })
|
||||
if (!goalCreated.result.ok) throw new Error('goal create failed')
|
||||
let ref = goalCreated.result.value.ref
|
||||
expect(ref.revision).toBe(1)
|
||||
const edited = await client.goals.edit({ sessionId: id, ref, objective: 'ship it v2' })
|
||||
if (!edited.result.ok) throw new Error('goal edit failed')
|
||||
ref = edited.result.value.ref
|
||||
const paused = await client.goals.pause({ sessionId: id, ref })
|
||||
if (!paused.result.ok) throw new Error('goal pause failed')
|
||||
ref = paused.result.value.ref
|
||||
const resumed = await client.goals.resume({ sessionId: id, ref })
|
||||
if (!resumed.result.ok) throw new Error('goal resume failed')
|
||||
ref = resumed.result.value.ref
|
||||
})
|
||||
|
||||
it('folds the goal lifecycle over the Goal Remotes', async () => {
|
||||
const client = new FixtureApiClient()
|
||||
const sessions = createSessionClient(client.rpc)
|
||||
const created = await sessions.create({})
|
||||
if (!created.result.ok) throw new Error('create failed')
|
||||
const id = created.result.value.sessionId
|
||||
const goal = (endpoint: string, args: Record<string, unknown>) =>
|
||||
client.rpc.call('/api', endpoint, { args: { agentId: id, ...args } })
|
||||
|
||||
// create → edit → pause → resume → complete → clear; each mutation advances the CAS
|
||||
// revision by one (state rides the projection frames).
|
||||
const goalCreated = await goal('goals/create', { request: { objective: 'ship it' } })
|
||||
if (!goalCreated.ok) throw new Error('goal create failed')
|
||||
const { id: goalId, revision } = (goalCreated.value as { ref: { id: string; revision: number } }).ref
|
||||
expect(revision).toBe(1)
|
||||
const ref = (at: number) => ({ id: goalId, revision: at })
|
||||
expect((await goal('goals/edit', { ref: ref(1), request: { objective: 'ship it v2' } })).ok).toBe(true)
|
||||
expect((await goal('goals/pause', { ref: ref(2) })).ok).toBe(true)
|
||||
expect((await goal('goals/resume', { ref: ref(3) })).ok).toBe(true)
|
||||
// A stale ref loses the CAS check.
|
||||
expect((await client.goals.pause({ sessionId: id, ref: { ...ref, revision: 1 } })).result.ok).toBe(false)
|
||||
const completed = await client.goals.complete({ sessionId: id, ref })
|
||||
if (!completed.result.ok) throw new Error('goal complete failed')
|
||||
ref = completed.result.value.ref
|
||||
expect((await goal('goals/pause', { ref: ref(1) })).ok).toBe(false)
|
||||
expect((await goal('goals/complete', { ref: ref(4) })).ok).toBe(true)
|
||||
// complete → complete is an invalid transition.
|
||||
expect((await client.goals.complete({ sessionId: id, ref })).result.ok).toBe(false)
|
||||
expect((await client.goals.clear({ sessionId: id, ref })).result).toEqual({ ok: true, value: { cleared: true } })
|
||||
expect((await goal('goals/complete', { ref: ref(5) })).ok).toBe(false)
|
||||
expect(await goal('goals/clear', { ref: ref(5) })).toEqual({ ok: true, value: ref(6) })
|
||||
|
||||
const goalHistory = await sessions.history({ sessionId: id })
|
||||
if (!goalHistory.result.ok) throw new Error('goal history failed')
|
||||
|
||||
@@ -188,7 +188,6 @@ class FakeApiClient implements IApiClient {
|
||||
declare readonly subagents: IApiClient['subagents']
|
||||
declare readonly skills: IApiClient['skills']
|
||||
declare readonly agentPresets: IApiClient['agentPresets']
|
||||
declare readonly goals: IApiClient['goals']
|
||||
declare readonly settings: IApiClient['settings']
|
||||
declare readonly credentials: IApiClient['credentials']
|
||||
declare readonly llm: IApiClient['llm']
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { homedir } from 'node:os'
|
||||
import { dirname } from 'node:path'
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
import type { Agent, ModelSelection } from '@deepseek-ai/dsh-agent'
|
||||
import type { ModelSelection } from '@deepseek-ai/dsh-agent'
|
||||
import type {} from '@deepseek-ai/dsh-agent-presets/types'
|
||||
import type { Session, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import { SubagentError } from '@deepseek-ai/dsh-subagent'
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
PresetNotWritableError, UnknownPresetError,
|
||||
} from '@deepseek-ai/dsh-agent-presets'
|
||||
import type {
|
||||
ApiProxy, ConfigurableProviderView, CredentialView, GoalRef,
|
||||
ApiProxy, ConfigurableProviderView, CredentialView,
|
||||
SettingsNamespaceView,
|
||||
} from './api/index.ts'
|
||||
import type { SessionRequestId } from '@deepseek-ai/dsh-api-session-controller/types'
|
||||
@@ -32,9 +32,6 @@ import {
|
||||
type SessionLogCompressionLevel,
|
||||
} from './session-export.ts'
|
||||
import type { SessionRawArtifact } from '@deepseek-ai/dsh-session-persistence'
|
||||
// GoalError narrows domain rejections to their stable codes at the wire boundary.
|
||||
import { GoalError } from '@deepseek-ai/dsh-goal'
|
||||
import type { GoalRef as CoreGoalRef } from '@deepseek-ai/dsh-goal'
|
||||
// Type-only edges: resolve the command-change stream and `ctx.get('skills')`.
|
||||
import type {} from '@deepseek-ai/dsh-commands'
|
||||
import type {} from '@deepseek-ai/dsh-skill'
|
||||
@@ -270,47 +267,6 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the goal service THIS agent runs.
|
||||
*
|
||||
* The service is per session: an agent preset mounts it behind an `isolate`
|
||||
* realm, which no host context resolves. Reading it from the root would
|
||||
* answer "absent" for a session whose composition mounts it — so the lookup
|
||||
* is keyed by the agent, and only a deployment composing it nowhere is
|
||||
* genuinely absent.
|
||||
*/
|
||||
function goalServiceFor(agent: Agent): NonNullable<ReturnType<typeof ctx.get<'goals'>>> | { error: RpcError } {
|
||||
const presets = ctx.get('agentPresets')
|
||||
const goals = presets?.serviceFor(agent, 'goals') ?? ctx.get('goals')
|
||||
if (goals === undefined) {
|
||||
return { error: { code: 'internal', message: 'goal service is absent: neither this session\'s agent preset nor the host composition mounts @deepseek-ai/dsh-goal', details: {} } }
|
||||
}
|
||||
return goals
|
||||
}
|
||||
|
||||
/** Map one goal-domain rejection to the wire error (stable GoalError codes ride in details). */
|
||||
function goalError(request: RpcRequest<unknown>, error: unknown): RpcResponse<never> {
|
||||
const details = error instanceof GoalError ? { goalCode: error.code } : {}
|
||||
return err(request, { code: 'internal', message: String(error), details })
|
||||
}
|
||||
|
||||
/** Resolve a session's agent, apply one goal mutation, and acknowledge with the new CAS ref. */
|
||||
async function mutateGoal(
|
||||
request: RpcRequest<{ sessionId: SessionId }>,
|
||||
mutation: (goals: NonNullable<ReturnType<typeof ctx.get<'goals'>>>, agent: Agent) => CoreGoalRef,
|
||||
): Promise<RpcResponse<{ ref: GoalRef }>> {
|
||||
const found = await agentFor(request.payload.sessionId)
|
||||
if ('error' in found) return err(request, found.error)
|
||||
const goals = goalServiceFor(found.agent)
|
||||
if ('error' in goals) return err(request, goals.error)
|
||||
try {
|
||||
const ref = mutation(goals, found.agent)
|
||||
return ok(request, { ref: { id: ref.id, revision: ref.revision } })
|
||||
} catch (error: unknown) {
|
||||
return goalError(request, error)
|
||||
}
|
||||
}
|
||||
|
||||
/** Missing-service report shared by the settings domain (skills-domain stance). */
|
||||
function settingsAbsent(): RpcError {
|
||||
return { code: 'internal', message: 'settings service is absent: this deployment does not mount a settings provider (e.g. @deepseek-ai/dsh-settings-file) in its composition', details: {} }
|
||||
@@ -623,54 +579,6 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro
|
||||
},
|
||||
},
|
||||
|
||||
goals: {
|
||||
// Mutations only — the read side is the 'goal' session projection.
|
||||
// Every verb resolves the session's agent (agentFor: implicit cold
|
||||
// resume, the command.* precedent) and acknowledges with the new CAS
|
||||
// ref; the committed goal/change event carries the whole value to every
|
||||
// client through the projection frames.
|
||||
async create(request) {
|
||||
const { objective, maxGoalRounds } = request.payload
|
||||
return mutateGoal(request, (goals, agent) => goals.create(agent, {
|
||||
objective,
|
||||
...(maxGoalRounds !== undefined ? { maxGoalRounds } : {}),
|
||||
}))
|
||||
},
|
||||
|
||||
async edit(request) {
|
||||
const { ref, objective, maxGoalRounds } = request.payload
|
||||
return mutateGoal(request, (goals, agent) => goals.edit(agent, ref, {
|
||||
...(objective !== undefined ? { objective } : {}),
|
||||
...(maxGoalRounds !== undefined ? { maxGoalRounds } : {}),
|
||||
}))
|
||||
},
|
||||
|
||||
async pause(request) {
|
||||
return mutateGoal(request, (goals, agent) => goals.pause(agent, request.payload.ref))
|
||||
},
|
||||
|
||||
async resume(request) {
|
||||
return mutateGoal(request, (goals, agent) => goals.resume(agent, request.payload.ref))
|
||||
},
|
||||
|
||||
async complete(request) {
|
||||
return mutateGoal(request, (goals, agent) => goals.complete(agent, request.payload.ref))
|
||||
},
|
||||
|
||||
async clear(request) {
|
||||
const found = await agentFor(request.payload.sessionId)
|
||||
if ('error' in found) return err(request, found.error)
|
||||
const goals = goalServiceFor(found.agent)
|
||||
if ('error' in goals) return err(request, goals.error)
|
||||
try {
|
||||
goals.clear(found.agent, request.payload.ref)
|
||||
return ok(request, { cleared: true as const })
|
||||
} catch (error: unknown) {
|
||||
return goalError(request, error)
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
agentPresets: {
|
||||
// A deployment with no roster answers with an empty list rather than an
|
||||
// error: composing no presets is a valid deployment, and the browser
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
/**
|
||||
* goals domain zod schemas. Mutation-only shapes: every value schema is a
|
||||
* `{ ref }` acknowledgement (clear: `{ cleared }`) — the current goal state
|
||||
* travels exclusively on the 'goal' session projection.
|
||||
*/
|
||||
|
||||
import { z } from 'zod'
|
||||
import type { Wire } from './rpc.schema.ts'
|
||||
import type { GoalRef, RequestPayload, ResponseValue } from './index.ts'
|
||||
|
||||
/** GoalRef schema. */
|
||||
export const goalRefSchema = z.object({
|
||||
id: z.string(),
|
||||
revision: z.number().int().positive(),
|
||||
}) as unknown as z.ZodType<Wire<GoalRef>>
|
||||
|
||||
/** Shared `{ ref }` acknowledgement value of every non-clear mutation. */
|
||||
const goalRefValueSchema = z.object({ ref: goalRefSchema })
|
||||
|
||||
/** goal.create request payload. */
|
||||
export const goalCreateRequestSchema = z.object({
|
||||
sessionId: z.string(),
|
||||
objective: z.string().min(1),
|
||||
maxGoalRounds: z.number().int().positive().optional(),
|
||||
}) as unknown as z.ZodType<Wire<RequestPayload<'goal.create'>>>
|
||||
|
||||
/** goal.create response value. */
|
||||
export const goalCreateValueSchema = goalRefValueSchema as unknown as z.ZodType<Wire<ResponseValue<'goal.create'>>>
|
||||
|
||||
/** goal.edit request payload. */
|
||||
export const goalEditRequestSchema = z.object({
|
||||
sessionId: z.string(),
|
||||
ref: goalRefSchema,
|
||||
objective: z.string().min(1).optional(),
|
||||
maxGoalRounds: z.number().int().positive().optional(),
|
||||
}).refine(value => value.objective !== undefined || value.maxGoalRounds !== undefined, {
|
||||
message: 'goal.edit requires objective or maxGoalRounds',
|
||||
}) as unknown as z.ZodType<Wire<RequestPayload<'goal.edit'>>>
|
||||
|
||||
/** goal.edit response value. */
|
||||
export const goalEditValueSchema = goalRefValueSchema as unknown as z.ZodType<Wire<ResponseValue<'goal.edit'>>>
|
||||
|
||||
/** goal.pause request payload. */
|
||||
export const goalPauseRequestSchema = z.object({
|
||||
sessionId: z.string(),
|
||||
ref: goalRefSchema,
|
||||
}) as unknown as z.ZodType<Wire<RequestPayload<'goal.pause'>>>
|
||||
|
||||
/** goal.pause response value. */
|
||||
export const goalPauseValueSchema = goalRefValueSchema as unknown as z.ZodType<Wire<ResponseValue<'goal.pause'>>>
|
||||
|
||||
/** goal.resume request payload. */
|
||||
export const goalResumeRequestSchema = z.object({
|
||||
sessionId: z.string(),
|
||||
ref: goalRefSchema,
|
||||
}) as unknown as z.ZodType<Wire<RequestPayload<'goal.resume'>>>
|
||||
|
||||
/** goal.resume response value. */
|
||||
export const goalResumeValueSchema = goalRefValueSchema as unknown as z.ZodType<Wire<ResponseValue<'goal.resume'>>>
|
||||
|
||||
/** goal.complete request payload. */
|
||||
export const goalCompleteRequestSchema = z.object({
|
||||
sessionId: z.string(),
|
||||
ref: goalRefSchema,
|
||||
}) as unknown as z.ZodType<Wire<RequestPayload<'goal.complete'>>>
|
||||
|
||||
/** goal.complete response value. */
|
||||
export const goalCompleteValueSchema = goalRefValueSchema as unknown as z.ZodType<Wire<ResponseValue<'goal.complete'>>>
|
||||
|
||||
/** goal.clear request payload. */
|
||||
export const goalClearRequestSchema = z.object({
|
||||
sessionId: z.string(),
|
||||
ref: goalRefSchema,
|
||||
}) as unknown as z.ZodType<Wire<RequestPayload<'goal.clear'>>>
|
||||
|
||||
/** goal.clear response value. */
|
||||
export const goalClearValueSchema = z.object({
|
||||
cleared: z.literal(true),
|
||||
}) as unknown as z.ZodType<Wire<ResponseValue<'goal.clear'>>>
|
||||
@@ -1,53 +0,0 @@
|
||||
/**
|
||||
* goals domain contract. Method signatures are the source of truth:
|
||||
* unary methods take the RpcRequest<P> narrow form and the impl echoes rpcId.
|
||||
*
|
||||
* Mutations only: the read side is the `goal` Session projection carried by
|
||||
* Session Controller history and control streams. There is no goal.get or
|
||||
* separate wire goal view; responses acknowledge with the new CAS ref, and
|
||||
* committed goal/change events update the projection.
|
||||
*/
|
||||
|
||||
import type { Branded } from '@deepseek-ai/dsh-brand'
|
||||
import type { SessionId } from '@deepseek-ai/dsh-session/types'
|
||||
import type { RpcRequest, RpcResponse } from './rpc.ts'
|
||||
|
||||
/** Identifies one goal across its durable revisions. */
|
||||
export type GoalId = Branded<'GoalId'>
|
||||
|
||||
/** Compare-and-set identity for one exact goal revision. */
|
||||
export interface GoalRef {
|
||||
readonly id: GoalId
|
||||
readonly revision: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Goal-domain unary methods. Every mutation resolves an ordinary session's
|
||||
* Agent and applies one CAS-guarded verb; session-backed subagents reject with
|
||||
* `agent-busy`.
|
||||
*/
|
||||
export interface GoalsApi {
|
||||
/** Create and arm a goal. */
|
||||
create(request: RpcRequest<{ sessionId: SessionId; objective: string; maxGoalRounds?: number }>):
|
||||
Promise<RpcResponse<{ ref: GoalRef }>>
|
||||
|
||||
/** Edit objective and/or round cap without changing phase. */
|
||||
edit(request: RpcRequest<{ sessionId: SessionId; ref: GoalRef; objective?: string; maxGoalRounds?: number }>):
|
||||
Promise<RpcResponse<{ ref: GoalRef }>>
|
||||
|
||||
/** Pause an active goal and disarm automatic continuation. */
|
||||
pause(request: RpcRequest<{ sessionId: SessionId; ref: GoalRef }>):
|
||||
Promise<RpcResponse<{ ref: GoalRef }>>
|
||||
|
||||
/** Resume and arm a stopped goal. */
|
||||
resume(request: RpcRequest<{ sessionId: SessionId; ref: GoalRef }>):
|
||||
Promise<RpcResponse<{ ref: GoalRef }>>
|
||||
|
||||
/** Mark a current non-complete goal complete and disarm it. */
|
||||
complete(request: RpcRequest<{ sessionId: SessionId; ref: GoalRef }>):
|
||||
Promise<RpcResponse<{ ref: GoalRef }>>
|
||||
|
||||
/** Clear the current goal while retaining a durable tombstone and history. */
|
||||
clear(request: RpcRequest<{ sessionId: SessionId; ref: GoalRef }>):
|
||||
Promise<RpcResponse<{ cleared: true }>>
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import type { HostApi } from './host.ts'
|
||||
import type { AgentPresetsApi } from './agent-presets.ts'
|
||||
import type { SkillsApi } from './skills.ts'
|
||||
import type { SubagentsApi } from './subagents.ts'
|
||||
import type { GoalsApi } from './goals.ts'
|
||||
import type { SettingsApi } from './settings.ts'
|
||||
import type { CredentialsApi } from './credentials.ts'
|
||||
import type { LlmApi } from './llm.ts'
|
||||
@@ -20,7 +19,6 @@ export interface ApiProxy {
|
||||
host: HostApi
|
||||
skills: SkillsApi
|
||||
agentPresets: AgentPresetsApi
|
||||
goals: GoalsApi
|
||||
settings: SettingsApi
|
||||
credentials: CredentialsApi
|
||||
llm: LlmApi
|
||||
@@ -40,7 +38,6 @@ export type {
|
||||
} from './subagents.ts'
|
||||
export type { SkillsApi, SkillEntry } from './skills.ts'
|
||||
export type { AgentPresetsApi, AgentPresetEntry } from './agent-presets.ts'
|
||||
export type { GoalsApi, GoalId, GoalRef } from './goals.ts'
|
||||
export type { SettingsApi, SettingsNamespaceView, SettingsPathOpView, SettingsSecretView } from './settings.ts'
|
||||
export type { CredentialsApi, CredentialView } from './credentials.ts'
|
||||
export type { ConfigurableProviderView, DiscoveredModelView, LlmApi } from './llm.ts'
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import type { HostApi } from './host.ts'
|
||||
import type { AgentPresetsApi } from './agent-presets.ts'
|
||||
import type { SkillsApi } from './skills.ts'
|
||||
import type { GoalsApi } from './goals.ts'
|
||||
import type { SettingsApi } from './settings.ts'
|
||||
import type { CredentialsApi } from './credentials.ts'
|
||||
import type { LlmApi } from './llm.ts'
|
||||
@@ -34,12 +33,6 @@ export interface RpcMethodMap {
|
||||
'agentPreset.copy': AgentPresetsApi['copy']
|
||||
'agentPreset.openDocument': AgentPresetsApi['openDocument']
|
||||
'agentPreset.remove': AgentPresetsApi['remove']
|
||||
'goal.create': GoalsApi['create']
|
||||
'goal.edit': GoalsApi['edit']
|
||||
'goal.pause': GoalsApi['pause']
|
||||
'goal.resume': GoalsApi['resume']
|
||||
'goal.complete': GoalsApi['complete']
|
||||
'goal.clear': GoalsApi['clear']
|
||||
'settings.describe': SettingsApi['describe']
|
||||
'settings.openDocument': SettingsApi['openDocument']
|
||||
'settings.update': SettingsApi['update']
|
||||
|
||||
@@ -21,14 +21,6 @@ import {
|
||||
agentPresetCopyValueSchema, agentPresetListValueSchema, agentPresetOpenDocumentValueSchema,
|
||||
agentPresetReadValueSchema, agentPresetRemoveValueSchema, agentPresetSelectValueSchema,
|
||||
} from '../api/agent-presets.schema.ts'
|
||||
import {
|
||||
goalCreateValueSchema,
|
||||
goalEditValueSchema,
|
||||
goalPauseValueSchema,
|
||||
goalResumeValueSchema,
|
||||
goalCompleteValueSchema,
|
||||
goalClearValueSchema,
|
||||
} from '../api/goals.schema.ts'
|
||||
import {
|
||||
settingsDescribeValueSchema, settingsMutateValueSchema, settingsOpenDocumentValueSchema,
|
||||
settingsReplaceValueSchema, settingsUpdateValueSchema,
|
||||
@@ -79,14 +71,6 @@ export interface IApiClient {
|
||||
openDocument(payload: RequestPayload<'agentPreset.openDocument'>, signal?: AbortSignal): Promise<RpcResponse<ResponseValue<'agentPreset.openDocument'>>>
|
||||
remove(payload: RequestPayload<'agentPreset.remove'>, signal?: AbortSignal): Promise<RpcResponse<ResponseValue<'agentPreset.remove'>>>
|
||||
}
|
||||
goals: {
|
||||
create(payload: RequestPayload<'goal.create'>, signal?: AbortSignal): Promise<RpcResponse<ResponseValue<'goal.create'>>>
|
||||
edit(payload: RequestPayload<'goal.edit'>, signal?: AbortSignal): Promise<RpcResponse<ResponseValue<'goal.edit'>>>
|
||||
pause(payload: RequestPayload<'goal.pause'>, signal?: AbortSignal): Promise<RpcResponse<ResponseValue<'goal.pause'>>>
|
||||
resume(payload: RequestPayload<'goal.resume'>, signal?: AbortSignal): Promise<RpcResponse<ResponseValue<'goal.resume'>>>
|
||||
complete(payload: RequestPayload<'goal.complete'>, signal?: AbortSignal): Promise<RpcResponse<ResponseValue<'goal.complete'>>>
|
||||
clear(payload: RequestPayload<'goal.clear'>, signal?: AbortSignal): Promise<RpcResponse<ResponseValue<'goal.clear'>>>
|
||||
}
|
||||
settings: {
|
||||
describe(payload: RequestPayload<'settings.describe'>, signal?: AbortSignal): Promise<RpcResponse<ResponseValue<'settings.describe'>>>
|
||||
openDocument(payload: RequestPayload<'settings.openDocument'>, signal?: AbortSignal): Promise<RpcResponse<ResponseValue<'settings.openDocument'>>>
|
||||
@@ -126,12 +110,6 @@ const UNARY_VALUE_SCHEMAS: { [K in keyof RpcMethodMap]: z.ZodType<Wire<ResponseV
|
||||
'agentPreset.copy': agentPresetCopyValueSchema,
|
||||
'agentPreset.openDocument': agentPresetOpenDocumentValueSchema,
|
||||
'agentPreset.remove': agentPresetRemoveValueSchema,
|
||||
'goal.create': goalCreateValueSchema,
|
||||
'goal.edit': goalEditValueSchema,
|
||||
'goal.pause': goalPauseValueSchema,
|
||||
'goal.resume': goalResumeValueSchema,
|
||||
'goal.complete': goalCompleteValueSchema,
|
||||
'goal.clear': goalClearValueSchema,
|
||||
'settings.describe': settingsDescribeValueSchema,
|
||||
'settings.openDocument': settingsOpenDocumentValueSchema,
|
||||
'settings.update': settingsUpdateValueSchema,
|
||||
@@ -309,15 +287,6 @@ export abstract class AbstractApiClient implements IApiClient {
|
||||
remove: (payload, signal) => this.callUnary('agentPreset.remove', payload, signal),
|
||||
}
|
||||
|
||||
readonly goals: IApiClient['goals'] = {
|
||||
create: (payload, signal) => this.callUnary('goal.create', payload, signal),
|
||||
edit: (payload, signal) => this.callUnary('goal.edit', payload, signal),
|
||||
pause: (payload, signal) => this.callUnary('goal.pause', payload, signal),
|
||||
resume: (payload, signal) => this.callUnary('goal.resume', payload, signal),
|
||||
complete: (payload, signal) => this.callUnary('goal.complete', payload, signal),
|
||||
clear: (payload, signal) => this.callUnary('goal.clear', payload, signal),
|
||||
}
|
||||
|
||||
readonly settings: IApiClient['settings'] = {
|
||||
describe: (payload, signal) => this.callUnary('settings.describe', payload, signal),
|
||||
openDocument: (payload, signal) => this.callUnary('settings.openDocument', payload, signal),
|
||||
|
||||
@@ -24,14 +24,6 @@ import {
|
||||
agentPresetCopyRequestSchema, agentPresetListRequestSchema, agentPresetOpenDocumentRequestSchema,
|
||||
agentPresetReadRequestSchema, agentPresetRemoveRequestSchema, agentPresetSelectRequestSchema,
|
||||
} from '../api/agent-presets.schema.ts'
|
||||
import {
|
||||
goalCreateRequestSchema,
|
||||
goalEditRequestSchema,
|
||||
goalPauseRequestSchema,
|
||||
goalResumeRequestSchema,
|
||||
goalCompleteRequestSchema,
|
||||
goalClearRequestSchema,
|
||||
} from '../api/goals.schema.ts'
|
||||
import {
|
||||
settingsDescribeRequestSchema, settingsMutateRequestSchema, settingsOpenDocumentRequestSchema,
|
||||
settingsReplaceRequestSchema, settingsUpdateRequestSchema,
|
||||
@@ -78,12 +70,6 @@ const UNARY_ROUTES: UnaryRoutes = {
|
||||
'agentPreset.copy': { schema: agentPresetCopyRequestSchema, invoke: (api, r) => api.agentPresets.copy(r) },
|
||||
'agentPreset.openDocument': { schema: agentPresetOpenDocumentRequestSchema, invoke: (api, r, signal) => api.agentPresets.openDocument(r, signal) },
|
||||
'agentPreset.remove': { schema: agentPresetRemoveRequestSchema, invoke: (api, r) => api.agentPresets.remove(r) },
|
||||
'goal.create': { schema: goalCreateRequestSchema, invoke: (api, r) => api.goals.create(r) },
|
||||
'goal.edit': { schema: goalEditRequestSchema, invoke: (api, r) => api.goals.edit(r) },
|
||||
'goal.pause': { schema: goalPauseRequestSchema, invoke: (api, r) => api.goals.pause(r) },
|
||||
'goal.resume': { schema: goalResumeRequestSchema, invoke: (api, r) => api.goals.resume(r) },
|
||||
'goal.complete': { schema: goalCompleteRequestSchema, invoke: (api, r) => api.goals.complete(r) },
|
||||
'goal.clear': { schema: goalClearRequestSchema, invoke: (api, r) => api.goals.clear(r) },
|
||||
'settings.describe': { schema: settingsDescribeRequestSchema, invoke: (api, r) => api.settings.describe(r) },
|
||||
'settings.openDocument': { schema: settingsOpenDocumentRequestSchema, invoke: (api, r, signal) => api.settings.openDocument(r, signal) },
|
||||
'settings.update': { schema: settingsUpdateRequestSchema, invoke: (api, r) => api.settings.update(r) },
|
||||
|
||||
@@ -74,7 +74,6 @@ export class ApiProxyService extends Service implements ApiProxy {
|
||||
|
||||
readonly subagents: ApiProxy['subagents']
|
||||
readonly host: ApiProxy['host']
|
||||
readonly goals: ApiProxy['goals']
|
||||
readonly skills: ApiProxy['skills']
|
||||
readonly agentPresets: ApiProxy['agentPresets']
|
||||
readonly settings: ApiProxy['settings']
|
||||
@@ -94,7 +93,6 @@ export class ApiProxyService extends Service implements ApiProxy {
|
||||
})
|
||||
this.subagents = api.subagents
|
||||
this.host = api.host
|
||||
this.goals = api.goals
|
||||
this.skills = api.skills
|
||||
this.agentPresets = api.agentPresets
|
||||
this.settings = api.settings
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
agentPresetProjectionDefinition, InvalidPresetIdError, PresetExistsError, UnknownPresetError,
|
||||
} from '@deepseek-ai/dsh-agent-presets'
|
||||
import type {} from '@deepseek-ai/dsh-agent-presets/types'
|
||||
import { GoalId } from '@deepseek-ai/dsh-goal'
|
||||
import { createApiProxy } from '../src/api-proxy.ts'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { SessionQueryError, type SessionObservation } from '@deepseek-ai/dsh-session-query'
|
||||
@@ -210,23 +209,6 @@ async function harness(
|
||||
* instance through the agent instead of reading a root-realm singleton.
|
||||
*/
|
||||
describe('a capability the session\'s preset mounts', () => {
|
||||
it('serves the goal RPC from the session\'s own goal service', async () => {
|
||||
const { api } = await harness(['standard'])
|
||||
await createSession(api, { sessionId: SessionId('g1'), agentPreset: 'standard' })
|
||||
const ref = { id: GoalId('goal-1'), revision: 1 }
|
||||
const paused: unknown[] = []
|
||||
services.set('g1', {
|
||||
goals: { pause: (agent: { id: unknown }, r: unknown) => { paused.push([String(agent.id), r]); return ref } },
|
||||
})
|
||||
|
||||
const response = await api.goals.pause(request({ sessionId: SessionId('g1'), ref }))
|
||||
|
||||
expect(response.result).toMatchObject({ ok: true, value: { ref } })
|
||||
// Reached the instance this session mounted, and was handed its own agent.
|
||||
expect(paused).toEqual([['g1', ref]])
|
||||
services.delete('g1')
|
||||
})
|
||||
|
||||
it('serves the skill catalog from the session\'s own registry', async () => {
|
||||
const { api } = await harness(['standard'])
|
||||
await createSession(api, { sessionId: SessionId('k1'), agentPreset: 'standard' })
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import type { ApiProxy, GoalRef, RpcMessage, RpcRequest, RpcResponse } from '@deepseek-ai/dsh-host-apiproxy'
|
||||
import type { ApiProxy, RpcMessage, RpcRequest, RpcResponse } from '@deepseek-ai/dsh-host-apiproxy'
|
||||
import { InProcessApiClient, RpcId, toFetchHandler } from '@deepseek-ai/dsh-host-apiproxy'
|
||||
|
||||
const sid = (id: string): SessionId => id as SessionId
|
||||
@@ -22,7 +22,6 @@ function scriptedApi(overrides: {
|
||||
host?: Partial<ApiProxy['host']>
|
||||
skills?: Partial<ApiProxy['skills']>
|
||||
agentPresets?: Partial<ApiProxy['agentPresets']>
|
||||
goals?: Partial<ApiProxy['goals']>
|
||||
settings?: Partial<ApiProxy['settings']>
|
||||
credentials?: Partial<ApiProxy['credentials']>
|
||||
llm?: Partial<ApiProxy['llm']>
|
||||
@@ -56,15 +55,6 @@ function scriptedApi(overrides: {
|
||||
remove: r => ok(r, {}),
|
||||
...overrides.agentPresets,
|
||||
},
|
||||
goals: {
|
||||
create: err,
|
||||
edit: err,
|
||||
pause: err,
|
||||
resume: err,
|
||||
complete: err,
|
||||
clear: err,
|
||||
...overrides.goals,
|
||||
},
|
||||
settings: {
|
||||
describe: r => ok(r, { writable: true, hasDocument: false, namespaces: [] }),
|
||||
openDocument: r => ok(r, { opened: true as const }),
|
||||
@@ -309,63 +299,6 @@ describe('unary round trip', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('goals unary surface', () => {
|
||||
const ref: GoalRef = { id: 'goal-1' as GoalRef['id'], revision: 1 }
|
||||
/** The `{ ref }` acknowledgement every non-clear mutation answers (state travels on the projection). */
|
||||
const ack = { ref: { id: 'goal-1' as GoalRef['id'], revision: 2 } }
|
||||
|
||||
it('round-trips every goal method with its own payload and value shape', async () => {
|
||||
const seen: { method: string; payload: unknown }[] = []
|
||||
const record = recorderInto(seen)
|
||||
const api = scriptedApi({
|
||||
goals: {
|
||||
create: record('goal.create', r => ok(r, ack)),
|
||||
edit: record('goal.edit', r => ok(r, { ref: { ...ack.ref, revision: 3 } })),
|
||||
pause: record('goal.pause', r => ok(r, ack)),
|
||||
resume: record('goal.resume', r => ok(r, ack)),
|
||||
complete: record('goal.complete', r => ok(r, ack)),
|
||||
clear: record('goal.clear', r => ok(r, { cleared: true as const })),
|
||||
},
|
||||
})
|
||||
const c = client(api)
|
||||
|
||||
const created = await c.goals.create({ sessionId: sid('s1'), objective: 'ship it', maxGoalRounds: 4 })
|
||||
expect(created.result).toEqual({ ok: true, value: ack })
|
||||
const edited = await c.goals.edit({ sessionId: sid('s1'), ref, objective: 'ship v2' })
|
||||
expect(edited.result).toEqual({ ok: true, value: { ref: { ...ack.ref, revision: 3 } } })
|
||||
expect((await c.goals.pause({ sessionId: sid('s1'), ref })).result).toEqual({ ok: true, value: ack })
|
||||
expect((await c.goals.resume({ sessionId: sid('s1'), ref })).result).toEqual({ ok: true, value: ack })
|
||||
expect((await c.goals.complete({ sessionId: sid('s1'), ref })).result).toEqual({ ok: true, value: ack })
|
||||
const cleared = await c.goals.clear({ sessionId: sid('s1'), ref })
|
||||
expect(cleared.result).toEqual({ ok: true, value: { cleared: true } })
|
||||
|
||||
// The handler dispatched each call through its own route row: payload parsed per method.
|
||||
expect(seen.map(s => s.method)).toEqual(['goal.create', 'goal.edit', 'goal.pause', 'goal.resume', 'goal.complete', 'goal.clear'])
|
||||
expect(seen[0]?.payload).toEqual({ sessionId: 's1', objective: 'ship it', maxGoalRounds: 4 })
|
||||
expect(seen[1]?.payload).toEqual({ sessionId: 's1', ref, objective: 'ship v2' })
|
||||
})
|
||||
|
||||
it('passes business errors through as results, not throws', async () => {
|
||||
// Default scripted goals impl answers an err result: it must arrive as a result, not a throw.
|
||||
const failed = await client(scriptedApi()).goals.pause({ sessionId: sid('s1'), ref })
|
||||
expect(failed.result.ok).toBe(false)
|
||||
if (!failed.result.ok) expect(failed.result.error.code).toBe('internal')
|
||||
})
|
||||
|
||||
it('rejects an invalid goal payload at the handler as bad-request', async () => {
|
||||
const response = await client(scriptedApi()).goals.create({ sessionId: sid('s1'), objective: '' })
|
||||
expect(response.result.ok).toBe(false)
|
||||
if (!response.result.ok) expect(response.result.error.code).toBe('bad-request')
|
||||
|
||||
let editCalls = 0
|
||||
const api = scriptedApi({ goals: { edit: (r) => { editCalls++; return ok(r, ack) } } })
|
||||
const emptyEdit = await client(api).goals.edit({ sessionId: sid('s1'), ref })
|
||||
expect(emptyEdit.result.ok).toBe(false)
|
||||
if (!emptyEdit.result.ok) expect(emptyEdit.result.error.code).toBe('bad-request')
|
||||
expect(editCalls).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('envelope tap', () => {
|
||||
it('delivers one microtask batch of full forms per unary call', async () => {
|
||||
const api = scriptedApi()
|
||||
|
||||
@@ -87,26 +87,6 @@ function fakeApi(overrides: Partial<{ crashOn: string }> = {}): ApiProxy {
|
||||
return { rpcId: request.rpcId, result: { ok: true, value: { skills: [{ name: 'commit-helper', description: 'Git commits', modelInvocable: true }] } } }
|
||||
},
|
||||
},
|
||||
goals: {
|
||||
async create(request) {
|
||||
return { rpcId: request.rpcId, result: { ok: false, error: { code: 'internal', message: 'stub', details: {} } } }
|
||||
},
|
||||
async edit(request) {
|
||||
return { rpcId: request.rpcId, result: { ok: false, error: { code: 'internal', message: 'stub', details: {} } } }
|
||||
},
|
||||
async pause(request) {
|
||||
return { rpcId: request.rpcId, result: { ok: false, error: { code: 'internal', message: 'stub', details: {} } } }
|
||||
},
|
||||
async resume(request) {
|
||||
return { rpcId: request.rpcId, result: { ok: false, error: { code: 'internal', message: 'stub', details: {} } } }
|
||||
},
|
||||
async complete(request) {
|
||||
return { rpcId: request.rpcId, result: { ok: false, error: { code: 'internal', message: 'stub', details: {} } } }
|
||||
},
|
||||
async clear(request) {
|
||||
return { rpcId: request.rpcId, result: { ok: false, error: { code: 'internal', message: 'stub', details: {} } } }
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
async describe(request) {
|
||||
return { rpcId: request.rpcId, result: { ok: true, value: { writable: true, hasDocument: false, namespaces: [] } } }
|
||||
|
||||
@@ -14,7 +14,6 @@ import { skillEntrySchema, skillListRequestSchema, skillListValueSchema } from '
|
||||
import {
|
||||
agentPresetEntrySchema, agentPresetListValueSchema, agentPresetOpenDocumentValueSchema,
|
||||
} from '../src/api/agent-presets.schema.ts'
|
||||
import { goalEditRequestSchema } from '../src/api/goals.schema.ts'
|
||||
import { subagentPromptRequestSchema } from '../src/api/subagents.schema.ts'
|
||||
|
||||
describe('RpcId', () => {
|
||||
@@ -178,15 +177,6 @@ describe('skills domain schemas', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('goals domain schemas', () => {
|
||||
it('requires at least one replacement field for goal.edit', () => {
|
||||
const ref = { id: 'g1', revision: 1 }
|
||||
expect(goalEditRequestSchema.parse({ sessionId: 's1', ref, objective: 'updated' }).objective).toBe('updated')
|
||||
expect(goalEditRequestSchema.parse({ sessionId: 's1', ref, maxGoalRounds: 3 }).maxGoalRounds).toBe(3)
|
||||
expect(() => goalEditRequestSchema.parse({ sessionId: 's1', ref })).toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
describe('agent-preset schemas', () => {
|
||||
it('accepts a roster row and rejects an unknown trust', () => {
|
||||
expect(agentPresetEntrySchema.parse({ id: 'standard', trust: 'system', isDefault: true }))
|
||||
|
||||
Reference in New Issue
Block a user