aiwcli 0.14.0 → 0.15.1

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.
Files changed (21) hide show
  1. package/dist/templates/_shared/.claude/skills/codex/prompt.md +25 -5
  2. package/dist/templates/_shared/lib-ts/agent-exec/index.ts +2 -0
  3. package/dist/templates/_shared/lib-ts/agent-exec/structured-output.ts +166 -0
  4. package/dist/templates/_shared/lib-ts/base/cli-args.ts +4 -0
  5. package/dist/templates/_shared/lib-ts/base/state-io.ts +1 -1
  6. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +4 -3
  7. package/dist/templates/_shared/lib-ts/context/context-store.ts +3 -0
  8. package/dist/templates/_shared/scripts/status_line.ts +36 -19
  9. package/dist/templates/_shared/skills/prompt-codex/CLAUDE.md +29 -4
  10. package/dist/templates/_shared/skills/prompt-codex/scripts/launch-codex.ts +140 -7
  11. package/dist/templates/_shared/skills/prompt-codex/scripts/watch-codex.ts +257 -0
  12. package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +9 -133
  13. package/dist/templates/cc-native/_cc-native/lib-ts/settings.ts +118 -42
  14. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +1 -0
  15. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +61 -0
  16. package/dist/templates/cc-native/_cc-native/plan-review/lib/agent-selection.ts +5 -4
  17. package/dist/templates/cc-native/_cc-native/plan-review/lib/orchestrator.ts +4 -4
  18. package/dist/templates/cc-native/_cc-native/plan-review/lib/review-pipeline.ts +16 -13
  19. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/providers/orchestrator-claude-agent.ts +54 -23
  20. package/oclif.manifest.json +1 -1
  21. package/package.json +1 -1
@@ -2,9 +2,29 @@
2
2
 
3
3
  | Command | Description |
4
4
  |---|---|
5
- | `bun launch-codex.ts [--model fast|standard|smart|<model-id>] [--sandbox read-only|disabled] plan` | Launch Codex REPL with active plan. |
6
- | `bun launch-codex.ts [--model fast|standard|smart|<model-id>] [--sandbox read-only|disabled] --file <path>` | Launch Codex REPL with file contents. |
7
- | `bun launch-codex.ts [--model fast|standard|smart|<model-id>] [--sandbox read-only|disabled] <inline text...>` | Launch Codex REPL with inline prompt. |
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
8
 
9
- - `--model`: model tier (`fast`/`standard`/`smart`) resolves to `gpt-5.3-codex-spark` / `gpt-5.3-codex-think`, or any explicit Codex model id.
10
- - `--sandbox`: `read-only` or `disabled` (default). `read-only` restricts file writes; `disabled` allows writes.
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.
@@ -2,3 +2,5 @@ export { BaseCliAgent, type AgentExecutionConfig } from "./base-agent.js";
2
2
  export type { ExecutionBackend, ExecutionRequest, ExecutionResult, AgentDebugLogger } from "./execution-backend.js";
3
3
  export { HeadlessBackend } from "./backends/headless.js";
4
4
  export { TmuxBackend } from "./backends/tmux.js";
