@nt-ai-lab/deterministic-agent-workflow-codex 0.4.0 → 0.4.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.
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BaseWorkflowState, RehydratableWorkflow } from '@nt-ai-lab/deterministic-agent-workflow-engine';
|
|
2
2
|
import type { CodexWorkflowCliConfig } from '../../../platform/domain/codex-workflow-cli-types';
|
|
3
|
+
export declare function resolveTranscriptPath(sessionId: string, suppliedPath: string | null, now: () => string): string;
|
|
3
4
|
/** @riviere-role cli-entrypoint */
|
|
4
5
|
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;
|
|
@@ -1,8 +1,56 @@
|
|
|
1
|
+
import { accessSync, constants, readdirSync, statSync, } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
1
3
|
import { join } from 'node:path';
|
|
2
4
|
import { reduceWorkflowStateFromStoredEvents, WorkflowEngine, } from '@nt-ai-lab/deterministic-agent-workflow-engine';
|
|
3
5
|
import { createPreToolUseHandler, createWorkflowRunner, formatContextInjection, formatDenyDecision, getRepositoryName, } from '@nt-ai-lab/deterministic-agent-workflow-cli';
|
|
4
6
|
import { codexHookInputSchema, codexPreToolUseInputSchema, codexSubagentStartInputSchema, } from '../../../platform/infra/external-clients/codex/codex-hook-schemas.js';
|
|
5
7
|
const EMPTY_TRANSCRIPT_READER = { readMessages: () => [] };
|
|
8
|
+
function requireNonEmptyString(value, name) {
|
|
9
|
+
if (value === undefined)
|
|
10
|
+
throw new TypeError(`${name} must be a non-empty string.`);
|
|
11
|
+
const trimmed = value.trim();
|
|
12
|
+
if (trimmed.length === 0)
|
|
13
|
+
throw new TypeError(`${name} must be a non-empty string.`);
|
|
14
|
+
return trimmed;
|
|
15
|
+
}
|
|
16
|
+
export function resolveTranscriptPath(sessionId, suppliedPath, now) {
|
|
17
|
+
const candidate = suppliedPath === null ? '' : suppliedPath.trim();
|
|
18
|
+
if (candidate.length > 0) {
|
|
19
|
+
try {
|
|
20
|
+
accessSync(candidate, constants.R_OK);
|
|
21
|
+
if (!statSync(candidate).isFile())
|
|
22
|
+
throw new TypeError('not a file');
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
throw new TypeError(`Codex transcript is not a readable file: ${candidate}`);
|
|
26
|
+
}
|
|
27
|
+
return candidate;
|
|
28
|
+
}
|
|
29
|
+
const startedAt = new Date(now());
|
|
30
|
+
const directory = join(homedir(), '.codex', 'sessions', String(startedAt.getUTCFullYear()), String(startedAt.getUTCMonth() + 1).padStart(2, '0'), String(startedAt.getUTCDate()).padStart(2, '0'));
|
|
31
|
+
const suffix = `-${sessionId}.jsonl`;
|
|
32
|
+
const matches = (() => {
|
|
33
|
+
try {
|
|
34
|
+
return readdirSync(directory).filter((name) => name.startsWith('rollout-') && name.endsWith(suffix)).map((name) => join(directory, name));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
throw new TypeError(`Unable to resolve exactly one readable Codex transcript for session ${sessionId} in ${directory}.`);
|
|
38
|
+
}
|
|
39
|
+
})();
|
|
40
|
+
if (matches.length !== 1) {
|
|
41
|
+
throw new TypeError(`Unable to resolve exactly one readable Codex transcript for session ${sessionId} in ${directory}.`);
|
|
42
|
+
}
|
|
43
|
+
const transcriptPath = requireNonEmptyString(matches[0], 'transcriptPath');
|
|
44
|
+
try {
|
|
45
|
+
accessSync(transcriptPath, constants.R_OK);
|
|
46
|
+
if (!statSync(transcriptPath).isFile())
|
|
47
|
+
throw new TypeError('not a file');
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
throw new TypeError(`Unable to resolve exactly one readable Codex transcript for session ${sessionId} in ${directory}.`);
|
|
51
|
+
}
|
|
52
|
+
return transcriptPath;
|
|
53
|
+
}
|
|
6
54
|
/** @riviere-role cli-entrypoint */
|
|
7
55
|
export function createCodexWorkflowCli(config) {
|
|
8
56
|
const root = config.workflowRoot ?? resolveWorkflowRoot();
|
|
@@ -65,16 +113,19 @@ function handleHookInvocation(config, engineDeps, root, now) {
|
|
|
65
113
|
const workflowDeps = buildWorkflowDeps(config, engineDeps.store, root, now, parsed.session_id);
|
|
66
114
|
const engine = new WorkflowEngine(config.workflowDefinition, engineDeps, workflowDeps);
|
|
67
115
|
switch (parsed.hook_event_name) {
|
|
68
|
-
case 'SessionStart': return startSession(config, engine, parsed.session_id, parsed.transcript_path, parsed.cwd);
|
|
116
|
+
case 'SessionStart': return startSession(config, engine, parsed.session_id, parsed.transcript_path, parsed.cwd, now);
|
|
69
117
|
case 'PreToolUse': return checkToolUse(config, engine, raw);
|
|
70
118
|
case 'SubagentStart': return registerSubagent(engine, raw);
|
|
71
119
|
case 'Stop': return preventUnsupportedStop(config, engineDeps, parsed.session_id);
|
|
72
120
|
}
|
|
73
121
|
}
|
|
74
|
-
function startSession(config, engine, sessionId, transcriptPath, cwd) {
|
|
122
|
+
function startSession(config, engine, sessionId, transcriptPath, cwd, now) {
|
|
75
123
|
if (!engine.hasSessionStarted(sessionId)) {
|
|
76
|
-
const
|
|
77
|
-
const
|
|
124
|
+
const transcriptFile = resolveTranscriptPath(sessionId, transcriptPath, now);
|
|
125
|
+
const repository = getRepositoryName(cwd);
|
|
126
|
+
if (repository === undefined)
|
|
127
|
+
throw new TypeError('repository must be a non-empty string.');
|
|
128
|
+
const result = engine.startSession(sessionId, transcriptFile, repository);
|
|
78
129
|
if (result.type !== 'success')
|
|
79
130
|
return toRunnerResult(result);
|
|
80
131
|
}
|
|
@@ -124,7 +175,10 @@ function checkPatchPaths(handler, engine, sessionId, toolInput) {
|
|
|
124
175
|
if (paths.length === 0)
|
|
125
176
|
return deny('Cannot determine every file edited by Codex apply_patch');
|
|
126
177
|
for (const path of paths) {
|
|
127
|
-
const result = handler(engine, sessionId, 'Write', {
|
|
178
|
+
const result = handler(engine, sessionId, 'Write', {
|
|
179
|
+
file_path: path,
|
|
180
|
+
command
|
|
181
|
+
});
|
|
128
182
|
if (result.type === 'blocked')
|
|
129
183
|
return toHookResult(result);
|
|
130
184
|
}
|
|
@@ -171,8 +225,8 @@ function preventUnsupportedStop(config, engineDeps, sessionId) {
|
|
|
171
225
|
};
|
|
172
226
|
return {
|
|
173
227
|
output: JSON.stringify({
|
|
174
|
-
|
|
175
|
-
|
|
228
|
+
decision: 'block',
|
|
229
|
+
reason: `Workflow state ${state.currentStateMachineState} does not allow stopping.`
|
|
176
230
|
}),
|
|
177
231
|
exitCode: 0,
|
|
178
232
|
};
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
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),
|
|
3
|
+
session_id: z.string().trim().min(1),
|
|
4
|
+
transcript_path: z.string().trim().min(1).nullable(),
|
|
5
|
+
cwd: z.string().trim().min(1),
|
|
6
6
|
hook_event_name: z.enum(['SessionStart', 'PreToolUse', 'SubagentStart', 'Stop']),
|
|
7
7
|
});
|
|
8
8
|
export const codexPreToolUseInputSchema = codexHookInputSchema.extend({
|
|
9
9
|
hook_event_name: z.literal('PreToolUse'),
|
|
10
|
-
tool_name: z.string().min(1),
|
|
10
|
+
tool_name: z.string().trim().min(1),
|
|
11
11
|
tool_input: z.record(z.unknown()),
|
|
12
12
|
});
|
|
13
13
|
export const codexSubagentStartInputSchema = codexHookInputSchema.extend({
|
|
14
14
|
hook_event_name: z.literal('SubagentStart'),
|
|
15
|
-
agent_id: z.string().min(1),
|
|
16
|
-
agent_type: z.string().min(1),
|
|
15
|
+
agent_id: z.string().trim().min(1),
|
|
16
|
+
agent_type: z.string().trim().min(1),
|
|
17
17
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nt-ai-lab/deterministic-agent-workflow-codex",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"zod": "^3.25.76",
|
|
20
|
-
"@nt-ai-lab/deterministic-agent-workflow-
|
|
21
|
-
"@nt-ai-lab/deterministic-agent-workflow-
|
|
22
|
-
"@nt-ai-lab/deterministic-agent-workflow-
|
|
20
|
+
"@nt-ai-lab/deterministic-agent-workflow-cli": "0.4.1",
|
|
21
|
+
"@nt-ai-lab/deterministic-agent-workflow-engine": "0.4.1",
|
|
22
|
+
"@nt-ai-lab/deterministic-agent-workflow-event-store": "0.4.1"
|
|
23
23
|
},
|
|
24
24
|
"publishConfig": {
|
|
25
25
|
"access": "public"
|