@dsh-enhanced/assistant-goals 0.1.30
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.md +274 -0
- package/cordis.patch.yml +3 -0
- package/lib/budget-store.d.ts +73 -0
- package/lib/budget-store.d.ts.map +1 -0
- package/lib/budget-store.js +345 -0
- package/lib/budget-store.js.map +1 -0
- package/lib/budget.d.ts +78 -0
- package/lib/budget.d.ts.map +1 -0
- package/lib/budget.js +344 -0
- package/lib/budget.js.map +1 -0
- package/lib/dependency.d.ts +11 -0
- package/lib/dependency.d.ts.map +1 -0
- package/lib/dependency.js +34 -0
- package/lib/dependency.js.map +1 -0
- package/lib/event-wait-store.d.ts +56 -0
- package/lib/event-wait-store.d.ts.map +1 -0
- package/lib/event-wait-store.js +189 -0
- package/lib/event-wait-store.js.map +1 -0
- package/lib/event-wait.d.ts +47 -0
- package/lib/event-wait.d.ts.map +1 -0
- package/lib/event-wait.js +372 -0
- package/lib/event-wait.js.map +1 -0
- package/lib/execution-store.d.ts +23 -0
- package/lib/execution-store.d.ts.map +1 -0
- package/lib/execution-store.js +207 -0
- package/lib/execution-store.js.map +1 -0
- package/lib/execution.d.ts +49 -0
- package/lib/execution.d.ts.map +1 -0
- package/lib/execution.js +438 -0
- package/lib/execution.js.map +1 -0
- package/lib/feedback.d.ts +63 -0
- package/lib/feedback.d.ts.map +1 -0
- package/lib/feedback.js +134 -0
- package/lib/feedback.js.map +1 -0
- package/lib/index.d.ts +27 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +9 -0
- package/lib/index.js.map +1 -0
- package/lib/outcome-store.d.ts +47 -0
- package/lib/outcome-store.d.ts.map +1 -0
- package/lib/outcome-store.js +271 -0
- package/lib/outcome-store.js.map +1 -0
- package/lib/outcome.d.ts +77 -0
- package/lib/outcome.d.ts.map +1 -0
- package/lib/outcome.js +417 -0
- package/lib/outcome.js.map +1 -0
- package/lib/service.d.ts +363 -0
- package/lib/service.d.ts.map +1 -0
- package/lib/service.js +1839 -0
- package/lib/service.js.map +1 -0
- package/lib/store.d.ts +21 -0
- package/lib/store.d.ts.map +1 -0
- package/lib/store.js +577 -0
- package/lib/store.js.map +1 -0
- package/lib/strategy-feedback.d.ts +29 -0
- package/lib/strategy-feedback.d.ts.map +1 -0
- package/lib/strategy-feedback.js +53 -0
- package/lib/strategy-feedback.js.map +1 -0
- package/lib/strategy-identity.d.ts +11 -0
- package/lib/strategy-identity.d.ts.map +1 -0
- package/lib/strategy-identity.js +10 -0
- package/lib/strategy-identity.js.map +1 -0
- package/lib/strategy-store.d.ts +76 -0
- package/lib/strategy-store.d.ts.map +1 -0
- package/lib/strategy-store.js +277 -0
- package/lib/strategy-store.js.map +1 -0
- package/lib/strategy.d.ts +56 -0
- package/lib/strategy.d.ts.map +1 -0
- package/lib/strategy.js +478 -0
- package/lib/strategy.js.map +1 -0
- package/lib/tools.d.ts +4 -0
- package/lib/tools.d.ts.map +1 -0
- package/lib/tools.js +130 -0
- package/lib/tools.js.map +1 -0
- package/lib/types.d.ts +285 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +10 -0
- package/lib/types.js.map +1 -0
- package/lib/verified-artifact.d.ts +72 -0
- package/lib/verified-artifact.d.ts.map +1 -0
- package/lib/verified-artifact.js +142 -0
- package/lib/verified-artifact.js.map +1 -0
- package/lib/verified-workflow.d.ts +120 -0
- package/lib/verified-workflow.d.ts.map +1 -0
- package/lib/verified-workflow.js +253 -0
- package/lib/verified-workflow.js.map +1 -0
- package/lib/version.d.ts +2 -0
- package/lib/version.d.ts.map +1 -0
- package/lib/version.js +2 -0
- package/lib/version.js.map +1 -0
- package/lib/wake-store.d.ts +38 -0
- package/lib/wake-store.d.ts.map +1 -0
- package/lib/wake-store.js +177 -0
- package/lib/wake-store.js.map +1 -0
- package/lib/wake.d.ts +45 -0
- package/lib/wake.d.ts.map +1 -0
- package/lib/wake.js +327 -0
- package/lib/wake.js.map +1 -0
- package/package.json +143 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { DeliveryPreferencePrincipalAttestation } from '@dsh-enhanced/assistant-delivery';
|
|
2
|
+
import type { GoalDefinition, GoalDependencyBinding, GoalScope, NativeGoalState } from './types.js';
|
|
3
|
+
export interface GoalWakeIntent {
|
|
4
|
+
id: string;
|
|
5
|
+
scope: GoalScope;
|
|
6
|
+
goalId: string;
|
|
7
|
+
definition: GoalDefinition;
|
|
8
|
+
native: NativeGoalState;
|
|
9
|
+
attestation: DeliveryPreferencePrincipalAttestation;
|
|
10
|
+
at: number;
|
|
11
|
+
expiresAt: number;
|
|
12
|
+
ownerRouteId: string;
|
|
13
|
+
budgetId: string;
|
|
14
|
+
/** Optional only for persisted pre-v3 wakes; new intents always bind this list. */
|
|
15
|
+
dependencies?: readonly GoalDependencyBinding[];
|
|
16
|
+
}
|
|
17
|
+
export interface GoalWake {
|
|
18
|
+
intent: GoalWakeIntent;
|
|
19
|
+
state: 'prepared' | 'scheduled' | 'dispatched' | 'succeeded' | 'unknown' | 'denied';
|
|
20
|
+
definitionHash?: string;
|
|
21
|
+
occurrenceId?: string;
|
|
22
|
+
dispatchedAt?: number;
|
|
23
|
+
completedAt?: number;
|
|
24
|
+
}
|
|
25
|
+
export declare function validateGoalWakeIntent(value: GoalWakeIntent): GoalWakeIntent;
|
|
26
|
+
export declare class GoalWakeStore {
|
|
27
|
+
#private;
|
|
28
|
+
constructor(path: string);
|
|
29
|
+
get(id: string): GoalWake | undefined;
|
|
30
|
+
prepare(input: GoalWakeIntent): GoalWake;
|
|
31
|
+
scheduled(id: string, definitionHash: string): GoalWake;
|
|
32
|
+
dispatch(id: string, occurrenceId: string, now: number): GoalWake;
|
|
33
|
+
finish(id: string, outcome: 'succeeded' | 'unknown' | 'denied', now: number): GoalWake;
|
|
34
|
+
listPending(): readonly GoalWake[];
|
|
35
|
+
listForGoal(scope: GoalScope, goalId: string, limit?: number): readonly GoalWake[];
|
|
36
|
+
close(): void;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=wake-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wake-store.d.ts","sourceRoot":"","sources":["../src/wake-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sCAAsC,EAAE,MAAM,kCAAkC,CAAA;AAE9F,OAAO,KAAK,EAAE,cAAc,EAAE,qBAAqB,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAGnG,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,eAAe,CAAA;IACjG,WAAW,EAAE,sCAAsC,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;IAC1H,mFAAmF;IACnF,YAAY,CAAC,EAAE,SAAS,qBAAqB,EAAE,CAAA;CAChD;AACD,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,UAAU,GAAG,WAAW,GAAG,YAAY,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAA;IAC3G,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAC5F;AAaD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,cAAc,GAAG,cAAc,CAe5E;AA4BD,qBAAa,aAAa;;gBAEZ,IAAI,EAAE,MAAM;IAkBxB,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS;IACrC,OAAO,CAAC,KAAK,EAAE,cAAc,GAAG,QAAQ;IASxC,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,QAAQ;IAUvD,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ;IAIjE,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,SAAS,GAAG,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ;IAItF,WAAW,IAAI,SAAS,QAAQ,EAAE;IAClC,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,SAAS,QAAQ,EAAE;IAE9E,KAAK,IAAI,IAAI;CACd"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import { acceptanceCanonicalJson, acceptanceDigest } from '@dsh-enhanced/task-acceptance-contract';
|
|
3
|
+
import { GoalStoreError } from './types.js';
|
|
4
|
+
import { prepareGoalStoreDatabaseFile } from './store.js';
|
|
5
|
+
const idPattern = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,511}$/u;
|
|
6
|
+
const routePattern = /^[A-Za-z0-9][A-Za-z0-9._@/:-]{0,511}$/u;
|
|
7
|
+
function fail(code) { throw new GoalStoreError(code); }
|
|
8
|
+
const same = (a, b) => acceptanceDigest(a) === acceptanceDigest(b);
|
|
9
|
+
const freeze = (value) => { if (value && typeof value === 'object') {
|
|
10
|
+
for (const child of Object.values(value))
|
|
11
|
+
freeze(child);
|
|
12
|
+
Object.freeze(value);
|
|
13
|
+
} ; return value; };
|
|
14
|
+
const integer = (value, min = 0) => Number.isSafeInteger(value) && value >= min;
|
|
15
|
+
const text = (value, max = 512) => typeof value === 'string' && value.length > 0 && value.length <= max && idPattern.test(value);
|
|
16
|
+
const routeText = (value, max = 512) => typeof value === 'string' && value.length > 0 && value.length <= max && routePattern.test(value);
|
|
17
|
+
const plain = (value) => value !== null && typeof value === 'object' && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
18
|
+
const exact = (value, keys) => plain(value) && Object.keys(value).length === keys.length && keys.every(key => Object.hasOwn(value, key));
|
|
19
|
+
export function validateGoalWakeIntent(value) {
|
|
20
|
+
const keys = ['id', 'scope', 'goalId', 'definition', 'native', 'attestation', 'at', 'expiresAt', 'ownerRouteId', 'budgetId'];
|
|
21
|
+
if (!(exact(value, keys) || exact(value, [...keys, 'dependencies'])) || !text(value.id) || !text(value.goalId) || !routeText(value.ownerRouteId) || !routeText(value.budgetId)
|
|
22
|
+
|| !exact(value.scope, ['principalId', 'principalRecordId', 'principalVersion', 'workspace', 'preset']) || !routeText(value.scope.principalId) || !text(value.scope.principalRecordId) || !integer(value.scope.principalVersion, 1) || typeof value.scope.workspace !== 'string' || !value.scope.workspace.startsWith('/') || !text(value.scope.preset)
|
|
23
|
+
|| !exact(value.definition, ['version', 'digest', 'objective']) || !integer(value.definition.version, 1) || !/^[a-f0-9]{64}$/u.test(value.definition.digest) || typeof value.definition.objective !== 'string' || value.definition.objective.length < 1 || value.definition.objective.length > 16384 || value.definition.digest !== acceptanceDigest({ objective: value.definition.objective })
|
|
24
|
+
|| !exact(value.native, ['sessionId', 'goalId', 'revision', 'objective', 'phase', 'roundsStarted', 'maxGoalRounds', 'updatedAt']) || value.native.phase !== 'paused' || !text(value.native.sessionId) || !text(value.native.goalId) || !integer(value.native.revision, 1) || typeof value.native.objective !== 'string' || value.native.objective.length < 1 || value.native.objective.length > 16384 || !integer(value.native.roundsStarted) || !integer(value.native.maxGoalRounds, 1) || value.native.roundsStarted >= value.native.maxGoalRounds || !integer(value.native.updatedAt)
|
|
25
|
+
|| value.native.objective !== value.definition.objective
|
|
26
|
+
|| !exact(value.attestation, ['scope', 'principalId', 'principalLineage', 'bindingId', 'bindingVersion', 'bindingGeneration', 'sessionId']) || !exact(value.attestation.scope, ['workspace', 'preset']) || value.attestation.scope.workspace !== value.scope.workspace || value.attestation.scope.preset !== value.scope.preset || value.attestation.principalId !== value.scope.principalId || !exact(value.attestation.principalLineage, ['principalRecordId', 'principalVersion']) || value.attestation.principalLineage.principalRecordId !== value.scope.principalRecordId || value.attestation.principalLineage.principalVersion !== value.scope.principalVersion || value.attestation.sessionId !== value.native.sessionId || !text(value.attestation.bindingId) || !integer(value.attestation.bindingVersion, 1) || !integer(value.attestation.bindingGeneration, 1)
|
|
27
|
+
|| !integer(value.at) || !integer(value.expiresAt) || value.expiresAt <= value.at || value.expiresAt - value.at > 300000
|
|
28
|
+
|| value.dependencies !== undefined && (!Array.isArray(value.dependencies) || value.dependencies.length > 16
|
|
29
|
+
|| new Set(value.dependencies.map(item => item?.goalId)).size !== value.dependencies.length
|
|
30
|
+
|| value.dependencies.some(item => !exact(item, ['goalId', 'definitionVersion', 'definitionDigest'])
|
|
31
|
+
|| !text(item.goalId) || !integer(item.definitionVersion, 1) || typeof item.definitionDigest !== 'string'
|
|
32
|
+
|| !/^[a-f0-9]{64}$/u.test(item.definitionDigest))))
|
|
33
|
+
fail('invalid-input');
|
|
34
|
+
return freeze(JSON.parse(JSON.stringify(value)));
|
|
35
|
+
}
|
|
36
|
+
function parse(value) { try {
|
|
37
|
+
return JSON.parse(value);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return fail('schema');
|
|
41
|
+
} }
|
|
42
|
+
function scopeKey(value) {
|
|
43
|
+
if (!plain(value) || !routeText(value.principalId) || !text(value.principalRecordId) || !integer(value.principalVersion, 1)
|
|
44
|
+
|| typeof value.workspace !== 'string' || !value.workspace.startsWith('/') || !text(value.preset))
|
|
45
|
+
fail('invalid-input');
|
|
46
|
+
return acceptanceCanonicalJson({ principalId: value.principalId, principalRecordId: value.principalRecordId, principalVersion: value.principalVersion, workspace: value.workspace, preset: value.preset });
|
|
47
|
+
}
|
|
48
|
+
function wake(row) {
|
|
49
|
+
const intent = validateGoalWakeIntent(parse(row.intent_json));
|
|
50
|
+
if (row.id !== intent.id || row.scope_key !== acceptanceCanonicalJson(intent.scope) || row.goal_id !== intent.goalId)
|
|
51
|
+
fail('schema');
|
|
52
|
+
if (!['prepared', 'scheduled', 'dispatched', 'succeeded', 'unknown', 'denied'].includes(row.state))
|
|
53
|
+
fail('schema');
|
|
54
|
+
const hashValid = /^[a-f0-9]{64}$/u.test(row.definition_hash ?? '');
|
|
55
|
+
if (row.definition_hash !== null && !hashValid)
|
|
56
|
+
fail('schema');
|
|
57
|
+
const dispatched = row.state === 'dispatched' || row.state === 'succeeded' || row.state === 'unknown';
|
|
58
|
+
const terminal = row.state === 'succeeded' || row.state === 'unknown' || row.state === 'denied';
|
|
59
|
+
if (row.state === 'prepared' && row.definition_hash !== null)
|
|
60
|
+
fail('schema');
|
|
61
|
+
if ((row.state === 'scheduled' || dispatched) && !hashValid)
|
|
62
|
+
fail('schema');
|
|
63
|
+
if (dispatched) {
|
|
64
|
+
if (!text(row.occurrence_id) || !integer(row.dispatched_at)
|
|
65
|
+
|| row.dispatched_at < intent.at || row.dispatched_at >= intent.expiresAt)
|
|
66
|
+
fail('schema');
|
|
67
|
+
}
|
|
68
|
+
else if (row.occurrence_id !== null || row.dispatched_at !== null)
|
|
69
|
+
fail('schema');
|
|
70
|
+
if (terminal) {
|
|
71
|
+
if (!integer(row.completed_at) || (dispatched && row.completed_at < row.dispatched_at)
|
|
72
|
+
|| (row.state === 'succeeded' && row.completed_at >= intent.expiresAt))
|
|
73
|
+
fail('schema');
|
|
74
|
+
}
|
|
75
|
+
else if (row.completed_at !== null)
|
|
76
|
+
fail('schema');
|
|
77
|
+
return freeze({ intent, state: row.state, ...(row.definition_hash === null ? {} : { definitionHash: row.definition_hash }), ...(row.occurrence_id === null ? {} : { occurrenceId: row.occurrence_id }), ...(row.dispatched_at === null ? {} : { dispatchedAt: row.dispatched_at }), ...(row.completed_at === null ? {} : { completedAt: row.completed_at }) });
|
|
78
|
+
}
|
|
79
|
+
export class GoalWakeStore {
|
|
80
|
+
#database;
|
|
81
|
+
constructor(path) {
|
|
82
|
+
if (path !== ':memory:')
|
|
83
|
+
prepareGoalStoreDatabaseFile(path);
|
|
84
|
+
this.#database = new DatabaseSync(path, { enableForeignKeyConstraints: true });
|
|
85
|
+
try {
|
|
86
|
+
this.#database.exec('PRAGMA foreign_keys = ON; PRAGMA busy_timeout = 250; PRAGMA journal_mode = WAL; PRAGMA synchronous = FULL;');
|
|
87
|
+
const version = this.#database.prepare('PRAGMA user_version').get().user_version;
|
|
88
|
+
const tables = this.#database.prepare("SELECT name FROM sqlite_schema WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name").all().map(row => row.name);
|
|
89
|
+
if (version === 0 && tables.length === 0)
|
|
90
|
+
this.#database.exec(`BEGIN IMMEDIATE; CREATE TABLE goal_wakes (id TEXT PRIMARY KEY, intent_json TEXT NOT NULL, state TEXT NOT NULL CHECK(state IN ('prepared','scheduled','dispatched','succeeded','unknown','denied')), definition_hash TEXT, occurrence_id TEXT, dispatched_at INTEGER, completed_at INTEGER, scope_key TEXT NOT NULL, goal_id TEXT NOT NULL) STRICT; CREATE INDEX goal_wakes_scope_goal ON goal_wakes(scope_key, goal_id, id); PRAGMA user_version = 1; COMMIT;`);
|
|
91
|
+
else if (version !== 1 || !same(tables, ['goal_wakes']))
|
|
92
|
+
fail('schema');
|
|
93
|
+
const columns = this.#database.prepare('PRAGMA table_info(goal_wakes)').all().map(row => row.name);
|
|
94
|
+
const sql = this.#database.prepare("SELECT sql FROM sqlite_schema WHERE type = 'table' AND name = 'goal_wakes'").get()?.sql;
|
|
95
|
+
if (!same(columns, ['id', 'intent_json', 'state', 'definition_hash', 'occurrence_id', 'dispatched_at', 'completed_at', 'scope_key', 'goal_id']) || !sql || !/\bSTRICT\b/u.test(sql))
|
|
96
|
+
fail('schema');
|
|
97
|
+
const check = this.#database.prepare('PRAGMA quick_check').get();
|
|
98
|
+
if (check.quick_check !== 'ok')
|
|
99
|
+
fail('schema');
|
|
100
|
+
for (const row of this.#database.prepare('SELECT id, intent_json, state, definition_hash, occurrence_id, dispatched_at, completed_at, scope_key, goal_id FROM goal_wakes').all())
|
|
101
|
+
wake(row);
|
|
102
|
+
if (path !== ':memory:')
|
|
103
|
+
prepareGoalStoreDatabaseFile(path);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
this.#database.close();
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
#get(id) { const row = this.#database.prepare('SELECT id, intent_json, state, definition_hash, occurrence_id, dispatched_at, completed_at, scope_key, goal_id FROM goal_wakes WHERE id = ?').get(id); return row === undefined ? undefined : wake(row); }
|
|
111
|
+
get(id) { return text(id) ? this.#get(id) : fail('invalid-input'); }
|
|
112
|
+
prepare(input) {
|
|
113
|
+
const intent = validateGoalWakeIntent(input);
|
|
114
|
+
return this.#transaction(() => {
|
|
115
|
+
const current = this.#get(intent.id);
|
|
116
|
+
if (current !== undefined) {
|
|
117
|
+
if (!same(current.intent, intent))
|
|
118
|
+
fail('conflict');
|
|
119
|
+
return current;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
this.#database.prepare("INSERT INTO goal_wakes(id, intent_json, state, scope_key, goal_id) VALUES (?, ?, 'prepared', ?, ?)").run(intent.id, JSON.stringify(intent), acceptanceCanonicalJson(intent.scope), intent.goalId);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
fail('conflict');
|
|
126
|
+
}
|
|
127
|
+
return this.#get(intent.id);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
scheduled(id, definitionHash) {
|
|
131
|
+
if (!text(id) || !/^[a-f0-9]{64}$/u.test(definitionHash))
|
|
132
|
+
fail('invalid-input');
|
|
133
|
+
return this.#transaction(() => {
|
|
134
|
+
const current = this.#get(id);
|
|
135
|
+
if (!current)
|
|
136
|
+
fail('not-found');
|
|
137
|
+
if (current.state === 'scheduled' && current.definitionHash === definitionHash)
|
|
138
|
+
return current;
|
|
139
|
+
if (current.state !== 'prepared')
|
|
140
|
+
fail('conflict');
|
|
141
|
+
if (Number(this.#database.prepare("UPDATE goal_wakes SET state = 'scheduled', definition_hash = ? WHERE id = ? AND state = 'prepared'").run(definitionHash, id).changes) !== 1)
|
|
142
|
+
fail('conflict');
|
|
143
|
+
return this.#get(id);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
dispatch(id, occurrenceId, now) {
|
|
147
|
+
if (!text(id) || !text(occurrenceId) || !integer(now))
|
|
148
|
+
fail('invalid-input');
|
|
149
|
+
return this.#transaction(() => { const current = this.#get(id); if (!current)
|
|
150
|
+
fail('not-found'); if (current.state !== 'scheduled' || now < current.intent.at || now >= current.intent.expiresAt)
|
|
151
|
+
fail('conflict'); if (Number(this.#database.prepare("UPDATE goal_wakes SET state = 'dispatched', occurrence_id = ?, dispatched_at = ? WHERE id = ? AND state = 'scheduled'").run(occurrenceId, now, id).changes) !== 1)
|
|
152
|
+
fail('conflict'); return this.#get(id); });
|
|
153
|
+
}
|
|
154
|
+
finish(id, outcome, now) {
|
|
155
|
+
if (!text(id) || !integer(now) || !['succeeded', 'unknown', 'denied'].includes(outcome))
|
|
156
|
+
fail('invalid-input');
|
|
157
|
+
return this.#transaction(() => { const current = this.#get(id); if (!current)
|
|
158
|
+
fail('not-found'); if (current.state === outcome)
|
|
159
|
+
return current; if ((outcome === 'denied' && (current.state !== 'prepared' && current.state !== 'scheduled')) || (outcome !== 'denied' && (current.state !== 'dispatched' || now < current.dispatchedAt || (outcome === 'succeeded' && now >= current.intent.expiresAt))))
|
|
160
|
+
fail('conflict'); if (Number(this.#database.prepare('UPDATE goal_wakes SET state = ?, completed_at = ? WHERE id = ? AND state = ?').run(outcome, now, id, current.state).changes) !== 1)
|
|
161
|
+
fail('conflict'); return this.#get(id); });
|
|
162
|
+
}
|
|
163
|
+
listPending() { return freeze(this.#database.prepare("SELECT id FROM goal_wakes WHERE state IN ('prepared', 'scheduled') ORDER BY id").all().map(row => this.#get(row.id))); }
|
|
164
|
+
listForGoal(scope, goalId, limit = 20) { if (!text(goalId) || !Number.isSafeInteger(limit) || limit < 1 || limit > 100)
|
|
165
|
+
fail('invalid-input'); return freeze(this.#database.prepare('SELECT id FROM goal_wakes WHERE scope_key = ? AND goal_id = ? ORDER BY id DESC LIMIT ?').all(scopeKey(scope), goalId, limit).map(row => this.#get(row.id))); }
|
|
166
|
+
#transaction(operation) { this.#database.exec('BEGIN IMMEDIATE'); try {
|
|
167
|
+
const result = operation();
|
|
168
|
+
this.#database.exec('COMMIT');
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
this.#database.exec('ROLLBACK');
|
|
173
|
+
throw error;
|
|
174
|
+
} }
|
|
175
|
+
close() { this.#database.close(); }
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=wake-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wake-store.js","sourceRoot":"","sources":["../src/wake-store.ts"],"names":[],"mappings":"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"}
|
package/lib/wake.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
3
|
+
import type { DeliveryGoalWakeInput, OwnerGoalOutcomeFeedbackTarget } from '@dsh-enhanced/assistant-delivery';
|
|
4
|
+
import type { GoalWake, GoalWakeIntent } from './wake-store.js';
|
|
5
|
+
import type { GoalRecord, GoalScope } from './types.js';
|
|
6
|
+
type VerifiedWakeOutcome = Readonly<{
|
|
7
|
+
assessmentId: string;
|
|
8
|
+
runId: string;
|
|
9
|
+
objectiveStatus: 'achieved' | 'not-achieved';
|
|
10
|
+
}>;
|
|
11
|
+
export interface GoalWakeConfig {
|
|
12
|
+
ownerRouteId: string;
|
|
13
|
+
budgetId: string;
|
|
14
|
+
maxDelayMs?: number;
|
|
15
|
+
runTimeoutMs?: number;
|
|
16
|
+
}
|
|
17
|
+
export declare function validateGoalWakeConfig(value: GoalWakeConfig): Required<GoalWakeConfig>;
|
|
18
|
+
/** One durable at job per owner-authorized intent; Automations owns all scheduling. */
|
|
19
|
+
export declare class GoalWakeRuntime {
|
|
20
|
+
#private;
|
|
21
|
+
private readonly ctx;
|
|
22
|
+
readonly config: Required<GoalWakeConfig>;
|
|
23
|
+
private readonly record;
|
|
24
|
+
private readonly ready;
|
|
25
|
+
private readonly settleExecution;
|
|
26
|
+
private readonly verifiedOutcome;
|
|
27
|
+
private readonly assertEventWait;
|
|
28
|
+
private readonly acceptedEventPause;
|
|
29
|
+
private readonly assertDependencies;
|
|
30
|
+
private readonly dependenciesReady;
|
|
31
|
+
private readonly outcomeFeedbackTarget?;
|
|
32
|
+
constructor(ctx: Context, path: string, config: Required<GoalWakeConfig>, record: (scope: GoalScope, goalId: string, agent?: Agent) => GoalRecord | undefined, ready: () => boolean, settleExecution: (agent: Agent, signal: AbortSignal) => Promise<void>, verifiedOutcome: (record: GoalRecord, wake: GoalWakeIntent['native']) => VerifiedWakeOutcome | undefined, assertEventWait?: (intent: GoalWakeIntent, phase: 'before-resume' | 'running' | 'terminal') => void, acceptedEventPause?: (record: GoalRecord, agent?: Agent) => boolean, assertDependencies?: (record: GoalRecord) => void, dependenciesReady?: () => boolean, outcomeFeedbackTarget?: ((intent: GoalWakeIntent, record: GoalRecord, outcome: VerifiedWakeOutcome) => Readonly<OwnerGoalOutcomeFeedbackTarget>) | undefined);
|
|
33
|
+
owns: (input: DeliveryGoalWakeInput) => boolean;
|
|
34
|
+
health: () => {
|
|
35
|
+
enabled: boolean;
|
|
36
|
+
connected: boolean;
|
|
37
|
+
reconciliationFailures: number;
|
|
38
|
+
};
|
|
39
|
+
reconcile: () => Promise<void>;
|
|
40
|
+
inspect: (scope: GoalScope, goalId: string) => readonly GoalWake[];
|
|
41
|
+
preflight(record: GoalRecord): void;
|
|
42
|
+
materialize(intent: GoalWakeIntent): GoalWake;
|
|
43
|
+
}
|
|
44
|
+
export {};
|
|
45
|
+
//# sourceMappingURL=wake.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wake.d.ts","sourceRoot":"","sources":["../src/wake.ts"],"names":[],"mappings":"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"}
|
package/lib/wake.js
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { acceptanceDigest } from '@dsh-enhanced/task-acceptance-contract';
|
|
3
|
+
import { GoalWakeStore } from './wake-store.js';
|
|
4
|
+
const owner = 'assistant-goals-wake/v1';
|
|
5
|
+
const catalogDigest = acceptanceDigest({ protocol: owner, operation: 'resume-paused-native-goal', version: 1 });
|
|
6
|
+
const hash = (value) => createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
7
|
+
const same = (a, b) => acceptanceDigest(a) === acceptanceDigest(b);
|
|
8
|
+
function reject() { throw new Error('assistant-goals: wake authority is unavailable, changed or expired'); }
|
|
9
|
+
export function validateGoalWakeConfig(value) {
|
|
10
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)
|
|
11
|
+
|| Object.keys(value).some(key => !['ownerRouteId', 'budgetId', 'maxDelayMs', 'runTimeoutMs'].includes(key))
|
|
12
|
+
|| [value.ownerRouteId, value.budgetId].some(item => typeof item !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,199}$/u.test(item)))
|
|
13
|
+
reject();
|
|
14
|
+
const config = { ...value, maxDelayMs: value.maxDelayMs ?? 86_400_000, runTimeoutMs: value.runTimeoutMs ?? 60_000 };
|
|
15
|
+
if (!Number.isSafeInteger(config.maxDelayMs) || config.maxDelayMs < 1 || config.maxDelayMs > 31 * 86_400_000
|
|
16
|
+
|| !Number.isSafeInteger(config.runTimeoutMs) || config.runTimeoutMs < 1_000 || config.runTimeoutMs > 300_000)
|
|
17
|
+
reject();
|
|
18
|
+
return Object.freeze(config);
|
|
19
|
+
}
|
|
20
|
+
/** One durable at job per owner-authorized intent; Automations owns all scheduling. */
|
|
21
|
+
export class GoalWakeRuntime {
|
|
22
|
+
ctx;
|
|
23
|
+
config;
|
|
24
|
+
record;
|
|
25
|
+
ready;
|
|
26
|
+
settleExecution;
|
|
27
|
+
verifiedOutcome;
|
|
28
|
+
assertEventWait;
|
|
29
|
+
acceptedEventPause;
|
|
30
|
+
assertDependencies;
|
|
31
|
+
dependenciesReady;
|
|
32
|
+
outcomeFeedbackTarget;
|
|
33
|
+
#store;
|
|
34
|
+
#capabilities = new Set();
|
|
35
|
+
#lifecycle = new AbortController();
|
|
36
|
+
#automations;
|
|
37
|
+
#live = true;
|
|
38
|
+
#failures = 0;
|
|
39
|
+
#reconciling = false;
|
|
40
|
+
constructor(ctx, path, config, record, ready, settleExecution, verifiedOutcome, assertEventWait = () => { }, acceptedEventPause = () => false, assertDependencies = () => { }, dependenciesReady = () => true, outcomeFeedbackTarget) {
|
|
41
|
+
this.ctx = ctx;
|
|
42
|
+
this.config = config;
|
|
43
|
+
this.record = record;
|
|
44
|
+
this.ready = ready;
|
|
45
|
+
this.settleExecution = settleExecution;
|
|
46
|
+
this.verifiedOutcome = verifiedOutcome;
|
|
47
|
+
this.assertEventWait = assertEventWait;
|
|
48
|
+
this.acceptedEventPause = acceptedEventPause;
|
|
49
|
+
this.assertDependencies = assertDependencies;
|
|
50
|
+
this.dependenciesReady = dependenciesReady;
|
|
51
|
+
this.outcomeFeedbackTarget = outcomeFeedbackTarget;
|
|
52
|
+
this.#store = new GoalWakeStore(path);
|
|
53
|
+
ctx.inject(['assistantAutomations', 'assistantDelivery', 'assistantPolicy'], runtime => {
|
|
54
|
+
const automations = runtime.assistantAutomations;
|
|
55
|
+
this.#automations = automations;
|
|
56
|
+
const dispose = automations.registerHostExecutor({
|
|
57
|
+
descriptor: { executorId: owner, contractVersion: 1, catalogDigest },
|
|
58
|
+
accepts: spec => spec.executorId === owner && spec.executorContractVersion === 1
|
|
59
|
+
&& spec.catalogDigest === catalogDigest && spec.runbookId === 'resume-paused-native-goal'
|
|
60
|
+
&& spec.runbookVersion === 1,
|
|
61
|
+
execute: input => this.#execute(input),
|
|
62
|
+
});
|
|
63
|
+
const timer = setInterval(() => { void this.#reconcilePending(); }, 500);
|
|
64
|
+
timer.unref?.();
|
|
65
|
+
void this.#reconcilePending();
|
|
66
|
+
return () => { clearInterval(timer); dispose(); if (this.#automations === automations)
|
|
67
|
+
this.#automations = undefined; };
|
|
68
|
+
});
|
|
69
|
+
ctx.effect(() => () => {
|
|
70
|
+
this.#live = false;
|
|
71
|
+
this.#lifecycle.abort();
|
|
72
|
+
this.#store.close();
|
|
73
|
+
}, 'assistant-goals.wakes');
|
|
74
|
+
}
|
|
75
|
+
owns = (input) => this.#live && this.#automations !== undefined && this.#capabilities.has(input);
|
|
76
|
+
health = () => ({ enabled: true, connected: this.#automations !== undefined, reconciliationFailures: this.#failures });
|
|
77
|
+
reconcile = () => this.#reconcilePending();
|
|
78
|
+
inspect = (scope, goalId) => this.#store.listForGoal(scope, goalId).map(wake => this.#reconcileTerminal(wake));
|
|
79
|
+
async #reconcilePending() {
|
|
80
|
+
if (!this.#live || this.#automations === undefined || !this.ready() || this.#reconciling)
|
|
81
|
+
return;
|
|
82
|
+
this.#reconciling = true;
|
|
83
|
+
try {
|
|
84
|
+
for (const wake of this.#store.listPending()) {
|
|
85
|
+
if ((wake.intent.dependencies?.length ?? 0) > 0 && !this.dependenciesReady())
|
|
86
|
+
continue;
|
|
87
|
+
if (Date.now() < wake.intent.expiresAt) {
|
|
88
|
+
try {
|
|
89
|
+
const record = this.#boundRecord(wake.intent);
|
|
90
|
+
if (!same(record.native, wake.intent.native) || record.native.phase !== 'paused')
|
|
91
|
+
reject();
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
try {
|
|
95
|
+
this.#store.finish(wake.intent.id, 'denied', Date.now());
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
this.#failures++;
|
|
99
|
+
}
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
this.materialize(wake.intent);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
this.#failures++;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
this.#reconciling = false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
#reconcileTerminal(wake) {
|
|
116
|
+
if (wake.state !== 'scheduled' || wake.definitionHash === undefined || this.#automations === undefined)
|
|
117
|
+
return wake;
|
|
118
|
+
const actual = this.#automations.inspectSystemOwned({ owner, automationId: wake.intent.id });
|
|
119
|
+
const terminal = actual.latestTerminalRuns.production;
|
|
120
|
+
if (actual.definitionHash === wake.definitionHash && terminal?.executionMode === 'production'
|
|
121
|
+
&& terminal.immutableContext.state === 'verified' && terminal.immutableContext.definitionHash === wake.definitionHash
|
|
122
|
+
&& terminal.immutableContext.scope.workspace === wake.intent.scope.workspace
|
|
123
|
+
&& terminal.immutableContext.scope.agentPreset === wake.intent.scope.preset) {
|
|
124
|
+
// The scheduler may have stopped after claim but before our CAS. This is
|
|
125
|
+
// proof that no native wake was submitted, not proof its budget was refunded.
|
|
126
|
+
return this.#store.finish(wake.intent.id, 'denied', Date.now());
|
|
127
|
+
}
|
|
128
|
+
return wake;
|
|
129
|
+
}
|
|
130
|
+
preflight(record) {
|
|
131
|
+
if (!this.#live || !this.ready() || this.#automations === undefined)
|
|
132
|
+
reject();
|
|
133
|
+
this.#requireSettlementCapability();
|
|
134
|
+
const policy = this.ctx.get('assistantPolicy');
|
|
135
|
+
if (policy?.getBudgetConfig(this.config.budgetId)?.metric !== 'automation-runs')
|
|
136
|
+
reject();
|
|
137
|
+
this.#route(record.scope, this.config.ownerRouteId);
|
|
138
|
+
this.assertDependencies(record);
|
|
139
|
+
}
|
|
140
|
+
#route(scope, routeId) {
|
|
141
|
+
const receipt = this.ctx.get('assistantDelivery')?.validateOwnerRoute({ authorityId: routeId,
|
|
142
|
+
principalId: scope.principalId, workspace: scope.workspace, agentPreset: scope.preset });
|
|
143
|
+
if (receipt?.principalRecordId !== scope.principalRecordId || receipt.principalVersion !== scope.principalVersion)
|
|
144
|
+
reject();
|
|
145
|
+
}
|
|
146
|
+
#requireSettlementCapability() {
|
|
147
|
+
const delivery = this.ctx.get('assistantDelivery');
|
|
148
|
+
if (delivery?.goalWakeSettlementVersion?.() !== 1)
|
|
149
|
+
reject();
|
|
150
|
+
}
|
|
151
|
+
#boundRecord(intent, agent) {
|
|
152
|
+
const record = this.record(intent.scope, intent.goalId, agent);
|
|
153
|
+
if (record === undefined || !same(record.scope, intent.scope) || !same(record.definition, intent.definition)
|
|
154
|
+
|| record.native.sessionId !== intent.native.sessionId || record.native.goalId !== intent.native.goalId
|
|
155
|
+
|| record.native.maxGoalRounds !== intent.native.maxGoalRounds)
|
|
156
|
+
reject();
|
|
157
|
+
if (intent.dependencies === undefined
|
|
158
|
+
? record.checkpoint.dependencies.length > 0
|
|
159
|
+
: !same(record.checkpoint.dependencyBindings ?? [], intent.dependencies))
|
|
160
|
+
reject();
|
|
161
|
+
try {
|
|
162
|
+
this.assertDependencies(record);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
reject();
|
|
166
|
+
}
|
|
167
|
+
return record;
|
|
168
|
+
}
|
|
169
|
+
#current(intent, phase, agent) {
|
|
170
|
+
if (!this.#live || !this.ready() || Date.now() >= intent.expiresAt)
|
|
171
|
+
reject();
|
|
172
|
+
this.#requireSettlementCapability();
|
|
173
|
+
if (intent.id.startsWith('goal-event-wake-') && this.ctx.get('assistantDelivery')?.goalWakeResultVersion?.() !== 1)
|
|
174
|
+
reject();
|
|
175
|
+
this.assertEventWait(intent, phase);
|
|
176
|
+
const record = this.#boundRecord(intent, agent);
|
|
177
|
+
const native = record.native;
|
|
178
|
+
const before = native.phase === 'paused' && native.revision === intent.native.revision
|
|
179
|
+
&& native.roundsStarted === intent.native.roundsStarted;
|
|
180
|
+
const running = native.phase === 'active' && native.revision === intent.native.revision + 1;
|
|
181
|
+
const terminal = (native.phase === 'complete' || native.phase === 'blocked')
|
|
182
|
+
&& native.revision === intent.native.revision + 2;
|
|
183
|
+
const verifiedCompletion = native.phase === 'complete' && native.revision === intent.native.revision + 3
|
|
184
|
+
&& this.verifiedOutcome(record, intent.native)?.objectiveStatus === 'achieved';
|
|
185
|
+
const waitingAgain = phase === 'terminal' && native.phase === 'paused' && native.revision === intent.native.revision + 2
|
|
186
|
+
&& native.roundsStarted > intent.native.roundsStarted && this.acceptedEventPause(record, agent);
|
|
187
|
+
if (!(phase === 'before-resume' ? before : running || phase === 'terminal' && (terminal || verifiedCompletion || waitingAgain)))
|
|
188
|
+
reject();
|
|
189
|
+
this.#route(intent.scope, intent.ownerRouteId);
|
|
190
|
+
return record;
|
|
191
|
+
}
|
|
192
|
+
materialize(intent) {
|
|
193
|
+
const automations = this.#automations;
|
|
194
|
+
if (automations === undefined || !this.#live)
|
|
195
|
+
reject();
|
|
196
|
+
if (Date.now() >= intent.expiresAt) {
|
|
197
|
+
const expired = this.#store.prepare(intent);
|
|
198
|
+
return expired.state === 'prepared' || expired.state === 'scheduled'
|
|
199
|
+
? this.#store.finish(intent.id, 'denied', Date.now()) : expired;
|
|
200
|
+
}
|
|
201
|
+
this.#current(intent, 'before-resume');
|
|
202
|
+
const wake = this.#store.prepare(intent);
|
|
203
|
+
if (wake.state !== 'prepared' && wake.state !== 'scheduled')
|
|
204
|
+
return wake;
|
|
205
|
+
const definition = {
|
|
206
|
+
name: 'Scheduled business goal', schedule: { kind: 'at', at: new Date(intent.at).toISOString() },
|
|
207
|
+
workspace: intent.scope.workspace, agentPreset: intent.scope.preset,
|
|
208
|
+
timeoutMs: intent.expiresAt - intent.at, misfire: { kind: 'latest' }, overlap: 'skip',
|
|
209
|
+
retrySafety: 'never', maxRetries: 0, principal: intent.scope.principalId,
|
|
210
|
+
budgetId: intent.budgetId, budgetAmount: 1,
|
|
211
|
+
execution: { kind: 'host', executorId: owner, executorContractVersion: 1,
|
|
212
|
+
runbookId: 'resume-paused-native-goal', runbookVersion: 1, catalogDigest,
|
|
213
|
+
targetScope: { workspace: intent.scope.workspace, preset: intent.scope.preset },
|
|
214
|
+
scopeDigest: '0'.repeat(64), ownerRouteId: intent.ownerRouteId, activationNonce: intent.id },
|
|
215
|
+
};
|
|
216
|
+
// Publish paused first: no coordinator may claim the at occurrence until
|
|
217
|
+
// the exact definition binding is committed in the Goals database.
|
|
218
|
+
const result = automations.reconcileSystem({ owner, automationId: intent.id,
|
|
219
|
+
idempotencyKey: `prepare:${intent.id}`, desiredStatus: 'paused', definition });
|
|
220
|
+
const definitionHash = hash(result.definition);
|
|
221
|
+
this.#store.scheduled(intent.id, definitionHash);
|
|
222
|
+
automations.reconcileSystem({ owner, automationId: intent.id,
|
|
223
|
+
idempotencyKey: `activate:${intent.id}`, desiredStatus: 'active', definition });
|
|
224
|
+
const actual = automations.inspectSystemOwned({ owner, automationId: intent.id });
|
|
225
|
+
if (actual.definitionHash !== definitionHash)
|
|
226
|
+
reject();
|
|
227
|
+
return this.#reconcileTerminal(this.#store.get(intent.id));
|
|
228
|
+
}
|
|
229
|
+
#result(outcome, code, dispatched) {
|
|
230
|
+
return outcome === 'succeeded'
|
|
231
|
+
? { outcome, failureClass: 'none', failurePhase: 'none', failureCode: 'none', sideEffectState: 'possible', retryability: 'unsafe' }
|
|
232
|
+
: { outcome, failureClass: dispatched ? 'unknown' : 'policy', failurePhase: 'host-execution', failureCode: code,
|
|
233
|
+
sideEffectState: dispatched ? 'unknown' : 'none', retryability: dispatched ? 'unsafe' : 'after-intervention' };
|
|
234
|
+
}
|
|
235
|
+
async #execute(input) {
|
|
236
|
+
const wake = this.#store.get(input.activationNonce);
|
|
237
|
+
if (!this.#live || wake === undefined || input.executionMode !== 'production'
|
|
238
|
+
|| wake.intent.id !== input.automationId || wake.definitionHash !== input.definitionHash
|
|
239
|
+
|| input.catalogDigest !== catalogDigest || !same(input.targetScope, { workspace: wake.intent.scope.workspace, preset: wake.intent.scope.preset })
|
|
240
|
+
|| input.principal !== wake.intent.scope.principalId || input.ownerRouteId !== wake.intent.ownerRouteId) {
|
|
241
|
+
return this.#result('failed', 'goal-wake-identity-denied', false);
|
|
242
|
+
}
|
|
243
|
+
if (wake.state === 'denied')
|
|
244
|
+
return this.#result('failed', 'goal-wake-prior-state', false);
|
|
245
|
+
if (wake.state !== 'scheduled')
|
|
246
|
+
return this.#result('unknown', 'goal-wake-prior-state', true);
|
|
247
|
+
const intent = wake.intent;
|
|
248
|
+
let dispatched = false;
|
|
249
|
+
const signal = AbortSignal.any([input.signal, this.#lifecycle.signal]);
|
|
250
|
+
let terminalSettlement;
|
|
251
|
+
let terminalOutcome;
|
|
252
|
+
let capability;
|
|
253
|
+
capability = Object.freeze({ attestation: intent.attestation,
|
|
254
|
+
native: { goalId: intent.native.goalId, revision: intent.native.revision }, deadlineAt: intent.expiresAt, signal,
|
|
255
|
+
...(intent.id.startsWith('goal-event-wake-') ? { includeOutput: true } : {}),
|
|
256
|
+
assertCurrent: (agent, phase) => {
|
|
257
|
+
signal.throwIfAborted();
|
|
258
|
+
this.#current(intent, phase, agent);
|
|
259
|
+
const current = this.#store.get(intent.id);
|
|
260
|
+
if (dispatched ? current?.state !== 'dispatched' || current.occurrenceId !== input.occurrenceId
|
|
261
|
+
: current?.state !== 'scheduled')
|
|
262
|
+
reject();
|
|
263
|
+
},
|
|
264
|
+
beforeResume: (agent) => {
|
|
265
|
+
signal.throwIfAborted();
|
|
266
|
+
this.#current(intent, 'before-resume', agent);
|
|
267
|
+
this.#store.dispatch(intent.id, input.occurrenceId, Date.now());
|
|
268
|
+
dispatched = true;
|
|
269
|
+
},
|
|
270
|
+
settle: async (agent, settleSignal) => {
|
|
271
|
+
const combined = AbortSignal.any([signal, settleSignal]);
|
|
272
|
+
combined.throwIfAborted();
|
|
273
|
+
this.#current(intent, 'terminal', agent);
|
|
274
|
+
await this.settleExecution(agent, combined);
|
|
275
|
+
combined.throwIfAborted();
|
|
276
|
+
const settled = this.#current(intent, 'terminal', agent);
|
|
277
|
+
if (settled.native.phase === 'complete' || settled.native.phase === 'blocked') {
|
|
278
|
+
terminalSettlement = settled;
|
|
279
|
+
terminalOutcome = this.verifiedOutcome(settled, intent.native);
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
resolveOutcomeFeedbackTarget: () => {
|
|
283
|
+
signal.throwIfAborted();
|
|
284
|
+
if (!this.#capabilities.has(capability) || !dispatched || this.outcomeFeedbackTarget === undefined
|
|
285
|
+
|| terminalSettlement === undefined || terminalOutcome === undefined
|
|
286
|
+
|| terminalSettlement.native.phase !== 'complete' && terminalSettlement.native.phase !== 'blocked')
|
|
287
|
+
reject();
|
|
288
|
+
const current = this.#current(intent, 'terminal');
|
|
289
|
+
const currentOutcome = this.verifiedOutcome(current, intent.native);
|
|
290
|
+
const wake = this.#store.get(intent.id);
|
|
291
|
+
if (wake?.state !== 'dispatched' || wake.occurrenceId !== input.occurrenceId
|
|
292
|
+
|| current.native.phase !== 'complete' && current.native.phase !== 'blocked'
|
|
293
|
+
|| !same(current.scope, terminalSettlement.scope) || current.id !== terminalSettlement.id
|
|
294
|
+
|| !same(current.definition, terminalSettlement.definition) || !same(current.native, terminalSettlement.native)
|
|
295
|
+
|| currentOutcome === undefined || !same(currentOutcome, terminalOutcome))
|
|
296
|
+
reject();
|
|
297
|
+
return this.outcomeFeedbackTarget(intent, current, currentOutcome);
|
|
298
|
+
}, });
|
|
299
|
+
this.#capabilities.add(capability);
|
|
300
|
+
try {
|
|
301
|
+
signal.throwIfAborted();
|
|
302
|
+
this.#current(intent, 'before-resume');
|
|
303
|
+
const delivery = this.ctx.get('assistantDelivery');
|
|
304
|
+
if (delivery === undefined)
|
|
305
|
+
reject();
|
|
306
|
+
const result = await delivery.resumeScheduledGoal(capability);
|
|
307
|
+
if (result.dispatched !== dispatched)
|
|
308
|
+
throw new Error('assistant-goals: wake dispatch disagreement');
|
|
309
|
+
const succeeded = result.outcome === 'succeeded' && result.quiescent && dispatched && !signal.aborted;
|
|
310
|
+
const settled = succeeded ? this.#current(intent, 'terminal') : undefined;
|
|
311
|
+
if (succeeded && settled?.native.phase !== 'paused' && intent.id.startsWith('goal-event-wake-')) {
|
|
312
|
+
delivery.enqueueScheduledGoalResult(capability);
|
|
313
|
+
}
|
|
314
|
+
this.#store.finish(intent.id, succeeded ? 'succeeded' : dispatched ? 'unknown' : 'denied', Date.now());
|
|
315
|
+
return this.#result(succeeded ? 'succeeded' : dispatched ? 'unknown' : 'failed', `goal-wake-${result.outcome}`, dispatched);
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
if (this.#live)
|
|
319
|
+
this.#store.finish(intent.id, dispatched ? 'unknown' : 'denied', Date.now());
|
|
320
|
+
return this.#result(dispatched ? 'unknown' : 'failed', 'goal-wake-execution-unconfirmed', dispatched);
|
|
321
|
+
}
|
|
322
|
+
finally {
|
|
323
|
+
this.#capabilities.delete(capability);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
//# sourceMappingURL=wake.js.map
|