@deepseek-ai/dsh-goal 0.2.0-rc.2 → 0.2.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -42,14 +42,14 @@
42
42
  en: 6f19e891ffc79754
43
43
  zh: 9599e3b142f1e6b5
44
44
  /deepseek-ai-dsh-goal/understand-the-implementation/source-map:
45
- en: f0451101cf73da7d
46
- zh: 73176ab69bb63a7f
45
+ en: aef62dff93f32796
46
+ zh: dba79da0466218a7
47
47
  /deepseek-ai-dsh-goal/understand-the-implementation/events-and-attribution:
48
48
  en: 502cf61dba8b22ca
49
49
  zh: 2b6f672bb2ac84ba
50
50
  /deepseek-ai-dsh-goal/further-exploration:
51
- en: d94098c1e3f3c8cf
52
- zh: f4be4de55703097a
51
+ en: 11e7ecd3115d0b6b
52
+ zh: d1dff8b7d05a2f09
53
53
  /deepseek-ai-dsh-goal/model-experience:
54
54
  en: 215e7ba838619b7b
55
55
  zh: a311da3843709f90
package/README.md CHANGED
@@ -109,7 +109,6 @@ This section explains how the service realizes the behavior above; the observabl
109
109
  | [`src/types.ts`](src/types.ts) | Pure client-safe types: `GoalView`, `GoalSnapshot`, `GoalActivationChanged`, projection-key declaration |
110
110
  | [`src/fold.ts`](src/fold.ts) | Strict replay fold and decoder for durable goal changes |
111
111
  | [`src/runtime.ts`](src/runtime.ts) | `GoalId` brand, `GoalError` codes, change-version constant |
112
- | [`src/invariant.ts`](src/invariant.ts) | Invariant companion: independent incremental fold over every attached session |
113
112
 
114
113
  ### Events and attribution
115
114
 
@@ -127,7 +126,7 @@ The package-level contract is enough for most consumers; read these when you nee
127
126
  - [Goal subsystem](../../../docs/subsystems/goal.md) — the goal types, durable change payloads, and generated service API.
128
127
  - [Goal group map](../README.md) — the goal packages and how they compose.
129
128
  - [Generated configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-goal) — every accepted config field and its source declaration.
130
- - [Goal domain Agent Note](../../../.agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.md) — the domain design, alternatives, and decisions.
129
+ - [historical Goal domain Agent Note](../../../.agents/notes/archived/feature/2026-07-19-persisted-same-session-goal-domain.md) — the domain design, alternatives, and decisions.
131
130
 
132
131
  -----
133
132
 
package/README.zh.md CHANGED
@@ -109,7 +109,6 @@ view.activation // 'armed' | 'disarmed' — not persisted
109
109
  | [`src/types.ts`](src/types.ts) | 纯客户端安全类型:`GoalView`、`GoalSnapshot`、`GoalActivationChanged`、投影键声明 |
110
110
  | [`src/fold.ts`](src/fold.ts) | 持久 goal 变更的严格回放折叠与解码器 |
111
111
  | [`src/runtime.ts`](src/runtime.ts) | `GoalId` 品牌、`GoalError` 代码、变更版本常量 |
112
- | [`src/invariant.ts`](src/invariant.ts) | 不变式配套模块:对每个已挂接会话的独立增量折叠 |
113
112
 
114
113
  ### 事件与归属
115
114
 
@@ -127,7 +126,7 @@ view.activation // 'armed' | 'disarmed' — not persisted
127
126
  - [goal 子系统](../../../docs/subsystems/goal.zh.md)——goal 类型、持久的变更载荷与生成的服务 API。
128
127
  - [goal 组地图](../README.zh.md)——goal 各包及其组合方式。
129
128
  - [生成的配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-goal)——每个受支持配置字段及其源声明。
130
- - [goal 领域 Agent Note](../../../.agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.zh.md)——领域设计、备选方案与决策。
129
+ - [历史goal 领域 Agent Note](../../../.agents/notes/archived/feature/2026-07-19-persisted-same-session-goal-domain.md)——领域设计、备选方案与决策。
131
130
 
132
131
  -----
133
132
 
@@ -785,6 +785,14 @@ export const TYPERT = {
785
785
  "name": "LlmFailure",
786
786
  "declaration": "export interface LlmFailure {\n readonly message: string;\n readonly code: string;\n readonly status?: number;\n readonly providerRetryAfterMs?: number;\n readonly requestId?: ProviderRequestId;\n readonly offloadImages?: number;\n}"
787
787
  },
