ultracode-for-codex 0.2.4 → 0.2.5

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/README.md CHANGED
@@ -26,7 +26,7 @@ npm run pack:ultracode-for-codex
26
26
  Install the tarball from a target project:
27
27
 
28
28
  ```bash
29
- npm install --save-dev /path/to/ultracode-for-codex-0.2.4.tgz
29
+ npm install --save-dev /path/to/ultracode-for-codex-0.2.5.tgz
30
30
  ```
31
31
 
32
32
  Run a workflow:
@@ -68,6 +68,10 @@ The built-in `task` and `code-review` workflows use an LLM planner first, then
68
68
  run work phase by phase. Within each phase, multiple focused Codex subagents run
69
69
  in parallel by default, followed by phase and final synthesis. The planner may
70
70
  choose a single-agent path only when parallel execution would add risk or waste.
71
+ Planner guidance includes dynamic workflow patterns such as classify-and-act,
72
+ fan-out-and-synthesize, adversarial verification, generate-and-filter,
73
+ tournament, and loop-until-done, so different work types can use different
74
+ phase shapes.
71
75
 
72
76
  ## Settings
73
77
 
@@ -80,7 +84,7 @@ Package defaults live in `settings.json`:
80
84
  "progress": "jsonl",
81
85
  "permission": "ask",
82
86
  "retryLimit": 0,
