oh-my-opencode 4.12.0 → 4.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +36 -17
- package/dist/cli-node/index.js +36 -17
- package/dist/index.js +361 -254
- package/dist/skills/ultraresearch/SKILL.md +11 -2
- package/dist/tools/background-task/constants.d.ts +1 -1
- package/dist/tui.js +9 -3
- package/package.json +13 -13
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +3 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +327 -171
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +275 -31
- package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/src/cli.ts +11 -0
- package/packages/omo-codex/plugin/components/codegraph/src/hook-types.ts +12 -0
- package/packages/omo-codex/plugin/components/codegraph/src/hook.ts +34 -0
- package/packages/omo-codex/plugin/components/codegraph/src/serve.ts +40 -3
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker.ts +6 -5
- package/packages/omo-codex/plugin/components/codegraph/test/hook.test.ts +49 -87
- package/packages/omo-codex/plugin/components/codegraph/test/provisioned-node-guard.test.ts +1 -1
- package/packages/omo-codex/plugin/components/codegraph/test/serve-provision.test.ts +48 -0
- package/packages/omo-codex/plugin/components/codegraph/test/serve.test.ts +1 -0
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-node-support.test.ts +142 -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/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/dist/cli.js +94 -0
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +17 -0
- package/packages/omo-codex/plugin/components/teammode/package.json +26 -0
- package/packages/omo-codex/plugin/components/teammode/src/cli.ts +12 -0
- package/packages/omo-codex/plugin/components/teammode/src/codex-hook.ts +125 -0
- package/packages/omo-codex/plugin/components/teammode/test/thread-title-hook.test.ts +116 -0
- package/packages/omo-codex/plugin/components/teammode/tsconfig.json +25 -0
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +17 -0
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +17 -0
- package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +30 -12
- package/packages/omo-codex/plugin/package.json +2 -1
- package/packages/omo-codex/plugin/scripts/hook-status-message.mjs +1 -0
- package/packages/omo-codex/plugin/skills/ultraresearch/SKILL.md +11 -2
- package/packages/omo-codex/plugin/test/aggregate-hooks.test.mjs +36 -0
- package/packages/omo-codex/plugin/test/aggregate-manifest.test.mjs +2 -1
- package/packages/omo-codex/plugin/test/aggregate-plugin-fixture.mjs +1 -1
- package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +1 -0
- package/packages/omo-codex/plugin/test/component-hook-contract-cases.mjs +24 -0
- package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +1 -0
- package/packages/omo-codex/plugin/test/ultraresearch-skill-contract.test.mjs +48 -0
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +14 -1
- package/packages/shared-skills/skills/ultraresearch/SKILL.md +11 -2
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
export interface PostToolUsePayload {
|
|
2
|
+
readonly hook_event_name: "PostToolUse";
|
|
3
|
+
readonly session_id: string;
|
|
4
|
+
readonly turn_id?: string;
|
|
5
|
+
readonly transcript_path?: string | null;
|
|
6
|
+
readonly cwd?: string;
|
|
7
|
+
readonly model?: string;
|
|
8
|
+
readonly permission_mode?: string;
|
|
9
|
+
readonly tool_name: string;
|
|
10
|
+
readonly tool_use_id?: string;
|
|
11
|
+
readonly tool_input: unknown;
|
|
12
|
+
readonly tool_response: unknown;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface PostToolUseHookOutput {
|
|
16
|
+
readonly hookSpecificOutput: {
|
|
17
|
+
readonly hookEventName: "PostToolUse";
|
|
18
|
+
readonly additionalContext: string;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type ThreadCreationReference =
|
|
23
|
+
| { readonly kind: "thread"; readonly id: string }
|
|
24
|
+
| { readonly kind: "pendingWorktree"; readonly id: string };
|
|
25
|
+
|
|
26
|
+
const CREATE_THREAD_TOOL_NAMES = new Set(["create_thread", "codex_app.create_thread"]);
|
|
27
|
+
|
|
28
|
+
export function parsePostToolUsePayload(raw: string): PostToolUsePayload | null {
|
|
29
|
+
if (raw.trim().length === 0) return null;
|
|
30
|
+
try {
|
|
31
|
+
const parsed: unknown = JSON.parse(raw);
|
|
32
|
+
return isPostToolUsePayload(parsed) ? parsed : null;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
if (error instanceof SyntaxError) return null;
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function runPostToolUseHook(payload: PostToolUsePayload): string {
|
|
40
|
+
if (payload.hook_event_name !== "PostToolUse") return "";
|
|
41
|
+
if (!CREATE_THREAD_TOOL_NAMES.has(payload.tool_name)) return "";
|
|
42
|
+
const threadReference = extractThreadCreationReference(payload.tool_response);
|
|
43
|
+
if (threadReference === null) return "";
|
|
44
|
+
const output: PostToolUseHookOutput = {
|
|
45
|
+
hookSpecificOutput: {
|
|
46
|
+
hookEventName: "PostToolUse",
|
|
47
|
+
additionalContext: threadTitleReminder(threadReference),
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
return `${JSON.stringify(output)}\n`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function runTeammodeHookCli(
|
|
54
|
+
stdin: NodeJS.ReadableStream,
|
|
55
|
+
stdout: NodeJS.WritableStream,
|
|
56
|
+
): Promise<void> {
|
|
57
|
+
const payload = parsePostToolUsePayload(await readAll(stdin));
|
|
58
|
+
if (payload === null) return;
|
|
59
|
+
const output = runPostToolUseHook(payload);
|
|
60
|
+
if (output.length > 0) stdout.write(output);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function threadTitleReminder(threadReference: ThreadCreationReference): string {
|
|
64
|
+
const id = formatIdentifier(threadReference.id);
|
|
65
|
+
return threadReference.kind === "thread"
|
|
66
|
+
? `THREAD ID ${id}: CALL codex_app.set_thread_title NOW. USE THE REAL TASK/ROLE.`
|
|
67
|
+
: `PENDING WORKTREE ID ${id}: CALL codex_app.set_thread_title AS SOON AS THREAD ID EXISTS. USE THE REAL TASK/ROLE.`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function formatIdentifier(value: string): string {
|
|
71
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
72
|
+
return normalized.length <= 200 ? normalized : `${normalized.slice(0, 197)}...`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function extractThreadCreationReference(toolResponse: unknown): ThreadCreationReference | null {
|
|
76
|
+
if (!isRecord(toolResponse)) return null;
|
|
77
|
+
const threadId = toolResponse["threadId"];
|
|
78
|
+
if (typeof threadId === "string" && threadId.trim().length > 0) {
|
|
79
|
+
return { kind: "thread", id: threadId };
|
|
80
|
+
}
|
|
81
|
+
const pendingWorktreeId = toolResponse["pendingWorktreeId"];
|
|
82
|
+
if (typeof pendingWorktreeId === "string" && pendingWorktreeId.trim().length > 0) {
|
|
83
|
+
return { kind: "pendingWorktree", id: pendingWorktreeId };
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function isPostToolUsePayload(value: unknown): value is PostToolUsePayload {
|
|
89
|
+
if (!isRecord(value)) return false;
|
|
90
|
+
return (
|
|
91
|
+
value["hook_event_name"] === "PostToolUse" &&
|
|
92
|
+
typeof value["session_id"] === "string" &&
|
|
93
|
+
typeof value["tool_name"] === "string" &&
|
|
94
|
+
Object.hasOwn(value, "tool_input") &&
|
|
95
|
+
Object.hasOwn(value, "tool_response") &&
|
|
96
|
+
optionalString(value["turn_id"]) &&
|
|
97
|
+
optionalString(value["cwd"]) &&
|
|
98
|
+
optionalString(value["model"]) &&
|
|
99
|
+
optionalString(value["permission_mode"]) &&
|
|
100
|
+
optionalString(value["tool_use_id"]) &&
|
|
101
|
+
(value["transcript_path"] === undefined ||
|
|
102
|
+
value["transcript_path"] === null ||
|
|
103
|
+
typeof value["transcript_path"] === "string")
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function optionalString(value: unknown): boolean {
|
|
108
|
+
return value === undefined || typeof value === "string";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
112
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function readAll(stdin: NodeJS.ReadableStream): Promise<string> {
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
let data = "";
|
|
118
|
+
stdin.setEncoding("utf8");
|
|
119
|
+
stdin.on("data", (chunk: unknown) => {
|
|
120
|
+
data += chunk instanceof Buffer ? chunk.toString() : String(chunk);
|
|
121
|
+
});
|
|
122
|
+
stdin.once("error", reject);
|
|
123
|
+
stdin.once("end", () => resolve(data));
|
|
124
|
+
});
|
|
125
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { runPostToolUseHook } from "../src/codex-hook.js";
|
|
4
|
+
|
|
5
|
+
describe("thread title PostToolUse guidance", () => {
|
|
6
|
+
it("#given codex_app.create_thread completed #when the hook runs #then it asks Codex to immediately set a descriptive title", () => {
|
|
7
|
+
// given
|
|
8
|
+
const output = runPostToolUseHook({
|
|
9
|
+
hook_event_name: "PostToolUse",
|
|
10
|
+
session_id: "s-team",
|
|
11
|
+
turn_id: "t-team",
|
|
12
|
+
transcript_path: null,
|
|
13
|
+
cwd: "/repo",
|
|
14
|
+
model: "gpt-5.5",
|
|
15
|
+
permission_mode: "default",
|
|
16
|
+
tool_name: "create_thread",
|
|
17
|
+
tool_use_id: "tool-create-thread",
|
|
18
|
+
tool_input: {
|
|
19
|
+
prompt: "Investigate package install failures",
|
|
20
|
+
target: { type: "project", projectId: "/repo", environment: { type: "local" } },
|
|
21
|
+
},
|
|
22
|
+
tool_response: { threadId: "thread-123" },
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// when
|
|
26
|
+
const parsed: unknown = JSON.parse(output);
|
|
27
|
+
|
|
28
|
+
// then
|
|
29
|
+
expect(isHookOutput(parsed)).toBe(true);
|
|
30
|
+
if (!isHookOutput(parsed)) return;
|
|
31
|
+
expect(parsed.hookSpecificOutput.hookEventName).toBe("PostToolUse");
|
|
32
|
+
expect(parsed.hookSpecificOutput.additionalContext).toBe(
|
|
33
|
+
"THREAD ID thread-123: CALL codex_app.set_thread_title NOW. USE THE REAL TASK/ROLE.",
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("#given an unrelated tool completed #when the hook runs #then it stays silent", () => {
|
|
38
|
+
// given
|
|
39
|
+
const output = runPostToolUseHook({
|
|
40
|
+
hook_event_name: "PostToolUse",
|
|
41
|
+
session_id: "s-team",
|
|
42
|
+
turn_id: "t-team",
|
|
43
|
+
transcript_path: null,
|
|
44
|
+
cwd: "/repo",
|
|
45
|
+
model: "gpt-5.5",
|
|
46
|
+
permission_mode: "default",
|
|
47
|
+
tool_name: "read_thread",
|
|
48
|
+
tool_use_id: "tool-read-thread",
|
|
49
|
+
tool_input: { threadId: "thread-123" },
|
|
50
|
+
tool_response: { status: "ok" },
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// when
|
|
54
|
+
const actual = output;
|
|
55
|
+
|
|
56
|
+
// then
|
|
57
|
+
expect(actual).toBe("");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("#given worktree-backed thread creation is pending #when the hook runs #then it tells Codex to title the thread once the thread id exists", () => {
|
|
61
|
+
// given
|
|
62
|
+
const output = runPostToolUseHook({
|
|
63
|
+
hook_event_name: "PostToolUse",
|
|
64
|
+
session_id: "s-team",
|
|
65
|
+
turn_id: "t-team",
|
|
66
|
+
transcript_path: null,
|
|
67
|
+
cwd: "/repo",
|
|
68
|
+
model: "gpt-5.5",
|
|
69
|
+
permission_mode: "default",
|
|
70
|
+
tool_name: "codex_app.create_thread",
|
|
71
|
+
tool_use_id: "tool-create-thread",
|
|
72
|
+
tool_input: {
|
|
73
|
+
prompt: "Fix CodeGraph provisioned launcher skip on Node 25",
|
|
74
|
+
target: {
|
|
75
|
+
type: "project",
|
|
76
|
+
projectId: "/repo",
|
|
77
|
+
environment: { type: "worktree", startingState: { type: "working-tree" } },
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
tool_response: {
|
|
81
|
+
pendingWorktreeId: "remote-control:env:test-worktree",
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// when
|
|
86
|
+
const parsed: unknown = JSON.parse(output);
|
|
87
|
+
|
|
88
|
+
// then
|
|
89
|
+
expect(isHookOutput(parsed)).toBe(true);
|
|
90
|
+
if (!isHookOutput(parsed)) return;
|
|
91
|
+
expect(parsed.hookSpecificOutput.additionalContext).toBe(
|
|
92
|
+
"PENDING WORKTREE ID remote-control:env:test-worktree: CALL codex_app.set_thread_title AS SOON AS THREAD ID EXISTS. USE THE REAL TASK/ROLE.",
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
interface HookOutput {
|
|
98
|
+
readonly hookSpecificOutput: {
|
|
99
|
+
readonly hookEventName: "PostToolUse";
|
|
100
|
+
readonly additionalContext: string;
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function isHookOutput(value: unknown): value is HookOutput {
|
|
105
|
+
if (!isRecord(value)) return false;
|
|
106
|
+
const hookSpecificOutput = value["hookSpecificOutput"];
|
|
107
|
+
return (
|
|
108
|
+
isRecord(hookSpecificOutput) &&
|
|
109
|
+
hookSpecificOutput["hookEventName"] === "PostToolUse" &&
|
|
110
|
+
typeof hookSpecificOutput["additionalContext"] === "string"
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
115
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
116
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "Node16",
|
|
5
|
+
"moduleResolution": "Node16",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"strict": true,
|
|
8
|
+
"exactOptionalPropertyTypes": true,
|
|
9
|
+
"noUncheckedIndexedAccess": true,
|
|
10
|
+
"noPropertyAccessFromIndexSignature": true,
|
|
11
|
+
"verbatimModuleSyntax": true,
|
|
12
|
+
"noImplicitOverride": true,
|
|
13
|
+
"noImplicitReturns": true,
|
|
14
|
+
"noFallthroughCasesInSwitch": true,
|
|
15
|
+
"noUnusedLocals": true,
|
|
16
|
+
"noUnusedParameters": true,
|
|
17
|
+
"esModuleInterop": true,
|
|
18
|
+
"allowImportingTsExtensions": true,
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
"forceConsistentCasingInFileNames": true,
|
|
21
|
+
"types": ["node", "bun-types"],
|
|
22
|
+
"noEmit": true
|
|
23
|
+
},
|
|
24
|
+
"include": ["src/**/*", "test/**/*"]
|
|
25
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/codex-ultrawork",
|
|
3
|
-
"version": "4.12.
|
|
3
|
+
"version": "4.12.1",
|
|
4
4
|
"description": "Codex plugin that injects the ultrawork orchestration directive and ships LazyCodex planning, review, QA, and gate agent roles.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "npm@11.12.1",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/codex-ulw-loop",
|
|
3
|
-
"version": "4.12.
|
|
3
|
+
"version": "4.12.1",
|
|
4
4
|
"description": "Codex plugin: durable repo-native multi-goal orchestration with embedded success criteria and observable evidence audit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "npm@11.12.1",
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PostToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"hooks": [
|
|
6
|
+
{
|
|
7
|
+
"type": "command",
|
|
8
|
+
"command": "node \"${PLUGIN_ROOT}/components/codegraph/dist/cli.js\" hook post-tool-use",
|
|
9
|
+
"timeout": 5,
|
|
10
|
+
"statusMessage": "(OmO) Checking CodeGraph Init Guidance"
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"matcher": "^(codegraph[._].*|mcp__codegraph__.*)$"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PostToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "^(create_thread|codex_app\\.create_thread)$",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "node \"${PLUGIN_ROOT}/components/teammode/dist/cli.js\" hook post-tool-use",
|
|
10
|
+
"timeout": 10,
|
|
11
|
+
"statusMessage": "(OmO) Checking Thread Title Hygiene"
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sisyphuslabs/omo-codex-plugin",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.12.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@sisyphuslabs/omo-codex-plugin",
|
|
9
|
-
"version": "4.
|
|
9
|
+
"version": "4.12.0",
|
|
10
10
|
"workspaces": [
|
|
11
11
|
"components/codegraph",
|
|
12
12
|
"components/comment-checker",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"components/rules",
|
|
16
16
|
"components/lsp",
|
|
17
17
|
"components/telemetry",
|
|
18
|
+
"components/teammode",
|
|
18
19
|
"components/start-work-continuation",
|
|
19
20
|
"components/ulw-loop",
|
|
20
21
|
"components/ultrawork"
|
|
@@ -92,7 +93,7 @@
|
|
|
92
93
|
},
|
|
93
94
|
"components/codegraph": {
|
|
94
95
|
"name": "@sisyphuslabs/codex-codegraph",
|
|
95
|
-
"version": "4.
|
|
96
|
+
"version": "4.12.0",
|
|
96
97
|
"bin": {
|
|
97
98
|
"omo-codegraph": "dist/cli.js"
|
|
98
99
|
},
|
|
@@ -108,7 +109,7 @@
|
|
|
108
109
|
},
|
|
109
110
|
"components/comment-checker": {
|
|
110
111
|
"name": "@code-yeongyu/codex-comment-checker",
|
|
111
|
-
"version": "4.
|
|
112
|
+
"version": "4.12.0",
|
|
112
113
|
"license": "MIT",
|
|
113
114
|
"bin": {
|
|
114
115
|
"omo-comment-checker": "dist/cli.js"
|
|
@@ -129,7 +130,7 @@
|
|
|
129
130
|
},
|
|
130
131
|
"components/git-bash": {
|
|
131
132
|
"name": "@sisyphuslabs/codex-git-bash-hook",
|
|
132
|
-
"version": "4.
|
|
133
|
+
"version": "4.12.0",
|
|
133
134
|
"bin": {
|
|
134
135
|
"omo-git-bash-hook": "dist/cli.js"
|
|
135
136
|
},
|
|
@@ -144,7 +145,7 @@
|
|
|
144
145
|
},
|
|
145
146
|
"components/lazycodex-executor-verify": {
|
|
146
147
|
"name": "@code-yeongyu/codex-lazycodex-executor-verify",
|
|
147
|
-
"version": "4.
|
|
148
|
+
"version": "4.12.0",
|
|
148
149
|
"license": "MIT",
|
|
149
150
|
"bin": {
|
|
150
151
|
"lazycodex-executor-verify": "dist/cli.js"
|
|
@@ -161,7 +162,7 @@
|
|
|
161
162
|
},
|
|
162
163
|
"components/lsp": {
|
|
163
164
|
"name": "@code-yeongyu/codex-lsp",
|
|
164
|
-
"version": "4.
|
|
165
|
+
"version": "4.12.0",
|
|
165
166
|
"license": "MIT",
|
|
166
167
|
"dependencies": {
|
|
167
168
|
"@code-yeongyu/lsp-daemon": "file:../../../../lsp-daemon"
|
|
@@ -181,7 +182,7 @@
|
|
|
181
182
|
},
|
|
182
183
|
"components/rules": {
|
|
183
184
|
"name": "@code-yeongyu/codex-rules",
|
|
184
|
-
"version": "4.
|
|
185
|
+
"version": "4.12.0",
|
|
185
186
|
"license": "MIT",
|
|
186
187
|
"dependencies": {
|
|
187
188
|
"picomatch": "^4.0.3"
|
|
@@ -203,7 +204,7 @@
|
|
|
203
204
|
},
|
|
204
205
|
"components/start-work-continuation": {
|
|
205
206
|
"name": "@code-yeongyu/codex-start-work-continuation",
|
|
206
|
-
"version": "4.
|
|
207
|
+
"version": "4.12.0",
|
|
207
208
|
"license": "MIT",
|
|
208
209
|
"bin": {
|
|
209
210
|
"omo-start-work-continuation": "dist/cli.js"
|
|
@@ -218,9 +219,22 @@
|
|
|
218
219
|
"node": ">=20.0.0"
|
|
219
220
|
}
|
|
220
221
|
},
|
|
222
|
+
"components/teammode": {
|
|
223
|
+
"name": "@sisyphuslabs/codex-teammode",
|
|
224
|
+
"version": "4.12.0",
|
|
225
|
+
"devDependencies": {
|
|
226
|
+
"@types/node": "^25.9.3",
|
|
227
|
+
"bun-types": "^1.3.1",
|
|
228
|
+
"typescript": "^6.0.3",
|
|
229
|
+
"vitest": "^4.1.8"
|
|
230
|
+
},
|
|
231
|
+
"engines": {
|
|
232
|
+
"node": ">=20.0.0"
|
|
233
|
+
}
|
|
234
|
+
},
|
|
221
235
|
"components/telemetry": {
|
|
222
236
|
"name": "@code-yeongyu/codex-telemetry",
|
|
223
|
-
"version": "4.
|
|
237
|
+
"version": "4.12.0",
|
|
224
238
|
"license": "MIT",
|
|
225
239
|
"bin": {
|
|
226
240
|
"omo-telemetry": "dist/cli.js"
|
|
@@ -238,7 +252,7 @@
|
|
|
238
252
|
},
|
|
239
253
|
"components/ultrawork": {
|
|
240
254
|
"name": "@code-yeongyu/codex-ultrawork",
|
|
241
|
-
"version": "4.
|
|
255
|
+
"version": "4.12.0",
|
|
242
256
|
"license": "MIT",
|
|
243
257
|
"bin": {
|
|
244
258
|
"omo-ultrawork": "dist/cli.js"
|
|
@@ -256,7 +270,7 @@
|
|
|
256
270
|
},
|
|
257
271
|
"components/ulw-loop": {
|
|
258
272
|
"name": "@code-yeongyu/codex-ulw-loop",
|
|
259
|
-
"version": "4.
|
|
273
|
+
"version": "4.12.0",
|
|
260
274
|
"license": "MIT",
|
|
261
275
|
"bin": {
|
|
262
276
|
"omo-ulw-loop": "dist/cli.js"
|
|
@@ -880,6 +894,10 @@
|
|
|
880
894
|
"resolved": "components/git-bash",
|
|
881
895
|
"link": true
|
|
882
896
|
},
|
|
897
|
+
"node_modules/@sisyphuslabs/codex-teammode": {
|
|
898
|
+
"resolved": "components/teammode",
|
|
899
|
+
"link": true
|
|
900
|
+
},
|
|
883
901
|
"node_modules/@standard-schema/spec": {
|
|
884
902
|
"version": "1.1.0",
|
|
885
903
|
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sisyphuslabs/omo-codex-plugin",
|
|
3
|
-
"version": "4.12.
|
|
3
|
+
"version": "4.12.1",
|
|
4
4
|
"description": "Aggregate Codex plugin root for OMO components.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "npm@11.12.1",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"components/rules",
|
|
14
14
|
"components/lsp",
|
|
15
15
|
"components/telemetry",
|
|
16
|
+
"components/teammode",
|
|
16
17
|
"components/start-work-continuation",
|
|
17
18
|
"components/ulw-loop",
|
|
18
19
|
"components/ultrawork"
|
|
@@ -49,6 +49,14 @@ The research is done when all of these hold:
|
|
|
49
49
|
- Every claim in the deliverable cites a source or a verification artifact.
|
|
50
50
|
- The session journal reconstructs what was searched, found, and expanded, wave by wave.
|
|
51
51
|
|
|
52
|
+
## Run the swarm as a cooperating team
|
|
53
|
+
|
|
54
|
+
Saturation research is the textbook case for a cooperating team, not isolated fire-and-forget workers: a lead one worker surfaces almost always reshapes what another should search next. So when your harness gives you real cooperating members — Codex: the `teammode` skill (`codex_app` threads); OpenCode: `team_mode` — run this swarm as a team. Fall back to the background-worker swarm below only when team mode is unavailable, or the axes are genuinely independent with no cross-pollination expected.
|
|
55
|
+
|
|
56
|
+
- **One member per axis — by part, ownership, or perspective, never a job title.** Each Phase 0 axis is one member owning one concrete slice: a codebase part, a source territory, or a question lens. No two members share an angle. "Backend researcher" or "the web person" gives no real boundary and invites overlap — name what the member owns.
|
|
57
|
+
- **The raise law — broadcast every lead the instant it surfaces.** Members over-communicate relentlessly: every new lead, finding, contradiction, and dead end is raised to you the moment it surfaces, never hoarded for a final dump. Through long passes they send `WORKING: <axis> - <phase>`, and `BLOCKED: <reason>` the moment progress stops, so you always know a member is alive. Too many small updates is correct here; going quiet is the only failure.
|
|
58
|
+
- **You lead; expand on each raised lead.** Members raise via message text, never write session files. Journal each lead and spawn its expansion the instant it lands (Phase 2), not only when a member's final reply arrives.
|
|
59
|
+
|
|
52
60
|
## Worker ground rules
|
|
53
61
|
|
|
54
62
|
Research workers (explore, librarian, browsing) differ by harness, but assume:
|
|
@@ -104,7 +112,7 @@ Append each digest the moment its worker returns, not in a batch at the end —
|
|
|
104
112
|
|
|
105
113
|
## Phase 1 — Saturation wave
|
|
106
114
|
|
|
107
|
-
Launch
|
|
115
|
+
Launch the entire first wave in one turn — every axis at once, as team members if you formed a team, else as background workers. Sequential launches and "start with one and see" defeat the mode.
|
|
108
116
|
|
|
109
117
|
Scaling floor — more angles always justify more workers:
|
|
110
118
|
|
|
@@ -135,7 +143,7 @@ End your reply with the ## EXPAND tail: '- LEAD: <discovery> — WHY: <why> —
|
|
|
135
143
|
|
|
136
144
|
## Phase 2 — Expand until convergence
|
|
137
145
|
|
|
138
|
-
This loop is what makes the mode research rather than search. Collect
|
|
146
|
+
This loop is what makes the mode research rather than search. Collect returns as they land — and in team mode, act on each lead the moment a member raises it, never waiting for the full wave or a member's final reply:
|
|
139
147
|
|
|
140
148
|
1. Journal the return: digest plus verbatim EXPAND markers into `wave-<N>-<kind>-<axis>.md`.
|
|
141
149
|
2. Deduplicate new markers against `expansion-log.md` — every lead ever seen, not just confirmed ones, or rejected leads resurface each wave.
|
|
@@ -219,6 +227,7 @@ High-yield combinations: official docs (`site:<docs domain>`), GitHub implementa
|
|
|
219
227
|
| Failure | Correction |
|
|
220
228
|
|---|---|
|
|
221
229
|
| Sequential spawning, or trimming the first wave | All first-wave workers in one turn, background, scaling floor respected |
|
|
230
|
+
| A team member hoards leads for one final dump | Raise law — every lead, finding, and dead end broadcast the moment it surfaces |
|
|
222
231
|
| Worker reply without the EXPAND tail | One follow-up demanding it; the lane stays open until it lands |
|
|
223
232
|
| Stopping after wave 1 because "enough was found" | Convergence rules only: 2+ expansion waves, leads run dry |
|
|
224
233
|
| Obeying a surrounding "stop exploring" rule mid-research | Authority section — those rules do not bind this mode |
|
|
@@ -30,9 +30,11 @@ test("#given isolated components #when hooks are inspected #then commands stay i
|
|
|
30
30
|
const componentMarkers = [
|
|
31
31
|
"components/comment-checker/dist/cli.js",
|
|
32
32
|
"components/lsp/dist/cli.js",
|
|
33
|
+
"components/codegraph/dist/cli.js",
|
|
33
34
|
"components/rules/dist/cli.js",
|
|
34
35
|
"components/start-work-continuation/dist/cli.js",
|
|
35
36
|
"components/telemetry/dist/cli.js",
|
|
37
|
+
"components/teammode/dist/cli.js",
|
|
36
38
|
"components/ulw-loop/dist/cli.js",
|
|
37
39
|
"components/ultrawork/dist/cli.js",
|
|
38
40
|
"scripts/auto-update.mjs",
|
|
@@ -179,6 +181,40 @@ test("#given aggregate SessionStart hooks #when inspected #then LazyCodex auto-u
|
|
|
179
181
|
assert(sessionStartCommands.some((command) => command.includes("scripts/auto-update.mjs")));
|
|
180
182
|
});
|
|
181
183
|
|
|
184
|
+
test("#given aggregate PostToolUse hooks #when inspected #then CodeGraph init guidance is registered for CodeGraph tools", async () => {
|
|
185
|
+
// given
|
|
186
|
+
const commandHooks = await readAggregateCommandHooks();
|
|
187
|
+
|
|
188
|
+
// when
|
|
189
|
+
const codegraphPostToolUseHooks = commandHooks.filter(
|
|
190
|
+
(hook) =>
|
|
191
|
+
hook.eventName === "PostToolUse" &&
|
|
192
|
+
hook.handler.command === 'node "${PLUGIN_ROOT}/components/codegraph/dist/cli.js" hook post-tool-use',
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// then
|
|
196
|
+
assert.equal(codegraphPostToolUseHooks.length, 1);
|
|
197
|
+
assert.equal(codegraphPostToolUseHooks[0]?.matcher, "^(codegraph[._].*|mcp__codegraph__.*)$");
|
|
198
|
+
assert.equal(codegraphPostToolUseHooks[0]?.handler.statusMessage, "(OmO) Checking CodeGraph Init Guidance");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("#given aggregate PostToolUse hooks #when inspected #then thread title hygiene is registered for created Codex threads", async () => {
|
|
202
|
+
// given
|
|
203
|
+
const commandHooks = await readAggregateCommandHooks();
|
|
204
|
+
|
|
205
|
+
// when
|
|
206
|
+
const threadTitleHooks = commandHooks.filter(
|
|
207
|
+
(hook) =>
|
|
208
|
+
hook.eventName === "PostToolUse" &&
|
|
209
|
+
hook.handler.command === 'node "${PLUGIN_ROOT}/components/teammode/dist/cli.js" hook post-tool-use',
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
// then
|
|
213
|
+
assert.equal(threadTitleHooks.length, 1);
|
|
214
|
+
assert.equal(threadTitleHooks[0]?.matcher, "^(create_thread|codex_app\\.create_thread)$");
|
|
215
|
+
assert.equal(threadTitleHooks[0]?.handler.statusMessage, "(OmO) Checking Thread Title Hygiene");
|
|
216
|
+
});
|
|
217
|
+
|
|
182
218
|
test("#given aggregate plugin packaging #when inspected #then hooks and compatibility sentinels stay Python-free", async () => {
|
|
183
219
|
// given
|
|
184
220
|
const hooksText = (await Promise.all((await readAggregateHookManifests()).map(({ source }) => readFile(join(root, source), "utf8")))).join("\n");
|
|
@@ -17,7 +17,7 @@ test("#given aggregate plugin manifest #when inspected #then it owns the omo nam
|
|
|
17
17
|
// then
|
|
18
18
|
assert.equal(manifest.name, "omo");
|
|
19
19
|
assert(Array.isArray(hookPaths));
|
|
20
|
-
assert.equal(hookPaths.length,
|
|
20
|
+
assert.equal(hookPaths.length, 21);
|
|
21
21
|
assert(hookPaths.every((hookPath) => typeof hookPath === "string" && hookPath.startsWith("./hooks/")));
|
|
22
22
|
assert(!hookPaths.includes("./hooks/hooks.json"));
|
|
23
23
|
assert.equal(skillsPath, "./skills/");
|
|
@@ -60,6 +60,7 @@ test("#given component directories #when scanned #then only intentional resource
|
|
|
60
60
|
"lsp",
|
|
61
61
|
"rules",
|
|
62
62
|
"start-work-continuation",
|
|
63
|
+
"teammode",
|
|
63
64
|
"telemetry",
|
|
64
65
|
"ultrawork",
|
|
65
66
|
"ulw-loop",
|
|
@@ -68,7 +68,7 @@ export function collectCommandHooks(hooks, source) {
|
|
|
68
68
|
}
|
|
69
69
|
group.hooks.forEach((handler, handlerIndex) => {
|
|
70
70
|
if (typeof handler !== "object" || handler === null || handler.type !== "command") return;
|
|
71
|
-
commandHooks.push({ source, eventName, groupIndex, handlerIndex, handler });
|
|
71
|
+
commandHooks.push({ source, eventName, groupIndex, handlerIndex, matcher: group.matcher, handler });
|
|
72
72
|
});
|
|
73
73
|
});
|
|
74
74
|
}
|
|
@@ -138,6 +138,30 @@ export function componentHookContractCases(tempRoot) {
|
|
|
138
138
|
assert.equal(stdout, "");
|
|
139
139
|
},
|
|
140
140
|
},
|
|
141
|
+
{
|
|
142
|
+
name: "teammode post-tool-use create-thread title reminder",
|
|
143
|
+
component: "teammode",
|
|
144
|
+
event: "post-tool-use",
|
|
145
|
+
payload: {
|
|
146
|
+
hook_event_name: "PostToolUse",
|
|
147
|
+
session_id: "s-task12",
|
|
148
|
+
turn_id: "t-task12",
|
|
149
|
+
transcript_path: null,
|
|
150
|
+
cwd: tempRoot,
|
|
151
|
+
model: "gpt-5.5",
|
|
152
|
+
permission_mode: "default",
|
|
153
|
+
tool_name: "create_thread",
|
|
154
|
+
tool_use_id: "tool-task12",
|
|
155
|
+
tool_input: { prompt: "Investigate flaky release packaging" },
|
|
156
|
+
tool_response: { threadId: "thread-task12" },
|
|
157
|
+
},
|
|
158
|
+
assertOutput(stdout) {
|
|
159
|
+
const output = JSON.parse(stdout);
|
|
160
|
+
assert.equal(output.hookSpecificOutput.hookEventName, "PostToolUse");
|
|
161
|
+
assert.match(output.hookSpecificOutput.additionalContext, /codex_app\.set_thread_title/);
|
|
162
|
+
assert.match(output.hookSpecificOutput.additionalContext, /thread-task12/);
|
|
163
|
+
},
|
|
164
|
+
},
|
|
141
165
|
{
|
|
142
166
|
name: "lsp post-compact reset",
|
|
143
167
|
component: "lsp",
|