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.
- package/dist/templates/_shared/.claude/skills/codex/prompt.md +25 -5
- package/dist/templates/_shared/lib-ts/agent-exec/index.ts +2 -0
- package/dist/templates/_shared/lib-ts/agent-exec/structured-output.ts +166 -0
- package/dist/templates/_shared/lib-ts/base/cli-args.ts +4 -0
- package/dist/templates/_shared/lib-ts/base/state-io.ts +1 -1
- package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +4 -3
- package/dist/templates/_shared/lib-ts/context/context-store.ts +3 -0
- package/dist/templates/_shared/scripts/status_line.ts +36 -19
- package/dist/templates/_shared/skills/prompt-codex/CLAUDE.md +29 -4
- package/dist/templates/_shared/skills/prompt-codex/scripts/launch-codex.ts +140 -7
- package/dist/templates/_shared/skills/prompt-codex/scripts/watch-codex.ts +257 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +9 -133
- package/dist/templates/cc-native/_cc-native/lib-ts/settings.ts +118 -42
- package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +1 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +61 -0
- package/dist/templates/cc-native/_cc-native/plan-review/lib/agent-selection.ts +5 -4
- package/dist/templates/cc-native/_cc-native/plan-review/lib/orchestrator.ts +4 -4
- package/dist/templates/cc-native/_cc-native/plan-review/lib/review-pipeline.ts +16 -13
- package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/providers/orchestrator-claude-agent.ts +54 -23
- package/oclif.manifest.json +1 -1
- 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|
|
|
6
|
-
| `bun launch-codex.ts [--model fast|standard|smart|<model-id>] [--sandbox read-only|
|
|
7
|
-
| `bun launch-codex.ts [--model fast|standard|smart|<model-id>] [--sandbox read-only|
|
|
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
|
|
10
|
-
- `--sandbox`: `read-only` or `
|
|
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: "
|
|
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
|
-
|
|
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) =>
|
|
20
|
+
let getContextBySessionId: (id: string, root?: string) => ContextState | null =
|
|
20
21
|
() => null;
|
|
21
|
-
let getContext: (id: string) =>
|
|
22
|
-
let loadState: (id: string) =>
|
|
23
|
-
let saveState: (id: string, state:
|
|
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:
|
|
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 === "
|
|
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 === "
|
|
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 =
|
|
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):
|
|
763
|
+
function loadContextState(contextId: string): ContextState | null {
|
|
763
764
|
try {
|
|
764
|
-
return loadState(contextId)
|
|
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)
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|