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.
Files changed (124) hide show
  1. package/.agents/skills/codex-qa/references/isolation.md +1 -1
  2. package/README.md +1 -1
  3. package/dist/cli/doctor/framework/types.d.ts +2 -0
  4. package/dist/cli/index.js +149 -1426
  5. package/dist/cli-node/index.js +149 -1426
  6. package/dist/index.js +4255 -714
  7. package/dist/plugin/native-skills.d.ts +9 -0
  8. package/dist/plugin/system-transform.d.ts +1 -1
  9. package/dist/skills/lcx-contribute-bug-fix/SKILL.md +44 -13
  10. package/dist/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
  11. package/dist/skills/lcx-doctor/SKILL.md +48 -13
  12. package/dist/skills/lcx-doctor/agents/openai.yaml +1 -1
  13. package/dist/skills/lcx-report-bug/SKILL.md +44 -13
  14. package/dist/skills/lcx-report-bug/agents/openai.yaml +1 -1
  15. package/dist/skills/visual-qa/SKILL.md +5 -5
  16. package/dist/skills/visual-qa/references/agent-browser-setup.md +5 -4
  17. package/dist/skills/visual-qa/scripts/cli.test.ts +7 -2
  18. package/dist/skills/visual-qa/scripts/skill-prompt-contract.test.ts +10 -2
  19. package/dist/skills/visual-qa/scripts/visual-qa.mjs +530 -0
  20. package/dist/tools/delegate-task/skill-resolver.d.ts +1 -0
  21. package/dist/tools/delegate-task/types.d.ts +2 -0
  22. package/dist/tools/skill/types.d.ts +1 -0
  23. package/package.json +26 -13
  24. package/packages/git-bash-mcp/dist/cli.js +2 -0
  25. package/packages/lsp-daemon/dist/cli.js +15 -4
  26. package/packages/lsp-daemon/dist/ensure-daemon.d.ts +1 -0
  27. package/packages/lsp-daemon/dist/ensure-daemon.js +9 -3
  28. package/packages/lsp-daemon/dist/index.js +15 -4
  29. package/packages/lsp-daemon/dist/paths.d.ts +1 -0
  30. package/packages/lsp-daemon/dist/paths.js +6 -1
  31. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  32. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +4 -7
  33. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  34. package/packages/omo-codex/plugin/components/bootstrap/src/setup.ts +1 -1
  35. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +123 -10
  36. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +123 -10
  37. package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
  38. package/packages/omo-codex/plugin/components/codegraph/src/mcp-bridge.ts +126 -9
  39. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +141 -0
  40. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  41. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  42. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  43. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +90 -25
  44. package/packages/omo-codex/plugin/components/lsp/dist/codex-hook.js +2 -0
  45. package/packages/omo-codex/plugin/components/lsp/dist/daemon-cli-path.d.ts +2 -0
  46. package/packages/omo-codex/plugin/components/lsp/dist/daemon-cli-path.js +56 -0
  47. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  48. package/packages/omo-codex/plugin/components/lsp/src/cli.ts +2 -5
  49. package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +2 -0
  50. package/packages/omo-codex/plugin/components/lsp/src/daemon-cli-path.ts +65 -0
  51. package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +6 -1
  52. package/packages/omo-codex/plugin/components/rules/dist/cli.js +8 -106
  53. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  54. package/packages/omo-codex/plugin/components/rules/src/static-injection.ts +4 -17
  55. package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
  56. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  57. package/packages/omo-codex/plugin/components/teammode/skills/teammode/SKILL.md +17 -5
  58. package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-state.mjs +63 -1
  59. package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team.mjs +80 -50
  60. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  61. package/packages/omo-codex/plugin/components/ultrawork/agents/explorer.toml +1 -1
  62. package/packages/omo-codex/plugin/components/ultrawork/directive.md +4 -12
  63. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  64. package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +7 -33
  65. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +4 -12
  66. package/packages/omo-codex/plugin/components/ulw-loop/package.json +4 -2
  67. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +1 -1
  68. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +4 -4
  69. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/sample-quality-gate.json +7 -7
  70. package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +3 -1
  71. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-doc.test.ts +2 -2
  72. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate.test.ts +7 -6
  73. package/packages/omo-codex/plugin/package-lock.json +16 -56
  74. package/packages/omo-codex/plugin/package.json +1 -1
  75. package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/SKILL.md +44 -13
  76. package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
  77. package/packages/omo-codex/plugin/skills/lcx-doctor/SKILL.md +48 -13
  78. package/packages/omo-codex/plugin/skills/lcx-doctor/agents/openai.yaml +1 -1
  79. package/packages/omo-codex/plugin/skills/lcx-report-bug/SKILL.md +44 -13
  80. package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +1 -1
  81. package/packages/omo-codex/plugin/skills/teammode/SKILL.md +17 -5
  82. package/packages/omo-codex/plugin/skills/teammode/scripts/team-state.mjs +63 -1
  83. package/packages/omo-codex/plugin/skills/teammode/scripts/team.mjs +80 -50
  84. package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +1 -1
  85. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +4 -4
  86. package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +5 -5
  87. package/packages/omo-codex/plugin/skills/visual-qa/references/agent-browser-setup.md +5 -4
  88. package/packages/omo-codex/plugin/skills/visual-qa/scripts/visual-qa.mjs +530 -0
  89. package/packages/omo-codex/plugin/test/bootstrap-binlinks.test.mjs +38 -2
  90. package/packages/omo-codex/plugin/test/component-bin-names.test.mjs +12 -11
  91. package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +99 -1
  92. package/packages/omo-codex/plugin/test/lcx-bug-skills.test.mjs +62 -8
  93. package/packages/omo-codex/plugin/test/node-install-surface.test.mjs +1 -0
  94. package/packages/omo-codex/plugin/test/teammode-archive-ambiguity.test.mjs +102 -0
  95. package/packages/omo-codex/plugin/test/teammode-safety-fixture.mjs +41 -1
  96. package/packages/omo-codex/plugin/test/teammode-safety.test.mjs +93 -0
  97. package/packages/omo-codex/scripts/install-bin-links.test.mjs +6 -0
  98. package/packages/omo-codex/scripts/install-cache-copy.test.mjs +28 -0
  99. package/packages/omo-codex/scripts/install-config-autonomous-features.test.mjs +43 -3
  100. package/packages/omo-codex/scripts/install-delegated-command.test.mjs +93 -0
  101. package/packages/omo-codex/scripts/install-dist/install-local.mjs +103 -28
  102. package/packages/omo-codex/scripts/install-local-entrypoint.test.mjs +21 -2
  103. package/packages/omo-codex/scripts/install-local.test.mjs +0 -1
  104. package/packages/shared-skills/skills/lcx-contribute-bug-fix/SKILL.md +44 -13
  105. package/packages/shared-skills/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
  106. package/packages/shared-skills/skills/lcx-doctor/SKILL.md +48 -13
  107. package/packages/shared-skills/skills/lcx-doctor/agents/openai.yaml +1 -1
  108. package/packages/shared-skills/skills/lcx-report-bug/SKILL.md +44 -13
  109. package/packages/shared-skills/skills/lcx-report-bug/agents/openai.yaml +1 -1
  110. package/packages/shared-skills/skills/visual-qa/SKILL.md +5 -5
  111. package/packages/shared-skills/skills/visual-qa/references/agent-browser-setup.md +5 -4
  112. package/packages/shared-skills/skills/visual-qa/scripts/cli.test.ts +7 -2
  113. package/packages/shared-skills/skills/visual-qa/scripts/skill-prompt-contract.test.ts +10 -2
  114. package/packages/shared-skills/skills/visual-qa/scripts/visual-qa.mjs +530 -0
  115. package/dist/cli/sparkshell-appserver-websocket.d.ts +0 -4
  116. package/dist/cli/sparkshell-appserver.d.ts +0 -16
  117. package/dist/cli/sparkshell-condense.d.ts +0 -10
  118. package/dist/cli/sparkshell-parse.d.ts +0 -24
  119. package/dist/cli/sparkshell-session-context.d.ts +0 -20
  120. package/dist/cli/sparkshell-spark.d.ts +0 -23
  121. package/dist/cli/sparkshell.d.ts +0 -33
  122. package/dist/shared/sparkshell-awareness.d.ts +0 -5
  123. package/packages/omo-codex/plugin/components/rules/src/sparkshell-awareness.ts +0 -106
  124. 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
- });