788
+ {
789
+ "name": "LlmRetryEventData",
790
+ "declaration": "export type LlmRetryEventData = { retryId: RetryId; turn: number; step: number; provider: string; mode: 'normal'; policyKey: string; retry: number; maxRetries: number; delayMs: number; failure: LlmFailure; } | { retryId: RetryId; turn: number; step: number; provider: string; mode: 'always'; policyKey: string; retry: number; delayMs: number; failure: LlmFailure; };"
791
+ },
792
+ {
793
+ "name": "LlmRetryStartedEventData",
794
+ "declaration": "export interface LlmRetryStartedEventData {\n retryId: RetryId;\n turn: number;\n step: number;\n retry: number;\n}"
795
+ },
788
796
  {
789
797
  "name": "Message",
790
798
  "declaration": "export type Message = MessageRoleMap[keyof MessageRoleMap];"
@@ -827,7 +835,7 @@ export const TYPERT = {
827
835
  },
828
836
  {
829
837
  "name": "MessageSourceMap",
830
- "declaration": "export interface MessageSourceMap {\n user: { kind: 'user'; };\n model: ModelMessageSource;\n tool: ToolMessageSource;\n 'system-prompt': SystemPromptMessageSource;\n 'model-selection': { kind: 'model-selection'; } & ContextFormed;\n 'user-approval': { kind: 'user-approval'; } & ContextFormed;\n 'ptc-mode': { kind: 'ptc-mode'; };\n 'tool-registry': { kind: 'tool-registry'; };\n 'user-rpc': { kind: 'user'; rpcId: SessionRequestId; clientTimeZone?: string; };\n 'agent-message': AgentMessageSource;\n 'subagent-settled': SubagentSettledMessageSource;\n 'skill-invocation': SkillInvocationSource;\n 'cordis-host-runner': { kind: 'cordis-host-runner'; };\n goal: GoalMessageSource;\n schedule: { kind: 'schedule'; } & ContextFormed;\n 'compact-checkpoint': CompactionCheckpointSource;\n 'session-reference': SessionReferenceSource;\n 'user-question-reply': { kind: 'user-question-reply'; callId: ToolCallId; outcome: 'answered'; };\n}"
838
+ "declaration": "export interface MessageSourceMap {\n user: { kind: 'user'; };\n model: ModelMessageSource;\n tool: ToolMessageSource;\n 'system-prompt': SystemPromptMessageSource;\n 'model-selection': { kind: 'model-selection'; } & ContextFormed;\n 'user-approval': { kind: 'user-approval'; } & ContextFormed;\n 'ptc-mode': { kind: 'ptc-mode'; };\n 'tool-registry': { kind: 'tool-registry'; };\n 'user-rpc': { kind: 'user'; rpcId: SessionRequestId; clientTimeZone?: string; };\n 'agent-message': AgentMessageSource;\n 'subagent-settled': SubagentSettledMessageSource;\n 'skill-invocation': SkillInvocationSource;\n 'cordis-host-runner': { kind: 'cordis-host-runner'; };\n 'compact-checkpoint': CompactionCheckpointSource;\n 'user-question-reply': { kind: 'user-question-reply'; callId: ToolCallId; outcome: 'answered'; };\n goal: GoalMessageSource;\n schedule: { kind: 'schedule'; } & ContextFormed;\n 'session-reference': SessionReferenceSource;\n}"
831
839
  },
832
840
  {
833
841
  "name": "ModelMessageSource",
@@ -881,6 +889,10 @@ export const TYPERT = {
881
889
  "name": "RequestHeaderReason",
882
890
  "declaration": "export type RequestHeaderReason = 'initial' | 'resume' | 'change' | 'series';"
883
891
  },
892
+ {
893
+ "name": "RetryId",
894
+ "declaration": "export type RetryId = Branded<'RetryId'>;"
895
+ },
884
896
  {
885
897
  "name": "SandboxMode",
886
898
  "declaration": "export type SandboxMode = 'read-only' | 'workspace-write' | 'danger-full-access';"
@@ -915,7 +927,7 @@ export const TYPERT = {
915
927
  },
916
928
  {
917
929
  "name": "SessionEventMap",
918
- "declaration": "export interface SessionEventMap {\n 'turn/start': { turn: number; };\n 'turn/end': { turn: number; reason: TurnEndReason; };\n 'step/start': { turn: number; step: number; };\n 'step/end': { turn: number; step: number; };\n 'user/message': UserMessage;\n 'developer/message': { turn: number; step: number; message: DeveloperMessage; headerSeq?: SessionSeq; };\n 'system/message': { turn: number; step: number; message: SystemMessage; };\n 'assistant/message': { turn: number; step: number; message: AssistantMessage; stream: AssistantStreamRecord[]; usage?: TokenUsage; interrupted?: true; };\n 'assistant/attempt': { turn: number; step: number; stream: AssistantStreamRecord[]; };\n 'tool/call': { turn: number; step: number; callId: ToolCallId; name: string; arguments: string; };\n 'tool/result': { turn: number; step: number; message: ToolResultMessage; error?: { name: string; code: string; reason?: string; }; meta?: JsonValue; };\n 'request/header': { header: EpochHeader; reason: RequestHeaderReason; startsSeries?: true; };\n 'request/context': RequestContext;\n 'session/end-seed': { inherited?: true; };\n 'agent/inbox/spliced': { target: InboxTarget; start: number; removedCount?: number; inserted: UserMessage[]; outcome?: 'canceled'; };\n 'sandbox/mode': { mode: SandboxMode; source?: 'delegation'; };\n 'approval/asked': { id: ApprovalRequestId; toolName: string; callId?: ToolCallId; reason?: string; };\n 'approval/decided': { id: ApprovalRequestId; outcome: ApprovalOutcome; };\n 'approval/policy': { policy: ApprovalPolicy; source?: 'delegation'; };\n 'tool/ptc-dispatch-start': PtcDispatchStartEventData;\n 'tool/ptc-dispatch': PtcDispatchEventData;\n 'agent-preset/selected': { agentPreset: string; };\n 'command/run': { commandId: CommandId; name: string; args?: string; source: CommandSource; };\n 'command/done': { commandId: CommandId; kind: 'success' | 'error'; text?: string; sourceEventSeq?: import('@deepseek-ai/dsh-session/types').SessionSeq; };\n 'image/offload': { targets: ImageOffloadTarget[]; };\n 'session/title': SessionTitleEventData;\n 'todo/write': { todos: TodoItem[]; };\n 'model/selection': ModelSelection;\n 'subagent/descriptor': SubagentDescriptorData;\n 'permission/preset': { preset: string; };\n 'subagent/catalog': SubagentCatalogEvent;\n 'compaction/start': { compactionId: CompactionId; sourceCommandId?: CommandId; turn: number | null; };\n 'compaction/summary': { compactionId: CompactionId; sourceCommandId?: CommandId; summary: ContentBlock[]; shadowedRange: { start: SessionSeq; end: SessionSeq; }; shadowedSeqs: SessionSeq[]; shadowedTokenCount: number; provider: string; model: string; maxTokens?: number; usage?: TokenUsage; } & ({ rawOutput: ContentBlock[]; llmStreamCall: true; } | { rawOutput?: ContentBlock[]; llmStreamCall?: never; });\n 'compaction/end': { compactionId: CompactionId; sourceCommandId?: CommandId; turn: number | null; error?: string; };\n 'compaction/prune': { shadowedRange: { start: SessionSeq; end: SessionSeq; }; shadowedSeqs: SessionSeq[]; shadowedTokenCount: number; };\n 'feedback/record': FeedbackRecord;\n 'goal/change': GoalChangeMeta;\n 'feedback/message-put': MessageFeedbackPut;\n 'feedback/message-delete': MessageFeedbackDelete;\n 'schedule/change': ScheduleChange;\n}"
930
+ "declaration": "export interface SessionEventMap {\n 'turn/start': { turn: number; };\n 'turn/end': { turn: number; reason: TurnEndReason; };\n 'step/start': { turn: number; step: number; };\n 'step/end': { turn: number; step: number; };\n 'user/message': UserMessage;\n 'developer/message': { turn: number; step: number; message: DeveloperMessage; headerSeq?: SessionSeq; };\n 'system/message': { turn: number; step: number; message: SystemMessage; };\n 'assistant/message': { turn: number; step: number; message: AssistantMessage; stream: AssistantStreamRecord[]; usage?: TokenUsage; interrupted?: true; };\n 'assistant/attempt': { turn: number; step: number; stream: AssistantStreamRecord[]; };\n 'tool/call': { turn: number; step: number; callId: ToolCallId; name: string; arguments: string; };\n 'tool/result': { turn: number; step: number; message: ToolResultMessage; error?: { name: string; code: string; reason?: string; }; meta?: JsonValue; };\n 'request/header': { header: EpochHeader; reason: RequestHeaderReason; startsSeries?: true; };\n 'request/context': RequestContext;\n 'session/end-seed': { inherited?: true; };\n 'agent/inbox/spliced': { target: InboxTarget; start: number; removedCount?: number; inserted: UserMessage[]; outcome?: 'canceled'; };\n 'sandbox/mode': { mode: SandboxMode; source?: 'delegation'; };\n 'approval/asked': { id: ApprovalRequestId; toolName: string; callId?: ToolCallId; reason?: string; };\n 'approval/decided': { id: ApprovalRequestId; outcome: ApprovalOutcome; };\n 'approval/policy': { policy: ApprovalPolicy; source?: 'delegation'; };\n 'tool/ptc-dispatch-start': PtcDispatchStartEventData;\n 'tool/ptc-dispatch': PtcDispatchEventData;\n 'agent-preset/selected': { agentPreset: string; };\n 'command/run': { commandId: CommandId; name: string; args?: string; source: CommandSource; };\n 'command/done': { commandId: CommandId; kind: 'success' | 'error'; text?: string; sourceEventSeq?: import('@deepseek-ai/dsh-session/types').SessionSeq; };\n 'image/offload': { targets: ImageOffloadTarget[]; };\n 'session/title': SessionTitleEventData;\n 'todo/write': { todos: TodoItem[]; };\n 'model/selection': ModelSelection;\n 'subagent/descriptor': SubagentDescriptorData;\n 'permission/preset': { preset: string; };\n 'subagent/catalog': SubagentCatalogEvent;\n 'compaction/start': { compactionId: CompactionId; sourceCommandId?: CommandId; turn: number | null; };\n 'compaction/summary': { compactionId: CompactionId; sourceCommandId?: CommandId; summary: ContentBlock[]; shadowedRange: { start: SessionSeq; end: SessionSeq; }; shadowedSeqs: SessionSeq[]; shadowedTokenCount: number; provider: string; model: string; maxTokens?: number; usage?: TokenUsage; } & ({ rawOutput: ContentBlock[]; llmStreamCall: true; } | { rawOutput?: ContentBlock[]; llmStreamCall?: never; });\n 'compaction/end': { compactionId: CompactionId; sourceCommandId?: CommandId; turn: number | null; error?: string; };\n 'compaction/prune': { shadowedRange: { start: SessionSeq; end: SessionSeq; }; shadowedSeqs: SessionSeq[]; shadowedTokenCount: number; };\n 'feedback/record': FeedbackRecord;\n 'llm/retry': LlmRetryEventData;\n 'llm/retry-started': LlmRetryStartedEventData;\n 'goal/change': GoalChangeMeta;\n 'feedback/message-put': MessageFeedbackPut;\n 'feedback/message-delete': MessageFeedbackDelete;\n 'schedule/change': ScheduleChange;\n}"
919
931
  },
920
932
  {
921
933
  "name": "SessionEventType",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-goal",
3
3
  "description": "Event-sourced same-session goal state and lifecycle service for the DeepSeek Harness",
4
- "version": "0.2.0-rc.2",
4
+ "version": "0.2.1-alpha.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -18,10 +18,6 @@
18
18
  "types": "./lib/types/index.d.ts",
19
19
  "default": "./lib/index.js"
20
20
  },
21
- "./invariant": {
22
- "types": "./lib/types/invariant.d.ts",
23
- "default": "./lib/invariant.js"
24
- },
25
21
  "./types": {
26
22
  "types": "./lib/types/types.d.ts",
27
23
  "default": "./lib/types/types.js"
@@ -43,7 +39,6 @@
43
39
  },
44
40
  "files": [
45
41
  "lib/index.js",
46
- "lib/invariant.js",
47
42
  "lib/types/**/*.js",
48
43
  "lib/types/**/*.d.ts",
49
44
  "lib/typert.host.js",
@@ -53,36 +48,34 @@
53
48
  ],
54
49
  "license": "MIT",
55
50
  "peerDependencies": {
56
- "@deepseek-ai/dsh-session-projection": "0.2.0-rc.2",
57
- "@deepseek-ai/dsh-brand": "0.2.0-rc.2",
58
- "@deepseek-ai/dsh-invariants": "0.2.0-rc.2",
59
- "@deepseek-ai/dsh-agent": "0.2.0-rc.2",
60
- "@deepseek-ai/dsh-llm": "0.2.0-rc.2",
61
- "@deepseek-ai/dsh-session": "0.2.0-rc.2",
62
- "@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.2",
63
- "@deepseek-ai/cordis": "~4.0.4",
64
- "@deepseek-ai/dsh-scope": "0.2.0-rc.2"
51
+ "@deepseek-ai/dsh-agent": "0.2.1-alpha.1",
52
+ "@deepseek-ai/dsh-session-projection": "0.2.1-alpha.1",
53
+ "@deepseek-ai/dsh-brand": "0.2.1-alpha.1",
54
+ "@deepseek-ai/dsh-llm": "0.2.1-alpha.1",
55
+ "@deepseek-ai/dsh-scope": "0.2.1-alpha.1",
56
+ "@deepseek-ai/dsh-session": "0.2.1-alpha.1",
57
+ "@deepseek-ai/dsh-typert-protocol": "0.2.1-alpha.1",
58
+ "@deepseek-ai/cordis": "~4.0.5-alpha.1"
65
59
  },
66
60
  "dependencies": {
67
61
  "zod": "^4.4.3",
68
- "@deepseek-ai/schemastery": "~3.18.4"
62
+ "@deepseek-ai/schemastery": "~3.18.5-alpha.1"
69
63
  },
70
64
  "devDependencies": {
71
- "@deepseek-ai/dsh-agent": "0.2.0-rc.2",
72
- "@deepseek-ai/dsh-bash-local": "0.2.0-rc.2",
73
- "@deepseek-ai/dsh-agent-loop": "0.2.0-rc.2",
74
- "@deepseek-ai/dsh-agent-loop-testkit": "0.2.0-rc.2",
75
- "@deepseek-ai/dsh-session-projection": "0.2.0-rc.2",
76
- "@deepseek-ai/dsh-brand": "0.2.0-rc.2",
77
- "@deepseek-ai/dsh-invariants": "0.2.0-rc.2",
78
- "@deepseek-ai/dsh-scope": "0.2.0-rc.2",
79
- "@deepseek-ai/dsh-session": "0.2.0-rc.2",
80
- "@deepseek-ai/dsh-session-checkpoint-policy": "0.2.0-rc.2",
81
- "@deepseek-ai/dsh-session-persistence-jsonl": "0.2.0-rc.2",
82
- "@deepseek-ai/dsh-subprocess-local": "0.2.0-rc.2",
83
- "@deepseek-ai/cordis": "~4.0.4",
84
- "@deepseek-ai/dsh-loader-smoke": "0.2.0-rc.2",
85
- "@deepseek-ai/dsh-llm": "0.2.0-rc.2",
86
- "@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.2"
65
+ "@deepseek-ai/dsh-agent": "0.2.1-alpha.1",
66
+ "@deepseek-ai/dsh-agent-loop": "0.2.1-alpha.1",
67
+ "@deepseek-ai/dsh-agent-loop-testkit": "0.2.1-alpha.1",
68
+ "@deepseek-ai/dsh-bash-local": "0.2.1-alpha.1",
69
+ "@deepseek-ai/dsh-session-projection": "0.2.1-alpha.1",
70
+ "@deepseek-ai/dsh-brand": "0.2.1-alpha.1",
71
+ "@deepseek-ai/dsh-scope": "0.2.1-alpha.1",
72
+ "@deepseek-ai/dsh-loader-smoke": "0.2.1-alpha.1",
73
+ "@deepseek-ai/dsh-llm": "0.2.1-alpha.1",
74
+ "@deepseek-ai/dsh-session": "0.2.1-alpha.1",
75
+ "@deepseek-ai/dsh-session-persistence-jsonl": "0.2.1-alpha.1",
76
+ "@deepseek-ai/dsh-subprocess-local": "0.2.1-alpha.1",
77
+ "@deepseek-ai/dsh-typert-protocol": "0.2.1-alpha.1",
78
+ "@deepseek-ai/cordis": "~4.0.5-alpha.1",
79
+ "@deepseek-ai/dsh-session-checkpoint-policy": "0.2.1-alpha.1"
87
80
  }
88
81
  }
package/lib/invariant.js DELETED
@@ -1,332 +0,0 @@
1
- import "@deepseek-ai/dsh-llm";
2
- //#region lib/types/runtime.js
3
- /**
4
- * Brand a string as a goal id.
5
- * @param id - raw goal identifier.
6
- * @returns the same string with the compile-time brand.
7
- */
8
- function GoalId(id) {
9
- return id;
10
- }
11
- //#endregion
12
- //#region lib/types/fold.js
13
- /** Pure replay fold and strict decoder for durable goal changes. */
14
- const SNAPSHOT_OPERATIONS = new Set([
15
- "create",
16
- "edit",
17
- "pause",
18
- "resume",
19
- "complete",
20
- "block"
21
- ]);
22
- const PHASES = new Set([
23
- "active",
24
- "paused",
25
- "blocked",
26
- "complete"
27
- ]);
28
- /**
29
- * Build an empty replay accumulator.
30
- * @returns mutable state with no current goal or prior ref.
31
- */
32
- function emptyGoalFoldState() {
33
- return {
34
- goal: void 0,
35
- roundsStarted: 0,
36
- createdAt: void 0,
37
- updatedAt: void 0,
38
- lastRef: void 0,
39
- seenGoalIds: /* @__PURE__ */ new Set()
40
- };
41
- }
42
- /** Whether a value is a JSON record rather than an array. */
43
- function isRecord(value) {
44
- return typeof value === "object" && value !== null && !Array.isArray(value);
45
- }
46
- /** Require one positive safe integer. */
47
- function positiveInteger(value, field) {
48
- if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1) throw new Error(`goal change ${field} must be a positive safe integer`);
49
- return value;
50
- }
51
- /** Require one non-negative safe integer. */
52
- function nonNegativeInteger(value, field) {
53
- if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw new Error(`goal change ${field} must be a non-negative safe integer`);
54
- return value;
55
- }
56
- /** Decode one canonical blocker explanation. */
57
- function decodeBlockReason(value) {
58
- if (!isRecord(value) || Object.keys(value).sort().join(",") !== "code,message") throw new Error("goal change goal.blockedReason must have exactly code and message fields");
59
- if (typeof value["code"] !== "string" || !/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(value["code"])) throw new Error("goal change goal.blockedReason.code must be lower-kebab-case");
60
- if (typeof value["message"] !== "string" || value["message"].trim().length === 0 || value["message"] !== value["message"].trim()) throw new Error("goal change goal.blockedReason.message must be non-empty and normalized");
61
- return {
62
- code: value["code"],
63
- message: value["message"]
64
- };
65
- }
66
- /** Decode and validate one snapshot. */
67
- function decodeSnapshot(value) {
68
- if (!isRecord(value)) throw new Error("goal change goal must be a record");
69
- if (typeof value["id"] !== "string" || value["id"].length === 0) throw new Error("goal change goal.id must be a non-empty string");
70
- if (typeof value["objective"] !== "string" || value["objective"].trim().length === 0 || value["objective"] !== value["objective"].trim()) throw new Error("goal change goal.objective must be non-empty and normalized");
71
- if (typeof value["phase"] !== "string" || !PHASES.has(value["phase"])) throw new Error("goal change goal.phase is invalid");
72
- const phase = value["phase"];
73
- const expectedKeys = phase === "blocked" ? "blockedReason,id,maxGoalRounds,objective,phase,revision" : "id,maxGoalRounds,objective,phase,revision";
74
- if (Object.keys(value).sort().join(",") !== expectedKeys) throw new Error(`goal change goal for phase ${phase} must have exactly ${expectedKeys} fields`);
75
- return {
76
- id: GoalId(value["id"]),
77
- revision: positiveInteger(value["revision"], "goal.revision"),
78
- objective: value["objective"],
79
- phase,
80
- maxGoalRounds: positiveInteger(value["maxGoalRounds"], "goal.maxGoalRounds"),
81
- ...phase === "blocked" ? { blockedReason: decodeBlockReason(value["blockedReason"]) } : {}
82
- };
83
- }
84
- /** Decode and validate one ref. */
85
- function decodeRef(value) {
86
- if (!isRecord(value) || Object.keys(value).sort().join(",") !== "id,revision") throw new Error("goal clear tombstone must have exactly id and revision fields");
87
- if (typeof value["id"] !== "string" || value["id"].length === 0) throw new Error("goal clear tombstone id must be a non-empty string");
88
- return {
89
- id: GoalId(value["id"]),
90
- revision: positiveInteger(value["revision"], "cleared.revision")
91
- };
92
- }
93
- /**
94
- * Decode a value that declares itself as a goal change. Unrelated values
95
- * return `undefined`; malformed goal changes fail replay loudly.
96
- * @param value - candidate source change.
97
- * @returns validated goal change or `undefined` for another value kind.
98
- */
99
- function decodeGoalChange(value) {
100
- if (!isRecord(value) || value["kind"] !== "goal/change") return void 0;
101
- if (value["version"] !== 1) throw new Error(`unsupported goal change version ${String(value["version"])}`);
102
- if (value["operation"] === "clear") {
103
- const allowed = [
104
- "cleared",
105
- "clearedAt",
106
- "kind",
107
- "operation",
108
- "version"
109
- ];
110
- if (Object.keys(value).sort().join(",") !== allowed.sort().join(",")) throw new Error(`goal clear change must have exactly ${allowed.sort().join(",")} fields`);
111
- return {
112
- kind: "goal/change",
113
- version: 1,
114
- operation: "clear",
115
- cleared: decodeRef(value["cleared"]),
116
- clearedAt: nonNegativeInteger(value["clearedAt"], "clearedAt")
117
- };
118
- }
119
- if (typeof value["operation"] !== "string" || !SNAPSHOT_OPERATIONS.has(value["operation"])) throw new Error("goal change operation is invalid");
120
- const allowed = [
121
- "createdAt",
122
- "goal",
123
- "kind",
124
- "operation",
125
- "roundsStarted",
126
- "updatedAt",
127
- "version"
128
- ];
129
- if (Object.keys(value).sort().join(",") !== allowed.sort().join(",")) throw new Error(`goal snapshot change must have exactly ${allowed.sort().join(",")} fields`);
130
- const createdAt = nonNegativeInteger(value["createdAt"], "createdAt");
131
- const updatedAt = nonNegativeInteger(value["updatedAt"], "updatedAt");
132
- if (updatedAt < createdAt) throw new Error("goal change updatedAt cannot precede createdAt");
133
- return {
134
- kind: "goal/change",
135
- version: 1,
136
- operation: value["operation"],
137
- goal: decodeSnapshot(value["goal"]),
138
- roundsStarted: nonNegativeInteger(value["roundsStarted"], "roundsStarted"),
139
- createdAt,
140
- updatedAt
141
- };
142
- }
143
- /** Narrow model attribution to a valid goal source. */
144
- function goalSource(source) {
145
- if (source.kind !== "goal") return void 0;
146
- if (typeof source.goalId !== "string" || source.goalId.length === 0 || !Number.isSafeInteger(source.revision) || source.revision < 1 || !Number.isSafeInteger(source.round) || source.round < 1) throw new Error("goal message source is invalid");
147
- return source;
148
- }
149
- /** Require two snapshots to retain fields that only `edit` may replace. */
150
- function requireSameDefinition(current, next, operation) {
151
- if (next.objective !== current.objective || next.maxGoalRounds !== current.maxGoalRounds) throw new Error(`goal ${operation} cannot change objective or maxGoalRounds`);
152
- }
153
- /** Require one exact next revision of the current goal. */
154
- function requireNextRevision(current, next, operation) {
155
- if (next.id !== current.id || next.revision !== current.revision + 1) throw new Error(`goal ${operation} must advance the current goal by one revision`);
156
- }
157
- /** Validate one non-create snapshot operation against the preceding projection. */
158
- function validateSnapshotTransition(state, change, current) {
159
- const next = change.goal;
160
- requireNextRevision(current, next, change.operation);
161
- /* v8 ignore next -- a current goal established by this fold always has an updatedAt */
162
- if (state.updatedAt === void 0) throw new Error("current goal fold lacks updatedAt");
163
- if (change.createdAt !== state.createdAt || change.updatedAt < state.updatedAt || change.roundsStarted !== state.roundsStarted) throw new Error(`goal ${change.operation} does not preserve the current counters and timestamps`);
164
- switch (change.operation) {
165
- case "edit":
166
- if (next.phase !== current.phase || JSON.stringify(next.blockedReason) !== JSON.stringify(current.blockedReason)) throw new Error("goal edit cannot change phase or blocked reason");
167
- break;
168
- case "pause":
169
- requireSameDefinition(current, next, change.operation);
170
- if (current.phase !== "active" || next.phase !== "paused") throw new Error("goal pause has an invalid phase transition");
171
- break;
172
- case "resume":
173
- requireSameDefinition(current, next, change.operation);
174
- if (!new Set([
175
- "active",
176
- "paused",
177
- "blocked"
178
- ]).has(current.phase) || next.phase !== "active" || state.roundsStarted >= next.maxGoalRounds) throw new Error("goal resume has an invalid phase transition or exhausted round budget");
179
- break;
180
- case "complete":
181
- requireSameDefinition(current, next, change.operation);
182
- if (current.phase === "complete" || next.phase !== "complete") throw new Error("goal complete has an invalid phase transition");
183
- break;
184
- case "block":
185
- requireSameDefinition(current, next, change.operation);
186
- if (current.phase !== "active" || next.phase !== "blocked") throw new Error("goal block has an invalid phase transition");
187
- break;
188
- /* v8 ignore start -- the caller excludes create and GoalOperation is closed; these arms retain fail-loud exhaustiveness */
189
- case "create": throw new Error("goal create cannot be validated as a current-goal transition");
190
- default:
191
- change.operation;
192
- throw new Error("unknown goal snapshot operation");
193
- }
194
- }
195
- /**
196
- * Return the revision identity carried by a snapshot or tombstone.
197
- * @param change - decoded goal mutation.
198
- * @returns stable identity used to reconcile a deferred change with its log event.
199
- */
200
- function goalChangeRef(change) {
201
- return change.operation === "clear" ? change.cleared : {
202
- id: change.goal.id,
203
- revision: change.goal.revision
204
- };
205
- }
206
- /**
207
- * Validate and apply one decoded change to a mutable accumulator.
208
- * @param state - preceding durable goal projection.
209
- * @param change - decoded full snapshot or clear tombstone.
210
- */
211
- function applyGoalChange(state, change) {
212
- const ref = goalChangeRef(change);
213
- if (change.operation === "clear") {
214
- const current = state.goal;
215
- if (current === void 0) throw new Error("goal clear requires a current goal");
216
- requireNextRevision(current, change.cleared, change.operation);
217
- /* v8 ignore next -- a current goal established by this fold always has an updatedAt */
218
- if (state.updatedAt === void 0) throw new Error("current goal fold lacks updatedAt");
219
- if (change.clearedAt < state.updatedAt) throw new Error("goal clear timestamp cannot precede the current goal update");
220
- state.goal = void 0;
221
- state.roundsStarted = 0;
222
- state.createdAt = void 0;
223
- state.updatedAt = void 0;
224
- state.lastRef = ref;
225
- return;
226
- }
227
- if (change.operation === "create") {
228
- if (change.goal.revision !== 1 || change.goal.phase !== "active" || change.roundsStarted !== 0 || state.goal !== void 0 && state.goal.phase !== "complete" || state.seenGoalIds.has(change.goal.id)) throw new Error("goal create requires a fresh active revision-one goal with zero rounds");
229
- state.seenGoalIds.add(change.goal.id);
230
- } else {
231
- const current = state.goal;
232
- if (current === void 0) throw new Error(`goal ${change.operation} requires a current goal`);
233
- validateSnapshotTransition(state, change, current);
234
- }
235
- state.goal = change.goal;
236
- state.roundsStarted = change.roundsStarted;
237
- state.createdAt = change.createdAt;
238
- state.updatedAt = change.updatedAt;
239
- state.lastRef = ref;
240
- }
241
- /**
242
- * Apply one session event to the strict durable goal fold.
243
- * @param state - mutable fold accumulator.
244
- * @param event - next event in sequence order.
245
- */
246
- function applyGoalEvent(state, event) {
247
- if (event.type === "goal/change") {
248
- const change = decodeGoalChange(event.data);
249
- /* v8 ignore next -- the event's declared payload always identifies itself as a goal change. */
250
- if (change === void 0) throw new Error(`goal change at session event ${event.seq} has an invalid kind`);
251
- applyGoalChange(state, change);
252
- return;
253
- }
254
- if (event.type === "user/message") {
255
- const source = goalSource(event.data.source);
256
- if (source === void 0) return;
257
- const current = state.goal;
258
- if (current === void 0 || current.phase !== "active" || source.goalId !== current.id || source.revision !== current.revision || source.round !== state.roundsStarted + 1 || source.round > current.maxGoalRounds) throw new Error(`goal round at session event ${event.seq} is not the next admitted round of the active goal`);
259
- state.roundsStarted = source.round;
260
- }
261
- }
262
- //#endregion
263
- //#region lib/types/invariant.js
264
- /** Package-owned durable goal-stream invariants. @module @deepseek-ai/dsh-goal/invariant */
265
- const PACKAGE_NAME = "@deepseek-ai/dsh-goal";
266
- /** Cordis companion plugin name. */
267
- const name = "goal-invariant";
268
- /** Service required before the companion can reserve package ownership. */
269
- const inject = ["invariants"];
270
- /** Copy the independent fold before validating one candidate event. */
271
- function cloneState(state) {
272
- return {
273
- goal: state.goal,
274
- roundsStarted: state.roundsStarted,
275
- createdAt: state.createdAt,
276
- updatedAt: state.updatedAt,
277
- lastRef: state.lastRef,
278
- seenGoalIds: new Set(state.seenGoalIds)
279
- };
280
- }
281
- /** Apply one event through the strict goal decoder and attribute failures. */
282
- function applyChecked(state, event, fail) {
283
- try {
284
- applyGoalEvent(state, event);
285
- } catch (error) {
286
- /* v8 ignore next -- the strict goal decoder throws Error instances */
287
- const message = error instanceof Error ? error.message : String(error);
288
- fail(`session event ${event.seq} violates the durable goal stream: ${message}`);
289
- }
290
- }
291
- /** Install an independent incremental fold over every attached session. */
292
- const install = Object.assign((ctx, fail) => {
293
- const states = /* @__PURE__ */ new WeakMap();
294
- const staged = /* @__PURE__ */ new WeakMap();
295
- const seed = (session) => {
296
- const state = emptyGoalFoldState();
297
- for (const event of session.snapshotEvents()) applyChecked(state, event, fail);
298
- states.set(session, state);
299
- return state;
300
- };
301
- /* v8 ignore next -- session/event always follows list() or session/created seeding */
302
- const stateFor = (session) => states.get(session) ?? seed(session);
303
- for (const session of ctx.sessions.list()) seed(session);
304
- ctx.on("session/created", (session) => {
305
- seed(session);
306
- }, { global: true });
307
- ctx.on("internal/dispatch", (_mode, eventName, args) => {
308
- if (eventName !== "session/event") return;
309
- const [session, event] = args;
310
- const state = cloneState(stateFor(session));
311
- applyChecked(state, event, fail);
312
- staged.set(event, {
313
- session,
314
- state
315
- });
316
- }, { global: true });
317
- ctx.on("session/event", (session, event) => {
318
- const candidate = staged.get(event);
319
- /* v8 ignore next 2 -- internal/dispatch stages the exact callback arguments */
320
- if (candidate === void 0 || candidate.session !== session) return fail("session/event reached publication without matching goal-fold validation");
321
- staged.delete(event);
322
- states.set(session, candidate.state);
323
- }, { global: true });
324
- }, { inject: ["sessions"] });
325
- /**
326
- * Register the goal-stream invariant companion.
327
- * @param ctx - Cordis context carrying the invariant service.
328
- * @returns the installed registration's disposer after setup succeeds.
329
- */
330
- const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
331
- //#endregion
332
- export { apply, inject, name };
@@ -1,13 +0,0 @@
1
- /** Package-owned durable goal-stream invariants. @module @deepseek-ai/dsh-goal/invariant */
2
- import type { Context } from '@deepseek-ai/cordis';
3
- /** Cordis companion plugin name. */
4
- export declare const name = "goal-invariant";
5
- /** Service required before the companion can reserve package ownership. */
6
- export declare const inject: string[];
7
- /**
8
- * Register the goal-stream invariant companion.
9
- * @param ctx - Cordis context carrying the invariant service.
10
- * @returns the installed registration's disposer after setup succeeds.
11
- */
12
- export declare const apply: (ctx: Context) => Promise<() => void>;
13
- //# sourceMappingURL=invariant.d.ts.map
@@ -1,71 +0,0 @@
1
- /** Package-owned durable goal-stream invariants. @module @deepseek-ai/dsh-goal/invariant */
2
- import { applyGoalEvent, emptyGoalFoldState } from "./fold.js";
3
- const PACKAGE_NAME = '@deepseek-ai/dsh-goal';
4
- /** Cordis companion plugin name. */
5
- export const name = 'goal-invariant';
6
- /** Service required before the companion can reserve package ownership. */
7
- export const inject = ['invariants'];
8
- /** Copy the independent fold before validating one candidate event. */
9
- function cloneState(state) {
10
- return {
11
- goal: state.goal,
12
- roundsStarted: state.roundsStarted,
13
- createdAt: state.createdAt,
14
- updatedAt: state.updatedAt,
15
- lastRef: state.lastRef,
16
- seenGoalIds: new Set(state.seenGoalIds),
17
- };
18
- }
19
- /** Apply one event through the strict goal decoder and attribute failures. */
20
- function applyChecked(state, event, fail) {
21
- try {
22
- applyGoalEvent(state, event);
23
- }
24
- catch (error) {
25
- /* v8 ignore next -- the strict goal decoder throws Error instances */
26
- const message = error instanceof Error ? error.message : String(error);
27
- fail(`session event ${event.seq} violates the durable goal stream: ${message}`);
28
- }
29
- }
30
- /** Install an independent incremental fold over every attached session. */
31
- const install = Object.assign((ctx, fail) => {
32
- const states = new WeakMap();
33
- const staged = new WeakMap();
34
- const seed = (session) => {
35
- const state = emptyGoalFoldState();
36
- // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred.
37
- for (const event of session.snapshotEvents())
38
- applyChecked(state, event, fail);
39
- states.set(session, state);
40
- return state;
41
- };
42
- /* v8 ignore next -- session/event always follows list() or session/created seeding */
43
- const stateFor = (session) => states.get(session) ?? seed(session);
44
- for (const session of ctx.sessions.list())
45
- seed(session);
46
- ctx.on('session/created', (session) => { seed(session); }, { global: true });
47
- ctx.on('internal/dispatch', (_mode, eventName, args) => {
48
- if (eventName !== 'session/event')
49
- return;
50
- const [session, event] = args;
51
- const state = cloneState(stateFor(session));
52
- applyChecked(state, event, fail);
53
- staged.set(event, { session, state });
54
- }, { global: true });
55
- ctx.on('session/event', (session, event) => {
56
- const candidate = staged.get(event);
57
- /* v8 ignore next 2 -- internal/dispatch stages the exact callback arguments */
58
- if (candidate === undefined || candidate.session !== session) {
59
- return fail('session/event reached publication without matching goal-fold validation');
60
- }
61
- staged.delete(event);
62
- states.set(session, candidate.state);
63
- }, { global: true });
64
- }, { inject: ['sessions'] });
65
- /**
66
- * Register the goal-stream invariant companion.
67
- * @param ctx - Cordis context carrying the invariant service.
68
- * @returns the installed registration's disposer after setup succeeds.
69
- */
70
- export const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
71
- //# sourceMappingURL=invariant.js.map