oh-my-codex 0.14.0 → 0.14.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/Cargo.lock +5 -5
- package/Cargo.toml +1 -1
- package/README.md +14 -8
- package/crates/omx-explore/src/main.rs +94 -1
- package/crates/omx-sparkshell/src/codex_bridge.rs +59 -12
- package/crates/omx-sparkshell/tests/execution.rs +48 -0
- package/dist/cli/__tests__/explore.test.js +33 -1
- package/dist/cli/__tests__/explore.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +11 -2
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/package-bin-contract.test.js +5 -0
- package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
- package/dist/cli/__tests__/question.test.js +139 -25
- package/dist/cli/__tests__/question.test.js.map +1 -1
- package/dist/cli/__tests__/session-scoped-runtime.test.js +30 -0
- package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
- package/dist/cli/__tests__/setup-agents-overwrite.test.js +32 -7
- package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
- package/dist/cli/__tests__/setup-refresh.test.js +8 -6
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/sparkshell-cli.test.js +23 -0
- package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
- package/dist/cli/__tests__/uninstall.test.js +65 -5
- package/dist/cli/__tests__/uninstall.test.js.map +1 -1
- package/dist/cli/__tests__/update.test.js +360 -26
- package/dist/cli/__tests__/update.test.js.map +1 -1
- package/dist/cli/explore.d.ts.map +1 -1
- package/dist/cli/explore.js +18 -3
- package/dist/cli/explore.js.map +1 -1
- package/dist/cli/index.d.ts +2 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +7 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +25 -3
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/sparkshell.d.ts.map +1 -1
- package/dist/cli/sparkshell.js +11 -1
- package/dist/cli/sparkshell.js.map +1 -1
- package/dist/cli/team.d.ts.map +1 -1
- package/dist/cli/team.js +159 -394
- package/dist/cli/team.js.map +1 -1
- package/dist/cli/uninstall.d.ts.map +1 -1
- package/dist/cli/uninstall.js +3 -1
- package/dist/cli/uninstall.js.map +1 -1
- package/dist/cli/update.d.ts +37 -9
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +204 -26
- package/dist/cli/update.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +51 -14
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/__tests__/generator-notify.test.js +35 -10
- package/dist/config/__tests__/generator-notify.test.js.map +1 -1
- package/dist/config/generator.d.ts +1 -0
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +61 -7
- package/dist/config/generator.js.map +1 -1
- package/dist/hooks/__tests__/code-review-skill-contract.test.d.ts +2 -0
- package/dist/hooks/__tests__/code-review-skill-contract.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/code-review-skill-contract.test.js +56 -0
- package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -0
- package/dist/hooks/__tests__/deep-interview-contract.test.js +31 -0
- package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/explicit-terminal-stop-docs-contract.test.d.ts +2 -0
- package/dist/hooks/__tests__/explicit-terminal-stop-docs-contract.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/explicit-terminal-stop-docs-contract.test.js +43 -0
- package/dist/hooks/__tests__/explicit-terminal-stop-docs-contract.test.js.map +1 -0
- package/dist/hooks/__tests__/explicit-terminal-stop-model-docs-contract.test.d.ts +2 -0
- package/dist/hooks/__tests__/explicit-terminal-stop-model-docs-contract.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/explicit-terminal-stop-model-docs-contract.test.js +38 -0
- package/dist/hooks/__tests__/explicit-terminal-stop-model-docs-contract.test.js.map +1 -0
- package/dist/hooks/__tests__/keyword-detector.test.js +108 -0
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-test-helpers.d.ts.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-test-helpers.js +16 -1
- package/dist/hooks/__tests__/prompt-guidance-test-helpers.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +34 -8
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/mcp/__tests__/bootstrap.test.js +7 -25
- package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
- package/dist/mcp/__tests__/server-lifecycle.test.js +60 -0
- package/dist/mcp/__tests__/server-lifecycle.test.js.map +1 -1
- package/dist/mcp/__tests__/state-server.test.js +177 -0
- package/dist/mcp/__tests__/state-server.test.js.map +1 -1
- package/dist/mcp/bootstrap.d.ts.map +1 -1
- package/dist/mcp/bootstrap.js +36 -18
- package/dist/mcp/bootstrap.js.map +1 -1
- package/dist/mcp/state-server.d.ts +17 -0
- package/dist/mcp/state-server.d.ts.map +1 -1
- package/dist/mcp/state-server.js +55 -1
- package/dist/mcp/state-server.js.map +1 -1
- package/dist/notifications/__tests__/index.test.js +0 -3
- package/dist/notifications/__tests__/index.test.js.map +1 -1
- package/dist/notifications/__tests__/session-status.test.js +90 -0
- package/dist/notifications/__tests__/session-status.test.js.map +1 -1
- package/dist/notifications/session-status.d.ts +2 -0
- package/dist/notifications/session-status.d.ts.map +1 -1
- package/dist/notifications/session-status.js +19 -4
- package/dist/notifications/session-status.js.map +1 -1
- package/dist/question/__tests__/deep-interview.test.js +44 -0
- package/dist/question/__tests__/deep-interview.test.js.map +1 -1
- package/dist/question/__tests__/renderer.test.js +192 -12
- package/dist/question/__tests__/renderer.test.js.map +1 -1
- package/dist/question/__tests__/state.test.js +21 -1
- package/dist/question/__tests__/state.test.js.map +1 -1
- package/dist/question/deep-interview.d.ts +3 -0
- package/dist/question/deep-interview.d.ts.map +1 -1
- package/dist/question/deep-interview.js +18 -1
- package/dist/question/deep-interview.js.map +1 -1
- package/dist/question/renderer.d.ts +4 -2
- package/dist/question/renderer.d.ts.map +1 -1
- package/dist/question/renderer.js +87 -18
- package/dist/question/renderer.js.map +1 -1
- package/dist/runtime/__tests__/run-outcome.test.js +38 -0
- package/dist/runtime/__tests__/run-outcome.test.js.map +1 -1
- package/dist/runtime/__tests__/run-state.test.d.ts +2 -0
- package/dist/runtime/__tests__/run-state.test.d.ts.map +1 -0
- package/dist/runtime/__tests__/run-state.test.js +37 -0
- package/dist/runtime/__tests__/run-state.test.js.map +1 -0
- package/dist/runtime/run-loop.d.ts +5 -1
- package/dist/runtime/run-loop.d.ts.map +1 -1
- package/dist/runtime/run-loop.js +8 -3
- package/dist/runtime/run-loop.js.map +1 -1
- package/dist/runtime/run-outcome.d.ts +18 -0
- package/dist/runtime/run-outcome.d.ts.map +1 -1
- package/dist/runtime/run-outcome.js +156 -7
- package/dist/runtime/run-outcome.js.map +1 -1
- package/dist/runtime/run-state.d.ts +5 -1
- package/dist/runtime/run-state.d.ts.map +1 -1
- package/dist/runtime/run-state.js +13 -3
- package/dist/runtime/run-state.js.map +1 -1
- package/dist/runtime/terminal-lifecycle.d.ts +11 -0
- package/dist/runtime/terminal-lifecycle.d.ts.map +1 -0
- package/dist/runtime/terminal-lifecycle.js +52 -0
- package/dist/runtime/terminal-lifecycle.js.map +1 -0
- package/dist/scripts/__tests__/codex-native-hook.test.js +370 -56
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/postinstall.test.d.ts +2 -0
- package/dist/scripts/__tests__/postinstall.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/postinstall.test.js +178 -0
- package/dist/scripts/__tests__/postinstall.test.js.map +1 -0
- package/dist/scripts/codex-native-hook.d.ts +1 -0
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +115 -56
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/postinstall.d.ts +22 -0
- package/dist/scripts/postinstall.d.ts.map +1 -0
- package/dist/scripts/postinstall.js +105 -0
- package/dist/scripts/postinstall.js.map +1 -0
- package/dist/state/__tests__/operations.test.js +60 -0
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +18 -1
- package/dist/state/operations.js.map +1 -1
- package/dist/team/__tests__/role-router.test.js +6 -0
- package/dist/team/__tests__/role-router.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +108 -2
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +18 -4
- package/dist/team/runtime.js.map +1 -1
- package/dist/utils/__tests__/dep-versions.test.js +25 -8
- package/dist/utils/__tests__/dep-versions.test.js.map +1 -1
- package/dist/utils/__tests__/paths.test.js +45 -0
- package/dist/utils/__tests__/paths.test.js.map +1 -1
- package/dist/utils/paths.d.ts +2 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +22 -7
- package/dist/utils/paths.js.map +1 -1
- package/dist/verification/__tests__/ci-rust-gates.test.js +1 -1
- package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
- package/package.json +3 -2
- package/prompts/architect.md +4 -0
- package/prompts/code-reviewer.md +3 -0
- package/skills/code-review/SKILL.md +94 -28
- package/skills/deep-interview/SKILL.md +91 -0
- package/src/scripts/__tests__/codex-native-hook.test.ts +468 -64
- package/src/scripts/__tests__/postinstall.test.ts +210 -0
- package/src/scripts/codex-native-hook.ts +136 -53
- package/src/scripts/postinstall-bootstrap.js +23 -0
- package/src/scripts/postinstall.ts +161 -0
- package/templates/AGENTS.md +1 -1
- package/templates/model-instructions/explore-lightweight-AGENTS.md +11 -0
- package/templates/model-instructions/sparkshell-lightweight-AGENTS.md +10 -0
|
@@ -4,6 +4,7 @@ import { existsSync } from "node:fs";
|
|
|
4
4
|
import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
7
8
|
import { afterEach, beforeEach, describe, it } from "node:test";
|
|
8
9
|
import { buildManagedCodexHooksConfig } from "../../config/codex-hooks.js";
|
|
9
10
|
import {
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
} from "../../team/state.js";
|
|
15
16
|
import {
|
|
16
17
|
dispatchCodexNativeHook,
|
|
18
|
+
isCodexNativeHookMainModule,
|
|
17
19
|
mapCodexHookEventToOmxEvent,
|
|
18
20
|
resolveSessionOwnerPidFromAncestry,
|
|
19
21
|
} from "../codex-native-hook.js";
|
|
@@ -25,6 +27,30 @@ async function writeJson(path: string, value: unknown): Promise<void> {
|
|
|
25
27
|
await writeFile(path, JSON.stringify(value, null, 2));
|
|
26
28
|
}
|
|
27
29
|
|
|
30
|
+
async function writeHookCounterPlugin(cwd: string): Promise<string> {
|
|
31
|
+
const markerPath = join(cwd, ".omx", "stop-hook-counter.json");
|
|
32
|
+
await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
|
|
33
|
+
await writeFile(
|
|
34
|
+
join(cwd, ".omx", "hooks", "count-stop-hook.mjs"),
|
|
35
|
+
`import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
36
|
+
import { dirname, join } from "node:path";
|
|
37
|
+
|
|
38
|
+
export async function onHookEvent(event) {
|
|
39
|
+
if (event.event !== "stop") return;
|
|
40
|
+
const outPath = join(process.cwd(), ".omx", "stop-hook-counter.json");
|
|
41
|
+
await mkdir(dirname(outPath), { recursive: true });
|
|
42
|
+
let count = 0;
|
|
43
|
+
try {
|
|
44
|
+
count = JSON.parse(await readFile(outPath, "utf-8")).count || 0;
|
|
45
|
+
} catch {}
|
|
46
|
+
await writeFile(outPath, JSON.stringify({ count: count + 1 }, null, 2));
|
|
47
|
+
}
|
|
48
|
+
`,
|
|
49
|
+
"utf-8",
|
|
50
|
+
);
|
|
51
|
+
return markerPath;
|
|
52
|
+
}
|
|
53
|
+
|
|
28
54
|
async function writeReleaseReadinessLeaderAttention(
|
|
29
55
|
teamName: string,
|
|
30
56
|
sessionId: string,
|
|
@@ -143,6 +169,25 @@ describe("codex native hook config", () => {
|
|
|
143
169
|
});
|
|
144
170
|
|
|
145
171
|
describe("codex native hook dispatch", () => {
|
|
172
|
+
it("treats space-containing argv entry paths as the main module", () => {
|
|
173
|
+
const entryPath = "/tmp/omx native/codex-native-hook.js";
|
|
174
|
+
|
|
175
|
+
assert.equal(
|
|
176
|
+
isCodexNativeHookMainModule(pathToFileURL(entryPath).href, entryPath),
|
|
177
|
+
true,
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("does not treat a different module url as the main module", () => {
|
|
182
|
+
assert.equal(
|
|
183
|
+
isCodexNativeHookMainModule(
|
|
184
|
+
pathToFileURL("/tmp/omx native/other-script.js").href,
|
|
185
|
+
"/tmp/omx native/codex-native-hook.js",
|
|
186
|
+
),
|
|
187
|
+
false,
|
|
188
|
+
);
|
|
189
|
+
});
|
|
190
|
+
|
|
146
191
|
it("emits deterministic JSON stdout when CLI stdin is malformed", () => {
|
|
147
192
|
const stdout = execFileSync(
|
|
148
193
|
process.execPath,
|
|
@@ -302,7 +347,7 @@ describe("codex native hook dispatch", () => {
|
|
|
302
347
|
}
|
|
303
348
|
});
|
|
304
349
|
|
|
305
|
-
it("
|
|
350
|
+
it("adds .omx/ to git info/exclude during SessionStart instead of mutating repo .gitignore", async () => {
|
|
306
351
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-gitignore-"));
|
|
307
352
|
try {
|
|
308
353
|
await writeFile(join(cwd, ".gitignore"), "node_modules/\n");
|
|
@@ -319,11 +364,68 @@ describe("codex native hook dispatch", () => {
|
|
|
319
364
|
|
|
320
365
|
assert.equal(result.omxEventName, "session-start");
|
|
321
366
|
const gitignore = await readFile(join(cwd, ".gitignore"), "utf-8");
|
|
322
|
-
assert.
|
|
367
|
+
assert.equal(gitignore, "node_modules/\n");
|
|
368
|
+
const exclude = await readFile(join(cwd, ".git", "info", "exclude"), "utf-8");
|
|
369
|
+
assert.match(exclude, /(?:^|\n)\.omx\/\n/);
|
|
323
370
|
assert.match(
|
|
324
371
|
JSON.stringify(result.outputJson),
|
|
325
|
-
/Added \.omx\/ to .*\.
|
|
372
|
+
/Added \.omx\/ to .*\.git[\/]info[\/]exclude/,
|
|
373
|
+
);
|
|
374
|
+
} finally {
|
|
375
|
+
await rm(cwd, { recursive: true, force: true });
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it("keeps SessionStart quiet when .omx/ is already ignored by repo-level gitignore", async () => {
|
|
380
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-existing-ignore-"));
|
|
381
|
+
try {
|
|
382
|
+
await writeFile(join(cwd, ".gitignore"), "node_modules/\n.omx/\n");
|
|
383
|
+
execFileSync("git", ["init"], { cwd, stdio: "pipe" });
|
|
384
|
+
|
|
385
|
+
const result = await dispatchCodexNativeHook(
|
|
386
|
+
{
|
|
387
|
+
hook_event_name: "SessionStart",
|
|
388
|
+
cwd,
|
|
389
|
+
session_id: "sess-gitignore-existing",
|
|
390
|
+
},
|
|
391
|
+
{ cwd, sessionOwnerPid: 43210 },
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
assert.equal(result.omxEventName, "session-start");
|
|
395
|
+
const gitignore = await readFile(join(cwd, ".gitignore"), "utf-8");
|
|
396
|
+
assert.equal(gitignore, "node_modules/\n.omx/\n");
|
|
397
|
+
const exclude = await readFile(join(cwd, ".git", "info", "exclude"), "utf-8");
|
|
398
|
+
assert.doesNotMatch(exclude, /(?:^|\n)\.omx\/\n/);
|
|
399
|
+
assert.doesNotMatch(JSON.stringify(result.outputJson), /Added \.omx\//);
|
|
400
|
+
} finally {
|
|
401
|
+
await rm(cwd, { recursive: true, force: true });
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it("respects existing Git ignore resolution before writing local excludes", async () => {
|
|
406
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-global-ignore-"));
|
|
407
|
+
const excludesFile = join(cwd, "global-ignore");
|
|
408
|
+
try {
|
|
409
|
+
await writeFile(join(cwd, ".gitignore"), "node_modules/\n");
|
|
410
|
+
await writeFile(excludesFile, ".omx/\n");
|
|
411
|
+
execFileSync("git", ["init"], { cwd, stdio: "pipe" });
|
|
412
|
+
execFileSync("git", ["config", "core.excludesfile", excludesFile], { cwd, stdio: "pipe" });
|
|
413
|
+
|
|
414
|
+
const result = await dispatchCodexNativeHook(
|
|
415
|
+
{
|
|
416
|
+
hook_event_name: "SessionStart",
|
|
417
|
+
cwd,
|
|
418
|
+
session_id: "sess-gitignore-global",
|
|
419
|
+
},
|
|
420
|
+
{ cwd, sessionOwnerPid: 43210 },
|
|
326
421
|
);
|
|
422
|
+
|
|
423
|
+
assert.equal(result.omxEventName, "session-start");
|
|
424
|
+
const gitignore = await readFile(join(cwd, ".gitignore"), "utf-8");
|
|
425
|
+
assert.equal(gitignore, "node_modules/\n");
|
|
426
|
+
const exclude = await readFile(join(cwd, ".git", "info", "exclude"), "utf-8");
|
|
427
|
+
assert.doesNotMatch(exclude, /(?:^|\n)\.omx\/\n/);
|
|
428
|
+
assert.doesNotMatch(JSON.stringify(result.outputJson), /Added \.omx\//);
|
|
327
429
|
} finally {
|
|
328
430
|
await rm(cwd, { recursive: true, force: true });
|
|
329
431
|
}
|
|
@@ -504,6 +606,35 @@ describe("codex native hook dispatch", () => {
|
|
|
504
606
|
}
|
|
505
607
|
});
|
|
506
608
|
|
|
609
|
+
it("normalizes the Korean keyboard typo for ulw during UserPromptSubmit activation", async () => {
|
|
610
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ulw-ko-"));
|
|
611
|
+
try {
|
|
612
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
613
|
+
const result = await dispatchCodexNativeHook(
|
|
614
|
+
{
|
|
615
|
+
hook_event_name: "UserPromptSubmit",
|
|
616
|
+
cwd,
|
|
617
|
+
session_id: "sess-ulw-ko",
|
|
618
|
+
thread_id: "thread-ulw-ko",
|
|
619
|
+
turn_id: "turn-ulw-ko",
|
|
620
|
+
prompt: "ㅕㅣㅈ로 병렬 처리해줘",
|
|
621
|
+
},
|
|
622
|
+
{ cwd },
|
|
623
|
+
);
|
|
624
|
+
|
|
625
|
+
assert.equal(result.omxEventName, "keyword-detector");
|
|
626
|
+
assert.equal(result.skillState?.skill, "ultrawork");
|
|
627
|
+
assert.equal(result.skillState?.keyword, "ulw");
|
|
628
|
+
const additionalContext = String(
|
|
629
|
+
(result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
|
|
630
|
+
);
|
|
631
|
+
assert.match(additionalContext, /workflow keyword \"ulw\" -> ultrawork/);
|
|
632
|
+
assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-ulw-ko", "ultrawork-state.json")), true);
|
|
633
|
+
} finally {
|
|
634
|
+
await rm(cwd, { recursive: true, force: true });
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
|
|
507
638
|
it("does not activate Ralph workflow state from a plain conversational mention", async () => {
|
|
508
639
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralph-plain-text-"));
|
|
509
640
|
try {
|
|
@@ -707,6 +838,9 @@ describe("codex native hook dispatch", () => {
|
|
|
707
838
|
assert.match(message, /skill: deep-interview activated and initial state initialized at \.omx\/state\/sessions\/sess-deep-interview-msg\/deep-interview-state\.json; write subsequent updates via omx_state MCP\./);
|
|
708
839
|
assert.match(message, /Deep-interview must ask each interview round via `omx question`/);
|
|
709
840
|
assert.match(message, /do not fall back to `request_user_input` or plain-text questioning/i);
|
|
841
|
+
assert.match(message, /After starting `omx question` in a background terminal, wait for that terminal to finish and read the JSON answer before continuing the interview\./);
|
|
842
|
+
assert.match(message, /If bare `omx question` is unavailable in this reused session, use the current-session CLI bridge command:/);
|
|
843
|
+
assert.match(message, /`'.+' '.+dist\/cli\/omx\.js' question`/);
|
|
710
844
|
assert.match(message, /Stop remains blocked while a deep-interview question obligation is pending\./);
|
|
711
845
|
} finally {
|
|
712
846
|
await rm(cwd, { recursive: true, force: true });
|
|
@@ -3160,6 +3294,62 @@ esac
|
|
|
3160
3294
|
}
|
|
3161
3295
|
});
|
|
3162
3296
|
|
|
3297
|
+
it("blocks Stop when a same-session deep-interview question obligation is pending even after the mode marked itself inactive", async () => {
|
|
3298
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-deep-interview-question-inactive-"));
|
|
3299
|
+
try {
|
|
3300
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
3301
|
+
await mkdir(join(stateDir, "sessions", "sess-stop-deep-interview-question-inactive"), { recursive: true });
|
|
3302
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-deep-interview-question-inactive" });
|
|
3303
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-deep-interview-question-inactive", "skill-active-state.json"), {
|
|
3304
|
+
version: 1,
|
|
3305
|
+
active: true,
|
|
3306
|
+
skill: "deep-interview",
|
|
3307
|
+
phase: "planning",
|
|
3308
|
+
session_id: "sess-stop-deep-interview-question-inactive",
|
|
3309
|
+
thread_id: "thread-stop-deep-interview-question-inactive",
|
|
3310
|
+
});
|
|
3311
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-deep-interview-question-inactive", "deep-interview-state.json"), {
|
|
3312
|
+
active: false,
|
|
3313
|
+
mode: "deep-interview",
|
|
3314
|
+
current_phase: "intent-first",
|
|
3315
|
+
lifecycle_outcome: "askuserQuestion",
|
|
3316
|
+
run_outcome: "blocked_on_user",
|
|
3317
|
+
completed_at: "2026-04-19T03:20:30.000Z",
|
|
3318
|
+
session_id: "sess-stop-deep-interview-question-inactive",
|
|
3319
|
+
thread_id: "thread-stop-deep-interview-question-inactive",
|
|
3320
|
+
question_enforcement: {
|
|
3321
|
+
obligation_id: "obligation-inactive",
|
|
3322
|
+
source: "omx-question",
|
|
3323
|
+
status: "pending",
|
|
3324
|
+
lifecycle_outcome: "askuserQuestion",
|
|
3325
|
+
requested_at: "2026-04-19T03:20:00.000Z",
|
|
3326
|
+
},
|
|
3327
|
+
});
|
|
3328
|
+
|
|
3329
|
+
const result = await dispatchCodexNativeHook(
|
|
3330
|
+
{
|
|
3331
|
+
hook_event_name: "Stop",
|
|
3332
|
+
cwd,
|
|
3333
|
+
session_id: "sess-stop-deep-interview-question-inactive",
|
|
3334
|
+
thread_id: "thread-stop-deep-interview-question-inactive",
|
|
3335
|
+
},
|
|
3336
|
+
{ cwd },
|
|
3337
|
+
);
|
|
3338
|
+
|
|
3339
|
+
assert.equal(result.omxEventName, "stop");
|
|
3340
|
+
assert.deepEqual(result.outputJson, {
|
|
3341
|
+
decision: "block",
|
|
3342
|
+
reason:
|
|
3343
|
+
"Deep interview is still active (phase: intent-first) and has a pending structured question obligation; use `omx question` before stopping.",
|
|
3344
|
+
stopReason: "deep_interview_question_required",
|
|
3345
|
+
systemMessage:
|
|
3346
|
+
"OMX deep-interview is still active (phase: intent-first) and requires a structured question via omx question before stopping.",
|
|
3347
|
+
});
|
|
3348
|
+
} finally {
|
|
3349
|
+
await rm(cwd, { recursive: true, force: true });
|
|
3350
|
+
}
|
|
3351
|
+
});
|
|
3352
|
+
|
|
3163
3353
|
it("keeps blocking pending deep-interview question Stop replays until the obligation changes", async () => {
|
|
3164
3354
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-deep-interview-question-replay-"));
|
|
3165
3355
|
try {
|
|
@@ -3477,6 +3667,55 @@ esac
|
|
|
3477
3667
|
}
|
|
3478
3668
|
});
|
|
3479
3669
|
|
|
3670
|
+
it("does not block Stop from stale current-session Ralph state when session.json points to a dead owner", async () => {
|
|
3671
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-current-session-ralph-"));
|
|
3672
|
+
try {
|
|
3673
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
3674
|
+
await mkdir(join(stateDir, "sessions", "sess-dead"), { recursive: true });
|
|
3675
|
+
await writeJson(join(stateDir, "session.json"), {
|
|
3676
|
+
session_id: "sess-dead",
|
|
3677
|
+
cwd,
|
|
3678
|
+
pid: Number.MAX_SAFE_INTEGER,
|
|
3679
|
+
started_at: "2026-01-01T00:00:00.000Z",
|
|
3680
|
+
});
|
|
3681
|
+
await writeJson(join(stateDir, "sessions", "sess-dead", "ralph-state.json"), {
|
|
3682
|
+
active: true,
|
|
3683
|
+
current_phase: "verifying",
|
|
3684
|
+
session_id: "sess-dead",
|
|
3685
|
+
});
|
|
3686
|
+
await writeJson(join(stateDir, "skill-active-state.json"), {
|
|
3687
|
+
active: true,
|
|
3688
|
+
skill: "team",
|
|
3689
|
+
phase: "team-exec",
|
|
3690
|
+
active_skills: [{ skill: "team", phase: "team-exec", active: true, session_id: "sess-dead" }],
|
|
3691
|
+
});
|
|
3692
|
+
await writeJson(join(stateDir, "native-stop-state.json"), {
|
|
3693
|
+
sessions: {
|
|
3694
|
+
"sess-dead": {
|
|
3695
|
+
last_signature: "ralph-stop|sess-dead|thread-1|no-message|verifying",
|
|
3696
|
+
updated_at: "2026-04-20T21:00:00.000Z",
|
|
3697
|
+
},
|
|
3698
|
+
},
|
|
3699
|
+
});
|
|
3700
|
+
|
|
3701
|
+
const result = await dispatchCodexNativeHook(
|
|
3702
|
+
{
|
|
3703
|
+
hook_event_name: "Stop",
|
|
3704
|
+
cwd,
|
|
3705
|
+
session_id: "sess-dead",
|
|
3706
|
+
thread_id: "thread-1",
|
|
3707
|
+
stop_hook_active: true,
|
|
3708
|
+
},
|
|
3709
|
+
{ cwd },
|
|
3710
|
+
);
|
|
3711
|
+
|
|
3712
|
+
assert.equal(result.omxEventName, "stop");
|
|
3713
|
+
assert.equal(result.outputJson, null);
|
|
3714
|
+
} finally {
|
|
3715
|
+
await rm(cwd, { recursive: true, force: true });
|
|
3716
|
+
}
|
|
3717
|
+
});
|
|
3718
|
+
|
|
3480
3719
|
it("does not block Stop from another session-scoped Ralph state when an explicit session_id has no active Ralph state", async () => {
|
|
3481
3720
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-explicit-session-ralph-"));
|
|
3482
3721
|
try {
|
|
@@ -3643,6 +3882,117 @@ esac
|
|
|
3643
3882
|
}
|
|
3644
3883
|
});
|
|
3645
3884
|
|
|
3885
|
+
it("lets dispatcher dedupe identical native stop hook replays after Stop payload normalization", async () => {
|
|
3886
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralph-hook-dedupe-"));
|
|
3887
|
+
const previousOmxSessionId = process.env.OMX_SESSION_ID;
|
|
3888
|
+
try {
|
|
3889
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
3890
|
+
await mkdir(join(stateDir, "sessions", "sess-stop-ralph-hook-dedupe"), { recursive: true });
|
|
3891
|
+
await writeHookCounterPlugin(cwd);
|
|
3892
|
+
await writeFile(
|
|
3893
|
+
join(stateDir, "sessions", "sess-stop-ralph-hook-dedupe", "ralph-state.json"),
|
|
3894
|
+
JSON.stringify({
|
|
3895
|
+
active: true,
|
|
3896
|
+
current_phase: "executing",
|
|
3897
|
+
session_id: "sess-stop-ralph-hook-dedupe",
|
|
3898
|
+
}),
|
|
3899
|
+
);
|
|
3900
|
+
|
|
3901
|
+
process.env.OMX_SESSION_ID = "sess-stop-ralph-hook-dedupe";
|
|
3902
|
+
const payload = {
|
|
3903
|
+
hook_event_name: "Stop",
|
|
3904
|
+
cwd,
|
|
3905
|
+
session_id: "sess-stop-ralph-hook-dedupe",
|
|
3906
|
+
thread_id: "thread-stop-ralph-hook-dedupe",
|
|
3907
|
+
turn_id: "turn-stop-ralph-hook-dedupe-1",
|
|
3908
|
+
last_assistant_message: "Next active targets:\n\n1. scheduler integration\n\nI am continuing.",
|
|
3909
|
+
};
|
|
3910
|
+
|
|
3911
|
+
await dispatchCodexNativeHook(payload, { cwd });
|
|
3912
|
+
await dispatchCodexNativeHook(
|
|
3913
|
+
{
|
|
3914
|
+
...payload,
|
|
3915
|
+
stop_hook_active: true,
|
|
3916
|
+
},
|
|
3917
|
+
{ cwd },
|
|
3918
|
+
);
|
|
3919
|
+
|
|
3920
|
+
const marker = JSON.parse(
|
|
3921
|
+
await readFile(join(cwd, ".omx", "stop-hook-counter.json"), "utf-8"),
|
|
3922
|
+
) as { count: number };
|
|
3923
|
+
assert.equal(marker.count, 1);
|
|
3924
|
+
} finally {
|
|
3925
|
+
if (typeof previousOmxSessionId === "string") process.env.OMX_SESSION_ID = previousOmxSessionId;
|
|
3926
|
+
else delete process.env.OMX_SESSION_ID;
|
|
3927
|
+
await rm(cwd, { recursive: true, force: true });
|
|
3928
|
+
}
|
|
3929
|
+
});
|
|
3930
|
+
|
|
3931
|
+
it("preserves per-turn native stop hook delivery even when stop_hook_active remains true", async () => {
|
|
3932
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralph-hook-refire-"));
|
|
3933
|
+
const previousOmxSessionId = process.env.OMX_SESSION_ID;
|
|
3934
|
+
try {
|
|
3935
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
3936
|
+
await mkdir(join(stateDir, "sessions", "sess-stop-ralph-hook-refire"), { recursive: true });
|
|
3937
|
+
await writeHookCounterPlugin(cwd);
|
|
3938
|
+
await writeFile(
|
|
3939
|
+
join(stateDir, "sessions", "sess-stop-ralph-hook-refire", "ralph-state.json"),
|
|
3940
|
+
JSON.stringify({
|
|
3941
|
+
active: true,
|
|
3942
|
+
current_phase: "executing",
|
|
3943
|
+
session_id: "sess-stop-ralph-hook-refire",
|
|
3944
|
+
}),
|
|
3945
|
+
);
|
|
3946
|
+
|
|
3947
|
+
process.env.OMX_SESSION_ID = "sess-stop-ralph-hook-refire";
|
|
3948
|
+
const payload = {
|
|
3949
|
+
hook_event_name: "Stop",
|
|
3950
|
+
cwd,
|
|
3951
|
+
session_id: "sess-stop-ralph-hook-refire",
|
|
3952
|
+
thread_id: "thread-stop-ralph-hook-refire",
|
|
3953
|
+
turn_id: "turn-stop-ralph-hook-refire-1",
|
|
3954
|
+
last_assistant_message: "Continuing current task.",
|
|
3955
|
+
};
|
|
3956
|
+
|
|
3957
|
+
await dispatchCodexNativeHook(payload, { cwd });
|
|
3958
|
+
await dispatchCodexNativeHook(
|
|
3959
|
+
{
|
|
3960
|
+
...payload,
|
|
3961
|
+
turn_id: "turn-stop-ralph-hook-refire-2",
|
|
3962
|
+
stop_hook_active: true,
|
|
3963
|
+
},
|
|
3964
|
+
{ cwd },
|
|
3965
|
+
);
|
|
3966
|
+
|
|
3967
|
+
await writeFile(
|
|
3968
|
+
join(stateDir, "sessions", "sess-stop-ralph-hook-refire", "ralph-state.json"),
|
|
3969
|
+
JSON.stringify({
|
|
3970
|
+
active: true,
|
|
3971
|
+
current_phase: "executing",
|
|
3972
|
+
session_id: "sess-stop-ralph-hook-refire",
|
|
3973
|
+
}),
|
|
3974
|
+
);
|
|
3975
|
+
|
|
3976
|
+
await dispatchCodexNativeHook(
|
|
3977
|
+
{
|
|
3978
|
+
...payload,
|
|
3979
|
+
turn_id: "turn-stop-ralph-hook-refire-3",
|
|
3980
|
+
stop_hook_active: true,
|
|
3981
|
+
},
|
|
3982
|
+
{ cwd },
|
|
3983
|
+
);
|
|
3984
|
+
|
|
3985
|
+
const marker = JSON.parse(
|
|
3986
|
+
await readFile(join(cwd, ".omx", "stop-hook-counter.json"), "utf-8"),
|
|
3987
|
+
) as { count: number };
|
|
3988
|
+
assert.equal(marker.count, 3);
|
|
3989
|
+
} finally {
|
|
3990
|
+
if (typeof previousOmxSessionId === "string") process.env.OMX_SESSION_ID = previousOmxSessionId;
|
|
3991
|
+
else delete process.env.OMX_SESSION_ID;
|
|
3992
|
+
await rm(cwd, { recursive: true, force: true });
|
|
3993
|
+
}
|
|
3994
|
+
});
|
|
3995
|
+
|
|
3646
3996
|
|
|
3647
3997
|
it("returns Stop continuation output for native auto-nudge stall prompts", async () => {
|
|
3648
3998
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-"));
|
|
@@ -3773,6 +4123,69 @@ esac
|
|
|
3773
4123
|
}
|
|
3774
4124
|
});
|
|
3775
4125
|
|
|
4126
|
+
it("dedupes native stop hook replay across owner launch SessionStart reconciliation drift", async () => {
|
|
4127
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-dispatch-session-drift-"));
|
|
4128
|
+
try {
|
|
4129
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
4130
|
+
await mkdir(join(stateDir, "sessions", "omx-canonical"), { recursive: true });
|
|
4131
|
+
await writeHookCounterPlugin(cwd);
|
|
4132
|
+
process.env.OMX_SESSION_ID = "omx-canonical";
|
|
4133
|
+
await writeSessionStart(cwd, "omx-canonical");
|
|
4134
|
+
await writeJson(join(stateDir, "sessions", "omx-canonical", "ralph-state.json"), {
|
|
4135
|
+
active: true,
|
|
4136
|
+
current_phase: "executing",
|
|
4137
|
+
session_id: "omx-canonical",
|
|
4138
|
+
});
|
|
4139
|
+
|
|
4140
|
+
await dispatchCodexNativeHook(
|
|
4141
|
+
{
|
|
4142
|
+
hook_event_name: "SessionStart",
|
|
4143
|
+
cwd,
|
|
4144
|
+
session_id: "codex-native-new",
|
|
4145
|
+
},
|
|
4146
|
+
{ cwd, sessionOwnerPid: process.pid },
|
|
4147
|
+
);
|
|
4148
|
+
|
|
4149
|
+
await dispatchCodexNativeHook(
|
|
4150
|
+
{
|
|
4151
|
+
hook_event_name: "Stop",
|
|
4152
|
+
cwd,
|
|
4153
|
+
session_id: "codex-native-new",
|
|
4154
|
+
thread_id: "thread-stop-hook-drift",
|
|
4155
|
+
turn_id: "turn-stop-hook-drift-1",
|
|
4156
|
+
last_assistant_message: "Keep going and finish the cleanup.",
|
|
4157
|
+
},
|
|
4158
|
+
{ cwd },
|
|
4159
|
+
);
|
|
4160
|
+
|
|
4161
|
+
await dispatchCodexNativeHook(
|
|
4162
|
+
{
|
|
4163
|
+
hook_event_name: "Stop",
|
|
4164
|
+
cwd,
|
|
4165
|
+
session_id: "omx-canonical",
|
|
4166
|
+
thread_id: "thread-stop-hook-drift",
|
|
4167
|
+
turn_id: "turn-stop-hook-drift-1",
|
|
4168
|
+
stop_hook_active: true,
|
|
4169
|
+
last_assistant_message: "Keep going and finish the cleanup.",
|
|
4170
|
+
},
|
|
4171
|
+
{ cwd },
|
|
4172
|
+
);
|
|
4173
|
+
|
|
4174
|
+
const marker = JSON.parse(
|
|
4175
|
+
await readFile(join(cwd, ".omx", "stop-hook-counter.json"), "utf-8"),
|
|
4176
|
+
) as { count: number };
|
|
4177
|
+
assert.equal(marker.count, 1);
|
|
4178
|
+
|
|
4179
|
+
const sessionState = JSON.parse(
|
|
4180
|
+
await readFile(join(stateDir, "session.json"), "utf-8"),
|
|
4181
|
+
) as { session_id?: string; native_session_id?: string };
|
|
4182
|
+
assert.equal(sessionState.session_id, "omx-canonical");
|
|
4183
|
+
assert.equal(sessionState.native_session_id, "codex-native-new");
|
|
4184
|
+
} finally {
|
|
4185
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4186
|
+
}
|
|
4187
|
+
});
|
|
4188
|
+
|
|
3776
4189
|
it("re-fires native auto-nudge for a later fresh Stop reply even when stop_hook_active is true", async () => {
|
|
3777
4190
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-refire-"));
|
|
3778
4191
|
try {
|
|
@@ -4329,71 +4742,62 @@ esac
|
|
|
4329
4742
|
}
|
|
4330
4743
|
});
|
|
4331
4744
|
|
|
4332
|
-
it("
|
|
4333
|
-
const
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
mode: "ultrawork",
|
|
4342
|
-
phase: "executing",
|
|
4343
|
-
reason:
|
|
4344
|
-
"OMX ultrawork is still active (phase: executing); continue the task and gather fresh verification evidence before stopping.",
|
|
4345
|
-
},
|
|
4346
|
-
{
|
|
4347
|
-
mode: "ultraqa",
|
|
4348
|
-
phase: "diagnose",
|
|
4349
|
-
reason:
|
|
4350
|
-
"OMX ultraqa is still active (phase: diagnose); continue the task and gather fresh verification evidence before stopping.",
|
|
4351
|
-
},
|
|
4352
|
-
] as const;
|
|
4745
|
+
it("suppresses duplicate ultrawork Stop replays while stop_hook_active stays true", async () => {
|
|
4746
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ultrawork-repeat-"));
|
|
4747
|
+
try {
|
|
4748
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
4749
|
+
await mkdir(stateDir, { recursive: true });
|
|
4750
|
+
await writeJson(join(stateDir, "ultrawork-state.json"), {
|
|
4751
|
+
active: true,
|
|
4752
|
+
current_phase: "executing",
|
|
4753
|
+
});
|
|
4353
4754
|
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
}
|
|
4755
|
+
const first = await dispatchCodexNativeHook(
|
|
4756
|
+
{
|
|
4757
|
+
hook_event_name: "Stop",
|
|
4758
|
+
cwd,
|
|
4759
|
+
session_id: "sess-stop-ultrawork-repeat",
|
|
4760
|
+
thread_id: "thread-stop-ultrawork-repeat",
|
|
4761
|
+
turn_id: "turn-stop-ultrawork-repeat-1",
|
|
4762
|
+
},
|
|
4763
|
+
{ cwd },
|
|
4764
|
+
);
|
|
4363
4765
|
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4766
|
+
const repeated = await dispatchCodexNativeHook(
|
|
4767
|
+
{
|
|
4768
|
+
hook_event_name: "Stop",
|
|
4769
|
+
cwd,
|
|
4770
|
+
session_id: "sess-stop-ultrawork-repeat",
|
|
4771
|
+
thread_id: "thread-stop-ultrawork-repeat",
|
|
4772
|
+
turn_id: "turn-stop-ultrawork-repeat-1",
|
|
4773
|
+
stop_hook_active: true,
|
|
4774
|
+
},
|
|
4775
|
+
{ cwd },
|
|
4776
|
+
);
|
|
4374
4777
|
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4778
|
+
const fresh = await dispatchCodexNativeHook(
|
|
4779
|
+
{
|
|
4780
|
+
hook_event_name: "Stop",
|
|
4781
|
+
cwd,
|
|
4782
|
+
session_id: "sess-stop-ultrawork-repeat",
|
|
4783
|
+
thread_id: "thread-stop-ultrawork-repeat",
|
|
4784
|
+
turn_id: "turn-stop-ultrawork-repeat-2",
|
|
4785
|
+
stop_hook_active: true,
|
|
4786
|
+
},
|
|
4787
|
+
{ cwd },
|
|
4788
|
+
);
|
|
4386
4789
|
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4790
|
+
assert.equal(first.omxEventName, "stop");
|
|
4791
|
+
assert.deepEqual(repeated.outputJson, null);
|
|
4792
|
+
assert.equal(fresh.omxEventName, "stop");
|
|
4793
|
+
assert.deepEqual(fresh.outputJson, {
|
|
4794
|
+
decision: "block",
|
|
4795
|
+
reason: "OMX ultrawork is still active (phase: executing); continue the task and gather fresh verification evidence before stopping.",
|
|
4796
|
+
stopReason: "ultrawork_executing",
|
|
4797
|
+
systemMessage: "OMX ultrawork is still active (phase: executing).",
|
|
4798
|
+
});
|
|
4799
|
+
} finally {
|
|
4800
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4397
4801
|
}
|
|
4398
4802
|
});
|
|
4399
4803
|
|