@nt-ai-lab/deterministic-agent-workflow-codex 0.4.1 → 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 noTranscriptPath = '';
77
- const result = engine.startSession(sessionId, transcriptPath ?? noTranscriptPath, getRepositoryName(cwd));
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', { file_path: path });
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
  }
@@ -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.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-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"