@nt-ai-lab/deterministic-agent-workflow-codex 0.4.1 → 0.4.3

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,14 +1,62 @@
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();
9
57
  const databasePath = resolveDatabasePath(config.processDeps);
10
58
  const store = config.processDeps.buildStore(databasePath);
11
- const now = () => new Date().toISOString();
59
+ const now = config.now ?? (() => new Date().toISOString());
12
60
  const engineDeps = {
13
61
  store,
14
62
  getPluginRoot: () => root,
@@ -36,7 +84,12 @@ function handleWorkflowCommand(config, engineDeps, root, now, args) {
36
84
  }
37
85
  const [operation, sessionId, ...operationArgs] = args;
38
86
  const workflowDeps = buildWorkflowDeps(config, engineDeps.store, root, now, sessionId);
39
- return createWorkflowRunner(config)([operation, ...operationArgs], engineDeps, workflowDeps, { getSessionId: () => sessionId, });
87
+ return createWorkflowRunner(config)([operation, ...operationArgs], engineDeps, workflowDeps, {
88
+ getSessionId: () => sessionId,
89
+ getSessionTranscriptPath: () => resolveTranscriptPath(sessionId, null, now),
90
+ getSessionRepository: () => getRepositoryName(process.cwd()),
91
+ getRepositoryRoot: () => process.cwd(),
92
+ });
40
93
  }
41
94
  function buildWorkflowDeps(config, store, root, now, sessionId) {
42
95
  const platform = {
@@ -65,16 +118,19 @@ function handleHookInvocation(config, engineDeps, root, now) {
65
118
  const workflowDeps = buildWorkflowDeps(config, engineDeps.store, root, now, parsed.session_id);
66
119
  const engine = new WorkflowEngine(config.workflowDefinition, engineDeps, workflowDeps);
67
120
  switch (parsed.hook_event_name) {
68
- case 'SessionStart': return startSession(config, engine, parsed.session_id, parsed.transcript_path, parsed.cwd);
121
+ case 'SessionStart': return startSession(config, engine, parsed.session_id, parsed.transcript_path, parsed.cwd, now);
69
122
  case 'PreToolUse': return checkToolUse(config, engine, raw);
70
123
  case 'SubagentStart': return registerSubagent(engine, raw);
71
124
  case 'Stop': return preventUnsupportedStop(config, engineDeps, parsed.session_id);
72
125
  }
73
126
  }
74
- function startSession(config, engine, sessionId, transcriptPath, cwd) {
127
+ function startSession(config, engine, sessionId, transcriptPath, cwd, now) {
75
128
  if (!engine.hasSessionStarted(sessionId)) {
76
- const noTranscriptPath = '';
77
- const result = engine.startSession(sessionId, transcriptPath ?? noTranscriptPath, getRepositoryName(cwd));
129
+ const transcriptFile = resolveTranscriptPath(sessionId, transcriptPath, now);
130
+ const repository = getRepositoryName(cwd);
131
+ if (repository === undefined)
132
+ throw new TypeError('repository must be a non-empty string.');
133
+ const result = engine.startSession(sessionId, transcriptFile, repository);
78
134
  if (result.type !== 'success')
79
135
  return toRunnerResult(result);
80
136
  }
@@ -124,7 +180,10 @@ function checkPatchPaths(handler, engine, sessionId, toolInput) {
124
180
  if (paths.length === 0)
125
181
  return deny('Cannot determine every file edited by Codex apply_patch');
126
182
  for (const path of paths) {
127
- const result = handler(engine, sessionId, 'Write', { file_path: path });
183
+ const result = handler(engine, sessionId, 'Write', {
184
+ file_path: path,
185
+ command
186
+ });
128
187
  if (result.type === 'blocked')
129
188
  return toHookResult(result);
130
189
  }
@@ -7,4 +7,5 @@ export type CodexWorkflowCliConfig<TWorkflow extends RehydratableWorkflow<TState
7
7
  readonly workflowCommand: string;
8
8
  readonly workflowRoot?: string;
9
9
  readonly transcriptReader?: TranscriptReader;
10
+ readonly now?: () => string;
10
11
  };
@@ -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.1",
3
+ "version": "0.4.3",
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-cli": "0.4.0",
21
- "@nt-ai-lab/deterministic-agent-workflow-event-store": "0.4.0",
22
- "@nt-ai-lab/deterministic-agent-workflow-engine": "0.4.0"
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"