5
+ export { parseJsonObjectMaybe, parseStructuredOutput } from "./structured-output.js";
6
+ export type { StructuredOutputParseOptions } from "./structured-output.js";
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Shared structured output parsing utilities for CLI-based agents.
3
+ * Supports Claude/Codex-style envelopes and heuristic JSON extraction.
4
+ */
5
+
6
+ import { logDebug, logError, logWarn } from "../base/logger.js";
7
+
8
+ export interface StructuredOutputParseOptions {
9
+ requireFields?: string[];
10
+ loggerTag?: string;
11
+ }
12
+
13
+ const DEFAULT_LOG_TAG = "structured_output";
14
+
15
+ function getTag(options?: StructuredOutputParseOptions): string {
16
+ return options?.loggerTag ?? DEFAULT_LOG_TAG;
17
+ }
18
+
19
+ function validateRequiredFields(
20
+ obj: Record<string, unknown>,
21
+ parseMethod: "strict" | "heuristic",
22
+ options?: StructuredOutputParseOptions,
23
+ ): Record<string, unknown> | null {
24
+ const required = options?.requireFields;
25
+ if (!required || required.length === 0) return obj;
26
+
27
+ const missing = required.filter((field) => !(field in obj) || obj[field] === undefined || obj[field] === null);
28
+ if (missing.length === 0) return obj;
29
+
30
+ const tag = getTag(options);
31
+ logWarn(tag, `Parsed JSON (${parseMethod}) missing required fields: ${JSON.stringify(missing)}`);
32
+ logDebug(tag, `Parsed keys: ${JSON.stringify(Object.keys(obj))}`);
33
+
34
+ // Heuristic extraction often grabs the wrong JSON blob. Reject in that case.
35
+ if (parseMethod === "heuristic") {
36
+ return null;
37
+ }
38
+ return obj;
39
+ }
40
+
41
+ /**
42
+ * Parse a JSON object from text using strict parse first, then heuristic
43
+ * extraction of the first object-like block.
44
+ */
45
+ export function parseJsonObjectMaybe(
46
+ text: string,
47
+ options?: StructuredOutputParseOptions,
48
+ ): Record<string, unknown> | null {
49
+ const tag = getTag(options);
50
+ const trimmed = text.trim();
51
+ if (!trimmed) return null;
52
+
53
+ // Strict parse first.
54
+ try {
55
+ const parsed: unknown = JSON.parse(trimmed);
56
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
57
+ return validateRequiredFields(parsed as Record<string, unknown>, "strict", options);
58
+ }
59
+ } catch {
60
+ // Fall through to heuristic extraction.
61
+ }
62
+
63
+ // Heuristic parse: extract the first object-like block.
64
+ const start = trimmed.indexOf("{");
65
+ const end = trimmed.lastIndexOf("}");
66
+ if (start === -1 || end === -1 || end <= start) return null;
67
+
68
+ const candidate = trimmed.slice(start, end + 1);
69
+ try {
70
+ const parsed: unknown = JSON.parse(candidate);
71
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
72
+ logDebug(tag, `Used heuristic JSON extraction (chars ${start}-${end})`);
73
+ return validateRequiredFields(parsed as Record<string, unknown>, "heuristic", options);
74
+ }
75
+ } catch {
76
+ logDebug(tag, `Heuristic JSON extraction failed (chars ${start}-${end})`);
77
+ }
78
+
79
+ return null;
80
+ }
81
+
82
+ function parseAssistantEnvelope(
83
+ envelope: Record<string, unknown>,
84
+ options?: StructuredOutputParseOptions,
85
+ ): Record<string, unknown> | null {
86
+ const tag = getTag(options);
87
+ const message = envelope.message;
88
+ if (!message || typeof message !== "object") return null;
89
+
90
+ const content = (message as Record<string, unknown>).content;
91
+ if (!Array.isArray(content)) return null;
92
+
93
+ for (const item of content) {
94
+ if (!item || typeof item !== "object") continue;
95
+ const toolUse = item as Record<string, unknown>;
96
+ if (toolUse.name !== "StructuredOutput") continue;
97
+ if (toolUse.input && typeof toolUse.input === "object" && !Array.isArray(toolUse.input)) {
98
+ logDebug(tag, "Found StructuredOutput in assistant envelope");
99
+ return toolUse.input as Record<string, unknown>;
100
+ }
101
+ }
102
+ return null;
103
+ }
104
+
105
+ /**
106
+ * Parse structured output across known CLI envelope formats.
107
+ * Falls back to generic JSON extraction when no recognized envelope exists.
108
+ */
109
+ export function parseStructuredOutput(
110
+ raw: string,
111
+ options?: StructuredOutputParseOptions,
112
+ ): Record<string, unknown> | null {
113
+ const tag = getTag(options);
114
+
115
+ try {
116
+ const parsed: unknown = JSON.parse(raw);
117
+
118
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
119
+ const obj = parsed as Record<string, unknown>;
120
+
121
+ if (obj.structured_output && typeof obj.structured_output === "object" && !Array.isArray(obj.structured_output)) {
122
+ logDebug(tag, "Found structured_output in root object");
123
+ return validateRequiredFields(obj.structured_output as Record<string, unknown>, "strict", options);
124
+ }
125
+
126
+ const assistantResult = parseAssistantEnvelope(obj, options);
127
+ if (assistantResult) return assistantResult;
128
+
129
+ // Session result envelope (no structured output tool call).
130
+ if (obj.type === "result" || ("duration_ms" in obj && "session_id" in obj)) {
131
+ if (obj.is_error === true || (Array.isArray(obj.errors) && obj.errors.length > 0)) {
132
+ logWarn(tag, `CLI returned error envelope: ${JSON.stringify(obj.errors ?? "is_error=true")}`);
133
+ return null;
134
+ }
135
+
136
+ if (typeof obj.result === "string" && obj.result.trim().length > 0) {
137
+ logDebug(tag, "Found text result in session envelope, attempting JSON extraction");
138
+ const extracted = parseJsonObjectMaybe(obj.result, options);
139
+ if (extracted) return extracted;
140
+ logWarn(tag, "Session envelope result contained no extractable JSON object");
141
+ }
142
+ return null;
143
+ }
144
+ } else if (Array.isArray(parsed)) {
145
+ for (let i = 0; i < parsed.length; i++) {
146
+ const event = parsed[i];
147
+ if (!event || typeof event !== "object") continue;
148
+ const eventObj = event as Record<string, unknown>;
149
+ const assistantResult = parseAssistantEnvelope(eventObj, options);
150
+ if (assistantResult) {
151
+ logDebug(tag, `Found StructuredOutput in event[${i}]`);
152
+ return assistantResult;
153
+ }
154
+ }
155
+ }
156
+ } catch (error: unknown) {
157
+ if (error instanceof SyntaxError) {
158
+ logWarn(tag, `JSON decode error: ${error.message}`);
159
+ } else {
160
+ logError(tag, `Unexpected parse error: ${error}`);
161
+ }
162
+ }
163
+
164
+ logDebug(tag, "No structured envelope found, falling back to generic JSON extraction");
165
+ return parseJsonObjectMaybe(raw, options);
166
+ }
@@ -44,6 +44,7 @@ export interface CodexReplSpec {
44
44
  mode: "repl";
45
45
  model?: string | ModelTier;
46
46
  sandbox?: CodexSandbox;
47
+ yolo?: boolean;
47
48
  extraArgs?: string[];
48
49
  }
