@nt-ai-lab/deterministic-agent-workflow-codex 0.3.7
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/dist/features/codex-code-cli/entrypoint/codex-code-workflow-cli.d.ts +4 -0
- package/dist/features/codex-code-cli/entrypoint/codex-code-workflow-cli.js +201 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/platform/domain/codex-workflow-cli-types.d.ts +10 -0
- package/dist/platform/domain/codex-workflow-cli-types.js +1 -0
- package/dist/platform/infra/external-clients/codex/codex-hook-schemas.d.ts +64 -0
- package/dist/platform/infra/external-clients/codex/codex-hook-schemas.js +17 -0
- package/package.json +27 -0
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { BaseWorkflowState, RehydratableWorkflow } from '@nt-ai-lab/deterministic-agent-workflow-engine';
|
|
2
|
+
import type { CodexWorkflowCliConfig } from '../../../platform/domain/codex-workflow-cli-types';
|
|
3
|
+
/** @riviere-role cli-entrypoint */
|
|
4
|
+
export declare function createCodexWorkflowCli<TWorkflow extends RehydratableWorkflow<TState>, TState extends BaseWorkflowState<TStateName>, TDeps, TStateName extends string = string, TOperation extends string = string>(config: CodexWorkflowCliConfig<TWorkflow, TState, TDeps, TStateName, TOperation>): void;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { reduceWorkflowStateFromStoredEvents, WorkflowEngine, } from '@nt-ai-lab/deterministic-agent-workflow-engine';
|
|
3
|
+
import { createPreToolUseHandler, createWorkflowRunner, formatContextInjection, formatDenyDecision, getRepositoryName, } from '@nt-ai-lab/deterministic-agent-workflow-cli';
|
|
4
|
+
import { codexHookInputSchema, codexPreToolUseInputSchema, codexSubagentStartInputSchema, } from '../../../platform/infra/external-clients/codex/codex-hook-schemas.js';
|
|
5
|
+
const EMPTY_TRANSCRIPT_READER = { readMessages: () => [] };
|
|
6
|
+
/** @riviere-role cli-entrypoint */
|
|
7
|
+
export function createCodexWorkflowCli(config) {
|
|
8
|
+
const root = config.workflowRoot ?? resolveWorkflowRoot();
|
|
9
|
+
const databasePath = resolveDatabasePath(config.processDeps);
|
|
10
|
+
const store = config.processDeps.buildStore(databasePath);
|
|
11
|
+
const now = () => new Date().toISOString();
|
|
12
|
+
const engineDeps = {
|
|
13
|
+
store,
|
|
14
|
+
getPluginRoot: () => root,
|
|
15
|
+
getEnvFilePath: () => join(root, '.codex', 'unused.env'),
|
|
16
|
+
readFile: config.processDeps.readFile,
|
|
17
|
+
appendToFile: config.processDeps.appendToFile,
|
|
18
|
+
now,
|
|
19
|
+
transcriptReader: config.transcriptReader ?? EMPTY_TRANSCRIPT_READER,
|
|
20
|
+
};
|
|
21
|
+
const args = config.processDeps.getArgv().slice(2);
|
|
22
|
+
try {
|
|
23
|
+
const result = args.length === 0
|
|
24
|
+
? handleHookInvocation(config, engineDeps, root, now)
|
|
25
|
+
: handleWorkflowCommand(config, engineDeps, root, now, args);
|
|
26
|
+
writeResult(config.processDeps, result);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
config.processDeps.writeStderr(`[${now()}] ERROR: ${String(error)}\n`);
|
|
30
|
+
config.processDeps.exit(1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function handleWorkflowCommand(config, engineDeps, root, now, args) {
|
|
34
|
+
if (args.length < 2 || args[0] === '' || args[1] === '') {
|
|
35
|
+
throw new TypeError('Codex workflow commands require <operation> <session-id> [args]');
|
|
36
|
+
}
|
|
37
|
+
const [operation, sessionId, ...operationArgs] = args;
|
|
38
|
+
const workflowDeps = buildWorkflowDeps(config, engineDeps.store, root, now, sessionId);
|
|
39
|
+
return createWorkflowRunner(config)([operation, ...operationArgs], engineDeps, workflowDeps, { getSessionId: () => sessionId, });
|
|
40
|
+
}
|
|
41
|
+
function buildWorkflowDeps(config, store, root, now, sessionId) {
|
|
42
|
+
const platform = {
|
|
43
|
+
getPluginRoot: () => root,
|
|
44
|
+
now,
|
|
45
|
+
getSessionId: () => sessionId,
|
|
46
|
+
store,
|
|
47
|
+
};
|
|
48
|
+
return config.buildWorkflowDeps(platform);
|
|
49
|
+
}
|
|
50
|
+
function resolveWorkflowRoot() {
|
|
51
|
+
return process.cwd();
|
|
52
|
+
}
|
|
53
|
+
function resolveDatabasePath(processDeps) {
|
|
54
|
+
const configured = processDeps.getEnv('WORKFLOW_EVENTS_DB');
|
|
55
|
+
if (configured !== undefined && configured !== '')
|
|
56
|
+
return configured;
|
|
57
|
+
const home = processDeps.getEnv('HOME');
|
|
58
|
+
if (home === undefined || home === '')
|
|
59
|
+
throw new TypeError('Missing required environment variable: HOME');
|
|
60
|
+
return join(home, '.workflow-events.db');
|
|
61
|
+
}
|
|
62
|
+
function handleHookInvocation(config, engineDeps, root, now) {
|
|
63
|
+
const raw = config.processDeps.readFile('/dev/stdin');
|
|
64
|
+
const parsed = codexHookInputSchema.parse(JSON.parse(raw));
|
|
65
|
+
const workflowDeps = buildWorkflowDeps(config, engineDeps.store, root, now, parsed.session_id);
|
|
66
|
+
const engine = new WorkflowEngine(config.workflowDefinition, engineDeps, workflowDeps);
|
|
67
|
+
switch (parsed.hook_event_name) {
|
|
68
|
+
case 'SessionStart': return startSession(config, engine, parsed.session_id, parsed.transcript_path, parsed.cwd);
|
|
69
|
+
case 'PreToolUse': return checkToolUse(config, engine, raw);
|
|
70
|
+
case 'SubagentStart': return registerSubagent(engine, raw);
|
|
71
|
+
case 'Stop': return preventUnsupportedStop(config, engineDeps, parsed.session_id);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function startSession(config, engine, sessionId, transcriptPath, cwd) {
|
|
75
|
+
if (!engine.hasSessionStarted(sessionId)) {
|
|
76
|
+
const noTranscriptPath = '';
|
|
77
|
+
const result = engine.startSession(sessionId, transcriptPath ?? noTranscriptPath, getRepositoryName(cwd));
|
|
78
|
+
if (result.type !== 'success')
|
|
79
|
+
return toRunnerResult(result);
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
output: formatContextInjection(`Workflow session: ${sessionId}. Use ${config.workflowCommand} transition ${sessionId} <STATE> for transitions, or ${config.workflowCommand} <OPERATION> ${sessionId} <ARGS> for workflow operations.`),
|
|
83
|
+
exitCode: 0,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function checkToolUse(config, engine, raw) {
|
|
87
|
+
const input = codexPreToolUseInputSchema.parse(JSON.parse(raw));
|
|
88
|
+
if (!engine.hasSessionStarted(input.session_id))
|
|
89
|
+
return {
|
|
90
|
+
output: '',
|
|
91
|
+
exitCode: 0
|
|
92
|
+
};
|
|
93
|
+
const handler = resolvePreToolUseHandler(config);
|
|
94
|
+
if (handler === undefined)
|
|
95
|
+
return {
|
|
96
|
+
output: '',
|
|
97
|
+
exitCode: 0
|
|
98
|
+
};
|
|
99
|
+
if (input.tool_name === 'apply_patch')
|
|
100
|
+
return checkPatchPaths(handler, engine, input.session_id, input.tool_input);
|
|
101
|
+
return toHookResult(handler(engine, input.session_id, input.tool_name, input.tool_input));
|
|
102
|
+
}
|
|
103
|
+
function resolvePreToolUseHandler(config) {
|
|
104
|
+
if (config.bashForbidden === undefined && config.isWriteAllowed === undefined) {
|
|
105
|
+
if (config.customGates !== undefined) {
|
|
106
|
+
throw new TypeError('CodexWorkflowCliConfig: customGates requires bashForbidden and isWriteAllowed.');
|
|
107
|
+
}
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
if (config.bashForbidden === undefined || config.isWriteAllowed === undefined) {
|
|
111
|
+
throw new TypeError('CodexWorkflowCliConfig: bashForbidden and isWriteAllowed must be provided together.');
|
|
112
|
+
}
|
|
113
|
+
return createPreToolUseHandler({
|
|
114
|
+
bashForbidden: config.bashForbidden,
|
|
115
|
+
isWriteAllowed: config.isWriteAllowed,
|
|
116
|
+
customGates: config.customGates,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function checkPatchPaths(handler, engine, sessionId, toolInput) {
|
|
120
|
+
const command = toolInput.command;
|
|
121
|
+
if (typeof command !== 'string')
|
|
122
|
+
return deny('Codex apply_patch hook is missing tool_input.command');
|
|
123
|
+
const paths = extractPatchPaths(command);
|
|
124
|
+
if (paths.length === 0)
|
|
125
|
+
return deny('Cannot determine every file edited by Codex apply_patch');
|
|
126
|
+
for (const path of paths) {
|
|
127
|
+
const result = handler(engine, sessionId, 'Write', { file_path: path });
|
|
128
|
+
if (result.type === 'blocked')
|
|
129
|
+
return toHookResult(result);
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
output: '',
|
|
133
|
+
exitCode: 0
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function extractPatchPaths(command) {
|
|
137
|
+
const paths = new Set();
|
|
138
|
+
for (const line of command.split('\n')) {
|
|
139
|
+
const match = /^\*\*\* Update File: (.+)$|^\*\*\* Add File: (.+)$|^\*\*\* Delete File: (.+)$/.exec(line);
|
|
140
|
+
const path = match?.[1] ?? match?.[2] ?? match?.[3];
|
|
141
|
+
if (path !== undefined && path !== '')
|
|
142
|
+
paths.add(path);
|
|
143
|
+
}
|
|
144
|
+
return [...paths];
|
|
145
|
+
}
|
|
146
|
+
function registerSubagent(engine, raw) {
|
|
147
|
+
const input = codexSubagentStartInputSchema.parse(JSON.parse(raw));
|
|
148
|
+
if (!engine.hasSessionStarted(input.session_id))
|
|
149
|
+
return {
|
|
150
|
+
output: '',
|
|
151
|
+
exitCode: 0
|
|
152
|
+
};
|
|
153
|
+
const result = engine.transaction(input.session_id, 'register-agent', (workflow) => workflow.registerAgent(input.agent_type, input.agent_id));
|
|
154
|
+
return {
|
|
155
|
+
output: formatContextInjection(result.type === 'success' ? result.output : ''),
|
|
156
|
+
exitCode: 0,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function preventUnsupportedStop(config, engineDeps, sessionId) {
|
|
160
|
+
const stored = engineDeps.store.readEvents(sessionId);
|
|
161
|
+
if (!engineDeps.store.hasSessionStarted(sessionId))
|
|
162
|
+
return {
|
|
163
|
+
output: '',
|
|
164
|
+
exitCode: 0
|
|
165
|
+
};
|
|
166
|
+
const state = reduceWorkflowStateFromStoredEvents(config.workflowDefinition, stored);
|
|
167
|
+
if (config.workflowDefinition.getRegistry()[state.currentStateMachineState].allowIdle === true)
|
|
168
|
+
return {
|
|
169
|
+
output: '',
|
|
170
|
+
exitCode: 0
|
|
171
|
+
};
|
|
172
|
+
return {
|
|
173
|
+
output: JSON.stringify({
|
|
174
|
+
continue: false,
|
|
175
|
+
stopReason: `Workflow state ${state.currentStateMachineState} does not allow stopping.`
|
|
176
|
+
}),
|
|
177
|
+
exitCode: 0,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function toHookResult(result) {
|
|
181
|
+
if (result.type === 'blocked')
|
|
182
|
+
return deny(result.output);
|
|
183
|
+
return toRunnerResult(result);
|
|
184
|
+
}
|
|
185
|
+
function toRunnerResult(result) {
|
|
186
|
+
return {
|
|
187
|
+
output: result.output,
|
|
188
|
+
exitCode: result.type === 'success' ? 0 : 1
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function deny(reason) {
|
|
192
|
+
return {
|
|
193
|
+
output: formatDenyDecision(reason),
|
|
194
|
+
exitCode: 0
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function writeResult(processDeps, result) {
|
|
198
|
+
if (result.output !== '')
|
|
199
|
+
processDeps.writeStdout(result.output);
|
|
200
|
+
processDeps.exit(result.exitCode);
|
|
201
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createCodexWorkflowCli } from './features/codex-code-cli/entrypoint/codex-code-workflow-cli.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BaseWorkflowState, RehydratableWorkflow, TranscriptReader } from '@nt-ai-lab/deterministic-agent-workflow-engine';
|
|
2
|
+
import type { PlatformContext, ProcessDeps, WorkflowRunnerConfig } from '@nt-ai-lab/deterministic-agent-workflow-cli';
|
|
3
|
+
/** @riviere-role value-object */
|
|
4
|
+
export type CodexWorkflowCliConfig<TWorkflow extends RehydratableWorkflow<TState>, TState extends BaseWorkflowState<TStateName>, TDeps, TStateName extends string = string, TOperation extends string = string> = WorkflowRunnerConfig<TWorkflow, TState, TDeps, TStateName, TOperation> & {
|
|
5
|
+
readonly buildWorkflowDeps: (platform: PlatformContext) => TDeps;
|
|
6
|
+
readonly processDeps: ProcessDeps;
|
|
7
|
+
readonly workflowCommand: string;
|
|
8
|
+
readonly workflowRoot?: string;
|
|
9
|
+
readonly transcriptReader?: TranscriptReader;
|
|
10
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const codexHookInputSchema: z.ZodObject<{
|
|
3
|
+
session_id: z.ZodString;
|
|
4
|
+
transcript_path: z.ZodNullable<z.ZodString>;
|
|
5
|
+
cwd: z.ZodString;
|
|
6
|
+
hook_event_name: z.ZodEnum<["SessionStart", "PreToolUse", "SubagentStart", "Stop"]>;
|
|
7
|
+
}, "strip", z.ZodTypeAny, {
|
|
8
|
+
session_id: string;
|
|
9
|
+
transcript_path: string | null;
|
|
10
|
+
cwd: string;
|
|
11
|
+
hook_event_name: "SessionStart" | "PreToolUse" | "SubagentStart" | "Stop";
|
|
12
|
+
}, {
|
|
13
|
+
session_id: string;
|
|
14
|
+
transcript_path: string | null;
|
|
15
|
+
cwd: string;
|
|
16
|
+
hook_event_name: "SessionStart" | "PreToolUse" | "SubagentStart" | "Stop";
|
|
17
|
+
}>;
|
|
18
|
+
export declare const codexPreToolUseInputSchema: z.ZodObject<{
|
|
19
|
+
session_id: z.ZodString;
|
|
20
|
+
transcript_path: z.ZodNullable<z.ZodString>;
|
|
21
|
+
cwd: z.ZodString;
|
|
22
|
+
} & {
|
|
23
|
+
hook_event_name: z.ZodLiteral<"PreToolUse">;
|
|
24
|
+
tool_name: z.ZodString;
|
|
25
|
+
tool_input: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
26
|
+
}, "strip", z.ZodTypeAny, {
|
|
27
|
+
session_id: string;
|
|
28
|
+
transcript_path: string | null;
|
|
29
|
+
cwd: string;
|
|
30
|
+
hook_event_name: "PreToolUse";
|
|
31
|
+
tool_name: string;
|
|
32
|
+
tool_input: Record<string, unknown>;
|
|
33
|
+
}, {
|
|
34
|
+
session_id: string;
|
|
35
|
+
transcript_path: string | null;
|
|
36
|
+
cwd: string;
|
|
37
|
+
hook_event_name: "PreToolUse";
|
|
38
|
+
tool_name: string;
|
|
39
|
+
tool_input: Record<string, unknown>;
|
|
40
|
+
}>;
|
|
41
|
+
export declare const codexSubagentStartInputSchema: z.ZodObject<{
|
|
42
|
+
session_id: z.ZodString;
|
|
43
|
+
transcript_path: z.ZodNullable<z.ZodString>;
|
|
44
|
+
cwd: z.ZodString;
|
|
45
|
+
} & {
|
|
46
|
+
hook_event_name: z.ZodLiteral<"SubagentStart">;
|
|
47
|
+
agent_id: z.ZodString;
|
|
48
|
+
agent_type: z.ZodString;
|
|
49
|
+
}, "strip", z.ZodTypeAny, {
|
|
50
|
+
session_id: string;
|
|
51
|
+
transcript_path: string | null;
|
|
52
|
+
cwd: string;
|
|
53
|
+
hook_event_name: "SubagentStart";
|
|
54
|
+
agent_id: string;
|
|
55
|
+
agent_type: string;
|
|
56
|
+
}, {
|
|
57
|
+
session_id: string;
|
|
58
|
+
transcript_path: string | null;
|
|
59
|
+
cwd: string;
|
|
60
|
+
hook_event_name: "SubagentStart";
|
|
61
|
+
agent_id: string;
|
|
62
|
+
agent_type: string;
|
|
63
|
+
}>;
|
|
64
|
+
export type CodexHookInput = z.infer<typeof codexHookInputSchema>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const codexHookInputSchema = z.object({
|
|
3
|
+
session_id: z.string().min(1),
|
|
4
|
+
transcript_path: z.string().nullable(),
|
|
5
|
+
cwd: z.string().min(1),
|
|
6
|
+
hook_event_name: z.enum(['SessionStart', 'PreToolUse', 'SubagentStart', 'Stop']),
|
|
7
|
+
});
|
|
8
|
+
export const codexPreToolUseInputSchema = codexHookInputSchema.extend({
|
|
9
|
+
hook_event_name: z.literal('PreToolUse'),
|
|
10
|
+
tool_name: z.string().min(1),
|
|
11
|
+
tool_input: z.record(z.unknown()),
|
|
12
|
+
});
|
|
13
|
+
export const codexSubagentStartInputSchema = codexHookInputSchema.extend({
|
|
14
|
+
hook_event_name: z.literal('SubagentStart'),
|
|
15
|
+
agent_id: z.string().min(1),
|
|
16
|
+
agent_type: z.string().min(1),
|
|
17
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nt-ai-lab/deterministic-agent-workflow-codex",
|
|
3
|
+
"version": "0.3.7",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/NTCoding/deterministic-agent-workflows.git",
|
|
9
|
+
"directory": "packages/deterministic-agent-workflows-codex"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"zod": "^3.25.76",
|
|
20
|
+
"@nt-ai-lab/deterministic-agent-workflow-cli": "0.3.7",
|
|
21
|
+
"@nt-ai-lab/deterministic-agent-workflow-engine": "0.3.6",
|
|
22
|
+
"@nt-ai-lab/deterministic-agent-workflow-event-store": "0.3.6"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
}
|
|
27
|
+
}
|