@zhang6hk/dsh-codex 0.0.0-stage → 0.1.0
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/LICENSE +201 -0
- package/NOTICE +18 -0
- package/README.md +75 -2
- package/README.zh.md +78 -0
- package/cordis.patch.yml +7 -0
- package/lib/args.js +118 -0
- package/lib/codex-path.js +31 -0
- package/lib/commands.js +242 -0
- package/lib/companion.js +177 -0
- package/lib/index.js +105 -0
- package/lib/render.js +114 -0
- package/lib/tool.js +103 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +49 -4
- package/vendor/codex/.claude-plugin/plugin.json +8 -0
- package/vendor/codex/prompts/adversarial-review.md +84 -0
- package/vendor/codex/prompts/stop-review-gate.md +36 -0
- package/vendor/codex/schemas/review-output.schema.json +87 -0
- package/vendor/codex/scripts/app-server-broker.mjs +252 -0
- package/vendor/codex/scripts/codex-companion.mjs +1073 -0
- package/vendor/codex/scripts/lib/app-server-protocol.d.ts +75 -0
- package/vendor/codex/scripts/lib/app-server.mjs +354 -0
- package/vendor/codex/scripts/lib/args.mjs +128 -0
- package/vendor/codex/scripts/lib/broker-endpoint.mjs +41 -0
- package/vendor/codex/scripts/lib/broker-lifecycle.mjs +209 -0
- package/vendor/codex/scripts/lib/claude-session-transfer.mjs +44 -0
- package/vendor/codex/scripts/lib/codex.mjs +1219 -0
- package/vendor/codex/scripts/lib/fs.mjs +40 -0
- package/vendor/codex/scripts/lib/git.mjs +347 -0
- package/vendor/codex/scripts/lib/job-control.mjs +308 -0
- package/vendor/codex/scripts/lib/process.mjs +135 -0
- package/vendor/codex/scripts/lib/prompts.mjs +13 -0
- package/vendor/codex/scripts/lib/render.mjs +465 -0
- package/vendor/codex/scripts/lib/state.mjs +191 -0
- package/vendor/codex/scripts/lib/tracked-jobs.mjs +204 -0
- package/vendor/codex/scripts/lib/workspace.mjs +9 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
import { resolveWorkspaceRoot } from "./workspace.mjs";
|
|
7
|
+
|
|
8
|
+
const STATE_VERSION = 1;
|
|
9
|
+
const PLUGIN_DATA_ENV = "CLAUDE_PLUGIN_DATA";
|
|
10
|
+
const FALLBACK_STATE_ROOT_DIR = path.join(os.tmpdir(), "codex-companion");
|
|
11
|
+
const STATE_FILE_NAME = "state.json";
|
|
12
|
+
const JOBS_DIR_NAME = "jobs";
|
|
13
|
+
const MAX_JOBS = 50;
|
|
14
|
+
|
|
15
|
+
function nowIso() {
|
|
16
|
+
return new Date().toISOString();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function defaultState() {
|
|
20
|
+
return {
|
|
21
|
+
version: STATE_VERSION,
|
|
22
|
+
config: {
|
|
23
|
+
stopReviewGate: false
|
|
24
|
+
},
|
|
25
|
+
jobs: []
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function resolveStateDir(cwd) {
|
|
30
|
+
const workspaceRoot = resolveWorkspaceRoot(cwd);
|
|
31
|
+
let canonicalWorkspaceRoot = workspaceRoot;
|
|
32
|
+
try {
|
|
33
|
+
canonicalWorkspaceRoot = fs.realpathSync.native(workspaceRoot);
|
|
34
|
+
} catch {
|
|
35
|
+
canonicalWorkspaceRoot = workspaceRoot;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const slugSource = path.basename(workspaceRoot) || "workspace";
|
|
39
|
+
const slug = slugSource.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "workspace";
|
|
40
|
+
const hash = createHash("sha256").update(canonicalWorkspaceRoot).digest("hex").slice(0, 16);
|
|
41
|
+
const pluginDataDir = process.env[PLUGIN_DATA_ENV];
|
|
42
|
+
const stateRoot = pluginDataDir ? path.join(pluginDataDir, "state") : FALLBACK_STATE_ROOT_DIR;
|
|
43
|
+
return path.join(stateRoot, `${slug}-${hash}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function resolveStateFile(cwd) {
|
|
47
|
+
return path.join(resolveStateDir(cwd), STATE_FILE_NAME);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function resolveJobsDir(cwd) {
|
|
51
|
+
return path.join(resolveStateDir(cwd), JOBS_DIR_NAME);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function ensureStateDir(cwd) {
|
|
55
|
+
fs.mkdirSync(resolveJobsDir(cwd), { recursive: true });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function loadState(cwd) {
|
|
59
|
+
const stateFile = resolveStateFile(cwd);
|
|
60
|
+
if (!fs.existsSync(stateFile)) {
|
|
61
|
+
return defaultState();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const parsed = JSON.parse(fs.readFileSync(stateFile, "utf8"));
|
|
66
|
+
return {
|
|
67
|
+
...defaultState(),
|
|
68
|
+
...parsed,
|
|
69
|
+
config: {
|
|
70
|
+
...defaultState().config,
|
|
71
|
+
...(parsed.config ?? {})
|
|
72
|
+
},
|
|
73
|
+
jobs: Array.isArray(parsed.jobs) ? parsed.jobs : []
|
|
74
|
+
};
|
|
75
|
+
} catch {
|
|
76
|
+
return defaultState();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function pruneJobs(jobs) {
|
|
81
|
+
return [...jobs]
|
|
82
|
+
.sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")))
|
|
83
|
+
.slice(0, MAX_JOBS);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function removeFileIfExists(filePath) {
|
|
87
|
+
if (filePath && fs.existsSync(filePath)) {
|
|
88
|
+
fs.unlinkSync(filePath);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function saveState(cwd, state) {
|
|
93
|
+
const previousJobs = loadState(cwd).jobs;
|
|
94
|
+
ensureStateDir(cwd);
|
|
95
|
+
const nextJobs = pruneJobs(state.jobs ?? []);
|
|
96
|
+
const nextState = {
|
|
97
|
+
version: STATE_VERSION,
|
|
98
|
+
config: {
|
|
99
|
+
...defaultState().config,
|
|
100
|
+
...(state.config ?? {})
|
|
101
|
+
},
|
|
102
|
+
jobs: nextJobs
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const retainedIds = new Set(nextJobs.map((job) => job.id));
|
|
106
|
+
for (const job of previousJobs) {
|
|
107
|
+
if (retainedIds.has(job.id)) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
removeJobFile(resolveJobFile(cwd, job.id));
|
|
111
|
+
removeFileIfExists(job.logFile);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
fs.writeFileSync(resolveStateFile(cwd), `${JSON.stringify(nextState, null, 2)}\n`, "utf8");
|
|
115
|
+
return nextState;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function updateState(cwd, mutate) {
|
|
119
|
+
const state = loadState(cwd);
|
|
120
|
+
mutate(state);
|
|
121
|
+
return saveState(cwd, state);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function generateJobId(prefix = "job") {
|
|
125
|
+
const random = Math.random().toString(36).slice(2, 8);
|
|
126
|
+
return `${prefix}-${Date.now().toString(36)}-${random}`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function upsertJob(cwd, jobPatch) {
|
|
130
|
+
return updateState(cwd, (state) => {
|
|
131
|
+
const timestamp = nowIso();
|
|
132
|
+
const existingIndex = state.jobs.findIndex((job) => job.id === jobPatch.id);
|
|
133
|
+
if (existingIndex === -1) {
|
|
134
|
+
state.jobs.unshift({
|
|
135
|
+
createdAt: timestamp,
|
|
136
|
+
updatedAt: timestamp,
|
|
137
|
+
...jobPatch
|
|
138
|
+
});
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
state.jobs[existingIndex] = {
|
|
142
|
+
...state.jobs[existingIndex],
|
|
143
|
+
...jobPatch,
|
|
144
|
+
updatedAt: timestamp
|
|
145
|
+
};
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function listJobs(cwd) {
|
|
150
|
+
return loadState(cwd).jobs;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function setConfig(cwd, key, value) {
|
|
154
|
+
return updateState(cwd, (state) => {
|
|
155
|
+
state.config = {
|
|
156
|
+
...state.config,
|
|
157
|
+
[key]: value
|
|
158
|
+
};
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function getConfig(cwd) {
|
|
163
|
+
return loadState(cwd).config;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function writeJobFile(cwd, jobId, payload) {
|
|
167
|
+
ensureStateDir(cwd);
|
|
168
|
+
const jobFile = resolveJobFile(cwd, jobId);
|
|
169
|
+
fs.writeFileSync(jobFile, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
170
|
+
return jobFile;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function readJobFile(jobFile) {
|
|
174
|
+
return JSON.parse(fs.readFileSync(jobFile, "utf8"));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function removeJobFile(jobFile) {
|
|
178
|
+
if (fs.existsSync(jobFile)) {
|
|
179
|
+
fs.unlinkSync(jobFile);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function resolveJobLogFile(cwd, jobId) {
|
|
184
|
+
ensureStateDir(cwd);
|
|
185
|
+
return path.join(resolveJobsDir(cwd), `${jobId}.log`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function resolveJobFile(cwd, jobId) {
|
|
189
|
+
ensureStateDir(cwd);
|
|
190
|
+
return path.join(resolveJobsDir(cwd), `${jobId}.json`);
|
|
191
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
|
|
4
|
+
import { readJobFile, resolveJobFile, resolveJobLogFile, upsertJob, writeJobFile } from "./state.mjs";
|
|
5
|
+
|
|
6
|
+
export const SESSION_ID_ENV = "CODEX_COMPANION_SESSION_ID";
|
|
7
|
+
|
|
8
|
+
export function nowIso() {
|
|
9
|
+
return new Date().toISOString();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function normalizeProgressEvent(value) {
|
|
13
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
14
|
+
return {
|
|
15
|
+
message: String(value.message ?? "").trim(),
|
|
16
|
+
phase: typeof value.phase === "string" && value.phase.trim() ? value.phase.trim() : null,
|
|
17
|
+
threadId: typeof value.threadId === "string" && value.threadId.trim() ? value.threadId.trim() : null,
|
|
18
|
+
turnId: typeof value.turnId === "string" && value.turnId.trim() ? value.turnId.trim() : null,
|
|
19
|
+
stderrMessage: value.stderrMessage == null ? null : String(value.stderrMessage).trim(),
|
|
20
|
+
logTitle: typeof value.logTitle === "string" && value.logTitle.trim() ? value.logTitle.trim() : null,
|
|
21
|
+
logBody: value.logBody == null ? null : String(value.logBody).trimEnd()
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
message: String(value ?? "").trim(),
|
|
27
|
+
phase: null,
|
|
28
|
+
threadId: null,
|
|
29
|
+
turnId: null,
|
|
30
|
+
stderrMessage: String(value ?? "").trim(),
|
|
31
|
+
logTitle: null,
|
|
32
|
+
logBody: null
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function appendLogLine(logFile, message) {
|
|
37
|
+
const normalized = String(message ?? "").trim();
|
|
38
|
+
if (!logFile || !normalized) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
fs.appendFileSync(logFile, `[${nowIso()}] ${normalized}\n`, "utf8");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function appendLogBlock(logFile, title, body) {
|
|
45
|
+
if (!logFile || !body) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
fs.appendFileSync(logFile, `\n[${nowIso()}] ${title}\n${String(body).trimEnd()}\n`, "utf8");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function createJobLogFile(workspaceRoot, jobId, title) {
|
|
52
|
+
const logFile = resolveJobLogFile(workspaceRoot, jobId);
|
|
53
|
+
fs.writeFileSync(logFile, "", "utf8");
|
|
54
|
+
if (title) {
|
|
55
|
+
appendLogLine(logFile, `Starting ${title}.`);
|
|
56
|
+
}
|
|
57
|
+
return logFile;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function createJobRecord(base, options = {}) {
|
|
61
|
+
const env = options.env ?? process.env;
|
|
62
|
+
const sessionId = env[options.sessionIdEnv ?? SESSION_ID_ENV];
|
|
63
|
+
return {
|
|
64
|
+
...base,
|
|
65
|
+
createdAt: nowIso(),
|
|
66
|
+
...(sessionId ? { sessionId } : {})
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function createJobProgressUpdater(workspaceRoot, jobId) {
|
|
71
|
+
let lastPhase = null;
|
|
72
|
+
let lastThreadId = null;
|
|
73
|
+
let lastTurnId = null;
|
|
74
|
+
|
|
75
|
+
return (event) => {
|
|
76
|
+
const normalized = normalizeProgressEvent(event);
|
|
77
|
+
const patch = { id: jobId };
|
|
78
|
+
let changed = false;
|
|
79
|
+
|
|
80
|
+
if (normalized.phase && normalized.phase !== lastPhase) {
|
|
81
|
+
lastPhase = normalized.phase;
|
|
82
|
+
patch.phase = normalized.phase;
|
|
83
|
+
changed = true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (normalized.threadId && normalized.threadId !== lastThreadId) {
|
|
87
|
+
lastThreadId = normalized.threadId;
|
|
88
|
+
patch.threadId = normalized.threadId;
|
|
89
|
+
changed = true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (normalized.turnId && normalized.turnId !== lastTurnId) {
|
|
93
|
+
lastTurnId = normalized.turnId;
|
|
94
|
+
patch.turnId = normalized.turnId;
|
|
95
|
+
changed = true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!changed) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
upsertJob(workspaceRoot, patch);
|
|
103
|
+
|
|
104
|
+
const jobFile = resolveJobFile(workspaceRoot, jobId);
|
|
105
|
+
if (!fs.existsSync(jobFile)) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const storedJob = readJobFile(jobFile);
|
|
110
|
+
writeJobFile(workspaceRoot, jobId, {
|
|
111
|
+
...storedJob,
|
|
112
|
+
...patch
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function createProgressReporter({ stderr = false, logFile = null, onEvent = null } = {}) {
|
|
118
|
+
if (!stderr && !logFile && !onEvent) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return (eventOrMessage) => {
|
|
123
|
+
const event = normalizeProgressEvent(eventOrMessage);
|
|
124
|
+
const stderrMessage = event.stderrMessage ?? event.message;
|
|
125
|
+
if (stderr && stderrMessage) {
|
|
126
|
+
process.stderr.write(`[codex] ${stderrMessage}\n`);
|
|
127
|
+
}
|
|
128
|
+
appendLogLine(logFile, event.message);
|
|
129
|
+
appendLogBlock(logFile, event.logTitle, event.logBody);
|
|
130
|
+
onEvent?.(event);
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function readStoredJobOrNull(workspaceRoot, jobId) {
|
|
135
|
+
const jobFile = resolveJobFile(workspaceRoot, jobId);
|
|
136
|
+
if (!fs.existsSync(jobFile)) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
return readJobFile(jobFile);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export async function runTrackedJob(job, runner, options = {}) {
|
|
143
|
+
const runningRecord = {
|
|
144
|
+
...job,
|
|
145
|
+
status: "running",
|
|
146
|
+
startedAt: nowIso(),
|
|
147
|
+
phase: "starting",
|
|
148
|
+
pid: process.pid,
|
|
149
|
+
logFile: options.logFile ?? job.logFile ?? null
|
|
150
|
+
};
|
|
151
|
+
writeJobFile(job.workspaceRoot, job.id, runningRecord);
|
|
152
|
+
upsertJob(job.workspaceRoot, runningRecord);
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
const execution = await runner();
|
|
156
|
+
const completionStatus = execution.exitStatus === 0 ? "completed" : "failed";
|
|
157
|
+
const completedAt = nowIso();
|
|
158
|
+
writeJobFile(job.workspaceRoot, job.id, {
|
|
159
|
+
...runningRecord,
|
|
160
|
+
status: completionStatus,
|
|
161
|
+
threadId: execution.threadId ?? null,
|
|
162
|
+
turnId: execution.turnId ?? null,
|
|
163
|
+
pid: null,
|
|
164
|
+
phase: completionStatus === "completed" ? "done" : "failed",
|
|
165
|
+
completedAt,
|
|
166
|
+
result: execution.payload,
|
|
167
|
+
rendered: execution.rendered
|
|
168
|
+
});
|
|
169
|
+
upsertJob(job.workspaceRoot, {
|
|
170
|
+
id: job.id,
|
|
171
|
+
status: completionStatus,
|
|
172
|
+
threadId: execution.threadId ?? null,
|
|
173
|
+
turnId: execution.turnId ?? null,
|
|
174
|
+
summary: execution.summary,
|
|
175
|
+
phase: completionStatus === "completed" ? "done" : "failed",
|
|
176
|
+
pid: null,
|
|
177
|
+
completedAt
|
|
178
|
+
});
|
|
179
|
+
appendLogBlock(options.logFile ?? job.logFile ?? null, "Final output", execution.rendered);
|
|
180
|
+
return execution;
|
|
181
|
+
} catch (error) {
|
|
182
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
183
|
+
const existing = readStoredJobOrNull(job.workspaceRoot, job.id) ?? runningRecord;
|
|
184
|
+
const completedAt = nowIso();
|
|
185
|
+
writeJobFile(job.workspaceRoot, job.id, {
|
|
186
|
+
...existing,
|
|
187
|
+
status: "failed",
|
|
188
|
+
phase: "failed",
|
|
189
|
+
errorMessage,
|
|
190
|
+
pid: null,
|
|
191
|
+
completedAt,
|
|
192
|
+
logFile: options.logFile ?? job.logFile ?? existing.logFile ?? null
|
|
193
|
+
});
|
|
194
|
+
upsertJob(job.workspaceRoot, {
|
|
195
|
+
id: job.id,
|
|
196
|
+
status: "failed",
|
|
197
|
+
phase: "failed",
|
|
198
|
+
pid: null,
|
|
199
|
+
errorMessage,
|
|
200
|
+
completedAt
|
|
201
|
+
});
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
}
|