49
50
 
@@ -203,6 +204,7 @@ function buildCodexReplInvocation(
203
204
  env: Record<string, string | undefined>,
204
205
  ): CliInvocation {
205
206
  const args: string[] = [];
207
+ if (spec.yolo) args.push("--dangerously-bypass-approvals-and-sandbox");
206
208
  if (spec.sandbox) args.push("--sandbox", spec.sandbox);
207
209
  if (model) args.push("--model", model);
208
210
  if (spec.extraArgs) args.push(...spec.extraArgs);
@@ -263,12 +265,14 @@ export function reviewSpec(
263
265
  export function codexReplSpec(
264
266
  model?: string,
265
267
  sandbox?: CodexSandbox,
268
+ yolo?: boolean,
266
269
  ): CodexReplSpec {
267
270
  return {
268
271
  provider: "codex",
269
272
  mode: "repl",
270
273
  model,
271
274
  sandbox,
275
+ yolo,
272
276
  };
273
277
  }
274
278
 
@@ -16,7 +16,7 @@ import type { ContextState, Mode } from "../types.js";
16
16
  const MODE_MIGRATION: Record<string, Mode> = {
17
17
  none: "idle",
18
18
  planning: "idle",
19
- pending_implementation: "has_plan",
19
+ pending_implementation: "has_staged_work",
20
20
  implementing: "active",
21
21
  };
22
22
 
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { execSync, execFile } from "node:child_process";
7
- import type { ChildProcess } from "node:child_process";
7
+ import type { ChildProcess, ExecSyncOptionsWithStringEncoding } from "node:child_process";
8
8
 
9
9
  // ─── Child Process Cleanup ─────────────────────────────────────────────────
10
10
  //
@@ -68,7 +68,7 @@ export function isInternalCall(): boolean {
68
68
  * claude instances can run without being blocked.
69
69
  */
70
70
  export function getInternalSubprocessEnv(): Record<string, string | undefined> {
71
- const env = {
71
+ const env: Record<string, string | undefined> = {
72
72
  ...process.env,
73
73
  AIWCLI_INTERNAL_CALL: "true",
74
74
  };
@@ -87,7 +87,8 @@ export function getInternalSubprocessEnv(): Record<string, string | undefined> {
87
87
  export function findExecutable(name: string): string | null {
88
88
  try {
89
89
  const cmd = process.platform === "win32" ? `where ${name}` : `which ${name}`;
90
- const lines = execSync(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], shell: true })
90
+ // shell: true is valid at runtime but Node's TS types restrict it to string for this overload
91
+ const lines = execSync(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], shell: true } as unknown as ExecSyncOptionsWithStringEncoding)
91
92
  .trim()
92
93
  .split(/\r?\n/)
93
94
  .map((l) => l.trim())
@@ -139,9 +139,12 @@ function migrateContextJson(contextId: string, projectRoot?: string): ContextSta
139
139
  plan_signature: null,
140
140
  plan_id: null,
141
141
  plan_anchors: [],
142
+ plan_hash_consumed: null,
142
143
  plan_consumed: false,
143
144
  handoff_path: inFlight.handoff_path ?? null,
144
145
  handoff_consumed: false,
146
+ work_consumed: false,
147
+ next_artifact_type: null,
145
148
  session_ids: sessionIds,
146
149
  last_session: null,
147
150
  tasks: [],
@@ -13,14 +13,15 @@ import { execFileSync } from "node:child_process";
13
13
  import * as fs from "node:fs";
14
14
  import { homedir } from "node:os";
15
15
  import * as path from "node:path";
16
+ import type { ContextState } from "../lib-ts/types.js";
16
17
 
17
18
  // PAI infrastructure imports — graceful fallback when libs aren't available
18
19
  let CONTEXT_BASELINE_TOKENS = 22_600;
19
- let getContextBySessionId: (id: string) => Record<string, unknown> | null =
20
+ let getContextBySessionId: (id: string, root?: string) => ContextState | null =
20
21
  () => null;
21
- let getContext: (id: string) => Record<string, unknown> | null = () => null;
22
- let loadState: (id: string) => Record<string, unknown> | null = () => null;
23
- let saveState: (id: string, state: unknown) => void = () => {};
22
+ let getContext: (id: string, root?: string) => ContextState | null = () => null;
23
+ let loadState: (id: string, root?: string) => ContextState | null = () => null;
24
+ let saveState: (id: string, state: ContextState) => void = () => {};
24
25
  let findLatestPlan: (contextId: string) => string | null = () => null;
25
26
 
26
27
  try {
@@ -619,7 +620,7 @@ function findActivePlanFile(): string | null {
619
620
  function renderContextManager(
620
621
  mode: string,
621
622
  contextId: string,
622
- contextState: Record<string, unknown> | null,
623
+ contextState: ContextState | null,
623
624
  ): string {
624
625
  // Strip YYMMDD-HHMM- timestamp prefix from context ID for display
625
626
  let displayId = contextId.replace(/^\d{6}-\d{4}-/, "");
@@ -649,7 +650,7 @@ function renderContextManager(
649
650
  if (isPlanning) {
650
651
  const label = mode === "nano" ? "Plan" : "Planning";
651
652
  modeBadge = ` ${SLATE_600}\u2502${RESET} ${CTX_SECONDARY}Mode:${RESET} ${AMBER}${label}${RESET}`;
652
- } else if (stateMode === "has_plan") {
653
+ } else if (stateMode === "has_staged_work") {
653
654
  const label = mode === "nano" ? "Ready" : "Plan Ready";
654
655
  modeBadge = ` ${SLATE_600}\u2502${RESET} ${CTX_SECONDARY}Mode:${RESET} ${EMERALD}${label}${RESET}`;
655
656
  } else if (stateMode === "active") {
@@ -663,7 +664,7 @@ function renderContextManager(
663
664
  planFilePath = activePlanFile;
664
665
  } else if (statePlanPath) {
665
666
  planFilePath = statePlanPath;
666
- } else if (stateMode === "has_plan" || stateMode === "active") {
667
+ } else if (stateMode === "has_staged_work" || stateMode === "active") {
667
668
  try {
668
669
  planFilePath = findLatestPlan(contextId) ?? null;
669
670
  } catch {
@@ -746,7 +747,7 @@ function resolveContextId(sessionId: string): string | null {
746
747
  const context = getContextBySessionId(sessionId);
747
748
  if (context) {
748
749
  if (!cache.sessions) cache.sessions = {};
749
- const ctxId = (context as Record<string, unknown>).id as string;
750
+ const ctxId = context.id;
750
751
  cache.sessions[sessionId] = { context_id: ctxId };
751
752
  saveCache(cache);
752
753
  return ctxId;
@@ -759,9 +760,9 @@ function resolveContextId(sessionId: string): string | null {
759
760
  return null;
760
761
  }
761
762
 
762
- function loadContextState(contextId: string): Record<string, unknown> | null {
763
+ function loadContextState(contextId: string): ContextState | null {
763
764
  try {
764
- return loadState(contextId) as Record<string, unknown> | null;
765
+ return loadState(contextId);
765
766
  } catch {
766
767
  return null;
767
768
  }
@@ -772,12 +773,12 @@ function writeContextWindow(
772
773
  contextWindowData: Record<string, unknown>,
773
774
  ): void {
774
775
  try {
775
- const state = getContext(contextId) as Record<string, unknown> | null;
776
+ const state = getContext(contextId);
776
777
  if (state) {
777
- if (!state.last_session) state.last_session = {};
778
- state.last_session.context_remaining_pct =
778
+ if (!state.last_session) state.last_session = { session_id: undefined, saved_at: undefined, save_reason: undefined, transcript_path: undefined };
779
+ (state.last_session as Record<string, unknown>).context_remaining_pct =
779
780
  contextWindowData.remaining_percentage;
780
- saveState(contextId, state as unknown);
781
+ saveState(contextId, state);
781
782
  }
782
783
  } catch {
783
784
  /* ignore */
@@ -788,11 +789,28 @@ function writeContextWindow(
788
789
  // Main
789
790
  // ---------------------------------------------------------------------------
790
791
 
792
+ /** Shape of the JSON payload piped to status_line.ts via stdin */
793
+ interface StatusLineInput {
794
+ session_id?: string;
795
+ model?: { display_name?: string };
796
+ workspace?: { project_dir?: string };
797
+ context_window?: {
798
+ current_usage?: {
799
+ cache_read_input_tokens?: number;
800
+ input_tokens?: number;
801
+ cache_creation_input_tokens?: number;
802
+ output_tokens?: number;
803
+ };
804
+ context_window_size?: number;
805
+ used_percentage?: number;
806
+ };
807
+ }
808
+
791
809
  function main(): void {
792
810
  // Read JSON from stdin
793
- let inputData: Record<string, unknown>;
811
+ let inputData: StatusLineInput;
794
812
  try {
795
- inputData = JSON.parse(fs.readFileSync(0, "utf-8"));
813
+ inputData = JSON.parse(fs.readFileSync(0, "utf-8")) as StatusLineInput;
796
814
  } catch {
797
815
  inputData = {};
798
816
  }
@@ -804,8 +822,7 @@ function main(): void {
804
822
  // Extract input fields
805
823
  const sessionId = inputData.session_id ?? "";
806
824
  const modelName = inputData.model?.display_name ?? "unknown";
807
- const workspace = inputData.workspace ?? {};
808
- const currentDir: string = workspace.project_dir ?? process.cwd();
825
+ const currentDir: string = inputData.workspace?.project_dir ?? process.cwd();
809
826
  const dirName = path.basename(currentDir);
810
827
 
811
828
  // Context window data
@@ -824,7 +841,7 @@ function main(): void {
824
841
  const contextUsed = totalInput + outputTokens + CONTEXT_BASELINE_TOKENS;
825
842
 
826
843
  if (usedPct !== undefined && usedPct !== null) {
827
- contextPct = Math.floor(usedPct);
844
+ contextPct = Math.floor(usedPct as number);
828
845
  } else {
829
846
  contextPct =
830
847
  contextMax > 0 ? Math.floor((contextUsed * 100) / contextMax) : 0;
@@ -8,16 +8,18 @@ Launch Codex CLI in a tmux pane and inject a prompt into its REPL.
8
8
  prompt-codex/
9
9
  ├── CLAUDE.md ← This file
10
10
  └── scripts/
11
- └── launch-codex.ts ← CLI entry point
11
+ ├── launch-codex.ts ← CLI entry point
12
+ └── watch-codex.ts ← Capture watcher and summarizer
12
13
  ```
13
14
 
14
15
  ## Script: launch-codex.ts
15
16
 
16
17
  **Usage:**
17
18
  ```bash
18
- bun .aiwcli/_shared/skills/prompt-codex/scripts/launch-codex.ts [--model <tier|id>] [--sandbox <sandbox-mode>] plan
19
- bun .aiwcli/_shared/skills/prompt-codex/scripts/launch-codex.ts [--model <tier|id>] [--sandbox <sandbox-mode>] --file <path>
20
- bun .aiwcli/_shared/skills/prompt-codex/scripts/launch-codex.ts [--model <tier|id>] [--sandbox <sandbox-mode>] <inline text...>
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>
21
23
  ```
22
24
 
23
25
  **Args:**
@@ -26,6 +28,12 @@ bun .aiwcli/_shared/skills/prompt-codex/scripts/launch-codex.ts [--model <tier|i
26
28
  - `<text...>` — join remaining args as inline prompt, write to temp file, inject
27
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.
28
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.
29
37
 
30
38
  **Plan discovery order:**
31
39
  1. `CLAUDE_SESSION_ID` env → `getContextBySessionId()` → `findLatestPlan(contextId)`
@@ -44,3 +52,20 @@ bun .aiwcli/_shared/skills/prompt-codex/scripts/launch-codex.ts [--model <tier|i
44
52
  - No exec fallback — REPL mode requires tmux
45
53
  - `_shared` only — never imports from `_cc-native`
46
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