83
- "timeoutMs": 180000,
87
+ "timeoutMs": 900000,
84
88
  "background": {
85
89
  "runDir": ".ultracode-for-codex/background/{jobId}",
86
90
  "resultFile": "result.json",
@@ -94,6 +98,7 @@ Package defaults live in `settings.json`:
94
98
 
95
99
  Use `--execution attached`, `--progress`, `--permission`, `--retry-limit`, and
96
100
  `--timeout-ms` to override settings for one run.
101
+ The package default workflow timeout is `900000` ms, or 15 minutes.
97
102
 
98
103
  ## CLI Controls
99
104
 
@@ -101,6 +106,8 @@ Use `--execution attached`, `--progress`, `--permission`, `--retry-limit`, and
101
106
  - The final workflow result is printed as JSON to stdout.
102
107
  - JSONL records include `kind`, `version`, `event`, `status`, and `summary`;
103
108
  agent records also include stable agent identity and label fields.
109
+ - Built-in `task` and `code-review` emit `workflow.plan.ready` before phase
110
+ agents start, including phase titles and planned agent role labels.
104
111
  - Press `Ctrl-C` once to cancel the active workflow.
105
112
  - Use `--retry-limit <n>` to retry failed workflows inside the same process.
106
113
  - `--timeout-ms` is the workflow timeout and the default per-agent silence
@@ -31,7 +31,7 @@ npm exec -- ultracode-for-codex --llm-guide
31
31
  For source-checkout validation, install the generated tarball instead:
32
32
 
33
33
  ```bash
34
- npm install --save-dev ./ultracode-for-codex-0.2.4.tgz
34
+ npm install --save-dev ./ultracode-for-codex-0.2.5.tgz
35
35
  ```
36
36
 
37
37
  Optional Codex companion skill:
@@ -63,6 +63,9 @@ Both start with an LLM planner, execute phase by phase, run multiple focused
63
63
  Codex subagents in parallel within each phase by default, and synthesize phase
64
64
  and final results. The planner chooses a single-agent path only when parallel
65
65
  execution would add risk or waste.
66
+ Planner guidance includes classify-and-act, fan-out-and-synthesize,
67
+ adversarial verification, generate-and-filter, tournament, and loop-until-done
68
+ patterns so different work types can use different phase shapes.
66
69
 
67
70
  Settings defaults:
68
71
 
@@ -73,7 +76,7 @@ Settings defaults:
73
76
  "progress": "jsonl",
74
77
  "permission": "ask",
75
78
  "retryLimit": 0,
76
- "timeoutMs": 180000,
79
+ "timeoutMs": 900000,
77
80
  "background": {
78
81
  "runDir": ".ultracode-for-codex/background/{jobId}",
79
82
  "resultFile": "result.json",
@@ -89,8 +92,11 @@ Useful controls:
89
92
 
90
93
  - Progress events are printed to stderr as JSONL by default.
91
94
  - The final workflow result is printed as JSON to stdout.
95
+ - The package default workflow timeout is `900000` ms, or 15 minutes.
92
96
  - JSONL records include `kind`, `version`, `event`, `status`, and `summary`;
93
97
  agent records also include stable agent identity and label fields.
98
+ - Built-in `task` and `code-review` emit `workflow.plan.ready` before phase
99
+ agents start, including phase titles and planned agent role labels.
94
100
  - Press `Ctrl-C` once to cancel the running workflow.
95
101
  - Use `--retry-limit <n>` to retry failed runs in the same process.
96
102
  - `--timeout-ms` is the workflow timeout and the default per-agent silence
package/dist/cli.js CHANGED
@@ -350,6 +350,15 @@ function renderWorkflowEvent(event, progressMode) {
350
350
  case 'workflow.phase.started':
351
351
  process.stderr.write(`[phase] ${event.title}${event.detail ? ` - ${event.detail}` : ''}\n`);
352
352
  return;
353
+ case 'workflow.plan.ready':
354
+ process.stderr.write(`[plan] mode=${event.mode} phases=${event.phases.length}${event.rationale ? ` - ${event.rationale}` : ''}\n`);
355
+ for (const [phaseIndex, phase] of event.phases.entries()) {
356
+ process.stderr.write(`[plan] ${phaseIndex + 1}. ${phase.title}${phase.goal ? ` - ${phase.goal}` : ''}\n`);
357
+ for (const agent of phase.agents) {
358
+ process.stderr.write(`[plan] - ${agent.title}${agent.label ? ` (${agent.label})` : ''}${agent.focus ? `: ${agent.focus}` : ''}\n`);
359
+ }
360
+ }
361
+ return;
353
362
  case 'workflow.log':
354
363
  process.stderr.write(`[log] ${event.message}\n`);
355
364
  return;
@@ -425,6 +434,18 @@ function progressPayloadForEvent(event) {
425
434
  title: event.title,
426
435
  detail: event.detail,
427
436
  };
437
+ case 'workflow.plan.ready':
438
+ return {
439
+ event: event.type,
440
+ status: 'planned',
441
+ summary: `Workflow plan ready: ${event.phases.length} phase${event.phases.length === 1 ? '' : 's'}, mode=${event.mode}`,
442
+ taskId: event.taskId,
443
+ runId: event.runId,
444
+ mode: event.mode,
445
+ rationale: event.rationale,
446
+ phaseCount: event.phases.length,
447
+ planPhases: event.phases,
448
+ };
428
449
  case 'workflow.log':
429
450
  return {
430
451
  event: event.type,
@@ -4,6 +4,18 @@ export type WorkflowTaskStatus = 'running' | 'completed' | 'failed';
4
4
  export type WorkflowTaskType = 'local_workflow';
5
5
  export type WorkflowSource = 'inline' | 'script_path' | 'project' | 'user' | 'plugin' | 'built_in';
6
6
  export type WorkflowPermissionDecision = 'allow' | 'deny';
7
+ export interface WorkflowPlanAgent {
8
+ readonly id?: string;
9
+ readonly title: string;
10
+ readonly focus?: string;
11
+ readonly label?: string;
12
+ }
13
+ export interface WorkflowPlanPhase {
14
+ readonly id?: string;
15
+ readonly title: string;
16
+ readonly goal?: string;
17
+ readonly agents: readonly WorkflowPlanAgent[];
18
+ }
7
19
  export type WorkflowEvent = {
8
20
  readonly type: 'workflow.started';
9
21
  readonly taskId: string;
@@ -20,6 +32,13 @@ export type WorkflowEvent = {
20
32
  readonly phaseIndex: number;
21
33
  readonly title: string;
22
34
  readonly detail?: string;
35
+ } | {
36
+ readonly type: 'workflow.plan.ready';
37
+ readonly taskId: string;
38
+ readonly runId: string;
39
+ readonly mode: string;
40
+ readonly rationale?: string;
41
+ readonly phases: readonly WorkflowPlanPhase[];
23
42
  } | {
24
43
  readonly type: 'workflow.log';
25
44
  readonly taskId: string;
@@ -250,6 +269,7 @@ export declare class WorkflowTaskRegistry implements WorkflowRuntime {
250
269
  private runAgentAttempt;
251
270
  private parallel;
252
271
  private pipeline;
272
+ private announcePlan;
253
273
  private phase;
254
274
  private completeTask;
255
275
  private failTask;
@@ -98,6 +98,16 @@ const WORKSPACE_CONTEXT_PRIORITY_FILES = new Set([
98
98
  'package.json',
99
99
  'tsconfig.json',
100
100
  ]);
101
+ const DYNAMIC_WORKFLOW_PATTERN_GUIDANCE = [
102
+ 'Use dynamic workflow patterns intentionally:',
103
+ '- classify-and-act: classify the request, risk, or repository area before choosing phase shape.',
104
+ '- fan-out-and-synthesize: split independent lenses across parallel agents, then merge evidence.',
105
+ '- adversarial verification: assign at least one agent to challenge correctness, security, or assumptions on high-risk work.',
106
+ '- generate-and-filter: create candidate approaches or fixes, then select by evidence and constraints.',
107
+ '- tournament: compare competing alternatives when the best path is unclear.',
108
+ '- loop-until-done: iterate repair and verification only when there is a clear stop condition.',
109
+ 'Prefer pipelines when later phases need earlier summaries; prefer parallel agents when independent evidence can be gathered at the same time.',
110
+ ].join('\n');
101
111
  const DEFAULT_BUILTIN_WORKFLOWS = [
102
112
  {
103
113
  name: 'task',
@@ -106,7 +116,7 @@ const DEFAULT_BUILTIN_WORKFLOWS = [
106
116
  description: 'Run an LLM-planned phase-wise parallel task workflow',
107
117
  defaultPrompt: 'Complete the requested repository task.',
108
118
  plannerKind: 'general task',
109
- plannerGuidance: 'Plan phases that make the work faster and more accurate through parallel agents. Default to phase_parallel. Choose single only for tiny changes, strictly sequential investigations, or one indivisible failure mode.',
119
+ plannerGuidance: 'Plan phases that make the work faster and more accurate through parallel agents. Default to phase_parallel. Choose single only for tiny changes, strictly sequential investigations, or one indivisible failure mode. Pick workflow patterns that match the request instead of using one fixed shape.',
110
120
  agentGuidance: 'Complete the assigned phase work. Prefer concrete evidence, file paths, commands, and risks over broad narration.',
111
121
  finalGuidance: 'Return the completed task result, key evidence, decisions made, verification status, and residual risk.',
112
122
  }),
@@ -118,7 +128,7 @@ const DEFAULT_BUILTIN_WORKFLOWS = [
118
128
  description: 'Run an LLM-planned phase-wise parallel code review workflow',
119
129
  defaultPrompt: 'Review the current repository for correctness risks.',
120
130
  plannerKind: 'code review',
121
- plannerGuidance: 'Plan an effective code review. Default to phase_parallel with multiple focused reviewers per phase. Commonly useful lenses include runtime correctness, security/capability boundaries, API/CLI contracts, persistence/retry/cancel behavior, and test coverage. Choose single only for a tiny scoped diff or one indivisible failure mode.',
131
+ plannerGuidance: 'Plan an effective code review. Default to phase_parallel with multiple focused reviewers per phase. Commonly useful lenses include runtime correctness, security/capability boundaries, API/CLI contracts, persistence/retry/cancel behavior, and test coverage. Prefer fan-out-and-synthesize plus adversarial verification unless the diff is tiny or one indivisible failure mode.',
122
132
  agentGuidance: 'Return material findings only. Prioritize root cause, severity, exact file/line evidence, reproduction or impact, and residual risk.',
123
133
  finalGuidance: 'Return findings ordered by severity with exact file/line references. Deduplicate overlaps, preserve dissent or uncertainty, and say clearly if there are no material findings.',
124
134
  }),
@@ -152,6 +162,8 @@ const plan = await agent([
152
162
  ${JSON.stringify(`Plan the phase-wise execution strategy for ${input.plannerKind}.`)},
153
163
  "",
154
164
  ${JSON.stringify(input.plannerGuidance)},
165
+ "",
166
+ ${JSON.stringify(DYNAMIC_WORKFLOW_PATTERN_GUIDANCE)},
155
167
  "A phase runs after previous phase summaries are available. Within each phase, use parallel agents by default.",
156
168
  "Return 1 to 4 phases. For ordinary work, prefer 2 phases with 2 to 4 parallel agents each. Use concise stable ids.",
157
169
  "",
@@ -202,6 +214,23 @@ const plan = await agent([
202
214
  }
203
215
  });
204
216
  const selectedPhases = plan.mode === "single" ? [plan.phases[0]] : plan.phases;
217
+ announcePlan({
218
+ mode: plan.mode,
219
+ rationale: plan.rationale,
220
+ phases: selectedPhases.map((phasePlan) => ({
221
+ id: phasePlan.id,
222
+ title: phasePlan.title,
223
+ goal: phasePlan.goal,
224
+ agents: (plan.mode === "single" ? [phasePlan.agents[0]] : phasePlan.agents).map((phaseAgent) => ({
225
+ id: phaseAgent.id,
226
+ title: phaseAgent.title,
227
+ focus: phaseAgent.focus,
228
+ label: plan.mode === "single"
229
+ ? ${JSON.stringify(`${input.name}-single`)}
230
+ : ${JSON.stringify(`${input.name}-`)} + phasePlan.id + "-" + phaseAgent.id
231
+ }))
232
+ }))
233
+ });
205
234
  if (plan.mode === "single") {
206
235
  const singlePhase = selectedPhases[0];
207
236
  const singleAgent = singlePhase.agents[0];
@@ -1048,6 +1077,7 @@ export class WorkflowTaskRegistry {
1048
1077
  host.workspaceContext = hardenCallable((options) => {
1049
1078
  return this.trackWorkflowPromise(ctx, this.workspaceContext(ctx, options));
1050
1079
  });
1080
+ host.announcePlan = hardenCallable((plan) => this.announcePlan(ctx, plan));
1051
1081
  host.phase = hardenCallable((title) => this.phase(ctx, title));
1052
1082
  host.log = hardenCallable(log);
1053
1083
  host.consoleLog = hardenCallable((...values) => {
@@ -1494,6 +1524,18 @@ export class WorkflowTaskRegistry {
1494
1524
  return current;
1495
1525
  });
1496
1526
  }
1527
+ announcePlan(ctx, plan) {
1528
+ if (ctx.controller.signal.aborted || ctx.task.status !== 'running') {
1529
+ throw workflowInputError('Workflow is aborted.');
1530
+ }
1531
+ const normalized = normalizeWorkflowExecutionPlan(plan);
1532
+ this.emit(ctx.task, {
1533
+ type: 'workflow.plan.ready',
1534
+ taskId: ctx.task.taskId,
1535
+ runId: ctx.task.runId,
1536
+ ...normalized,
1537
+ });
1538
+ }
1497
1539
  phase(ctx, title) {
1498
1540
  if (typeof title !== 'string' || title.trim() === '') {
1499
1541
  throw workflowInputError('phase() requires a non-empty string title.');
@@ -2929,6 +2971,7 @@ function installWorkflowVmGlobals(context, globals) {
2929
2971
  ' define(globalThis, "parallel", { value: (...values) => __host.parallel(...values), writable: false, configurable: false });',
2930
2972
  ' define(globalThis, "pipeline", { value: (...values) => __host.pipeline(...values), writable: false, configurable: false });',
2931
2973
  ' define(globalThis, "workspaceContext", { value: (...values) => __host.workspaceContext(...values), writable: false, configurable: false });',
2974
+ ' define(globalThis, "announcePlan", { value: (...values) => __host.announcePlan(...values), writable: false, configurable: false });',
2932
2975
  ' define(globalThis, "phase", { value: (...values) => __host.phase(...values), writable: false, configurable: false });',
2933
2976
  ' define(globalThis, "log", { value: (...values) => __host.log(...values), writable: false, configurable: false });',
2934
2977
  ' define(globalThis, "workflow", { value: (...values) => __host.workflow(...values), writable: false, configurable: false });',
@@ -3612,6 +3655,56 @@ function previewValue(value, limit) {
3612
3655
  : JSON.stringify(value) ?? String(value);
3613
3656
  return preview(text, limit);
3614
3657
  }
3658
+ function normalizeWorkflowExecutionPlan(value) {
3659
+ const record = asRecord(value);
3660
+ if (!record)
3661
+ throw workflowInputError('announcePlan(plan) requires a plan object.');
3662
+ const mode = boundedPlanString(record.mode, 'phase_parallel', 48);
3663
+ const rationale = optionalBoundedPlanString(record.rationale, 400);
3664
+ const rawPhases = Array.isArray(record.phases) ? Array.from(record.phases) : [];
3665
+ if (rawPhases.length === 0)
3666
+ throw workflowInputError('announcePlan(plan) requires at least one phase.');
3667
+ const phases = rawPhases.slice(0, 16).map((phaseValue, phaseIndex) => {
3668
+ const phase = asRecord(phaseValue);
3669
+ if (!phase)
3670
+ throw workflowInputError(`announcePlan(plan).phases[${phaseIndex}] must be an object.`);
3671
+ const rawAgents = Array.isArray(phase.agents) ? Array.from(phase.agents) : [];
3672
+ if (rawAgents.length === 0) {
3673
+ throw workflowInputError(`announcePlan(plan).phases[${phaseIndex}].agents requires at least one agent.`);
3674
+ }
3675
+ return {
3676
+ ...(typeof phase.id === 'string' && phase.id.trim() ? { id: boundedPlanString(phase.id, '', 48) } : {}),
3677
+ title: boundedPlanString(phase.title, `Phase ${phaseIndex + 1}`, 96),
3678
+ ...(typeof phase.goal === 'string' && phase.goal.trim() ? { goal: boundedPlanString(phase.goal, '', 600) } : {}),
3679
+ agents: rawAgents.slice(0, 16).map((agentValue, agentIndex) => {
3680
+ const agent = asRecord(agentValue);
3681
+ if (!agent) {
3682
+ throw workflowInputError(`announcePlan(plan).phases[${phaseIndex}].agents[${agentIndex}] must be an object.`);
3683
+ }
3684
+ return {
3685
+ ...(typeof agent.id === 'string' && agent.id.trim() ? { id: boundedPlanString(agent.id, '', 48) } : {}),
3686
+ title: boundedPlanString(agent.title, `Agent ${agentIndex + 1}`, 96),
3687
+ ...(typeof agent.focus === 'string' && agent.focus.trim() ? { focus: boundedPlanString(agent.focus, '', 600) } : {}),
3688
+ ...(typeof agent.label === 'string' && agent.label.trim() ? { label: boundedPlanString(agent.label, '', 96) } : {}),
3689
+ };
3690
+ }),
3691
+ };
3692
+ });
3693
+ return {
3694
+ mode,
3695
+ ...(rationale ? { rationale } : {}),
3696
+ phases,
3697
+ };
3698
+ }
3699
+ function boundedPlanString(value, fallback, limit) {
3700
+ const text = typeof value === 'string' && value.trim() ? value.trim() : fallback;
3701
+ return preview(text, limit);
3702
+ }
3703
+ function optionalBoundedPlanString(value, limit) {
3704
+ if (typeof value !== 'string' || !value.trim())
3705
+ return undefined;
3706
+ return boundedPlanString(value, '', limit);
3707
+ }
3615
3708
  function asRecord(value) {
3616
3709
  if (!value || typeof value !== 'object' || Array.isArray(value))
3617
3710
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ultracode-for-codex",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Run local Codex-backed workflows from a command-owned CLI runtime.",
5
5
  "keywords": [
6
6
  "codex",
package/settings.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "progress": "jsonl",
5
5
  "permission": "ask",
6
6
  "retryLimit": 0,
7
- "timeoutMs": 180000,
7
+ "timeoutMs": 900000,
8
8
  "background": {
9
9
  "runDir": ".ultracode-for-codex/background/{jobId}",
10
10
  "resultFile": "result.json",
@@ -22,6 +22,9 @@ The default Ultracode work shape is phase-wise parallel execution: built-in
22
22
  phase with parallel focused subagents by default, followed by phase and final
23
23
  synthesis. A single-agent path is reserved for cases where the planner judges
24
24
  parallel execution risky or wasteful.
25
+ Planner guidance includes classify-and-act, fan-out-and-synthesize,
26
+ adversarial verification, generate-and-filter, tournament, and loop-until-done
27
+ patterns so workflow shape can follow the task instead of a fixed template.
25
28
 
26
29
  ## Install And Run
27
30
 
@@ -41,7 +44,7 @@ For source-checkout validation before publish:
41
44
 
42
45
  ```bash
43
46
  npm run pack:ultracode-for-codex
44
- npm install --save-dev ./artifacts/ultracode-for-codex-0.2.4.tgz
47
+ npm install --save-dev ./artifacts/ultracode-for-codex-0.2.5.tgz
45
48
  ```
46
49
 
47
50
  CLI behavior:
@@ -53,10 +56,13 @@ CLI behavior:
53
56
  - attached final workflow result prints as JSON to stdout;
54
57
  - JSONL records include `kind`, `version`, `event`, `status`, and `summary`,
55
58
  with agent identity and label fields on agent records;
59
+ - built-in `task` and `code-review` emit `workflow.plan.ready` before phase
60
+ agents start, including phase titles and planned agent role labels;
56
61
  - `Ctrl-C` cancels the active attached workflow;
57
62
  - `--retry-limit <n>` retries failed workflows inside the same process;
58
63
  - `--timeout-ms` is the workflow timeout and the default per-agent silence
59
- budget; it is not divided by the retry budget.
64
+ budget; the package default is `900000` ms, or 15 minutes, and it is not
65
+ divided by the retry budget.
60
66
  - `--permission ask|allow|deny` handles project/user/plugin/scriptPath reviews.
61
67
  - `--progress plain` switches to human-readable progress lines.
62
68
  - background file locations are controlled by `workflow.background` in