aiwcli 0.15.1 → 0.15.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.
- package/dist/templates/_shared/.claude/skills/codex/SKILL.md +19 -26
- package/dist/templates/_shared/.codex/workflows/codex.md +11 -0
- package/dist/templates/_shared/lib-ts/base/tmux-driver.ts +70 -3
- package/dist/templates/_shared/lib-ts/base/tmux-pane-placement.ts +70 -0
- package/dist/templates/_shared/scripts/resolve-run.ts +1 -1
- package/dist/templates/_shared/skills/codex/CLAUDE.md +77 -0
- package/dist/templates/_shared/skills/codex/SKILL.md +71 -0
- package/dist/templates/_shared/skills/{prompt-codex/scripts/watch-codex.ts → codex/lib/codex-watcher.ts} +31 -62
- package/dist/templates/_shared/skills/{prompt-codex → codex}/scripts/launch-codex.ts +56 -22
- package/dist/templates/_shared/skills/codex/scripts/watch-codex.ts +42 -0
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/dist/templates/_shared/.claude/skills/codex/prompt.md +0 -30
- package/dist/templates/_shared/skills/prompt-codex/CLAUDE.md +0 -71
|
@@ -1,42 +1,35 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: codex
|
|
3
|
-
description:
|
|
3
|
+
description: Delegate implementation to Codex sub-agents. USE WHEN codex OR send to codex OR codex implement OR hand off to codex OR launch codex OR codex plan OR run codex.
|
|
4
4
|
user-invocable: true
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Read `.aiwcli/_shared/skills/codex/SKILL.md` for delegation patterns and examples.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## Role
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
You are the orchestrator. Codex instances are your implementation sub-agents. Decide what to delegate, how to split work, and review results when summaries arrive.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
## Command
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
```
|
|
16
|
+
bun ~/.aiwcli/bin/resolve-run.ts .aiwcli/_shared/skills/codex/scripts/launch-codex.ts [flags] <mode>
|
|
17
|
+
```
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
- `--model <name>` — Model aliases: `spark`, `codex`, `gpt`. Tiers: `fast`, `standard`, `smart`. Or any full model ID.
|
|
19
|
-
- `--sandbox <mode>` — `read-only`, `workspace-write`, `danger-full-access`. Default: Codex default.
|
|
20
|
-
- `--context <id>` — Pass the active context ID so Codex receives project orientation (context folder, notes path). **Always pass this when an active context exists** (check the `Active Context:` system reminder for the ID).
|
|
19
|
+
The script blocks until Codex exits and prints a summary — run with Bash `run_in_background: true` so you stay unblocked.
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
**Modes:** `plan` | `--file <path>` | `<inline text...>`
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
| `fast` | `gpt-5.3-codex-spark` (tier) |
|
|
30
|
-
| `standard` | `gpt-5.3-codex` (tier) |
|
|
31
|
-
| `smart` | `gpt-5.3-codex` (tier) |
|
|
23
|
+
**Key flags:**
|
|
24
|
+
- `--context <id>` — Project orientation. Pass when implementing a plan.
|
|
25
|
+
- `--prompt <text>` — Scope the agent's work to a specific plan section or task.
|
|
26
|
+
- `--model <name>` — `spark`, `codex`, `gpt`, or tier: `fast`, `standard`, `smart`.
|
|
27
|
+
- `--no-watch` — Fire-and-forget (skip waiting for summary).
|
|
32
28
|
|
|
33
|
-
##
|
|
29
|
+
## Delegation Decision
|
|
34
30
|
|
|
35
|
-
-
|
|
36
|
-
- `/codex --model codex --context <context-id> Refactor auth to use JWT` — inline prompt with context
|
|
37
|
-
- `/codex --file ./spec.md` — inject file contents (no context)
|
|
31
|
+
**One-shot:** Plan is small or tightly coupled → launch one Codex with `plan` mode. Wait for the summary, then review.
|
|
38
32
|
|
|
39
|
-
|
|
33
|
+
**Parallel:** Plan has independent sections → launch multiple Codex instances, each scoped with `--prompt` to its section. All share the same `--context`. Review when summaries arrive, check for conflicts.
|
|
40
34
|
|
|
41
|
-
-
|
|
42
|
-
- `codex` CLI must be on PATH
|
|
35
|
+
**Ad-hoc:** No plan, just a task → pass inline text (e.g., `"Fix the failing test in auth.ts"`).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Codex Workflow
|
|
2
|
+
|
|
3
|
+
Use Codex CLI handoff instructions from `.aiwcli/_shared/skills/codex/SKILL.md`.
|
|
4
|
+
|
|
5
|
+
## Command
|
|
6
|
+
|
|
7
|
+
`bun ~/.aiwcli/bin/resolve-run.ts .aiwcli/_shared/skills/codex/scripts/launch-codex.ts [flags] <mode>`
|
|
8
|
+
|
|
9
|
+
**Modes:** `plan` | `--file <path>` | `<inline text...>`
|
|
10
|
+
|
|
11
|
+
**Common flags:** `--model <name>`, `--sandbox <mode>`, `--context <id>`, `--prompt <text>`, `--no-yolo`, `--capture`
|
|
@@ -12,9 +12,11 @@
|
|
|
12
12
|
import * as fs from "node:fs";
|
|
13
13
|
|
|
14
14
|
import { execFileAsync, findExecutable } from "./subprocess-utils.js";
|
|
15
|
+
import { findBestSplit, listPanes } from "./tmux-pane-placement.js";
|
|
15
16
|
|
|
16
17
|
export type DriverMode = "exec" | "repl";
|
|
17
18
|
export type TmuxSplitFlag = "-h" | "-v";
|
|
19
|
+
export type TmuxSplitOption = TmuxSplitFlag | "auto";
|
|
18
20
|
|
|
19
21
|
export interface DriverPreflightResult {
|
|
20
22
|
available: boolean;
|
|
@@ -53,7 +55,7 @@ export interface LaunchDriverOptions {
|
|
|
53
55
|
env?: Record<string, string>;
|
|
54
56
|
promptPath?: string;
|
|
55
57
|
sendPromptInRepl?: boolean;
|
|
56
|
-
splitFlag?:
|
|
58
|
+
splitFlag?: TmuxSplitOption;
|
|
57
59
|
splitTarget?: string;
|
|
58
60
|
autoClose?: boolean;
|
|
59
61
|
holdPane?: boolean;
|
|
@@ -165,6 +167,54 @@ export function normalizeSplitFlag(value: string | undefined): TmuxSplitFlag {
|
|
|
165
167
|
return value?.trim() === "-v" ? "-v" : "-h";
|
|
166
168
|
}
|
|
167
169
|
|
|
170
|
+
function splitFlagFromDimensions(width: number, height: number): TmuxSplitFlag {
|
|
171
|
+
return width >= height ? "-h" : "-v";
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function resolveSplitFlagForTargetPane(
|
|
175
|
+
tmuxPath: string,
|
|
176
|
+
splitTarget: string,
|
|
177
|
+
): Promise<TmuxSplitFlag | null> {
|
|
178
|
+
const size = await execFileAsync(
|
|
179
|
+
tmuxPath,
|
|
180
|
+
["display-message", "-p", "-t", splitTarget, "#{pane_width} #{pane_height}"],
|
|
181
|
+
{ timeout: 3000 },
|
|
182
|
+
);
|
|
183
|
+
if (size.exitCode !== 0) return null;
|
|
184
|
+
|
|
185
|
+
const parts = size.stdout.trim().split(/\s+/);
|
|
186
|
+
if (parts.length < 2) return null;
|
|
187
|
+
|
|
188
|
+
const width = Number.parseInt(parts[0] ?? "", 10);
|
|
189
|
+
const height = Number.parseInt(parts[1] ?? "", 10);
|
|
190
|
+
if (!Number.isFinite(width) || !Number.isFinite(height)) return null;
|
|
191
|
+
|
|
192
|
+
return splitFlagFromDimensions(width, height);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function resolveAutoSplit(
|
|
196
|
+
tmuxPath: string,
|
|
197
|
+
splitTarget?: string,
|
|
198
|
+
): Promise<{ splitFlag: TmuxSplitFlag; splitTarget?: string }> {
|
|
199
|
+
const explicitTarget = splitTarget?.trim();
|
|
200
|
+
if (explicitTarget) {
|
|
201
|
+
const splitFlag = await resolveSplitFlagForTargetPane(tmuxPath, explicitTarget);
|
|
202
|
+
return {
|
|
203
|
+
splitFlag: splitFlag ?? "-h",
|
|
204
|
+
splitTarget: explicitTarget,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const panes = await listPanes(tmuxPath);
|
|
209
|
+
const placement = findBestSplit(panes);
|
|
210
|
+
if (!placement) return { splitFlag: "-h" };
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
splitFlag: placement.splitFlag,
|
|
214
|
+
splitTarget: placement.targetPane,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
168
218
|
export function isTruthy(value: string | undefined): boolean {
|
|
169
219
|
if (!value) return false;
|
|
170
220
|
const normalized = value.trim().toLowerCase();
|
|
@@ -287,8 +337,25 @@ export async function launchDriverInTmuxOrFallback(
|
|
|
287
337
|
|
|
288
338
|
const tmux = getTmuxAvailability();
|
|
289
339
|
if (tmux.available && tmux.tmuxPath) {
|
|
290
|
-
const
|
|
291
|
-
const
|
|
340
|
+
const requestedSplitFlag = options.splitFlag;
|
|
341
|
+
const explicitSplitTarget = options.splitTarget?.trim();
|
|
342
|
+
let splitFlag: TmuxSplitFlag;
|
|
343
|
+
let splitTarget: string | undefined;
|
|
344
|
+
|
|
345
|
+
if (requestedSplitFlag === "auto") {
|
|
346
|
+
try {
|
|
347
|
+
const resolved = await resolveAutoSplit(tmux.tmuxPath, explicitSplitTarget);
|
|
348
|
+
splitFlag = resolved.splitFlag;
|
|
349
|
+
splitTarget = resolved.splitTarget;
|
|
350
|
+
} catch {
|
|
351
|
+
splitFlag = "-h";
|
|
352
|
+
splitTarget = explicitSplitTarget;
|
|
353
|
+
}
|
|
354
|
+
} else {
|
|
355
|
+
splitFlag = normalizeSplitFlag(requestedSplitFlag);
|
|
356
|
+
splitTarget = explicitSplitTarget;
|
|
357
|
+
}
|
|
358
|
+
|
|
292
359
|
const baseCmd = buildToolCommand(toolPath, args, envVars, mode, options.promptPath);
|
|
293
360
|
const holdMessage = options.holdMessage ?? "[aiwcli] Driver exited. Pane held open.";
|
|
294
361
|
const paneBody = wrapPaneCommand(
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { execFileAsync } from "./subprocess-utils.js";
|
|
2
|
+
|
|
3
|
+
export type TmuxSplitFlag = "-h" | "-v";
|
|
4
|
+
|
|
5
|
+
export interface TmuxPaneInfo {
|
|
6
|
+
paneId: string;
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
active: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface PlacementResult {
|
|
13
|
+
targetPane: string;
|
|
14
|
+
splitFlag: TmuxSplitFlag;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const LIST_PANES_FORMAT = "#{pane_id} #{pane_width} #{pane_height} #{pane_active}";
|
|
18
|
+
|
|
19
|
+
export async function listPanes(tmuxPath: string): Promise<TmuxPaneInfo[]> {
|
|
20
|
+
const result = await execFileAsync(tmuxPath, ["list-panes", "-F", LIST_PANES_FORMAT], {
|
|
21
|
+
timeout: 3000,
|
|
22
|
+
});
|
|
23
|
+
if (result.exitCode !== 0) return [];
|
|
24
|
+
|
|
25
|
+
const panes: TmuxPaneInfo[] = [];
|
|
26
|
+
for (const rawLine of result.stdout.split(/\r?\n/)) {
|
|
27
|
+
const line = rawLine.trim();
|
|
28
|
+
if (!line) continue;
|
|
29
|
+
|
|
30
|
+
const parts = line.split(/\s+/);
|
|
31
|
+
if (parts.length < 4) continue;
|
|
32
|
+
|
|
33
|
+
const paneId = parts[0] ?? "";
|
|
34
|
+
const width = Number.parseInt(parts[1] ?? "", 10);
|
|
35
|
+
const height = Number.parseInt(parts[2] ?? "", 10);
|
|
36
|
+
const activeRaw = parts[3] ?? "";
|
|
37
|
+
|
|
38
|
+
if (!paneId || !Number.isFinite(width) || !Number.isFinite(height)) continue;
|
|
39
|
+
panes.push({
|
|
40
|
+
paneId,
|
|
41
|
+
width,
|
|
42
|
+
height,
|
|
43
|
+
active: activeRaw === "1",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return panes;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function findBestSplit(panes: TmuxPaneInfo[]): PlacementResult | null {
|
|
51
|
+
if (panes.length === 0) return null;
|
|
52
|
+
|
|
53
|
+
let best = panes[0];
|
|
54
|
+
let bestArea = best.width * best.height;
|
|
55
|
+
|
|
56
|
+
for (let i = 1; i < panes.length; i++) {
|
|
57
|
+
const pane = panes[i];
|
|
58
|
+
if (!pane) continue;
|
|
59
|
+
const area = pane.width * pane.height;
|
|
60
|
+
if (area > bestArea) {
|
|
61
|
+
best = pane;
|
|
62
|
+
bestArea = area;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
targetPane: best.paneId,
|
|
68
|
+
splitFlag: best.width >= best.height ? "-h" : "-v",
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Codex Skill
|
|
2
|
+
|
|
3
|
+
Launch Codex CLI in a tmux pane and inject a prompt into its REPL.
|
|
4
|
+
|
|
5
|
+
## Directory Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
codex/
|
|
9
|
+
├── CLAUDE.md ← This file
|
|
10
|
+
├── lib/
|
|
11
|
+
│ └── codex-watcher.ts ← Reusable watch/summarize library
|
|
12
|
+
└── scripts/
|
|
13
|
+
└── launch-codex.ts ← Single entry point (launch + optional watch)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Script: launch-codex.ts
|
|
17
|
+
|
|
18
|
+
**Usage:**
|
|
19
|
+
```bash
|
|
20
|
+
bun ~/.aiwcli/bin/resolve-run.ts .aiwcli/_shared/skills/codex/scripts/launch-codex.ts [--model <tier|id>] [--sandbox <sandbox-mode>] [--prompt <text>] [--no-yolo] [--no-watch] [--context <id>] plan
|
|
21
|
+
bun ~/.aiwcli/bin/resolve-run.ts .aiwcli/_shared/skills/codex/scripts/launch-codex.ts [--model <tier|id>] [--sandbox <sandbox-mode>] [--prompt <text>] [--no-yolo] [--no-watch] [--context <id>] --file <path>
|
|
22
|
+
bun ~/.aiwcli/bin/resolve-run.ts .aiwcli/_shared/skills/codex/scripts/launch-codex.ts [--model <tier|id>] [--sandbox <sandbox-mode>] [--prompt <text>] [--no-yolo] [--no-watch] [--context <id>] <inline text...>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Args:**
|
|
26
|
+
- `plan` — discover active plan via context system, inject into Codex REPL
|
|
27
|
+
- `--file <path>` — inject file contents into Codex REPL
|
|
28
|
+
- `<text...>` — join remaining args as inline prompt, write to temp file, inject
|
|
29
|
+
- `--model <alias|tier|id>` — Aliases: `spark` → `gpt-5.3-codex-spark`, `codex` → `gpt-5.3-codex`, `gpt` → `gpt-5.2`. Tiers: `fast`/`standard`/`smart` (resolved via `resolveModelForProvider()`). Or any full model ID. Aliases are checked first (local `CODEX_ALIASES` constant in `launch-codex.ts`), then tiers, then pass-through. Omitted = Codex default.
|
|
30
|
+
- `--sandbox <mode>` — `read-only`, `workspace-write`, or `danger-full-access`. Default is `danger-full-access` for implementation handoffs.
|
|
31
|
+
- `--prompt <text>` — append extra instructions under `## Additional Instructions` after the main prompt body.
|
|
32
|
+
- `--no-yolo` — Disable YOLO mode (on by default). YOLO maps to Codex CLI's `--dangerously-bypass-approvals-and-sandbox`. Use `--no-yolo` to restore normal approval prompts.
|
|
33
|
+
- `--no-watch` — Disable watch/summarize mode. Launch exits immediately after Codex starts.
|
|
34
|
+
|
|
35
|
+
**Plan discovery order:**
|
|
36
|
+
1. `CLAUDE_SESSION_ID` env → `getContextBySessionId()` → `findLatestPlan(contextId)`
|
|
37
|
+
2. Fallback: scan `_output/contexts/*/plans/*.md` by mtime (inline, no `_cc-native` import)
|
|
38
|
+
|
|
39
|
+
**Dependencies (all from `_shared/lib-ts/`):**
|
|
40
|
+
- `base/tmux-driver.ts` — `launchDriverInTmuxOrFallback()`, `getTmuxAvailability()`
|
|
41
|
+
- `base/cli-args.ts` — `resolveCodexModel()`, `codexReplSpec()`, `buildCliInvocation()`, `isCodexSandbox()`
|
|
42
|
+
- `base/logger.ts` — `logDebug()`, `logWarn()` (injection diagnostics)
|
|
43
|
+
- `context/context-store.ts` — `getContextBySessionId()`
|
|
44
|
+
- `context/context-formatter.ts` — `buildExternalAgentContext()` (orientation header for Codex)
|
|
45
|
+
- `context/plan-manager.ts` — `findLatestPlan()`
|
|
46
|
+
|
|
47
|
+
**Watch behavior (single entry point):**
|
|
48
|
+
- Watch is enabled by default.
|
|
49
|
+
- `launch-codex.ts` launches Codex, waits for pane close (or timeout), and prints a summary.
|
|
50
|
+
- Summary cascade:
|
|
51
|
+
1. Spark transcript summary from session file
|
|
52
|
+
2. `codex exec resume <session_id>` summary
|
|
53
|
+
3. Transcript-line fallback
|
|
54
|
+
4. Static `Summary unavailable` message
|
|
55
|
+
- Watch flow is best-effort and does not change launch success semantics.
|
|
56
|
+
|
|
57
|
+
**Design decisions:**
|
|
58
|
+
- Always creates a new tmux pane (no pane reuse/tracking)
|
|
59
|
+
- No exec fallback — REPL mode requires tmux
|
|
60
|
+
- `_shared` only — never imports from `_cc-native`
|
|
61
|
+
- Temp file cleanup after injection confirmed
|
|
62
|
+
|
|
63
|
+
## Library: lib/codex-watcher.ts
|
|
64
|
+
|
|
65
|
+
Reusable side-effect-free watch/summarize functions used by launch flow:
|
|
66
|
+
- `waitForPaneClose(paneId, timeoutMs?)`
|
|
67
|
+
- `summarizeViaSessionFileSpark(sessionFile)`
|
|
68
|
+
- `summarizeViaResume(sessionId)`
|
|
69
|
+
- `summarizeFromSessionFileFallback(sessionFile)`
|
|
70
|
+
- `collectTranscriptLines(sessionFile)`
|
|
71
|
+
|
|
72
|
+
Constants and helper utilities are exported for reuse and testing (`POLL_INTERVAL_MS`, `SUMMARY_UNAVAILABLE_MESSAGE`, `normalizeText`, `looksLikeBadSummary`, etc.).
|
|
73
|
+
|
|
74
|
+
**Resilience policy:**
|
|
75
|
+
- Watch path is best-effort and never fails a successful launch
|
|
76
|
+
- Pane-wait timeout defaults to 4 hours; when reached, summarization continues with available transcript state
|
|
77
|
+
- Summary functions degrade through layered fallbacks and end with static message
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# /codex
|
|
2
|
+
|
|
3
|
+
## Role
|
|
4
|
+
|
|
5
|
+
You are the orchestrator. Each Codex launch spawns an implementation sub-agent. Delegate implementation work to Codex, then review results when summaries arrive.
|
|
6
|
+
|
|
7
|
+
The script blocks until Codex exits and prints a session summary. Run with Bash `run_in_background: true` so you stay unblocked and receive the summary as a background task notification.
|
|
8
|
+
|
|
9
|
+
## Command
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
bun ~/.aiwcli/bin/resolve-run.ts .aiwcli/_shared/skills/codex/scripts/launch-codex.ts [flags] <mode>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**Modes:** `plan` | `--file <path>` | `<inline text...>`
|
|
16
|
+
|
|
17
|
+
**Flags:**
|
|
18
|
+
- `--context <id>` — Project orientation for the sub-agent. Pass when implementing a plan so Codex understands the project structure.
|
|
19
|
+
- `--prompt <text>` — Scope the agent's work. Direct each instance to a specific plan section or task.
|
|
20
|
+
- `--model <name>` — Aliases: `spark`, `codex`, `gpt`. Tiers: `fast`, `standard`, `smart`. Or any full model ID.
|
|
21
|
+
- `--sandbox <mode>` — `read-only`, `workspace-write`, `danger-full-access`.
|
|
22
|
+
- `--no-yolo` — Disable YOLO mode (on by default).
|
|
23
|
+
- `--no-watch` — Fire-and-forget: exit immediately after launch, skip waiting for summary.
|
|
24
|
+
|
|
25
|
+
## Delegation Patterns
|
|
26
|
+
|
|
27
|
+
### One-shot
|
|
28
|
+
|
|
29
|
+
For small or tightly coupled plans. One sub-agent implements the whole plan.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
bun ~/.aiwcli/bin/resolve-run.ts .aiwcli/_shared/skills/codex/scripts/launch-codex.ts --context <ctx-id> plan
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Run with `run_in_background: true`. Wait for the summary. Review the changes.
|
|
36
|
+
|
|
37
|
+
### Parallel
|
|
38
|
+
|
|
39
|
+
For plans with independent sections. Each sub-agent owns one section, scoped by `--prompt`.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Sub-agent A — section 1
|
|
43
|
+
bun ~/.aiwcli/bin/resolve-run.ts .aiwcli/_shared/skills/codex/scripts/launch-codex.ts \
|
|
44
|
+
--context <ctx-id> --prompt "Implement section 1: Extract watch logic into lib/codex-watcher.ts" plan
|
|
45
|
+
|
|
46
|
+
# Sub-agent B — section 3
|
|
47
|
+
bun ~/.aiwcli/bin/resolve-run.ts .aiwcli/_shared/skills/codex/scripts/launch-codex.ts \
|
|
48
|
+
--context <ctx-id> --prompt "Implement section 3: Update launch-codex.ts arg parsing" plan
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Run each with `run_in_background: true`. Summaries arrive as separate background task notifications. When all complete, review for conflicts between agents, then verify with tests or import checks.
|
|
52
|
+
|
|
53
|
+
### Ad-hoc
|
|
54
|
+
|
|
55
|
+
For tasks outside a plan. Pass inline text or a file path.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
bun ~/.aiwcli/bin/resolve-run.ts .aiwcli/_shared/skills/codex/scripts/launch-codex.ts \
|
|
59
|
+
"Fix the failing test in auth.ts"
|
|
60
|
+
|
|
61
|
+
bun ~/.aiwcli/bin/resolve-run.ts .aiwcli/_shared/skills/codex/scripts/launch-codex.ts \
|
|
62
|
+
--file path/to/task-description.md
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Orchestrator Checklist
|
|
66
|
+
|
|
67
|
+
- **Delegate implementation.** If the work involves writing code and Codex can handle it, send it to Codex.
|
|
68
|
+
- **Split independent sections** into parallel sub-agents for faster execution.
|
|
69
|
+
- **Pass `--context`** when implementing a plan — Codex needs project orientation to make good decisions.
|
|
70
|
+
- **Scope with `--prompt`** when running parallel agents — each sub-agent performs better when it knows exactly which section it owns.
|
|
71
|
+
- **Review results** when summaries arrive. Check for merge conflicts between parallel agents, then verify with `tsc --noEmit`, tests, or manual inspection.
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
1
|
import * as fs from "node:fs";
|
|
4
2
|
import * as os from "node:os";
|
|
5
3
|
import * as path from "node:path";
|
|
@@ -10,15 +8,16 @@ import { CODEX_MODELS } from "../../../lib-ts/base/models.js";
|
|
|
10
8
|
import { execFileAsync } from "../../../lib-ts/base/subprocess-utils.js";
|
|
11
9
|
import { getTmuxAvailability } from "../../../lib-ts/base/tmux-driver.js";
|
|
12
10
|
|
|
13
|
-
const POLL_INTERVAL_MS = 2000;
|
|
14
|
-
const POLL_TIMEOUT_MS = 3000;
|
|
15
|
-
const SUMMARY_TIMEOUT_SEC = 8;
|
|
16
|
-
const RESUME_TIMEOUT_MS = 45000;
|
|
17
|
-
const MAX_TRANSCRIPT_LINES = 220;
|
|
18
|
-
const MAX_LINE_LENGTH = 500;
|
|
19
|
-
const
|
|
11
|
+
export const POLL_INTERVAL_MS = 2000;
|
|
12
|
+
export const POLL_TIMEOUT_MS = 3000;
|
|
13
|
+
export const SUMMARY_TIMEOUT_SEC = 8;
|
|
14
|
+
export const RESUME_TIMEOUT_MS = 45000;
|
|
15
|
+
export const MAX_TRANSCRIPT_LINES = 220;
|
|
16
|
+
export const MAX_LINE_LENGTH = 500;
|
|
17
|
+
export const WAIT_TIMEOUT_MS_DEFAULT = 14_400_000;
|
|
18
|
+
export const SUMMARY_UNAVAILABLE_MESSAGE = "Codex session completed. Summary unavailable.";
|
|
20
19
|
|
|
21
|
-
const TRANSCRIPT_SUMMARY_PROMPT = `Summarize this Codex session transcript excerpt.
|
|
20
|
+
export const TRANSCRIPT_SUMMARY_PROMPT = `Summarize this Codex session transcript excerpt.
|
|
22
21
|
Return 3-5 concise bullet points.
|
|
23
22
|
Focus on:
|
|
24
23
|
- what was accomplished
|
|
@@ -28,7 +27,7 @@ Do not ask follow-up questions.
|
|
|
28
27
|
Do not request additional input.
|
|
29
28
|
If information is partial, provide best-effort summary from available text.`;
|
|
30
29
|
|
|
31
|
-
const RESUME_SUMMARY_PROMPT = `Summarize the previous Codex session in 3-5 concise bullet points.
|
|
30
|
+
export const RESUME_SUMMARY_PROMPT = `Summarize the previous Codex session in 3-5 concise bullet points.
|
|
32
31
|
Focus on:
|
|
33
32
|
- what was accomplished
|
|
34
33
|
- files changed
|
|
@@ -37,11 +36,11 @@ Do not ask follow-up questions.
|
|
|
37
36
|
Do not request additional input.
|
|
38
37
|
If the prior session was brief, still provide a best-effort summary.`;
|
|
39
38
|
|
|
40
|
-
function sleep(ms: number): Promise<void> {
|
|
39
|
+
export function sleep(ms: number): Promise<void> {
|
|
41
40
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
function safeCleanup(filePath: string): void {
|
|
43
|
+
export function safeCleanup(filePath: string): void {
|
|
45
44
|
try {
|
|
46
45
|
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
|
|
47
46
|
} catch {
|
|
@@ -49,7 +48,7 @@ function safeCleanup(filePath: string): void {
|
|
|
49
48
|
}
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
function readTextIfExists(filePath: string): string {
|
|
51
|
+
export function readTextIfExists(filePath: string): string {
|
|
53
52
|
try {
|
|
54
53
|
if (!filePath || !fs.existsSync(filePath)) return "";
|
|
55
54
|
return fs.readFileSync(filePath, "utf-8").trim();
|
|
@@ -58,7 +57,7 @@ function readTextIfExists(filePath: string): string {
|
|
|
58
57
|
}
|
|
59
58
|
}
|
|
60
59
|
|
|
61
|
-
function normalizeText(text: string): string {
|
|
60
|
+
export function normalizeText(text: string): string {
|
|
62
61
|
return text
|
|
63
62
|
.replace(/\r/g, "")
|
|
64
63
|
.replace(/[\x00-\x08\x0B-\x1F\x7F]/g, "")
|
|
@@ -66,7 +65,7 @@ function normalizeText(text: string): string {
|
|
|
66
65
|
.trim();
|
|
67
66
|
}
|
|
68
67
|
|
|
69
|
-
function getMessageContentText(content: unknown): string {
|
|
68
|
+
export function getMessageContentText(content: unknown): string {
|
|
70
69
|
if (!Array.isArray(content)) return "";
|
|
71
70
|
return content
|
|
72
71
|
.map((entry: any) => {
|
|
@@ -77,7 +76,7 @@ function getMessageContentText(content: unknown): string {
|
|
|
77
76
|
.join("\n");
|
|
78
77
|
}
|
|
79
78
|
|
|
80
|
-
function collectTranscriptLines(sessionFile: string): string[] {
|
|
79
|
+
export function collectTranscriptLines(sessionFile: string): string[] {
|
|
81
80
|
if (!sessionFile || !fs.existsSync(sessionFile)) return [];
|
|
82
81
|
|
|
83
82
|
const out: string[] = [];
|
|
@@ -126,7 +125,7 @@ function collectTranscriptLines(sessionFile: string): string[] {
|
|
|
126
125
|
return out.slice(-MAX_TRANSCRIPT_LINES);
|
|
127
126
|
}
|
|
128
127
|
|
|
129
|
-
function looksLikeBadSummary(output: string): boolean {
|
|
128
|
+
export function looksLikeBadSummary(output: string): boolean {
|
|
130
129
|
const normalized = output.toLowerCase();
|
|
131
130
|
return (
|
|
132
131
|
normalized.includes("don't see") ||
|
|
@@ -136,14 +135,22 @@ function looksLikeBadSummary(output: string): boolean {
|
|
|
136
135
|
);
|
|
137
136
|
}
|
|
138
137
|
|
|
139
|
-
async function waitForPaneClose(paneId: string): Promise<void> {
|
|
138
|
+
export async function waitForPaneClose(paneId: string, timeoutMs = WAIT_TIMEOUT_MS_DEFAULT): Promise<void> {
|
|
139
|
+
if (!paneId) return;
|
|
140
|
+
|
|
140
141
|
const tmux = getTmuxAvailability();
|
|
141
142
|
if (!tmux.available || !tmux.tmuxPath) {
|
|
142
143
|
logWarn("codex-capture", `tmux unavailable while watching pane ${paneId}: ${tmux.reason ?? "unknown reason"}`);
|
|
143
144
|
return;
|
|
144
145
|
}
|
|
145
146
|
|
|
147
|
+
const deadline = Date.now() + timeoutMs;
|
|
146
148
|
while (true) {
|
|
149
|
+
if (Date.now() >= deadline) {
|
|
150
|
+
logDebug("codex-capture", `watch timeout reached for pane ${paneId} after ${timeoutMs}ms`);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
147
154
|
const result = await execFileAsync(tmux.tmuxPath, ["list-panes", "-a", "-F", "#{pane_id}"], {
|
|
148
155
|
timeout: POLL_TIMEOUT_MS,
|
|
149
156
|
});
|
|
@@ -156,11 +163,12 @@ async function waitForPaneClose(paneId: string): Promise<void> {
|
|
|
156
163
|
const activePaneIds = result.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
157
164
|
if (!activePaneIds.includes(paneId)) return;
|
|
158
165
|
|
|
159
|
-
|
|
166
|
+
const remainingMs = deadline - Date.now();
|
|
167
|
+
await sleep(Math.max(0, Math.min(POLL_INTERVAL_MS, remainingMs)));
|
|
160
168
|
}
|
|
161
169
|
}
|
|
162
170
|
|
|
163
|
-
function summarizeViaSessionFileSpark(sessionFile: string): string | null {
|
|
171
|
+
export function summarizeViaSessionFileSpark(sessionFile: string): string | null {
|
|
164
172
|
const transcriptLines = collectTranscriptLines(sessionFile);
|
|
165
173
|
if (transcriptLines.length === 0) return null;
|
|
166
174
|
|
|
@@ -184,7 +192,7 @@ function summarizeViaSessionFileSpark(sessionFile: string): string | null {
|
|
|
184
192
|
return null;
|
|
185
193
|
}
|
|
186
194
|
|
|
187
|
-
async function summarizeViaResume(sessionId: string): Promise<string | null> {
|
|
195
|
+
export async function summarizeViaResume(sessionId: string): Promise<string | null> {
|
|
188
196
|
const outputFile = path.join(os.tmpdir(), `codex-resume-summary-${Date.now()}-${process.pid}.txt`);
|
|
189
197
|
|
|
190
198
|
const result = await execFileAsync(
|
|
@@ -211,47 +219,8 @@ async function summarizeViaResume(sessionId: string): Promise<string | null> {
|
|
|
211
219
|
return null;
|
|
212
220
|
}
|
|
213
221
|
|
|
214
|
-
function summarizeFromSessionFileFallback(sessionFile: string): string | null {
|
|
222
|
+
export function summarizeFromSessionFileFallback(sessionFile: string): string | null {
|
|
215
223
|
const lines = collectTranscriptLines(sessionFile).slice(-12);
|
|
216
224
|
if (lines.length === 0) return null;
|
|
217
225
|
return `Codex session completed. Transcript fallback:\n- ${lines.join("\n- ")}`;
|
|
218
226
|
}
|
|
219
|
-
|
|
220
|
-
async function main(): Promise<void> {
|
|
221
|
-
const [paneId, sessionId, sessionFile] = process.argv.slice(2);
|
|
222
|
-
|
|
223
|
-
if (!paneId) {
|
|
224
|
-
console.log(SUMMARY_UNAVAILABLE_MESSAGE);
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
await waitForPaneClose(paneId);
|
|
229
|
-
|
|
230
|
-
const transcriptSummary = summarizeViaSessionFileSpark(sessionFile ?? "");
|
|
231
|
-
if (transcriptSummary) {
|
|
232
|
-
console.log(transcriptSummary);
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (sessionId) {
|
|
237
|
-
const resumeSummary = await summarizeViaResume(sessionId);
|
|
238
|
-
if (resumeSummary) {
|
|
239
|
-
console.log(resumeSummary);
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const fallback = summarizeFromSessionFileFallback(sessionFile ?? "");
|
|
245
|
-
if (fallback) {
|
|
246
|
-
console.log(fallback);
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
console.log(SUMMARY_UNAVAILABLE_MESSAGE);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
main().catch((error) => {
|
|
254
|
-
logWarn("codex-capture", `watch-codex failed: ${String(error)}`);
|
|
255
|
-
console.log(SUMMARY_UNAVAILABLE_MESSAGE);
|
|
256
|
-
process.exit(0);
|
|
257
|
-
});
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* Launch Codex in a tmux pane and inject a prompt into its REPL.
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
* bun launch-codex.ts [--model fast|standard|smart|<model-id>] [--sandbox read-only|workspace-write|danger-full-access] [--no-yolo] [--
|
|
7
|
-
* bun launch-codex.ts [--model fast|standard|smart|<model-id>] [--sandbox read-only|workspace-write|danger-full-access] [--no-yolo] [--
|
|
8
|
-
* bun launch-codex.ts [--model fast|standard|smart|<model-id>] [--sandbox read-only|workspace-write|danger-full-access] [--no-yolo] [--
|
|
6
|
+
* bun launch-codex.ts [--model fast|standard|smart|<model-id>] [--sandbox read-only|workspace-write|danger-full-access] [--no-yolo] [--no-watch] [--context <id>] [--prompt <text>] plan
|
|
7
|
+
* bun launch-codex.ts [--model fast|standard|smart|<model-id>] [--sandbox read-only|workspace-write|danger-full-access] [--no-yolo] [--no-watch] [--context <id>] [--prompt <text>] --file <path>
|
|
8
|
+
* bun launch-codex.ts [--model fast|standard|smart|<model-id>] [--sandbox read-only|workspace-write|danger-full-access] [--no-yolo] [--no-watch] [--context <id>] [--prompt <text>] <inline text...>
|
|
9
9
|
*/
|
|
10
10
|
import * as fs from "node:fs";
|
|
11
11
|
import * as os from "node:os";
|
|
@@ -179,16 +179,17 @@ function findLatestPlanByMtime(projectRoot: string): string | null {
|
|
|
179
179
|
const rawArgs = process.argv.slice(2);
|
|
180
180
|
|
|
181
181
|
if (rawArgs.length === 0) {
|
|
182
|
-
eprint("Usage: launch-codex.ts [--model <model>] [--sandbox <mode>] [--no-yolo] [--
|
|
182
|
+
eprint("Usage: launch-codex.ts [--model <model>] [--sandbox <mode>] [--no-yolo] [--no-watch] [--context <id>] [--prompt <text>] plan | --file <path> | <text...>");
|
|
183
183
|
process.exit(1);
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
// Extract
|
|
186
|
+
// Extract flags before mode dispatch
|
|
187
187
|
let modelFlag: string | undefined;
|
|
188
188
|
let sandboxFlag: CodexSandbox | undefined;
|
|
189
189
|
let contextFlag: string | undefined;
|
|
190
|
+
let extraPrompt: string | undefined;
|
|
190
191
|
let yolo = true;
|
|
191
|
-
let
|
|
192
|
+
let watch = true;
|
|
192
193
|
const args: string[] = [];
|
|
193
194
|
|
|
194
195
|
for (let i = 0; i < rawArgs.length; i++) {
|
|
@@ -203,19 +204,24 @@ for (let i = 0; i < rawArgs.length; i++) {
|
|
|
203
204
|
sandboxFlag = val;
|
|
204
205
|
} else if (rawArgs[i] === "--context" && i + 1 < rawArgs.length) {
|
|
205
206
|
contextFlag = rawArgs[++i];
|
|
207
|
+
} else if (rawArgs[i] === "--prompt" && i + 1 < rawArgs.length) {
|
|
208
|
+
extraPrompt = rawArgs[++i];
|
|
209
|
+
} else if (rawArgs[i] === "--prompt") {
|
|
210
|
+
eprint("Error: --prompt requires a text argument.");
|
|
211
|
+
process.exit(1);
|
|
206
212
|
} else if (rawArgs[i] === "--yolo") {
|
|
207
213
|
yolo = true;
|
|
208
214
|
} else if (rawArgs[i] === "--no-yolo") {
|
|
209
215
|
yolo = false;
|
|
210
|
-
} else if (rawArgs[i] === "--
|
|
211
|
-
|
|
216
|
+
} else if (rawArgs[i] === "--no-watch") {
|
|
217
|
+
watch = false;
|
|
212
218
|
} else {
|
|
213
219
|
args.push(rawArgs[i]);
|
|
214
220
|
}
|
|
215
221
|
}
|
|
216
222
|
|
|
217
223
|
if (args.length === 0) {
|
|
218
|
-
eprint("Usage: launch-codex.ts [--model <model>] [--sandbox <mode>] [--no-yolo] [--
|
|
224
|
+
eprint("Usage: launch-codex.ts [--model <model>] [--sandbox <mode>] [--no-yolo] [--no-watch] [--context <id>] [--prompt <text>] plan | --file <path> | <text...>");
|
|
219
225
|
process.exit(1);
|
|
220
226
|
}
|
|
221
227
|
|
|
@@ -303,6 +309,22 @@ if (ctx && promptPath) {
|
|
|
303
309
|
}
|
|
304
310
|
}
|
|
305
311
|
|
|
312
|
+
if (extraPrompt && promptPath) {
|
|
313
|
+
try {
|
|
314
|
+
const base = fs.readFileSync(promptPath, "utf-8");
|
|
315
|
+
const combined = `${base}\n\n---\n\n## Additional Instructions\n\n${extraPrompt}`;
|
|
316
|
+
const extraPromptPath = path.join(os.tmpdir(), `codex-extra-prompt-${Date.now()}.md`);
|
|
317
|
+
fs.writeFileSync(extraPromptPath, combined, "utf-8");
|
|
318
|
+
if (tempFile) {
|
|
319
|
+
try { fs.unlinkSync(tempFile); } catch { /* ignore */ }
|
|
320
|
+
}
|
|
321
|
+
promptPath = extraPromptPath;
|
|
322
|
+
tempFile = extraPromptPath;
|
|
323
|
+
} catch {
|
|
324
|
+
logWarn("codex-skill", "Extra prompt append failed, continuing without it");
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
306
328
|
// ---------------------------------------------------------------------------
|
|
307
329
|
// Pre-flight: tmux required
|
|
308
330
|
// ---------------------------------------------------------------------------
|
|
@@ -324,13 +346,14 @@ if (yolo) console.log("Mode: YOLO (bypass approvals and sandbox)");
|
|
|
324
346
|
if (sandboxFlag) console.log(`Sandbox: ${sandboxFlag}`);
|
|
325
347
|
if (resolvedModel) console.log(`Model: ${resolvedModel}${modelFlag !== resolvedModel ? ` (from "${modelFlag}")` : ""}`);
|
|
326
348
|
|
|
327
|
-
logDebug("codex-skill", `Launching: model=${resolvedModel ?? "default"}, sandbox=${sandboxFlag ?? "default"}, yolo=${yolo}, source=${args[0]}, bytes=${promptPath ? fs.statSync(promptPath).size : 0}`);
|
|
349
|
+
logDebug("codex-skill", `Launching: model=${resolvedModel ?? "default"}, sandbox=${sandboxFlag ?? "default"}, yolo=${yolo}, extraPrompt=${!!extraPrompt}, source=${args[0]}, bytes=${promptPath ? fs.statSync(promptPath).size : 0}`);
|
|
328
350
|
const launchStartedAtMs = Date.now();
|
|
329
351
|
|
|
330
352
|
const result = await launchDriverInTmuxOrFallback({
|
|
331
353
|
toolName: "codex",
|
|
332
354
|
mode: "repl",
|
|
333
355
|
args: codexArgs,
|
|
356
|
+
splitFlag: "auto",
|
|
334
357
|
promptPath,
|
|
335
358
|
sendPromptInRepl: true,
|
|
336
359
|
allowExecFallback: false,
|
|
@@ -363,21 +386,32 @@ if (result.paneId) {
|
|
|
363
386
|
console.log("Codex launched in tmux pane.");
|
|
364
387
|
}
|
|
365
388
|
|
|
366
|
-
if (
|
|
389
|
+
if (watch && result.paneId) {
|
|
367
390
|
try {
|
|
391
|
+
const {
|
|
392
|
+
SUMMARY_UNAVAILABLE_MESSAGE,
|
|
393
|
+
summarizeFromSessionFileFallback,
|
|
394
|
+
summarizeViaResume,
|
|
395
|
+
summarizeViaSessionFileSpark,
|
|
396
|
+
waitForPaneClose,
|
|
397
|
+
} = await import("../lib/codex-watcher.js");
|
|
398
|
+
|
|
368
399
|
const sessionInfo = await waitForCaptureSession(projectRoot, launchStartedAtMs);
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
400
|
+
await waitForPaneClose(result.paneId);
|
|
401
|
+
|
|
402
|
+
const sessionFile = sessionInfo?.sessionFile ?? "";
|
|
403
|
+
const sessionId = sessionInfo?.sessionId ?? "";
|
|
404
|
+
const summary = summarizeViaSessionFileSpark(sessionFile)
|
|
405
|
+
?? (sessionId ? await summarizeViaResume(sessionId) : null)
|
|
406
|
+
?? summarizeFromSessionFileFallback(sessionFile)
|
|
407
|
+
?? SUMMARY_UNAVAILABLE_MESSAGE;
|
|
408
|
+
|
|
409
|
+
console.log("\n--- Codex Session Summary ---");
|
|
410
|
+
console.log(summary);
|
|
379
411
|
} catch (error) {
|
|
380
|
-
logWarn("codex-skill", `
|
|
412
|
+
logWarn("codex-skill", `Watch flow failed for ${result.paneId}: ${String(error)}`);
|
|
413
|
+
console.log("\n--- Codex Session Summary ---");
|
|
414
|
+
console.log("Codex session completed. Summary unavailable.");
|
|
381
415
|
}
|
|
382
416
|
}
|
|
383
417
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Thin CLI wrapper over lib/codex-watcher.ts for backward compatibility.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* bun watch-codex.ts <pane_id> <session_id> <session_file>
|
|
7
|
+
*
|
|
8
|
+
* Prefer using launch-codex.ts directly (watch is built-in by default).
|
|
9
|
+
*/
|
|
10
|
+
import {
|
|
11
|
+
SUMMARY_UNAVAILABLE_MESSAGE,
|
|
12
|
+
summarizeFromSessionFileFallback,
|
|
13
|
+
summarizeViaResume,
|
|
14
|
+
summarizeViaSessionFileSpark,
|
|
15
|
+
waitForPaneClose,
|
|
16
|
+
} from "../lib/codex-watcher.js";
|
|
17
|
+
|
|
18
|
+
async function main(): Promise<void> {
|
|
19
|
+
const [paneId, sessionId, sessionFile] = process.argv.slice(2);
|
|
20
|
+
if (!paneId) {
|
|
21
|
+
console.log(SUMMARY_UNAVAILABLE_MESSAGE);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
await waitForPaneClose(paneId);
|
|
26
|
+
|
|
27
|
+
const sf = sessionFile ?? "";
|
|
28
|
+
const sid = sessionId ?? "";
|
|
29
|
+
const summary =
|
|
30
|
+
summarizeViaSessionFileSpark(sf) ??
|
|
31
|
+
(sid ? await summarizeViaResume(sid) : null) ??
|
|
32
|
+
summarizeFromSessionFileFallback(sf) ??
|
|
33
|
+
SUMMARY_UNAVAILABLE_MESSAGE;
|
|
34
|
+
|
|
35
|
+
console.log("\n--- Codex Session Summary ---");
|
|
36
|
+
console.log(summary);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
main().catch((error) => {
|
|
40
|
+
console.error(`watch-codex error: ${String(error)}`);
|
|
41
|
+
console.log(SUMMARY_UNAVAILABLE_MESSAGE);
|
|
42
|
+
});
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# /codex
|
|
2
|
-
|
|
3
|
-
| Command | Description |
|
|
4
|
-
|---|---|
|
|
5
|
-
| `bun launch-codex.ts [--model fast|standard|smart|<model-id>] [--sandbox read-only|workspace-write|danger-full-access] [--no-yolo] [--capture] plan` | Launch Codex REPL with active plan. |
|
|
6
|
-
| `bun launch-codex.ts [--model fast|standard|smart|<model-id>] [--sandbox read-only|workspace-write|danger-full-access] [--no-yolo] [--capture] --file <path>` | Launch Codex REPL with file contents. |
|
|
7
|
-
| `bun launch-codex.ts [--model fast|standard|smart|<model-id>] [--sandbox read-only|workspace-write|danger-full-access] [--no-yolo] [--capture] <inline text...>` | Launch Codex REPL with inline prompt. |
|
|
8
|
-
|
|
9
|
-
- `--model`: model tier (`fast`/`standard`/`smart`) resolves to `gpt-5.3-codex-spark` / `gpt-5.3-codex`, or any explicit Codex model id. Aliases: `spark`, `codex`, `gpt`.
|
|
10
|
-
- `--sandbox`: `read-only`, `workspace-write`, or `danger-full-access`. Default is `danger-full-access` for implementation handoffs.
|
|
11
|
-
- YOLO mode is **on by default** — bypasses all approvals and sandbox (`--dangerously-bypass-approvals-and-sandbox`). Use `--no-yolo` to disable.
|
|
12
|
-
- `--capture`: best-effort session capture. If setup succeeds, launch output includes:
|
|
13
|
-
- `CODEX_CAPTURE_PANE=<pane_id>`
|
|
14
|
-
- `CODEX_CAPTURE_SESSION_ID=<session_id>`
|
|
15
|
-
- `CODEX_CAPTURE_SESSION_FILE=<path>`
|
|
16
|
-
|
|
17
|
-
If launch output includes `CODEX_CAPTURE_PANE` and `CODEX_CAPTURE_SESSION_ID`:
|
|
18
|
-
|
|
19
|
-
1. Parse pane/session metadata from stdout.
|
|
20
|
-
2. Start a background watcher:
|
|
21
|
-
```bash
|
|
22
|
-
bun .aiwcli/_shared/skills/prompt-codex/scripts/watch-codex.ts <pane_id> <session_id> <session_file>
|
|
23
|
-
```
|
|
24
|
-
Use `Bash` with `run_in_background: true`.
|
|
25
|
-
3. Tell the user: `Codex is running in the tmux pane. I'll receive a summary when you exit.`
|
|
26
|
-
4. Continue with other work; the background task output will arrive as a notification.
|
|
27
|
-
|
|
28
|
-
Watcher behavior:
|
|
29
|
-
- Primary: summarize from `CODEX_CAPTURE_SESSION_FILE` with Spark.
|
|
30
|
-
- Fallback: use `codex exec resume <session_id>` if transcript summarization fails.
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
# Prompt Codex Skill
|
|
2
|
-
|
|
3
|
-
Launch Codex CLI in a tmux pane and inject a prompt into its REPL.
|
|
4
|
-
|
|
5
|
-
## Directory Structure
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
prompt-codex/
|
|
9
|
-
├── CLAUDE.md ← This file
|
|
10
|
-
└── scripts/
|
|
11
|
-
├── launch-codex.ts ← CLI entry point
|
|
12
|
-
└── watch-codex.ts ← Capture watcher and summarizer
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## Script: launch-codex.ts
|
|
16
|
-
|
|
17
|
-
**Usage:**
|
|
18
|
-
```bash
|
|
19
|
-
bun .aiwcli/_shared/skills/prompt-codex/scripts/launch-codex.ts [--model <tier|id>] [--sandbox <sandbox-mode>] [--full-auto] plan
|
|
20
|
-
bun .aiwcli/_shared/skills/prompt-codex/scripts/launch-codex.ts [--model <tier|id>] [--sandbox <sandbox-mode>] [--full-auto] --file <path>
|
|
21
|
-
bun .aiwcli/_shared/skills/prompt-codex/scripts/launch-codex.ts [--model <tier|id>] [--sandbox <sandbox-mode>] [--full-auto] <inline text...>
|
|
22
|
-
bun .aiwcli/_shared/skills/prompt-codex/scripts/launch-codex.ts [--model <tier|id>] [--sandbox <sandbox-mode>] [--full-auto] [--capture] <mode>
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
**Args:**
|
|
26
|
-
- `plan` — discover active plan via context system, inject into Codex REPL
|
|
27
|
-
- `--file <path>` — inject file contents into Codex REPL
|
|
28
|
-
- `<text...>` — join remaining args as inline prompt, write to temp file, inject
|
|
29
|
-
- `--model <alias|tier|id>` — Aliases: `spark` → `gpt-5.3-codex-spark`, `codex` → `gpt-5.3-codex`, `gpt` → `gpt-5.2`. Tiers: `fast`/`standard`/`smart` (resolved via `resolveModelForProvider()`). Or any full model ID. Aliases are checked first (local `CODEX_ALIASES` constant in `launch-codex.ts`), then tiers, then pass-through. Omitted = Codex default.
|
|
30
|
-
- `--sandbox <mode>` — `read-only`, `workspace-write`, or `danger-full-access`. Default is `danger-full-access` for implementation handoffs.
|
|
31
|
-
- `--no-yolo` — Disable YOLO mode (on by default). YOLO maps to Codex CLI's `--dangerously-bypass-approvals-and-sandbox`. Use `--no-yolo` to restore normal approval prompts.
|
|
32
|
-
- `--capture` — Best-effort session capture. On success, prints:
|
|
33
|
-
- `CODEX_CAPTURE_PANE=<pane_id>`
|
|
34
|
-
- `CODEX_CAPTURE_SESSION_ID=<session_id>`
|
|
35
|
-
- `CODEX_CAPTURE_SESSION_FILE=<session_file>`
|
|
36
|
-
These are consumed by the skill prompt to run `watch-codex.ts` as a background task.
|
|
37
|
-
|
|
38
|
-
**Plan discovery order:**
|
|
39
|
-
1. `CLAUDE_SESSION_ID` env → `getContextBySessionId()` → `findLatestPlan(contextId)`
|
|
40
|
-
2. Fallback: scan `_output/contexts/*/plans/*.md` by mtime (inline, no `_cc-native` import)
|
|
41
|
-
|
|
42
|
-
**Dependencies (all from `_shared/lib-ts/`):**
|
|
43
|
-
- `base/tmux-driver.ts` — `launchDriverInTmuxOrFallback()`, `getTmuxAvailability()`
|
|
44
|
-
- `base/cli-args.ts` — `resolveCodexModel()`, `codexReplSpec()`, `buildCliInvocation()`, `isCodexSandbox()`
|
|
45
|
-
- `base/logger.ts` — `logDebug()`, `logWarn()` (injection diagnostics)
|
|
46
|
-
- `context/context-store.ts` — `getContextBySessionId()`
|
|
47
|
-
- `context/context-formatter.ts` — `buildExternalAgentContext()` (orientation header for Codex)
|
|
48
|
-
- `context/plan-manager.ts` — `findLatestPlan()`
|
|
49
|
-
|
|
50
|
-
**Design decisions:**
|
|
51
|
-
- Always creates a new tmux pane (no pane reuse/tracking)
|
|
52
|
-
- No exec fallback — REPL mode requires tmux
|
|
53
|
-
- `_shared` only — never imports from `_cc-native`
|
|
54
|
-
- Temp file cleanup after injection confirmed
|
|
55
|
-
|
|
56
|
-
## Script: watch-codex.ts
|
|
57
|
-
|
|
58
|
-
**Usage:**
|
|
59
|
-
```bash
|
|
60
|
-
bun .aiwcli/_shared/skills/prompt-codex/scripts/watch-codex.ts <pane_id> <session_id> <session_file>
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
**Behavior:**
|
|
64
|
-
- Polls tmux until `<pane_id>` closes
|
|
65
|
-
- Primary: parses `<session_file>` transcript and summarizes via Spark (`inference()` + `CODEX_MODELS.spark`)
|
|
66
|
-
- Fallback: runs `codex exec resume <session_id>` if transcript summarization fails
|
|
67
|
-
- Final fallback: emits concise transcript lines directly from `<session_file>`
|
|
68
|
-
|
|
69
|
-
**Resilience policy:**
|
|
70
|
-
- Capture path is best-effort and never blocks Codex launch
|
|
71
|
-
- Watcher exits cleanly on poll/summary/parse failures with fallback text
|