Files
deepseek-harness/packages/goal/goal/src/domain.ts
imccyu c991f76cef feat(goal): split the pure type outlet and register the 'goal' projection unit
dsh-goal/types becomes the client-safe pure outlet (tool-todo dual-outlet
shape): GoalId/GoalRef/GoalPhase/GoalBlockReason/GoalSnapshot, the new
GoalProjection payload, and the SessionProjectionMap 'goal' key merge — zero
host imports. Host-coupled vocabulary (GoalView/activation, change metas,
message source, folds, GoalError codes, the scoped goal/changed event) moves
to src/domain.ts, re-exported from the package root. ./client re-exports the
outlet for client aggregates.

GoalService registers the projection unit under ctx.inject(['sessionProjections']):
applyGoalProjection is a projection-grade last-wins fold — plain-JSON state,
same-reference return on non-goal or malformed events (a throwing apply
would tear down the registry drive; strict validation stays with the write
side and foldGoal). Activation is process-local and deliberately absent from
the projection value.
2026-07-28 21:31:30 +08:00

138 lines
4.6 KiB
TypeScript

/**
* Host-side vocabulary of the goal domain: live views, durable change
* payloads, message attribution, replay folds, and the scoped `goal/changed`
* event. Split from ./types.ts (the pure client-safe outlet) because these
* declarations pull dsh-agent, dsh-llm, and cordis into the program — the
* one-program-per-side layout forbids that on client aggregates.
* @module @deepseek-ai/dsh-goal
*/
import type { Agent } from '@deepseek-ai/dsh-agent'
import type { GoalId, GoalRef, GoalSnapshot } from './types.ts'
/** Whether this live process may automatically continue an active goal. */
export type GoalActivation = 'armed' | 'disarmed'
/** Current goal projection, including values derived from the session log. */
export interface GoalView extends GoalSnapshot {
/** Highest admitted round number for this goal. */
readonly roundsStarted: number
/** Epoch milliseconds of the create mutation. */
readonly createdAt: number
/** Epoch milliseconds of the latest mutation. */
readonly updatedAt: number
/** Process-local continuation eligibility; never persisted. */
readonly activation: GoalActivation
}
/** Goal state-changing verbs recorded in the durable source change. */
export type GoalOperation =
| 'create'
| 'edit'
| 'pause'
| 'resume'
| 'complete'
| 'block'
| 'clear'
/** Full-snapshot goal mutation retained in a model-visible context event. */
export interface GoalSnapshotChangeMeta {
readonly kind: 'goal/change'
readonly version: 1
readonly operation: Exclude<GoalOperation, 'clear'>
readonly goal: GoalSnapshot
readonly roundsStarted: number
readonly createdAt: number
readonly updatedAt: number
}
/** Tombstone retained when the current goal is cleared. */
export interface GoalClearChangeMeta {
readonly kind: 'goal/change'
readonly version: 1
readonly operation: 'clear'
readonly cleared: GoalRef
readonly clearedAt: number
}
/** Durable change union carried by a goal-owned round-zero message source. */
export type GoalChangeMeta = GoalSnapshotChangeMeta | GoalClearChangeMeta
/** Message attribution for durable goal state and continuation rounds. */
export interface GoalMessageSource {
readonly kind: 'goal'
readonly goalId: GoalId
readonly revision: number
/** Zero for state changes; positive for admitted continuation rounds. */
readonly round: number
/** Complete durable mutation carried only by round-zero state-change messages. */
readonly change?: GoalChangeMeta
}
declare module '@deepseek-ai/dsh-llm' {
interface MessageSourceMap {
goal: GoalMessageSource
}
}
/** Pure replay fold of durable goal facts. */
export interface FoldedGoal {
/** Current goal, absent after a clear or before the first create. */
readonly goal?: GoalSnapshot
/** Highest admitted round for the current goal. */
readonly roundsStarted: number
/** Current goal creation time, absent without a current goal. */
readonly createdAt?: number
/** Current goal mutation time, absent without a current goal. */
readonly updatedAt?: number
/** Latest mutation ref, including a clear tombstone. */
readonly lastRef?: GoalRef
}
/** Input whose omitted round cap is resolved by the service configuration. */
export interface CreateGoalRequest {
readonly objective: string
readonly maxGoalRounds?: number
}
/** Fields changed by an edit; at least one must be present. */
export interface EditGoalRequest {
readonly objective?: string
readonly maxGoalRounds?: number
}
/** Live notification after one goal mutation has been accepted for logging. */
export interface GoalChanged {
readonly operation: GoalOperation
readonly ref: GoalRef
/** Absent for a clear tombstone. */
readonly goal?: GoalView
}
/** Stable error codes for rejected goal reads and mutations. */
export type GoalErrorCode =
| 'GOAL_AGENT_NOT_LIVE'
| 'GOAL_NOT_FOUND'
| 'GOAL_ALREADY_EXISTS'
| 'GOAL_STALE_REVISION'
| 'GOAL_INVALID_OBJECTIVE'
| 'GOAL_INVALID_MAX_ROUNDS'
| 'GOAL_INVALID_BLOCK_REASON'
| 'GOAL_INVALID_EDIT'
| 'GOAL_INVALID_TRANSITION'
declare module 'cordis' {
interface Events {
/**
* Goal mutation accepted by one live agent. The matching context event is
* already appended or queued in that agent's active tool-batch FIFO.
* Listener failures are contained.
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
* @param agent - agent whose session owns the goal.
* @param change - fresh current projection or clear tombstone.
* @mode emit
*/
'goal/changed'(this: import('@deepseek-ai/dsh-scope').Scoped<Agent>, agent: Agent, change: GoalChanged): void
}
}