@nt-ai-lab/deterministic-agent-workflow-engine 0.3.0 → 0.3.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.
|
@@ -315,7 +315,6 @@ export class WorkflowEngine {
|
|
|
315
315
|
const transcriptPath = workflow.getTranscriptPath();
|
|
316
316
|
const state = workflow.getState().currentStateMachineState;
|
|
317
317
|
const registry = this.factory.getRegistry();
|
|
318
|
-
const expectedPrefix = getExpectedPrefix(state, registry);
|
|
319
318
|
const pattern = buildPrefixPattern(registry);
|
|
320
319
|
const messages = this.engineDeps.transcriptReader.readMessages(transcriptPath);
|
|
321
320
|
const identityCheckResult = checkIdentity(messages, pattern);
|
|
@@ -326,7 +325,17 @@ export class WorkflowEngine {
|
|
|
326
325
|
transcriptPath,
|
|
327
326
|
}], workflow.getState()));
|
|
328
327
|
if (identityCheckResult.status === 'lost') {
|
|
329
|
-
|
|
328
|
+
const currentProcedure = readProcedure(this.engineDeps, state);
|
|
329
|
+
return [
|
|
330
|
+
'Your last message is missing the required state prefix.',
|
|
331
|
+
'',
|
|
332
|
+
`- send a new message starting with: ${getExpectedPrefix(state, registry)}`,
|
|
333
|
+
'- then continue with the current procedure',
|
|
334
|
+
'',
|
|
335
|
+
'Current procedure:',
|
|
336
|
+
'',
|
|
337
|
+
currentProcedure,
|
|
338
|
+
].join('\n');
|
|
330
339
|
}
|
|
331
340
|
return undefined;
|
|
332
341
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { WorkflowEngine, pass, } from '../../../../index.js';
|
|
3
|
+
import { SEPARATOR } from './output-guidance.js';
|
|
4
|
+
function isSessionStartedEvent(event) {
|
|
5
|
+
return event.type === 'session-started';
|
|
6
|
+
}
|
|
7
|
+
class PlanningWorkflow {
|
|
8
|
+
state;
|
|
9
|
+
pendingEvents;
|
|
10
|
+
constructor(state, pendingEvents = []) {
|
|
11
|
+
this.state = state;
|
|
12
|
+
this.pendingEvents = pendingEvents;
|
|
13
|
+
}
|
|
14
|
+
getState() {
|
|
15
|
+
return this.state;
|
|
16
|
+
}
|
|
17
|
+
appendEvent(event) {
|
|
18
|
+
this.pendingEvents = [...this.pendingEvents, event];
|
|
19
|
+
if (isSessionStartedEvent(event)) {
|
|
20
|
+
this.state = {
|
|
21
|
+
...this.state,
|
|
22
|
+
transcriptPath: event.transcriptPath,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
getPendingEvents() {
|
|
27
|
+
return this.pendingEvents;
|
|
28
|
+
}
|
|
29
|
+
startSession(transcriptPath, _repository) {
|
|
30
|
+
void _repository;
|
|
31
|
+
this.state = {
|
|
32
|
+
...this.state,
|
|
33
|
+
transcriptPath,
|
|
34
|
+
};
|
|
35
|
+
this.pendingEvents = [...this.pendingEvents, {
|
|
36
|
+
type: 'session-started',
|
|
37
|
+
at: '2026-01-01T00:00:00Z',
|
|
38
|
+
transcriptPath,
|
|
39
|
+
currentState: this.state.currentStateMachineState,
|
|
40
|
+
states: ['PLANNING'],
|
|
41
|
+
}];
|
|
42
|
+
}
|
|
43
|
+
getTranscriptPath() {
|
|
44
|
+
return this.state.transcriptPath;
|
|
45
|
+
}
|
|
46
|
+
registerAgent(_agentType, _agentId) {
|
|
47
|
+
void _agentType;
|
|
48
|
+
void _agentId;
|
|
49
|
+
return pass();
|
|
50
|
+
}
|
|
51
|
+
handleTeammateIdle(_agentName) {
|
|
52
|
+
void _agentName;
|
|
53
|
+
return pass();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
class ReflectionStoreNotConfiguredError extends Error {
|
|
57
|
+
constructor() {
|
|
58
|
+
super('Reflection storage is not configured for this test');
|
|
59
|
+
this.name = 'ReflectionStoreNotConfiguredError';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
class MemoryStore {
|
|
63
|
+
eventsBySessionId = new Map();
|
|
64
|
+
readEvents(sessionId) {
|
|
65
|
+
return this.eventsBySessionId.get(sessionId) ?? [];
|
|
66
|
+
}
|
|
67
|
+
appendEvents(sessionId, events) {
|
|
68
|
+
const existingEvents = this.eventsBySessionId.get(sessionId) ?? [];
|
|
69
|
+
this.eventsBySessionId.set(sessionId, [...existingEvents, ...events]);
|
|
70
|
+
}
|
|
71
|
+
sessionExists(sessionId) {
|
|
72
|
+
return this.eventsBySessionId.has(sessionId);
|
|
73
|
+
}
|
|
74
|
+
hasSessionStarted(sessionId) {
|
|
75
|
+
return this.eventsBySessionId.get(sessionId)?.some((event) => {
|
|
76
|
+
return event.envelope.type === 'session-started';
|
|
77
|
+
}) ?? false;
|
|
78
|
+
}
|
|
79
|
+
recordReflection(_sessionId, _createdAt, _input) {
|
|
80
|
+
void _sessionId;
|
|
81
|
+
void _createdAt;
|
|
82
|
+
void _input;
|
|
83
|
+
throw new ReflectionStoreNotConfiguredError();
|
|
84
|
+
}
|
|
85
|
+
listReflections() {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const procedureContent = 'PLANNING instructions';
|
|
90
|
+
const workflowDefinition = {
|
|
91
|
+
fold(state, event) {
|
|
92
|
+
if (!isSessionStartedEvent(event)) {
|
|
93
|
+
return state;
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
...state,
|
|
97
|
+
transcriptPath: event.transcriptPath,
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
buildWorkflow(state, _deps) {
|
|
101
|
+
void _deps;
|
|
102
|
+
return new PlanningWorkflow(state);
|
|
103
|
+
},
|
|
104
|
+
stateSchema: z.literal('PLANNING'),
|
|
105
|
+
initialState() {
|
|
106
|
+
return {
|
|
107
|
+
currentStateMachineState: 'PLANNING',
|
|
108
|
+
transcriptPath: '',
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
getRegistry() {
|
|
112
|
+
return {
|
|
113
|
+
PLANNING: {
|
|
114
|
+
emoji: '🧭',
|
|
115
|
+
agentInstructions: procedureContent,
|
|
116
|
+
canTransitionTo: [],
|
|
117
|
+
allowedWorkflowOperations: ['write'],
|
|
118
|
+
forbidden: { write: true },
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
},
|
|
122
|
+
buildTransitionContext(state, from, to) {
|
|
123
|
+
return {
|
|
124
|
+
state,
|
|
125
|
+
from,
|
|
126
|
+
to,
|
|
127
|
+
gitInfo: {
|
|
128
|
+
currentBranch: 'main',
|
|
129
|
+
workingTreeClean: true,
|
|
130
|
+
headCommit: 'abc123',
|
|
131
|
+
changedFilesVsDefault: [],
|
|
132
|
+
hasCommitsVsDefault: false,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
const transcriptReader = {
|
|
138
|
+
readMessages: () => [{
|
|
139
|
+
id: 'message-1',
|
|
140
|
+
textContent: 'plain text without the expected prefix',
|
|
141
|
+
}],
|
|
142
|
+
};
|
|
143
|
+
const engineDeps = {
|
|
144
|
+
store: new MemoryStore(),
|
|
145
|
+
getPluginRoot: () => '/plugin-root',
|
|
146
|
+
getEnvFilePath: () => '/plugin-root/.env',
|
|
147
|
+
readFile: (path) => {
|
|
148
|
+
if (path === '/plugin-root/states/planning.md') {
|
|
149
|
+
return procedureContent;
|
|
150
|
+
}
|
|
151
|
+
return '';
|
|
152
|
+
},
|
|
153
|
+
appendToFile: (filePath, content) => {
|
|
154
|
+
void filePath;
|
|
155
|
+
void content;
|
|
156
|
+
},
|
|
157
|
+
now: () => '2026-01-01T00:00:00Z',
|
|
158
|
+
transcriptReader,
|
|
159
|
+
};
|
|
160
|
+
it('reinserts the current procedure when identity verification fails', () => {
|
|
161
|
+
const emptyWorkflowDeps = {};
|
|
162
|
+
const engine = new WorkflowEngine(workflowDefinition, engineDeps, emptyWorkflowDeps);
|
|
163
|
+
engine.startSession('session-1', '/transcripts/session-1.jsonl');
|
|
164
|
+
const result = engine.checkWrite('session-1', 'Write', '/workspace/note.md', () => true);
|
|
165
|
+
const expectedOutput = [
|
|
166
|
+
'Cannot write-check',
|
|
167
|
+
SEPARATOR,
|
|
168
|
+
'Your last message is missing the required state prefix.',
|
|
169
|
+
'',
|
|
170
|
+
'- send a new message starting with: 🧭 PLANNING',
|
|
171
|
+
'- then continue with the current procedure',
|
|
172
|
+
'',
|
|
173
|
+
'Current procedure:',
|
|
174
|
+
'',
|
|
175
|
+
procedureContent,
|
|
176
|
+
'',
|
|
177
|
+
'Next message MUST begin with: 🧭 PLANNING',
|
|
178
|
+
].join('\n');
|
|
179
|
+
const expectedResult = {
|
|
180
|
+
type: 'blocked',
|
|
181
|
+
output: expectedOutput,
|
|
182
|
+
};
|
|
183
|
+
expect(result).toEqual(expectedResult);
|
|
184
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nt-ai-lab/deterministic-agent-workflow-engine",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"zod": "^3.25.76"
|
|
15
15
|
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"vitest": "^2.1.9"
|
|
18
|
+
},
|
|
16
19
|
"publishConfig": {
|
|
17
20
|
"access": "public"
|
|
18
21
|
}
|