oh-my-opencode 4.14.0 → 4.14.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/.agents/skills/codex-qa/references/isolation.md +1 -1
- package/README.md +1 -1
- package/dist/cli/doctor/framework/types.d.ts +2 -0
- package/dist/cli/index.js +149 -1426
- package/dist/cli-node/index.js +149 -1426
- package/dist/index.js +4255 -714
- package/dist/plugin/native-skills.d.ts +9 -0
- package/dist/plugin/system-transform.d.ts +1 -1
- package/dist/skills/lcx-contribute-bug-fix/SKILL.md +44 -13
- package/dist/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
- package/dist/skills/lcx-doctor/SKILL.md +48 -13
- package/dist/skills/lcx-doctor/agents/openai.yaml +1 -1
- package/dist/skills/lcx-report-bug/SKILL.md +44 -13
- package/dist/skills/lcx-report-bug/agents/openai.yaml +1 -1
- package/dist/skills/visual-qa/SKILL.md +5 -5
- package/dist/skills/visual-qa/references/agent-browser-setup.md +5 -4
- package/dist/skills/visual-qa/scripts/cli.test.ts +7 -2
- package/dist/skills/visual-qa/scripts/skill-prompt-contract.test.ts +10 -2
- package/dist/skills/visual-qa/scripts/visual-qa.mjs +530 -0
- package/dist/tools/delegate-task/skill-resolver.d.ts +1 -0
- package/dist/tools/delegate-task/types.d.ts +2 -0
- package/dist/tools/skill/types.d.ts +1 -0
- package/package.json +26 -13
- package/packages/git-bash-mcp/dist/cli.js +2 -0
- package/packages/lsp-daemon/dist/cli.js +15 -4
- package/packages/lsp-daemon/dist/ensure-daemon.d.ts +1 -0
- package/packages/lsp-daemon/dist/ensure-daemon.js +9 -3
- package/packages/lsp-daemon/dist/index.js +15 -4
- package/packages/lsp-daemon/dist/paths.d.ts +1 -0
- package/packages/lsp-daemon/dist/paths.js +6 -1
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +4 -7
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/src/setup.ts +1 -1
- package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +123 -10
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +123 -10
- package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/src/mcp-bridge.ts +126 -9
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +141 -0
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/cli.js +90 -25
- package/packages/omo-codex/plugin/components/lsp/dist/codex-hook.js +2 -0
- package/packages/omo-codex/plugin/components/lsp/dist/daemon-cli-path.d.ts +2 -0
- package/packages/omo-codex/plugin/components/lsp/dist/daemon-cli-path.js +56 -0
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/src/cli.ts +2 -5
- package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +2 -0
- package/packages/omo-codex/plugin/components/lsp/src/daemon-cli-path.ts +65 -0
- package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +6 -1
- package/packages/omo-codex/plugin/components/rules/dist/cli.js +8 -106
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/src/static-injection.ts +4 -17
- package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/SKILL.md +17 -5
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-state.mjs +63 -1
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team.mjs +80 -50
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/explorer.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/directive.md +4 -12
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +7 -33
- package/packages/omo-codex/plugin/components/ulw-loop/directive.md +4 -12
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +4 -2
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/sample-quality-gate.json +7 -7
- package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +3 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-doc.test.ts +2 -2
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate.test.ts +7 -6
- package/packages/omo-codex/plugin/package-lock.json +16 -56
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/SKILL.md +44 -13
- package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/lcx-doctor/SKILL.md +48 -13
- package/packages/omo-codex/plugin/skills/lcx-doctor/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/lcx-report-bug/SKILL.md +44 -13
- package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/teammode/SKILL.md +17 -5
- package/packages/omo-codex/plugin/skills/teammode/scripts/team-state.mjs +63 -1
- package/packages/omo-codex/plugin/skills/teammode/scripts/team.mjs +80 -50
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +4 -4
- package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +5 -5
- package/packages/omo-codex/plugin/skills/visual-qa/references/agent-browser-setup.md +5 -4
- package/packages/omo-codex/plugin/skills/visual-qa/scripts/visual-qa.mjs +530 -0
- package/packages/omo-codex/plugin/test/bootstrap-binlinks.test.mjs +38 -2
- package/packages/omo-codex/plugin/test/component-bin-names.test.mjs +12 -11
- package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +99 -1
- package/packages/omo-codex/plugin/test/lcx-bug-skills.test.mjs +62 -8
- package/packages/omo-codex/plugin/test/node-install-surface.test.mjs +1 -0
- package/packages/omo-codex/plugin/test/teammode-archive-ambiguity.test.mjs +102 -0
- package/packages/omo-codex/plugin/test/teammode-safety-fixture.mjs +41 -1
- package/packages/omo-codex/plugin/test/teammode-safety.test.mjs +93 -0
- package/packages/omo-codex/scripts/install-bin-links.test.mjs +6 -0
- package/packages/omo-codex/scripts/install-cache-copy.test.mjs +28 -0
- package/packages/omo-codex/scripts/install-config-autonomous-features.test.mjs +43 -3
- package/packages/omo-codex/scripts/install-delegated-command.test.mjs +93 -0
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +103 -28
- package/packages/omo-codex/scripts/install-local-entrypoint.test.mjs +21 -2
- package/packages/omo-codex/scripts/install-local.test.mjs +0 -1
- package/packages/shared-skills/skills/lcx-contribute-bug-fix/SKILL.md +44 -13
- package/packages/shared-skills/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
- package/packages/shared-skills/skills/lcx-doctor/SKILL.md +48 -13
- package/packages/shared-skills/skills/lcx-doctor/agents/openai.yaml +1 -1
- package/packages/shared-skills/skills/lcx-report-bug/SKILL.md +44 -13
- package/packages/shared-skills/skills/lcx-report-bug/agents/openai.yaml +1 -1
- package/packages/shared-skills/skills/visual-qa/SKILL.md +5 -5
- package/packages/shared-skills/skills/visual-qa/references/agent-browser-setup.md +5 -4
- package/packages/shared-skills/skills/visual-qa/scripts/cli.test.ts +7 -2
- package/packages/shared-skills/skills/visual-qa/scripts/skill-prompt-contract.test.ts +10 -2
- package/packages/shared-skills/skills/visual-qa/scripts/visual-qa.mjs +530 -0
- package/dist/cli/sparkshell-appserver-websocket.d.ts +0 -4
- package/dist/cli/sparkshell-appserver.d.ts +0 -16
- package/dist/cli/sparkshell-condense.d.ts +0 -10
- package/dist/cli/sparkshell-parse.d.ts +0 -24
- package/dist/cli/sparkshell-session-context.d.ts +0 -20
- package/dist/cli/sparkshell-spark.d.ts +0 -23
- package/dist/cli/sparkshell.d.ts +0 -33
- package/dist/shared/sparkshell-awareness.d.ts +0 -5
- package/packages/omo-codex/plugin/components/rules/src/sparkshell-awareness.ts +0 -106
- package/packages/omo-codex/plugin/components/rules/test/sparkshell-awareness.test.ts +0 -355
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
|
|
4
|
-
type RuntimeEnv = Readonly<Record<string, string | undefined>>;
|
|
5
|
-
|
|
6
|
-
interface OmoResolutionDeps {
|
|
7
|
-
readonly fileExists?: (path: string) => boolean;
|
|
8
|
-
readonly platform?: NodeJS.Platform;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const SPARKSHELL_AWARENESS_MARKER = "## Sparkshell Runtime";
|
|
12
|
-
|
|
13
|
-
export const SPARKSHELL_AWARENESS_DEDUP_KEY = "__omo_sparkshell_awareness__";
|
|
14
|
-
|
|
15
|
-
export function isCodexAppServerActive(env: RuntimeEnv = process.env): boolean {
|
|
16
|
-
const originator = env["CODEX_INTERNAL_ORIGINATOR_OVERRIDE"]?.toLowerCase() ?? "";
|
|
17
|
-
const bundleIdentifier = env["__CFBundleIdentifier"]?.toLowerCase() ?? "";
|
|
18
|
-
const shellActive = isTruthy(env["CODEX_SHELL"]);
|
|
19
|
-
|
|
20
|
-
return (
|
|
21
|
-
shellActive &&
|
|
22
|
-
(originator.includes("codex desktop") ||
|
|
23
|
-
originator.includes("codex app") ||
|
|
24
|
-
bundleIdentifier === "com.openai.codex")
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function isSparkShellAppServerConfigured(env: RuntimeEnv = process.env): boolean {
|
|
29
|
-
const codexSocketPath = env["CODEX_APP_SERVER_SOCKET"]?.trim() ?? "";
|
|
30
|
-
const omoSocketPath = env["OMO_SPARKSHELL_APP_SERVER_SOCKET"]?.trim() ?? "";
|
|
31
|
-
return codexSocketPath.length > 0 || omoSocketPath.length > 0;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function resolveOmoInvocation(env: RuntimeEnv = process.env, deps: OmoResolutionDeps = {}): string | null {
|
|
35
|
-
const fileExists = deps.fileExists ?? existsSync;
|
|
36
|
-
const platform = deps.platform ?? process.platform;
|
|
37
|
-
const binNames = platform === "win32" ? ["omo.cmd", "omo.exe", "omo"] : ["omo"];
|
|
38
|
-
const pathDelimiter = platform === "win32" ? ";" : ":";
|
|
39
|
-
const pathEntries = (env["PATH"] ?? "").split(pathDelimiter).filter((entry) => entry.trim().length > 0);
|
|
40
|
-
for (const pathEntry of pathEntries) {
|
|
41
|
-
for (const binName of binNames) {
|
|
42
|
-
if (fileExists(join(pathEntry, binName))) return "omo";
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
for (const candidateDir of omoCandidateBinDirs(env)) {
|
|
46
|
-
for (const binName of binNames) {
|
|
47
|
-
const candidate = join(candidateDir, binName);
|
|
48
|
-
if (fileExists(candidate)) return candidate;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function omoCandidateBinDirs(env: RuntimeEnv): readonly string[] {
|
|
55
|
-
const dirs: string[] = [];
|
|
56
|
-
const localBinDir = env["CODEX_LOCAL_BIN_DIR"]?.trim() ?? "";
|
|
57
|
-
if (localBinDir.length > 0) dirs.push(localBinDir);
|
|
58
|
-
const home = env["HOME"]?.trim() || env["USERPROFILE"]?.trim() || "";
|
|
59
|
-
const codexHome = env["CODEX_HOME"]?.trim() || (home.length > 0 ? join(home, ".codex") : "");
|
|
60
|
-
if (codexHome.length > 0) dirs.push(join(codexHome, "bin"));
|
|
61
|
-
if (home.length > 0) dirs.push(join(home, ".local", "bin"));
|
|
62
|
-
return dirs;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function getSparkShellRuntimeAwareness(env: RuntimeEnv = process.env, deps: OmoResolutionDeps = {}): string {
|
|
66
|
-
const override = env["OMO_SPARKSHELL_AWARENESS"] ?? env["LAZYCODEX_SPARKSHELL_AWARENESS"];
|
|
67
|
-
if (isFalsy(override)) {
|
|
68
|
-
return "";
|
|
69
|
-
}
|
|
70
|
-
if (!isTruthy(override) && !isCodexAppServerActive(env) && !isSparkShellAppServerConfigured(env)) {
|
|
71
|
-
return "";
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const resolved = resolveOmoInvocation(env, deps);
|
|
75
|
-
const invocation = resolved ?? (isTruthy(override) ? "omo" : null);
|
|
76
|
-
if (invocation === null) {
|
|
77
|
-
return "";
|
|
78
|
-
}
|
|
79
|
-
const command = /\s/.test(invocation) ? `"${invocation}"` : invocation;
|
|
80
|
-
|
|
81
|
-
return [
|
|
82
|
-
SPARKSHELL_AWARENESS_MARKER,
|
|
83
|
-
"",
|
|
84
|
-
`- Use \`${command} sparkshell <command> [args...]\` first for repo inspection, CLI smoke tests, git/history checks, and bounded command output. Pass ordinary commands as executable and arguments in separate argv tokens, for example \`${command} sparkshell rg --files\`; not \`${command} sparkshell 'rg --files'\`, because the quoted string is treated as one executable name. Raw \`rg\`/\`grep\`/\`cat\`/\`git\` are fallbacks when Sparkshell is unavailable or too narrow for the task.`,
|
|
85
|
-
`- Use \`${command} sparkshell --shell '<command>'\` only for shell syntax such as metacharacters, pipelines, redirects, command substitution, or variable expansion.`,
|
|
86
|
-
`- Use \`${command} sparkshell --tmux-pane <pane-id> --tail-lines 400\` only to inspect an existing pane, never to launch ordinary commands. Tail lines must stay between 100 and 1000.`,
|
|
87
|
-
"- When no native sidecar or appserver is available, Sparkshell silently falls back to raw command execution. `OMO_SPARKSHELL_BIN` selects a native sidecar path.",
|
|
88
|
-
"- When `CODEX_THREAD_ID` identifies a Codex session, Sparkshell feeds recent session context (first/latest user request + last 5 conversation messages) into oversized-output condensation for relevance ranking, but never appends that context to command output. `OMO_SPARKSHELL_SESSION_CONTEXT=0` disables the lookup.",
|
|
89
|
-
`- Route potentially huge output (full log files, big diffs, \`cat\`/\`grep\` over large artifacts) through \`${command} sparkshell\` instead of reading it raw: oversized output is condensed to a budget while preserving error signatures, repeated patterns, session-goal-relevant lines, and head/tail. Tune with \`--budget <chars>\`; disable with \`OMO_SPARKSHELL_CONDENSE=0\`.`,
|
|
90
|
-
"- Oversized output is first summarized by the spark model (`codex exec`, default `gpt-5.3-codex-spark`) fed with the shell output plus session context: the summary keeps selected output as-is (no masking) and ends with a `[sparkshell caption]` line describing what ran, what the full output contained, and which lines were omitted. `OMO_SPARKSHELL_SPARK=0` skips the model and uses deterministic condensation directly.",
|
|
91
|
-
].join("\n");
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function isTruthy(value: string | undefined): boolean {
|
|
95
|
-
if (value === undefined) {
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
return ["1", "true", "yes", "on"].includes(value.trim().toLowerCase());
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function isFalsy(value: string | undefined): boolean {
|
|
102
|
-
if (value === undefined) {
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
return ["0", "false", "no", "off"].includes(value.trim().toLowerCase());
|
|
106
|
-
}
|
|
@@ -1,355 +0,0 @@
|
|
|
1
|
-
// allow: SIZE_OK - rules sparkshell-awareness coverage validates one injected guidance contract; this release adds narrow prompt cases and future growth should split by guidance surface.
|
|
2
|
-
|
|
3
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
import { join } from "node:path";
|
|
6
|
-
|
|
7
|
-
import { afterAll, describe, expect, it } from "vitest";
|
|
8
|
-
|
|
9
|
-
import { runSessionStartHook, runUserPromptSubmitHook } from "../src/codex-hook.js";
|
|
10
|
-
import { formatAdditionalContextOutput } from "../src/hook-output.js";
|
|
11
|
-
|
|
12
|
-
type HookOutput = {
|
|
13
|
-
readonly hookSpecificOutput?: {
|
|
14
|
-
readonly additionalContext?: string;
|
|
15
|
-
};
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
function parseAdditionalContext(output: string): string {
|
|
19
|
-
expect(output.trim().length).toBeGreaterThan(0);
|
|
20
|
-
const parsed = parseHookOutput(JSON.parse(output));
|
|
21
|
-
return parsed.hookSpecificOutput?.additionalContext ?? "";
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function normalizeGuidance(value: string): string {
|
|
25
|
-
return value.toLowerCase().replace(/\s+/g, " ").trim();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function expectSparkshellFirstContract(value: string): void {
|
|
29
|
-
const guidance = normalizeGuidance(value);
|
|
30
|
-
|
|
31
|
-
expect(value).toContain("`omo sparkshell <command> [args...]`");
|
|
32
|
-
expect(guidance).toMatch(/`omo sparkshell <command> \[args\.\.\.\]`[^.]*\bfirst\b/);
|
|
33
|
-
expect(guidance).toMatch(/\brepo inspection\b/);
|
|
34
|
-
expect(guidance).toMatch(/\bcli smoke tests\b/);
|
|
35
|
-
expect(guidance).toMatch(/\bgit\/history checks\b/);
|
|
36
|
-
expect(guidance).toMatch(/\bbounded command output\b/);
|
|
37
|
-
expect(guidance).toMatch(/\braw\b[^.]*`rg`\/`grep`\/`cat`\/`git`[^.]*\bfallbacks?\b/);
|
|
38
|
-
expect(guidance).toMatch(/\bsparkshell is unavailable\b/);
|
|
39
|
-
expect(guidance).toMatch(/\btoo narrow\b/);
|
|
40
|
-
expect(value).toContain("`omo sparkshell rg --files`");
|
|
41
|
-
expect(guidance).toMatch(/\bseparate argv tokens\b/);
|
|
42
|
-
expect(value).toContain("not `omo sparkshell 'rg --files'`");
|
|
43
|
-
expect(guidance).toMatch(/\bone executable name\b/);
|
|
44
|
-
expect(guidance).toMatch(/`omo sparkshell --shell '<command>'`[^.]*\bmetacharacters\b[^.]*\bpipelines\b/);
|
|
45
|
-
expect(guidance).toMatch(
|
|
46
|
-
/`omo sparkshell --tmux-pane <pane-id> --tail-lines 400`[^.]*\bonly\b[^.]*\binspect\b[^.]*\bexisting pane\b/,
|
|
47
|
-
);
|
|
48
|
-
expect(guidance).toMatch(
|
|
49
|
-
/`omo sparkshell --tmux-pane <pane-id> --tail-lines 400`[^.]*\bnever\b[^.]*\blaunch ordinary commands\b/,
|
|
50
|
-
);
|
|
51
|
-
expect(guidance).not.toMatch(/\bprefer\b[^.]*\bbefore raw shell commands\b/);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function parseHookOutput(value: unknown): HookOutput {
|
|
55
|
-
if (typeof value !== "object" || value === null) {
|
|
56
|
-
return {};
|
|
57
|
-
}
|
|
58
|
-
const record = value;
|
|
59
|
-
if (!("hookSpecificOutput" in record)) {
|
|
60
|
-
return {};
|
|
61
|
-
}
|
|
62
|
-
const hookSpecificOutput = record.hookSpecificOutput;
|
|
63
|
-
if (typeof hookSpecificOutput !== "object" || hookSpecificOutput === null) {
|
|
64
|
-
return {};
|
|
65
|
-
}
|
|
66
|
-
if (!("additionalContext" in hookSpecificOutput)) {
|
|
67
|
-
return { hookSpecificOutput: {} };
|
|
68
|
-
}
|
|
69
|
-
const additionalContext = hookSpecificOutput.additionalContext;
|
|
70
|
-
if (typeof additionalContext !== "string") {
|
|
71
|
-
return { hookSpecificOutput: {} };
|
|
72
|
-
}
|
|
73
|
-
return {
|
|
74
|
-
hookSpecificOutput: {
|
|
75
|
-
additionalContext,
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const fixtureRoot = mkdtempSync(join(tmpdir(), "codex-sparkshell-omo-bin-"));
|
|
81
|
-
const omoOnPathDir = join(fixtureRoot, "path-bin");
|
|
82
|
-
const emptyHomeDir = join(fixtureRoot, "empty-home");
|
|
83
|
-
const localBinHomeDir = join(fixtureRoot, "local-bin-home");
|
|
84
|
-
mkdirSync(omoOnPathDir, { recursive: true });
|
|
85
|
-
mkdirSync(emptyHomeDir, { recursive: true });
|
|
86
|
-
mkdirSync(join(localBinHomeDir, ".local", "bin"), { recursive: true });
|
|
87
|
-
writeFileSync(join(omoOnPathDir, "omo"), "#!/bin/sh\n");
|
|
88
|
-
writeFileSync(join(localBinHomeDir, ".local", "bin", "omo"), "#!/bin/sh\n");
|
|
89
|
-
|
|
90
|
-
describe("Codex Sparkshell awareness", () => {
|
|
91
|
-
afterAll(() => {
|
|
92
|
-
rmSync(fixtureRoot, { recursive: true, force: true });
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("#given active Codex app server env with omo on PATH #when SessionStart runs #then emits Sparkshell guidance", async () => {
|
|
96
|
-
// given
|
|
97
|
-
const env = {
|
|
98
|
-
CODEX_INTERNAL_ORIGINATOR_OVERRIDE: "Codex Desktop",
|
|
99
|
-
CODEX_SHELL: "1",
|
|
100
|
-
CODEX_RULES_ENABLED_SOURCES: ".omo/rules",
|
|
101
|
-
PATH: omoOnPathDir,
|
|
102
|
-
HOME: emptyHomeDir,
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
// when
|
|
106
|
-
const output = await runSessionStartHook(
|
|
107
|
-
{
|
|
108
|
-
session_id: "session-sparkshell-active",
|
|
109
|
-
transcript_path: null,
|
|
110
|
-
cwd: process.cwd(),
|
|
111
|
-
hook_event_name: "SessionStart",
|
|
112
|
-
model: "gpt-5.5",
|
|
113
|
-
permission_mode: "default",
|
|
114
|
-
source: "startup",
|
|
115
|
-
},
|
|
116
|
-
{ env },
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
// then
|
|
120
|
-
const context = parseAdditionalContext(output);
|
|
121
|
-
expectSparkshellFirstContract(context);
|
|
122
|
-
expect(context).toContain("OMO_SPARKSHELL_SESSION_CONTEXT");
|
|
123
|
-
expect(context).toContain("OMO_SPARKSHELL_CONDENSE");
|
|
124
|
-
expect(context).toContain("OMO_SPARKSHELL_SPARK");
|
|
125
|
-
expect(context).toContain("[sparkshell caption]");
|
|
126
|
-
expect(context).toContain("never appends that context to command output");
|
|
127
|
-
expect(context).toContain("what the full output contained");
|
|
128
|
-
expect(context).not.toContain("[REDACTED]");
|
|
129
|
-
expect(context).not.toContain("appends recent session context");
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it("#given inactive env #when SessionStart runs #then emits no Sparkshell guidance", async () => {
|
|
133
|
-
// given
|
|
134
|
-
const env = {
|
|
135
|
-
CODEX_RULES_ENABLED_SOURCES: ".omo/rules",
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
// when
|
|
139
|
-
const output = await runSessionStartHook(
|
|
140
|
-
{
|
|
141
|
-
session_id: "session-sparkshell-inactive",
|
|
142
|
-
transcript_path: null,
|
|
143
|
-
cwd: process.cwd(),
|
|
144
|
-
hook_event_name: "SessionStart",
|
|
145
|
-
model: "gpt-5.5",
|
|
146
|
-
permission_mode: "default",
|
|
147
|
-
source: "startup",
|
|
148
|
-
},
|
|
149
|
-
{ env },
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
// then
|
|
153
|
-
expect(output).toBe("");
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it("#given Codex CLI appserver socket env #when SessionStart runs #then emits Sparkshell guidance", async () => {
|
|
157
|
-
// given
|
|
158
|
-
const env = {
|
|
159
|
-
OMO_SPARKSHELL_APP_SERVER_SOCKET: "/tmp/app-server-control.sock",
|
|
160
|
-
CODEX_THREAD_ID: "thread-sparkshell-cli",
|
|
161
|
-
CODEX_RULES_ENABLED_SOURCES: ".omo/rules",
|
|
162
|
-
PATH: omoOnPathDir,
|
|
163
|
-
HOME: emptyHomeDir,
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
// when
|
|
167
|
-
const output = await runSessionStartHook(
|
|
168
|
-
{
|
|
169
|
-
session_id: "session-sparkshell-cli-wrapper",
|
|
170
|
-
transcript_path: null,
|
|
171
|
-
cwd: process.cwd(),
|
|
172
|
-
hook_event_name: "SessionStart",
|
|
173
|
-
model: "gpt-5.5",
|
|
174
|
-
permission_mode: "default",
|
|
175
|
-
source: "startup",
|
|
176
|
-
},
|
|
177
|
-
{ env },
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
// then
|
|
181
|
-
expect(parseAdditionalContext(output)).toContain("omo sparkshell <command>");
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
it("#given active Codex app env without a resolvable omo command #when SessionStart runs #then emits no Sparkshell guidance", async () => {
|
|
185
|
-
// given
|
|
186
|
-
const env = {
|
|
187
|
-
CODEX_INTERNAL_ORIGINATOR_OVERRIDE: "Codex Desktop",
|
|
188
|
-
CODEX_SHELL: "1",
|
|
189
|
-
CODEX_RULES_ENABLED_SOURCES: ".omo/rules",
|
|
190
|
-
PATH: join(fixtureRoot, "missing-path-entry"),
|
|
191
|
-
HOME: emptyHomeDir,
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
// when
|
|
195
|
-
const output = await runSessionStartHook(
|
|
196
|
-
{
|
|
197
|
-
session_id: "session-sparkshell-unresolvable",
|
|
198
|
-
transcript_path: null,
|
|
199
|
-
cwd: process.cwd(),
|
|
200
|
-
hook_event_name: "SessionStart",
|
|
201
|
-
model: "gpt-5.5",
|
|
202
|
-
permission_mode: "default",
|
|
203
|
-
source: "startup",
|
|
204
|
-
},
|
|
205
|
-
{ env },
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
// then
|
|
209
|
-
expect(output).toBe("");
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it("#given omo only under HOME/.local/bin #when SessionStart runs #then emits guidance with the absolute omo path", async () => {
|
|
213
|
-
// given
|
|
214
|
-
const env = {
|
|
215
|
-
CODEX_INTERNAL_ORIGINATOR_OVERRIDE: "Codex Desktop",
|
|
216
|
-
CODEX_SHELL: "1",
|
|
217
|
-
CODEX_RULES_ENABLED_SOURCES: ".omo/rules",
|
|
218
|
-
PATH: join(fixtureRoot, "missing-path-entry"),
|
|
219
|
-
HOME: localBinHomeDir,
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
// when
|
|
223
|
-
const output = await runSessionStartHook(
|
|
224
|
-
{
|
|
225
|
-
session_id: "session-sparkshell-local-bin",
|
|
226
|
-
transcript_path: null,
|
|
227
|
-
cwd: process.cwd(),
|
|
228
|
-
hook_event_name: "SessionStart",
|
|
229
|
-
model: "gpt-5.5",
|
|
230
|
-
permission_mode: "default",
|
|
231
|
-
source: "startup",
|
|
232
|
-
},
|
|
233
|
-
{ env },
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
// then
|
|
237
|
-
const context = parseAdditionalContext(output);
|
|
238
|
-
expect(context).toContain(`${join(localBinHomeDir, ".local", "bin", "omo")} sparkshell <command>`);
|
|
239
|
-
expect(context).not.toContain("`omo sparkshell <command>`");
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it("#given explicit force-on env #when SessionStart runs #then emits Sparkshell guidance", async () => {
|
|
243
|
-
// given
|
|
244
|
-
const env = {
|
|
245
|
-
OMO_SPARKSHELL_AWARENESS: "1",
|
|
246
|
-
CODEX_RULES_ENABLED_SOURCES: ".omo/rules",
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
// when
|
|
250
|
-
const output = await runSessionStartHook(
|
|
251
|
-
{
|
|
252
|
-
session_id: "session-sparkshell-force-on",
|
|
253
|
-
transcript_path: null,
|
|
254
|
-
cwd: process.cwd(),
|
|
255
|
-
hook_event_name: "SessionStart",
|
|
256
|
-
model: "gpt-5.5",
|
|
257
|
-
permission_mode: "default",
|
|
258
|
-
source: "startup",
|
|
259
|
-
},
|
|
260
|
-
{ env },
|
|
261
|
-
);
|
|
262
|
-
|
|
263
|
-
// then
|
|
264
|
-
expect(parseAdditionalContext(output)).toContain("omo sparkshell <command>");
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
it("#given explicit force-off env with active Codex app context #when SessionStart runs #then emits no Sparkshell guidance", async () => {
|
|
268
|
-
// given
|
|
269
|
-
const env = {
|
|
270
|
-
OMO_SPARKSHELL_AWARENESS: "0",
|
|
271
|
-
CODEX_INTERNAL_ORIGINATOR_OVERRIDE: "Codex Desktop",
|
|
272
|
-
CODEX_SHELL: "1",
|
|
273
|
-
CODEX_RULES_ENABLED_SOURCES: ".omo/rules",
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
// when
|
|
277
|
-
const output = await runSessionStartHook(
|
|
278
|
-
{
|
|
279
|
-
session_id: "session-sparkshell-force-off",
|
|
280
|
-
transcript_path: null,
|
|
281
|
-
cwd: process.cwd(),
|
|
282
|
-
hook_event_name: "SessionStart",
|
|
283
|
-
model: "gpt-5.5",
|
|
284
|
-
permission_mode: "default",
|
|
285
|
-
source: "startup",
|
|
286
|
-
},
|
|
287
|
-
{ env },
|
|
288
|
-
);
|
|
289
|
-
|
|
290
|
-
// then
|
|
291
|
-
expect(output).toBe("");
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
it("#given Sparkshell awareness already emitted for a session #when UserPromptSubmit runs #then emits no duplicate guidance", async () => {
|
|
295
|
-
// given
|
|
296
|
-
const pluginDataRoot = mkdtempSync(join(tmpdir(), "codex-sparkshell-awareness-"));
|
|
297
|
-
const env = {
|
|
298
|
-
CODEX_INTERNAL_ORIGINATOR_OVERRIDE: "Codex Desktop",
|
|
299
|
-
CODEX_SHELL: "1",
|
|
300
|
-
CODEX_RULES_ENABLED_SOURCES: ".omo/rules",
|
|
301
|
-
PATH: omoOnPathDir,
|
|
302
|
-
HOME: emptyHomeDir,
|
|
303
|
-
};
|
|
304
|
-
try {
|
|
305
|
-
const firstOutput = await runSessionStartHook(
|
|
306
|
-
{
|
|
307
|
-
session_id: "session-sparkshell-dedupe",
|
|
308
|
-
transcript_path: null,
|
|
309
|
-
cwd: process.cwd(),
|
|
310
|
-
hook_event_name: "SessionStart",
|
|
311
|
-
model: "gpt-5.5",
|
|
312
|
-
permission_mode: "default",
|
|
313
|
-
source: "startup",
|
|
314
|
-
},
|
|
315
|
-
{ env, pluginDataRoot },
|
|
316
|
-
);
|
|
317
|
-
expect(parseAdditionalContext(firstOutput)).toContain("omo sparkshell <command>");
|
|
318
|
-
|
|
319
|
-
// when
|
|
320
|
-
const secondOutput = await runUserPromptSubmitHook(
|
|
321
|
-
{
|
|
322
|
-
session_id: "session-sparkshell-dedupe",
|
|
323
|
-
turn_id: "turn-1",
|
|
324
|
-
transcript_path: null,
|
|
325
|
-
cwd: process.cwd(),
|
|
326
|
-
hook_event_name: "UserPromptSubmit",
|
|
327
|
-
model: "gpt-5.5",
|
|
328
|
-
permission_mode: "default",
|
|
329
|
-
prompt: "continue",
|
|
330
|
-
},
|
|
331
|
-
{ env, pluginDataRoot },
|
|
332
|
-
);
|
|
333
|
-
|
|
334
|
-
// then
|
|
335
|
-
expect(secondOutput).toBe("");
|
|
336
|
-
} finally {
|
|
337
|
-
rmSync(pluginDataRoot, { recursive: true, force: true });
|
|
338
|
-
}
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
it("#given explicit force-on env #when hook output is formatted #then awareness remains valid hook JSON", () => {
|
|
342
|
-
// given
|
|
343
|
-
const context = [
|
|
344
|
-
"## Sparkshell Runtime",
|
|
345
|
-
"",
|
|
346
|
-
"- Prefer `omo sparkshell <command>` for shell-native inspection.",
|
|
347
|
-
].join("\n");
|
|
348
|
-
|
|
349
|
-
// when
|
|
350
|
-
const output = formatAdditionalContextOutput("SessionStart", context);
|
|
351
|
-
|
|
352
|
-
// then
|
|
353
|
-
expect(parseAdditionalContext(output)).toContain("## Sparkshell Runtime");
|
|
354
|
-
});
|
|
355
|
-
});
|