@nt-ai-lab/deterministic-agent-workflow-engine 0.3.0 → 0.3.2

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
- return `You forgot. Next message MUST begin with: ${expectedPrefix}`;
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
  }
@@ -5,7 +5,9 @@ export const JOURNAL_GUIDANCE = [
5
5
  'PLATFORM NOTIFICATION',
6
6
  '',
7
7
  'Record your progress and reasoning as you work by calling:',
8
- ' write-journal <agent-name> "<1\u20133 sentence note>"',
8
+ ' <workflow-command> write-journal <agent-name> "<detailed journal entry>"',
9
+ '',
10
+ 'Use the same workflow command prefix used for other workflow commands such as transition.',
9
11
  '',
10
12
  'Use it for key decisions, progress milestones, and blockers.',
11
13
  'Every session should have a journal trail of the work performed.',
@@ -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.0",
3
+ "version": "0.3.2",
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
  }