@stackstackstack/dsh-goal 0.1.5
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/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +58 -0
- package/README.zh.md +58 -0
- package/lib/index.js +827 -0
- package/lib/invariant.js +332 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +815 -0
- package/lib/typert.remote-client.d.ts +41 -0
- package/lib/typert.remote-client.js +366 -0
- package/lib/types/client.d.ts +10 -0
- package/lib/types/client.js +10 -0
- package/lib/types/domain.d.ts +92 -0
- package/lib/types/domain.js +10 -0
- package/lib/types/fold.d.ts +50 -0
- package/lib/types/fold.js +322 -0
- package/lib/types/index.d.ts +155 -0
- package/lib/types/index.js +495 -0
- package/lib/types/invariant.d.ts +13 -0
- package/lib/types/invariant.js +70 -0
- package/lib/types/runtime.d.ts +21 -0
- package/lib/types/runtime.js +25 -0
- package/lib/types/types.d.ts +96 -0
- package/lib/types/types.js +13 -0
- package/package.json +82 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/** Pure replay fold and strict decoder for durable goal changes. */
|
|
2
|
+
import { GOAL_CHANGE_VERSION, GoalId } from "./runtime.js";
|
|
3
|
+
const SNAPSHOT_OPERATIONS = new Set([
|
|
4
|
+
'create',
|
|
5
|
+
'edit',
|
|
6
|
+
'pause',
|
|
7
|
+
'resume',
|
|
8
|
+
'complete',
|
|
9
|
+
'block',
|
|
10
|
+
]);
|
|
11
|
+
const PHASES = new Set(['active', 'paused', 'blocked', 'complete']);
|
|
12
|
+
/**
|
|
13
|
+
* Build an empty replay accumulator.
|
|
14
|
+
* @returns mutable state with no current goal or prior ref.
|
|
15
|
+
*/
|
|
16
|
+
export function emptyGoalFoldState() {
|
|
17
|
+
return {
|
|
18
|
+
goal: undefined,
|
|
19
|
+
roundsStarted: 0,
|
|
20
|
+
createdAt: undefined,
|
|
21
|
+
updatedAt: undefined,
|
|
22
|
+
lastRef: undefined,
|
|
23
|
+
seenGoalIds: new Set(),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** Whether a value is a JSON record rather than an array. */
|
|
27
|
+
function isRecord(value) {
|
|
28
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
29
|
+
}
|
|
30
|
+
/** Require one positive safe integer. */
|
|
31
|
+
function positiveInteger(value, field) {
|
|
32
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 1) {
|
|
33
|
+
throw new Error(`goal change ${field} must be a positive safe integer`);
|
|
34
|
+
}
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
/** Require one non-negative safe integer. */
|
|
38
|
+
function nonNegativeInteger(value, field) {
|
|
39
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) {
|
|
40
|
+
throw new Error(`goal change ${field} must be a non-negative safe integer`);
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
/** Decode one canonical blocker explanation. */
|
|
45
|
+
function decodeBlockReason(value) {
|
|
46
|
+
if (!isRecord(value) || Object.keys(value).sort().join(',') !== 'code,message') {
|
|
47
|
+
throw new Error('goal change goal.blockedReason must have exactly code and message fields');
|
|
48
|
+
}
|
|
49
|
+
if (typeof value['code'] !== 'string' || !/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(value['code'])) {
|
|
50
|
+
throw new Error('goal change goal.blockedReason.code must be lower-kebab-case');
|
|
51
|
+
}
|
|
52
|
+
if (typeof value['message'] !== 'string' || value['message'].trim().length === 0
|
|
53
|
+
|| value['message'] !== value['message'].trim()) {
|
|
54
|
+
throw new Error('goal change goal.blockedReason.message must be non-empty and normalized');
|
|
55
|
+
}
|
|
56
|
+
return { code: value['code'], message: value['message'] };
|
|
57
|
+
}
|
|
58
|
+
/** Decode and validate one snapshot. */
|
|
59
|
+
function decodeSnapshot(value) {
|
|
60
|
+
if (!isRecord(value))
|
|
61
|
+
throw new Error('goal change goal must be a record');
|
|
62
|
+
if (typeof value['id'] !== 'string' || value['id'].length === 0) {
|
|
63
|
+
throw new Error('goal change goal.id must be a non-empty string');
|
|
64
|
+
}
|
|
65
|
+
if (typeof value['objective'] !== 'string' || value['objective'].trim().length === 0
|
|
66
|
+
|| value['objective'] !== value['objective'].trim()) {
|
|
67
|
+
throw new Error('goal change goal.objective must be non-empty and normalized');
|
|
68
|
+
}
|
|
69
|
+
if (typeof value['phase'] !== 'string' || !PHASES.has(value['phase'])) {
|
|
70
|
+
throw new Error('goal change goal.phase is invalid');
|
|
71
|
+
}
|
|
72
|
+
const phase = value['phase'];
|
|
73
|
+
const expectedKeys = phase === 'blocked'
|
|
74
|
+
? 'blockedReason,id,maxGoalRounds,objective,phase,revision'
|
|
75
|
+
: 'id,maxGoalRounds,objective,phase,revision';
|
|
76
|
+
if (Object.keys(value).sort().join(',') !== expectedKeys) {
|
|
77
|
+
throw new Error(`goal change goal for phase ${phase} must have exactly ${expectedKeys} fields`);
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
id: GoalId(value['id']),
|
|
81
|
+
revision: positiveInteger(value['revision'], 'goal.revision'),
|
|
82
|
+
objective: value['objective'],
|
|
83
|
+
phase,
|
|
84
|
+
maxGoalRounds: positiveInteger(value['maxGoalRounds'], 'goal.maxGoalRounds'),
|
|
85
|
+
...phase === 'blocked' ? { blockedReason: decodeBlockReason(value['blockedReason']) } : {},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/** Decode and validate one ref. */
|
|
89
|
+
function decodeRef(value) {
|
|
90
|
+
if (!isRecord(value) || Object.keys(value).sort().join(',') !== 'id,revision') {
|
|
91
|
+
throw new Error('goal clear tombstone must have exactly id and revision fields');
|
|
92
|
+
}
|
|
93
|
+
if (typeof value['id'] !== 'string' || value['id'].length === 0) {
|
|
94
|
+
throw new Error('goal clear tombstone id must be a non-empty string');
|
|
95
|
+
}
|
|
96
|
+
return { id: GoalId(value['id']), revision: positiveInteger(value['revision'], 'cleared.revision') };
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Decode a value that declares itself as a goal change. Unrelated values
|
|
100
|
+
* return `undefined`; malformed goal changes fail replay loudly.
|
|
101
|
+
* @param value - candidate source change.
|
|
102
|
+
* @returns validated goal change or `undefined` for another value kind.
|
|
103
|
+
*/
|
|
104
|
+
export function decodeGoalChange(value) {
|
|
105
|
+
if (!isRecord(value) || value['kind'] !== 'goal/change')
|
|
106
|
+
return undefined;
|
|
107
|
+
if (value['version'] !== GOAL_CHANGE_VERSION) {
|
|
108
|
+
throw new Error(`unsupported goal change version ${String(value['version'])}`);
|
|
109
|
+
}
|
|
110
|
+
if (value['operation'] === 'clear') {
|
|
111
|
+
const allowed = ['cleared', 'clearedAt', 'kind', 'operation', 'version'];
|
|
112
|
+
if (Object.keys(value).sort().join(',') !== allowed.sort().join(',')) {
|
|
113
|
+
throw new Error(`goal clear change must have exactly ${allowed.sort().join(',')} fields`);
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
kind: 'goal/change',
|
|
117
|
+
version: GOAL_CHANGE_VERSION,
|
|
118
|
+
operation: 'clear',
|
|
119
|
+
cleared: decodeRef(value['cleared']),
|
|
120
|
+
clearedAt: nonNegativeInteger(value['clearedAt'], 'clearedAt'),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (typeof value['operation'] !== 'string'
|
|
124
|
+
|| !SNAPSHOT_OPERATIONS.has(value['operation'])) {
|
|
125
|
+
throw new Error('goal change operation is invalid');
|
|
126
|
+
}
|
|
127
|
+
const allowed = ['createdAt', 'goal', 'kind', 'operation', 'roundsStarted', 'updatedAt', 'version'];
|
|
128
|
+
if (Object.keys(value).sort().join(',') !== allowed.sort().join(',')) {
|
|
129
|
+
throw new Error(`goal snapshot change must have exactly ${allowed.sort().join(',')} fields`);
|
|
130
|
+
}
|
|
131
|
+
const createdAt = nonNegativeInteger(value['createdAt'], 'createdAt');
|
|
132
|
+
const updatedAt = nonNegativeInteger(value['updatedAt'], 'updatedAt');
|
|
133
|
+
if (updatedAt < createdAt)
|
|
134
|
+
throw new Error('goal change updatedAt cannot precede createdAt');
|
|
135
|
+
return {
|
|
136
|
+
kind: 'goal/change',
|
|
137
|
+
version: GOAL_CHANGE_VERSION,
|
|
138
|
+
operation: value['operation'],
|
|
139
|
+
goal: decodeSnapshot(value['goal']),
|
|
140
|
+
roundsStarted: nonNegativeInteger(value['roundsStarted'], 'roundsStarted'),
|
|
141
|
+
createdAt,
|
|
142
|
+
updatedAt,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/** Narrow model attribution to a valid goal source. */
|
|
146
|
+
function goalSource(source) {
|
|
147
|
+
if (source.kind !== 'goal')
|
|
148
|
+
return undefined;
|
|
149
|
+
if (typeof source.goalId !== 'string' || source.goalId.length === 0
|
|
150
|
+
|| !Number.isSafeInteger(source.revision) || source.revision < 1
|
|
151
|
+
|| !Number.isSafeInteger(source.round) || source.round < 1) {
|
|
152
|
+
throw new Error('goal message source is invalid');
|
|
153
|
+
}
|
|
154
|
+
return source;
|
|
155
|
+
}
|
|
156
|
+
/** Require two snapshots to retain fields that only `edit` may replace. */
|
|
157
|
+
function requireSameDefinition(current, next, operation) {
|
|
158
|
+
if (next.objective !== current.objective || next.maxGoalRounds !== current.maxGoalRounds) {
|
|
159
|
+
throw new Error(`goal ${operation} cannot change objective or maxGoalRounds`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/** Require one exact next revision of the current goal. */
|
|
163
|
+
function requireNextRevision(current, next, operation) {
|
|
164
|
+
if (next.id !== current.id || next.revision !== current.revision + 1) {
|
|
165
|
+
throw new Error(`goal ${operation} must advance the current goal by one revision`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/** Validate one non-create snapshot operation against the preceding projection. */
|
|
169
|
+
function validateSnapshotTransition(state, change, current) {
|
|
170
|
+
const next = change.goal;
|
|
171
|
+
requireNextRevision(current, next, change.operation);
|
|
172
|
+
/* v8 ignore next -- a current goal established by this fold always has an updatedAt */
|
|
173
|
+
if (state.updatedAt === undefined)
|
|
174
|
+
throw new Error('current goal fold lacks updatedAt');
|
|
175
|
+
if (change.createdAt !== state.createdAt
|
|
176
|
+
|| change.updatedAt < state.updatedAt
|
|
177
|
+
|| change.roundsStarted !== state.roundsStarted) {
|
|
178
|
+
throw new Error(`goal ${change.operation} does not preserve the current counters and timestamps`);
|
|
179
|
+
}
|
|
180
|
+
switch (change.operation) {
|
|
181
|
+
case 'edit':
|
|
182
|
+
if (next.phase !== current.phase
|
|
183
|
+
|| JSON.stringify(next.blockedReason) !== JSON.stringify(current.blockedReason)) {
|
|
184
|
+
throw new Error('goal edit cannot change phase or blocked reason');
|
|
185
|
+
}
|
|
186
|
+
break;
|
|
187
|
+
case 'pause':
|
|
188
|
+
requireSameDefinition(current, next, change.operation);
|
|
189
|
+
if (current.phase !== 'active' || next.phase !== 'paused')
|
|
190
|
+
throw new Error('goal pause has an invalid phase transition');
|
|
191
|
+
break;
|
|
192
|
+
case 'resume': {
|
|
193
|
+
requireSameDefinition(current, next, change.operation);
|
|
194
|
+
const resumable = new Set([
|
|
195
|
+
'active',
|
|
196
|
+
'paused',
|
|
197
|
+
'blocked',
|
|
198
|
+
]);
|
|
199
|
+
if (!resumable.has(current.phase) || next.phase !== 'active' || state.roundsStarted >= next.maxGoalRounds) {
|
|
200
|
+
throw new Error('goal resume has an invalid phase transition or exhausted round budget');
|
|
201
|
+
}
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
case 'complete':
|
|
205
|
+
requireSameDefinition(current, next, change.operation);
|
|
206
|
+
if (current.phase === 'complete' || next.phase !== 'complete')
|
|
207
|
+
throw new Error('goal complete has an invalid phase transition');
|
|
208
|
+
break;
|
|
209
|
+
case 'block':
|
|
210
|
+
requireSameDefinition(current, next, change.operation);
|
|
211
|
+
if (current.phase !== 'active' || next.phase !== 'blocked')
|
|
212
|
+
throw new Error('goal block has an invalid phase transition');
|
|
213
|
+
break;
|
|
214
|
+
/* v8 ignore start -- the caller excludes create and GoalOperation is closed; these arms retain fail-loud exhaustiveness */
|
|
215
|
+
case 'create':
|
|
216
|
+
throw new Error('goal create cannot be validated as a current-goal transition');
|
|
217
|
+
default:
|
|
218
|
+
change.operation;
|
|
219
|
+
throw new Error('unknown goal snapshot operation');
|
|
220
|
+
/* v8 ignore stop */
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Return the revision identity carried by a snapshot or tombstone.
|
|
225
|
+
* @param change - decoded goal mutation.
|
|
226
|
+
* @returns stable identity used to reconcile a deferred change with its log event.
|
|
227
|
+
*/
|
|
228
|
+
export function goalChangeRef(change) {
|
|
229
|
+
return change.operation === 'clear'
|
|
230
|
+
? change.cleared
|
|
231
|
+
: { id: change.goal.id, revision: change.goal.revision };
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Validate and apply one decoded change to a mutable accumulator.
|
|
235
|
+
* @param state - preceding durable goal projection.
|
|
236
|
+
* @param change - decoded full snapshot or clear tombstone.
|
|
237
|
+
*/
|
|
238
|
+
export function applyGoalChange(state, change) {
|
|
239
|
+
const ref = goalChangeRef(change);
|
|
240
|
+
if (change.operation === 'clear') {
|
|
241
|
+
const current = state.goal;
|
|
242
|
+
if (current === undefined)
|
|
243
|
+
throw new Error('goal clear requires a current goal');
|
|
244
|
+
requireNextRevision(current, change.cleared, change.operation);
|
|
245
|
+
/* v8 ignore next -- a current goal established by this fold always has an updatedAt */
|
|
246
|
+
if (state.updatedAt === undefined)
|
|
247
|
+
throw new Error('current goal fold lacks updatedAt');
|
|
248
|
+
if (change.clearedAt < state.updatedAt) {
|
|
249
|
+
throw new Error('goal clear timestamp cannot precede the current goal update');
|
|
250
|
+
}
|
|
251
|
+
state.goal = undefined;
|
|
252
|
+
state.roundsStarted = 0;
|
|
253
|
+
state.createdAt = undefined;
|
|
254
|
+
state.updatedAt = undefined;
|
|
255
|
+
state.lastRef = ref;
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (change.operation === 'create') {
|
|
259
|
+
if (change.goal.revision !== 1 || change.goal.phase !== 'active' || change.roundsStarted !== 0
|
|
260
|
+
|| (state.goal !== undefined && state.goal.phase !== 'complete')
|
|
261
|
+
|| state.seenGoalIds.has(change.goal.id)) {
|
|
262
|
+
throw new Error('goal create requires a fresh active revision-one goal with zero rounds');
|
|
263
|
+
}
|
|
264
|
+
state.seenGoalIds.add(change.goal.id);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
const current = state.goal;
|
|
268
|
+
if (current === undefined)
|
|
269
|
+
throw new Error(`goal ${change.operation} requires a current goal`);
|
|
270
|
+
validateSnapshotTransition(state, change, current);
|
|
271
|
+
}
|
|
272
|
+
state.goal = change.goal;
|
|
273
|
+
state.roundsStarted = change.roundsStarted;
|
|
274
|
+
state.createdAt = change.createdAt;
|
|
275
|
+
state.updatedAt = change.updatedAt;
|
|
276
|
+
state.lastRef = ref;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Apply one session event to the strict durable goal fold.
|
|
280
|
+
* @param state - mutable fold accumulator.
|
|
281
|
+
* @param event - next event in sequence order.
|
|
282
|
+
*/
|
|
283
|
+
export function applyGoalEvent(state, event) {
|
|
284
|
+
if (event.type === 'goal/change') {
|
|
285
|
+
const change = decodeGoalChange(event.data);
|
|
286
|
+
/* v8 ignore next -- the event's declared payload always identifies itself as a goal change. */
|
|
287
|
+
if (change === undefined)
|
|
288
|
+
throw new Error(`goal change at session event ${event.seq} has an invalid kind`);
|
|
289
|
+
applyGoalChange(state, change);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (event.type === 'user/message') {
|
|
293
|
+
const source = goalSource(event.data.source);
|
|
294
|
+
if (source === undefined)
|
|
295
|
+
return;
|
|
296
|
+
const current = state.goal;
|
|
297
|
+
if (current === undefined || current.phase !== 'active' || source.goalId !== current.id
|
|
298
|
+
|| source.revision !== current.revision || source.round !== state.roundsStarted + 1
|
|
299
|
+
|| source.round > current.maxGoalRounds) {
|
|
300
|
+
throw new Error(`goal round at session event ${event.seq} is not the next admitted round of the active goal`);
|
|
301
|
+
}
|
|
302
|
+
state.roundsStarted = source.round;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Fold current goal state from a contiguous session event log.
|
|
307
|
+
* @param events - session events in sequence order.
|
|
308
|
+
* @returns a fresh durable projection; activation is deliberately absent.
|
|
309
|
+
*/
|
|
310
|
+
export function foldGoal(events) {
|
|
311
|
+
const state = emptyGoalFoldState();
|
|
312
|
+
for (const event of events)
|
|
313
|
+
applyGoalEvent(state, event);
|
|
314
|
+
return {
|
|
315
|
+
...state.goal === undefined ? {} : { goal: { ...state.goal } },
|
|
316
|
+
roundsStarted: state.roundsStarted,
|
|
317
|
+
...state.createdAt === undefined ? {} : { createdAt: state.createdAt },
|
|
318
|
+
...state.updatedAt === undefined ? {} : { updatedAt: state.updatedAt },
|
|
319
|
+
...state.lastRef === undefined ? {} : { lastRef: { ...state.lastRef } },
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
//# sourceMappingURL=fold.js.map
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Same-session goal domain: event-sourced state, compare-and-set mutations,
|
|
3
|
+
* and process-local continuation activation.
|
|
4
|
+
* @module @stackstackstack/dsh-goal
|
|
5
|
+
*/
|
|
6
|
+
import { Context } from '@deepseek-ai/cordis';
|
|
7
|
+
import z from '@deepseek-ai/schemastery';
|
|
8
|
+
import type { Agent } from '@stackstackstack/dsh-agent';
|
|
9
|
+
import type { SessionEvent } from '@stackstackstack/dsh-session';
|
|
10
|
+
import { TypertRemoteService } from '@stackstackstack/dsh-typert-protocol';
|
|
11
|
+
import type { CreateGoalRequest, CreateGoalResult, EditGoalRequest, GoalBlockReason, GoalProjection, GoalRef, GoalView } from './types.ts';
|
|
12
|
+
export type * from './types.ts';
|
|
13
|
+
export type * from './domain.ts';
|
|
14
|
+
export { GOAL_CHANGE_VERSION, GoalError, GoalId } from './runtime.ts';
|
|
15
|
+
export { decodeGoalChange, foldGoal, goalChangeRef } from './fold.ts';
|
|
16
|
+
declare module '@deepseek-ai/cordis' {
|
|
17
|
+
interface Context {
|
|
18
|
+
goals: GoalService;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Light last-wins fold of the `goal` projection unit. Unlike the strict
|
|
23
|
+
* replay fold (fold.ts: transition validation, fail-loud on malformed
|
|
24
|
+
* changes, Set-typed state), this transition is projection-grade: the state
|
|
25
|
+
* is plain JSON (persisted-cache precondition), any non-goal or malformed
|
|
26
|
+
* event returns the same reference (the registry's Object.is gate — the
|
|
27
|
+
* title/todos posture), and correctness of the written change is the write
|
|
28
|
+
* side's job (GoalService validated it before appending; the package
|
|
29
|
+
* invariant rejects a violating stream fail-loud where it is installed).
|
|
30
|
+
* @param state - the projection covering all prior events.
|
|
31
|
+
* @param event - the next committed session event.
|
|
32
|
+
* @returns the next projection (same reference when the event is not a goal change).
|
|
33
|
+
*/
|
|
34
|
+
export declare function applyGoalProjection(state: GoalProjection | null, event: SessionEvent): GoalProjection | null;
|
|
35
|
+
/** Deployment defaults for goal creation. */
|
|
36
|
+
export interface Config {
|
|
37
|
+
/** Total rounds used when a create request omits its own cap. */
|
|
38
|
+
defaultMaxGoalRounds?: number;
|
|
39
|
+
}
|
|
40
|
+
/** Resolved defaults. */
|
|
41
|
+
export interface ResolvedConfig {
|
|
42
|
+
/** Validated positive safe-integer default round cap. */
|
|
43
|
+
defaultMaxGoalRounds: number;
|
|
44
|
+
}
|
|
45
|
+
/** Goal service (`ctx.goals`) backed exclusively by the owning session log. */
|
|
46
|
+
export declare class GoalService extends TypertRemoteService {
|
|
47
|
+
static inject: string[];
|
|
48
|
+
static Config: z<Config>;
|
|
49
|
+
private readonly resolved;
|
|
50
|
+
private readonly caches;
|
|
51
|
+
constructor(ctx: Context, config?: Config);
|
|
52
|
+
/**
|
|
53
|
+
* Read the current goal for one exact live agent.
|
|
54
|
+
* @param agent - owning live agent.
|
|
55
|
+
* @returns a fresh view or `undefined` when no goal is current.
|
|
56
|
+
* @throws {@link GoalError} when the agent is not the registry's live instance.
|
|
57
|
+
*/
|
|
58
|
+
get(agent: Agent): GoalView | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Remove process-local continuation authority without changing durable goal
|
|
61
|
+
* phase or revision. Lifecycle owners use this before unloading a driver;
|
|
62
|
+
* a later human-authorized {@link resume} records the new activation edge.
|
|
63
|
+
* @param agent - owning live agent.
|
|
64
|
+
* @returns a fresh disarmed view, or `undefined` when no goal is current.
|
|
65
|
+
*/
|
|
66
|
+
disarm(agent: Agent): GoalView | undefined;
|
|
67
|
+
/**
|
|
68
|
+
* Create and arm a goal. A completed goal may be replaced; every other
|
|
69
|
+
* current phase must be cleared or resumed instead.
|
|
70
|
+
* @param agent - owning live agent.
|
|
71
|
+
* @param request - objective and optional round cap.
|
|
72
|
+
* @returns the created live view.
|
|
73
|
+
*/
|
|
74
|
+
create(agent: Agent, request: CreateGoalRequest): GoalView;
|
|
75
|
+
/**
|
|
76
|
+
* Edit objective and/or round cap without changing phase.
|
|
77
|
+
* @param agent - owning live agent.
|
|
78
|
+
* @param ref - expected current revision.
|
|
79
|
+
* @param request - at least one replacement field.
|
|
80
|
+
* @returns the edited view.
|
|
81
|
+
*/
|
|
82
|
+
edit(agent: Agent, ref: GoalRef, request: EditGoalRequest): GoalView;
|
|
83
|
+
/**
|
|
84
|
+
* Pause an active goal and disarm automatic continuation.
|
|
85
|
+
* @param agent - owning live agent.
|
|
86
|
+
* @param ref - expected current revision.
|
|
87
|
+
* @returns the paused view.
|
|
88
|
+
*/
|
|
89
|
+
pause(agent: Agent, ref: GoalRef): GoalView;
|
|
90
|
+
/**
|
|
91
|
+
* Resume and arm a stopped goal, or rearm an active goal after a
|
|
92
|
+
* session-start edge, while its round budget still has capacity.
|
|
93
|
+
* @param agent - owning live agent.
|
|
94
|
+
* @param ref - expected current revision.
|
|
95
|
+
* @returns the active view.
|
|
96
|
+
*/
|
|
97
|
+
resume(agent: Agent, ref: GoalRef): GoalView;
|
|
98
|
+
/**
|
|
99
|
+
* Mark a current non-complete goal complete and disarm it.
|
|
100
|
+
* @param agent - owning live agent.
|
|
101
|
+
* @param ref - expected current revision.
|
|
102
|
+
* @returns the completed view.
|
|
103
|
+
*/
|
|
104
|
+
complete(agent: Agent, ref: GoalRef): GoalView;
|
|
105
|
+
/**
|
|
106
|
+
* Mark an active goal blocked and disarm it.
|
|
107
|
+
* @param agent - owning live agent.
|
|
108
|
+
* @param ref - expected current revision.
|
|
109
|
+
* @param reason - policy-owned stable code and human-readable explanation.
|
|
110
|
+
* @returns the blocked view with its durable reason.
|
|
111
|
+
*/
|
|
112
|
+
block(agent: Agent, ref: GoalRef, reason: GoalBlockReason): GoalView;
|
|
113
|
+
/**
|
|
114
|
+
* Clear the current goal while retaining a durable tombstone and history.
|
|
115
|
+
* @param agent - owning live agent.
|
|
116
|
+
* @param ref - expected current revision.
|
|
117
|
+
* @returns the tombstone ref whose revision is one past the cleared snapshot.
|
|
118
|
+
*/
|
|
119
|
+
clear(agent: Agent, ref: GoalRef): GoalRef;
|
|
120
|
+
/** Resolve and validate the cache used by a mutation. */
|
|
121
|
+
private prepareMutation;
|
|
122
|
+
/** Reject stale or missing current-state refs. */
|
|
123
|
+
private expectCurrent;
|
|
124
|
+
/** Enforce exact live-agent identity rather than trusting a matching id. */
|
|
125
|
+
private assertLive;
|
|
126
|
+
/** Return the per-session cache, folding a seed once with activation disarmed. */
|
|
127
|
+
private cache;
|
|
128
|
+
/** Incrementally observe durable events and reconcile local activation intent. */
|
|
129
|
+
private sync;
|
|
130
|
+
/** Build a new revision with one replacement phase. */
|
|
131
|
+
private withPhase;
|
|
132
|
+
/** Shared validated phase transition. */
|
|
133
|
+
private transition;
|
|
134
|
+
/** Render a stable invalid-transition error. */
|
|
135
|
+
private transitionError;
|
|
136
|
+
/** Commit a mutation that retains the current goal's derived counters/times. */
|
|
137
|
+
private commitCurrent;
|
|
138
|
+
/** Clamp a current goal's next timestamp across backward wall-clock movement. */
|
|
139
|
+
private nextMutationTime;
|
|
140
|
+
/** Build and commit one full-snapshot mutation. */
|
|
141
|
+
private commitSnapshot;
|
|
142
|
+
/** Commit one mutation into the goal log, cache, and live event stream. */
|
|
143
|
+
private commit;
|
|
144
|
+
/** Build a detached current view. */
|
|
145
|
+
private view;
|
|
146
|
+
/**
|
|
147
|
+
* Create one Goal through the remote boundary.
|
|
148
|
+
* @param agent - exact live Agent resolved from the wire identity.
|
|
149
|
+
* @param request - objective and optional round cap.
|
|
150
|
+
* @returns the created Goal identity.
|
|
151
|
+
*/
|
|
152
|
+
remoteExportCreate(agent: Agent, request: CreateGoalRequest): CreateGoalResult;
|
|
153
|
+
}
|
|
154
|
+
export default GoalService;
|
|
155
|
+
//# sourceMappingURL=index.d.ts.map
|