oh-my-opencode 5.1.12 → 5.1.14
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 +45 -18
- package/dist/cli-node/index.js +45 -18
- package/dist/features/background-agent/fallback-provider-model-filter.d.ts +12 -0
- package/dist/features/background-agent/manager.d.ts +1 -0
- package/dist/features/background-agent/session-stopped-on-error.d.ts +9 -0
- package/dist/features/background-agent/types.d.ts +2 -0
- package/dist/index.js +173 -29
- package/dist/oh-my-opencode.schema.json +0 -107
- package/dist/shared/posthog.d.ts +1 -0
- package/dist/skills/browser/SKILL.md +26 -2
- package/dist/skills/browser/references/commands.md +10 -1
- package/dist/skills/browser/references/install.md +7 -0
- package/dist/skills/browser/runtime/omowright/manifest.json +1 -1
- package/dist/skills/browser/scripts/browser-engine-guard.mjs +506 -0
- package/dist/skills/browser/scripts/omowright.mjs +29 -2
- package/dist/tui.js +16 -16
- package/package.json +14 -14
- package/packages/lsp-core/src/post-edit/not-installed.test.ts +2 -2
- package/packages/lsp-core/src/post-edit/orchestration.test.ts +13 -13
- package/packages/lsp-core/src/post-edit/orchestration.ts +9 -3
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +4 -4
- package/packages/omo-codex/plugin/components/lsp/dist/cli.js +33 -19
- package/packages/omo-codex/plugin/components/lsp/dist/codex-hook.js +27 -17
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +36 -18
- package/packages/omo-codex/plugin/components/lsp/test/codex-hook-not-installed.test.ts +203 -0
- package/packages/omo-codex/plugin/components/rules/CHANGELOG.md +1 -0
- package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-5.5.md +1 -1
- package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-5.6.md +1 -1
- package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-6.md +2 -2
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +89 -28
- package/packages/omo-codex/plugin/components/ulw-loop/dist/registered-agent-roles.d.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/registered-agent-roles.js +61 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.js +2 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-role-guard.d.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-role-guard.js +6 -2
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/registered-agent-roles.ts +60 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-guard.ts +2 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-role-guard.ts +8 -2
- package/packages/omo-codex/plugin/components/ulw-loop/test/spawn-role-matrix.test.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/spawn-role-registered.test.ts +128 -0
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +12 -12
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/skills/browser/SKILL.md +26 -2
- package/packages/omo-codex/plugin/skills/browser/references/commands.md +10 -1
- package/packages/omo-codex/plugin/skills/browser/references/install.md +7 -0
- package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
- package/packages/omo-codex/plugin/skills/browser/scripts/browser-engine-guard.mjs +506 -0
- package/packages/omo-codex/plugin/skills/browser/scripts/omowright.mjs +29 -2
- package/packages/omo-codex/plugin/test/aggregate-plugin-fixture.mjs +4 -3
- package/packages/omo-codex/plugin/test/aggregate.test.mjs +2 -2
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +2 -2
- package/packages/shared-skills/skills/browser/SKILL.md +26 -2
- package/packages/shared-skills/skills/browser/references/commands.md +10 -1
- package/packages/shared-skills/skills/browser/references/install.md +7 -0
- package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
- package/packages/shared-skills/skills/browser/scripts/browser-engine-guard.mjs +506 -0
- package/packages/shared-skills/skills/browser/scripts/omowright.mjs +29 -2
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
|
|
6
|
+
import { runLspPostToolUseHook } from "../src/codex-hook.js";
|
|
7
|
+
|
|
8
|
+
interface DaemonResult {
|
|
9
|
+
readonly content: readonly { readonly type: string; readonly text?: string }[];
|
|
10
|
+
readonly details?: unknown;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const daemonResults = vi.hoisted(() => new Map<string, DaemonResult>());
|
|
14
|
+
|
|
15
|
+
vi.mock("@code-yeongyu/lsp-daemon/client", async (importOriginal) => ({
|
|
16
|
+
...(await importOriginal<Record<string, unknown>>()),
|
|
17
|
+
callDiagnosticsViaDaemon: async (filePath: string) => {
|
|
18
|
+
const result = daemonResults.get(filePath);
|
|
19
|
+
if (result === undefined) throw new Error(`unexpected diagnostics request for ${filePath}`);
|
|
20
|
+
return result;
|
|
21
|
+
},
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
const UNDECIDED_TEXT = [
|
|
25
|
+
"LSP server 'biome' for .json, .jsonc is NOT INSTALLED.",
|
|
26
|
+
"",
|
|
27
|
+
"Command not found: biome",
|
|
28
|
+
"",
|
|
29
|
+
"To install, run:",
|
|
30
|
+
" npm install -g @biomejs/biome",
|
|
31
|
+
].join("\n");
|
|
32
|
+
const DECLINED_TEXT =
|
|
33
|
+
"LSP server 'biome' (.json, .jsonc) is NOT INSTALLED; user previously declined installation — proceed without LSP.";
|
|
34
|
+
const TS_ERROR = "error[typescript] (2322) at 1:7: Type 'string' is not assignable to type 'number'.";
|
|
35
|
+
|
|
36
|
+
const tempDirs: string[] = [];
|
|
37
|
+
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
daemonResults.clear();
|
|
40
|
+
for (const tempDir of tempDirs.splice(0)) rmSync(tempDir, { recursive: true, force: true });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("codex PostToolUse hook with a language server that is not installed", () => {
|
|
44
|
+
it("#given biome is not installed and the user has not decided #when a .json file is edited #then the install guidance is a note, not a block", async () => {
|
|
45
|
+
// given
|
|
46
|
+
daemonAnswering({ "config.json": biomeNotInstalled(null, UNDECIDED_TEXT) });
|
|
47
|
+
|
|
48
|
+
// when
|
|
49
|
+
const output = await withPluginData(() => runLspPostToolUseHook(editOf("s-undecided", "config.json")));
|
|
50
|
+
|
|
51
|
+
// then
|
|
52
|
+
const parsed = parseHookOutput(output);
|
|
53
|
+
expect(parsed["decision"]).toBeUndefined();
|
|
54
|
+
expect(parsed["reason"]).toBeUndefined();
|
|
55
|
+
expect(additionalContext(parsed)).toContain("npm install -g @biomejs/biome");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("#given the user declined biome #when a .json file is edited #then the hook does not block", async () => {
|
|
59
|
+
// given
|
|
60
|
+
daemonAnswering({ "config.json": biomeNotInstalled("declined", DECLINED_TEXT) });
|
|
61
|
+
|
|
62
|
+
// when
|
|
63
|
+
const output = await withPluginData(() => runLspPostToolUseHook(editOf("s-declined", "config.json")));
|
|
64
|
+
|
|
65
|
+
// then
|
|
66
|
+
const parsed = parseHookOutput(output);
|
|
67
|
+
expect(parsed["decision"]).toBeUndefined();
|
|
68
|
+
expect(additionalContext(parsed)).toContain("proceed without LSP");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("#given biome is not installed #when the same session edits .json twice #then neither edit is blocked", async () => {
|
|
72
|
+
// given
|
|
73
|
+
daemonAnswering({ "config.json": biomeNotInstalled(null, UNDECIDED_TEXT) });
|
|
74
|
+
|
|
75
|
+
// when
|
|
76
|
+
const outputs = await withPluginData(async () => [
|
|
77
|
+
await runLspPostToolUseHook(editOf("s-repeat", "config.json")),
|
|
78
|
+
await runLspPostToolUseHook(editOf("s-repeat", "config.json")),
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
// then
|
|
82
|
+
for (const output of outputs) expect(parseHookOutput(output)["decision"]).toBeUndefined();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("#given one edit touches a .json without biome and a .ts with a type error #when the hook runs #then the type error still blocks", async () => {
|
|
86
|
+
// given
|
|
87
|
+
daemonAnswering({
|
|
88
|
+
"config.json": biomeNotInstalled(null, UNDECIDED_TEXT),
|
|
89
|
+
"src/value.ts": diagnosticsText(TS_ERROR),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// when
|
|
93
|
+
const output = await withPluginData(() =>
|
|
94
|
+
runLspPostToolUseHook(patchOf("s-mixed", ["config.json", "src/value.ts"])),
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// then
|
|
98
|
+
const parsed = parseHookOutput(output);
|
|
99
|
+
expect(parsed["decision"]).toBe("block");
|
|
100
|
+
expect(parsed["reason"]).toContain(TS_ERROR);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("#given only a real type error #when a .ts file is edited #then the hook blocks with the diagnostic", async () => {
|
|
104
|
+
// given
|
|
105
|
+
daemonAnswering({ "src/value.ts": diagnosticsText(TS_ERROR) });
|
|
106
|
+
|
|
107
|
+
// when
|
|
108
|
+
const output = await withPluginData(() => runLspPostToolUseHook(editOf("s-real", "src/value.ts")));
|
|
109
|
+
|
|
110
|
+
// then
|
|
111
|
+
const parsed = parseHookOutput(output);
|
|
112
|
+
expect(parsed["decision"]).toBe("block");
|
|
113
|
+
expect(parsed["reason"]).toContain(TS_ERROR);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
function daemonAnswering(results: Readonly<Record<string, DaemonResult>>): void {
|
|
118
|
+
for (const [filePath, result] of Object.entries(results)) daemonResults.set(filePath, result);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function biomeNotInstalled(decision: "declined" | null, text: string): DaemonResult {
|
|
122
|
+
return {
|
|
123
|
+
content: [{ type: "text", text }],
|
|
124
|
+
details: {
|
|
125
|
+
error: text,
|
|
126
|
+
errorKind: "missing_dependency",
|
|
127
|
+
availability: {
|
|
128
|
+
kind: "not_installed",
|
|
129
|
+
serverId: "biome",
|
|
130
|
+
command: ["biome", "lsp-proxy"],
|
|
131
|
+
extensions: [".json", ".jsonc"],
|
|
132
|
+
installHint: "npm install -g @biomejs/biome",
|
|
133
|
+
installDecisionTool: true,
|
|
134
|
+
installDecisionsPath: "/codex-home/lsp-install-decisions.json",
|
|
135
|
+
decision,
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function diagnosticsText(text: string): DaemonResult {
|
|
142
|
+
return { content: [{ type: "text", text }] };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function editOf(sessionId: string, filePath: string) {
|
|
146
|
+
return hookInput(sessionId, "write", { path: filePath });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function patchOf(sessionId: string, filePaths: readonly string[]) {
|
|
150
|
+
const command = [
|
|
151
|
+
"*** Begin Patch",
|
|
152
|
+
...filePaths.flatMap((file) => [`*** Update File: ${file}`, "@@", "-a", "+b"]),
|
|
153
|
+
"*** End Patch",
|
|
154
|
+
];
|
|
155
|
+
return hookInput(sessionId, "apply_patch", { command: command.join("\n") });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function hookInput(sessionId: string, toolName: string, toolInput: Record<string, unknown>) {
|
|
159
|
+
return {
|
|
160
|
+
cwd: "/repo",
|
|
161
|
+
hook_event_name: "PostToolUse",
|
|
162
|
+
model: "gpt-5.5",
|
|
163
|
+
permission_mode: "default",
|
|
164
|
+
session_id: sessionId,
|
|
165
|
+
tool_input: toolInput,
|
|
166
|
+
tool_name: toolName,
|
|
167
|
+
tool_response: { ok: true },
|
|
168
|
+
tool_use_id: "tool-use-1",
|
|
169
|
+
transcript_path: null,
|
|
170
|
+
turn_id: "turn-1",
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function withPluginData<T>(fn: () => Promise<T>): Promise<T> {
|
|
175
|
+
const pluginData = mkdtempSync(path.join(tmpdir(), "codex-lsp-not-installed-"));
|
|
176
|
+
tempDirs.push(pluginData);
|
|
177
|
+
const previous = process.env["PLUGIN_DATA"];
|
|
178
|
+
process.env["PLUGIN_DATA"] = pluginData;
|
|
179
|
+
try {
|
|
180
|
+
return await fn();
|
|
181
|
+
} finally {
|
|
182
|
+
if (previous === undefined) delete process.env["PLUGIN_DATA"];
|
|
183
|
+
else process.env["PLUGIN_DATA"] = previous;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function parseHookOutput(output: string): Record<string, unknown> {
|
|
188
|
+
expect(output).not.toBe("");
|
|
189
|
+
const parsed: unknown = JSON.parse(output);
|
|
190
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
191
|
+
throw new Error(`hook output is not an object: ${output}`);
|
|
192
|
+
return { ...parsed };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function additionalContext(parsed: Record<string, unknown>): string {
|
|
196
|
+
const specific = parsed["hookSpecificOutput"];
|
|
197
|
+
if (typeof specific !== "object" || specific === null) throw new Error("hookSpecificOutput missing");
|
|
198
|
+
const context = Reflect.get(specific, "additionalContext");
|
|
199
|
+
const eventName = Reflect.get(specific, "hookEventName");
|
|
200
|
+
expect(eventName).toBe("PostToolUse");
|
|
201
|
+
if (typeof context !== "string") throw new Error("additionalContext missing");
|
|
202
|
+
return context;
|
|
203
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- Align the Hephaestus GPT-6 rule's delegation paragraph with the senpi GPT-6 preset (oh-my-openagent#9499): reading, lookups and own-change checks stay in the session however many calls they take; a subagent is for a track that runs beside the session's own and lands the task sooner.
|
|
5
6
|
- Restrict the default `PostToolUse` hook matcher to Codex's canonical `apply_patch` tool name.
|
|
6
7
|
- Add opt-in `NODE_DEBUG=codex-rules` phase timing logs for `PostToolUse` debugging.
|
|
7
8
|
- Harden dynamic hook coverage for additional-context JSON output, disabled/static modes, failed tool responses, and duplicate suppression.
|
|
@@ -41,7 +41,7 @@ omo-codex auto-runs LSP diagnostics after every edit and injects the result: any
|
|
|
41
41
|
|
|
42
42
|
# Subagents
|
|
43
43
|
|
|
44
|
-
Read-only Codex subagent roles live in `CODEX_HOME/agents/`. Spawn: `multi_agent_v1.spawn_agent({"message":"TASK: act as a <role>. ...","agent_type":"<role>","fork_context":false})`. If your tool list instead has a flat `spawn_agent` with a required `task_name` (`multi_agent_v2`): `spawn_agent({"task_name":"<lowercase_digits_underscores>","message":"TASK: act as a <role>. ...","fork_turns":"none"})` — finished agents end on their own; `wait_agent` takes only `timeout_ms`.
|
|
44
|
+
Read-only Codex subagent roles live in `CODEX_HOME/agents/`. Spawn: `multi_agent_v1.spawn_agent({"message":"TASK: act as a <role>. ...","agent_type":"<role>","fork_context":false})`. If your tool list instead has a flat `spawn_agent` with a required `task_name` (`multi_agent_v2`): `spawn_agent({"task_name":"<lowercase_digits_underscores>","agent_type":"<role>","message":"TASK: act as a <role>. ...","fork_turns":"none"})` — finished agents end on their own; `wait_agent` takes only `timeout_ms`.
|
|
45
45
|
|
|
46
46
|
- `explorer` - codebase search
|
|
47
47
|
- `librarian` - external docs, OSS code, API contracts
|
|
@@ -31,7 +31,7 @@ Waiting is not free: a status poll replays the whole accumulated context through
|
|
|
31
31
|
|
|
32
32
|
# Subagents
|
|
33
33
|
|
|
34
|
-
Read-only Codex subagent roles live in `CODEX_HOME/agents/`. Spawn: `multi_agent_v1.spawn_agent({"message":"TASK: act as a <role>. GOAL: ... STOP WHEN: ... EVIDENCE: ...","agent_type":"<role>","fork_context":false})`. If your tool list instead has a flat `spawn_agent` with a required `task_name` (`multi_agent_v2`): `spawn_agent({"task_name":"<lowercase_digits_underscores>","message":"TASK: act as a <role>. GOAL: ... STOP WHEN: ... EVIDENCE: ...","fork_turns":"none"})` - finished agents end on their own; `wait_agent` takes only `timeout_ms`.
|
|
34
|
+
Read-only Codex subagent roles live in `CODEX_HOME/agents/`. Spawn: `multi_agent_v1.spawn_agent({"message":"TASK: act as a <role>. GOAL: ... STOP WHEN: ... EVIDENCE: ...","agent_type":"<role>","fork_context":false})`. If your tool list instead has a flat `spawn_agent` with a required `task_name` (`multi_agent_v2`): `spawn_agent({"task_name":"<lowercase_digits_underscores>","agent_type":"<role>","message":"TASK: act as a <role>. GOAL: ... STOP WHEN: ... EVIDENCE: ...","fork_turns":"none"})` - finished agents end on their own; `wait_agent` takes only `timeout_ms`.
|
|
35
35
|
|
|
36
36
|
- `explorer` - codebase search
|
|
37
37
|
- `librarian` - external docs, OSS code, API contracts
|
|
@@ -37,7 +37,7 @@ Use direct calls when batching buys nothing: a lone call, an already-small resul
|
|
|
37
37
|
|
|
38
38
|
Memory of file contents is unreliable: read before claiming and re-read before editing or accepting a hand-off. Where LSP tools exist, use them for definitions, callers, rename impact, workspace symbols, and diagnostics on touched files. Text search is for literal strings, filenames, and commit history. Stop searching once a wave answers the question or two waves add nothing new. A finding that looks too simple deserves one more layer of callers or dependencies; prefer the root fix over the symptom.
|
|
39
39
|
|
|
40
|
-
Do the work yourself by default
|
|
40
|
+
Do the work yourself by default: reading, lookups, and checks on your own change are yours however many calls they take, and a follow-up on delegated work is yours to take back, not forward. A subagent is for a track that runs beside yours and lands the task sooner - a wide investigation across many files, or an implementation unit beyond one coherent edit in files you are not touching. Spawn those tracks together in the background, each brief naming its output, allowed edit paths, stop condition, and returned evidence. Do non-overlapping work while they run, then check their evidence and integrate the results. Never duplicate a running search. Agent messages and final answers are read by people: full sentences, proper spaces between words and numbers, no private shorthand. Concrete spawn contracts are below.
|
|
41
41
|
|
|
42
42
|
Use `update_plan` for multi-step work, uncertain scope, multiple files, or branching investigation. Cut items into the smallest standalone outcomes, each pairing an edit with its proof. A one-step ask carries no list. Keep exactly one item `in_progress`; move each item the instant it opens, finishes, is discovered and appended, or is abandoned and removed. Update the plan in the same response when discovery changes it. Before ending, reconcile every item as completed, blocked with a reason, or removed with a reason. Commit follow-up work to the plan only if you will do it now.
|
|
43
43
|
|
|
@@ -78,7 +78,7 @@ When an approach fails, change something material - an algorithm, library, or pa
|
|
|
78
78
|
|
|
79
79
|
## Codex tool and skills notes
|
|
80
80
|
|
|
81
|
-
The actual Codex tool list and schemas determine the route. Read-only subagent roles live in `CODEX_HOME/agents/`. For `multi_agent_v1`, use `multi_agent_v1.spawn_agent({"message":"TASK: act as a <role>. GOAL: ... STOP WHEN: ... EVIDENCE: ...","agent_type":"<role>","fork_context":false})`. If the tool list instead exposes a flat `spawn_agent` requiring `task_name` (`multi_agent_v2`), use `spawn_agent({"task_name":"<lowercase_digits_underscores>","message":"TASK: act as a <role>. GOAL: ... STOP WHEN: ... EVIDENCE: ...","fork_turns":"none"})`. Finished agents end on their own; `wait_agent` takes only `timeout_ms`. Keep the two payloads distinct and do not send v1 fields to v2 or vice versa.
|
|
81
|
+
The actual Codex tool list and schemas determine the route. Read-only subagent roles live in `CODEX_HOME/agents/`. For `multi_agent_v1`, use `multi_agent_v1.spawn_agent({"message":"TASK: act as a <role>. GOAL: ... STOP WHEN: ... EVIDENCE: ...","agent_type":"<role>","fork_context":false})`. If the tool list instead exposes a flat `spawn_agent` requiring `task_name` (`multi_agent_v2`), use `spawn_agent({"task_name":"<lowercase_digits_underscores>","agent_type":"<role>","message":"TASK: act as a <role>. GOAL: ... STOP WHEN: ... EVIDENCE: ...","fork_turns":"none"})`. Finished agents end on their own; `wait_agent` takes only `timeout_ms`. Keep the two payloads distinct and do not send v1 fields to v2 or vice versa.
|
|
82
82
|
|
|
83
83
|
- `explorer`: codebase search.
|
|
84
84
|
- `librarian`: external docs, OSS code, and API contracts.
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"type": "command",
|
|
8
8
|
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook session-start",
|
|
9
9
|
"timeout": 10,
|
|
10
|
-
"statusMessage": "(OmO 5.1.
|
|
10
|
+
"statusMessage": "(OmO 5.1.14) Loading Project Rules"
|
|
11
11
|
}
|
|
12
12
|
]
|
|
13
13
|
}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"type": "command",
|
|
20
20
|
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook user-prompt-submit",
|
|
21
21
|
"timeout": 10,
|
|
22
|
-
"statusMessage": "(OmO 5.1.
|
|
22
|
+
"statusMessage": "(OmO 5.1.14) Loading Project Rules"
|
|
23
23
|
}
|
|
24
24
|
]
|
|
25
25
|
}
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"type": "command",
|
|
33
33
|
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-tool-use",
|
|
34
34
|
"timeout": 10,
|
|
35
|
-
"statusMessage": "(OmO 5.1.
|
|
35
|
+
"statusMessage": "(OmO 5.1.14) Matching Project Rules"
|
|
36
36
|
}
|
|
37
37
|
]
|
|
38
38
|
}
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"type": "command",
|
|
46
46
|
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-compact",
|
|
47
47
|
"timeout": 10,
|
|
48
|
-
"statusMessage": "(OmO 5.1.
|
|
48
|
+
"statusMessage": "(OmO 5.1.14) Resetting Project Rule Cache"
|
|
49
49
|
}
|
|
50
50
|
]
|
|
51
51
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/codex-ultrawork",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.14",
|
|
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",
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"type": "command",
|
|
8
8
|
"command": "node \"${PLUGIN_ROOT}/components/ulw-execute-continuation/dist/cli.js\" hook stop",
|
|
9
9
|
"timeout": 10,
|
|
10
|
-
"statusMessage": "(OmO 5.1.
|
|
10
|
+
"statusMessage": "(OmO 5.1.14) Checking Ulw-Execute Continuation"
|
|
11
11
|
}
|
|
12
12
|
]
|
|
13
13
|
}
|
|
@@ -4360,25 +4360,82 @@ function readAll(stdin) {
|
|
|
4360
4360
|
|
|
4361
4361
|
// components/ulw-loop/src/spawn-guard.ts
|
|
4362
4362
|
import { mkdirSync as mkdirSync3 } from "node:fs";
|
|
4363
|
-
import { join as
|
|
4363
|
+
import { join as join8 } from "node:path";
|
|
4364
|
+
|
|
4365
|
+
// components/ulw-loop/src/registered-agent-roles.ts
|
|
4366
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync6 } from "node:fs";
|
|
4367
|
+
import { homedir } from "node:os";
|
|
4368
|
+
import { basename, dirname as dirname3, join as join6, resolve as resolve5 } from "node:path";
|
|
4369
|
+
var NAME_LINE = /^\s*name\s*=\s*"([^"\n]+)"\s*(?:#.*)?$/m;
|
|
4370
|
+
var AGENT_TABLE = /^\s*\[agents\.(?:"([^"\n]+)"|([A-Za-z0-9_-]+))\]\s*(?:#.*)?$/gm;
|
|
4371
|
+
function readText(path) {
|
|
4372
|
+
try {
|
|
4373
|
+
return readFileSync6(path, "utf8");
|
|
4374
|
+
} catch {
|
|
4375
|
+
return null;
|
|
4376
|
+
}
|
|
4377
|
+
}
|
|
4378
|
+
function standaloneRoleNames(agentsDir) {
|
|
4379
|
+
let entries;
|
|
4380
|
+
try {
|
|
4381
|
+
entries = readdirSync3(agentsDir);
|
|
4382
|
+
} catch {
|
|
4383
|
+
return [];
|
|
4384
|
+
}
|
|
4385
|
+
const names = [];
|
|
4386
|
+
for (const entry of entries) {
|
|
4387
|
+
if (!entry.endsWith(".toml"))
|
|
4388
|
+
continue;
|
|
4389
|
+
const text = readText(join6(agentsDir, entry));
|
|
4390
|
+
if (text === null)
|
|
4391
|
+
continue;
|
|
4392
|
+
names.push(text.match(NAME_LINE)?.[1] ?? basename(entry, ".toml"));
|
|
4393
|
+
}
|
|
4394
|
+
return names;
|
|
4395
|
+
}
|
|
4396
|
+
function configTableRoleNames(configPath) {
|
|
4397
|
+
const text = readText(configPath);
|
|
4398
|
+
if (text === null)
|
|
4399
|
+
return [];
|
|
4400
|
+
return [...text.matchAll(AGENT_TABLE)].map((match) => match[1] ?? match[2] ?? "").filter(Boolean);
|
|
4401
|
+
}
|
|
4402
|
+
function codexDirs(cwd, env) {
|
|
4403
|
+
const dirs = [resolve5(env["CODEX_HOME"]?.trim() || join6(homedir(), ".codex"))];
|
|
4404
|
+
for (let dir = resolve5(cwd);; dir = dirname3(dir)) {
|
|
4405
|
+
dirs.push(join6(dir, ".codex"));
|
|
4406
|
+
if (dirname3(dir) === dir)
|
|
4407
|
+
break;
|
|
4408
|
+
}
|
|
4409
|
+
return [...new Set(dirs)];
|
|
4410
|
+
}
|
|
4411
|
+
function registeredAgentRoles(cwd, env = process.env) {
|
|
4412
|
+
const names = new Set;
|
|
4413
|
+
for (const dir of codexDirs(cwd, env)) {
|
|
4414
|
+
for (const name of standaloneRoleNames(join6(dir, "agents")))
|
|
4415
|
+
names.add(name);
|
|
4416
|
+
for (const name of configTableRoleNames(join6(dir, "config.toml")))
|
|
4417
|
+
names.add(name);
|
|
4418
|
+
}
|
|
4419
|
+
return names;
|
|
4420
|
+
}
|
|
4364
4421
|
|
|
4365
4422
|
// components/ulw-loop/src/spawn-budget-io.ts
|
|
4366
4423
|
import { randomBytes } from "node:crypto";
|
|
4367
|
-
import { existsSync as existsSync7, readFileSync as
|
|
4368
|
-
import { dirname as
|
|
4424
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, renameSync as renameSync2, statSync as statSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
4425
|
+
import { dirname as dirname4, join as join7 } from "node:path";
|
|
4369
4426
|
function readAdmissionBreaker(sessionId) {
|
|
4370
4427
|
const dataDir = process.env["PLUGIN_DATA"];
|
|
4371
4428
|
if (typeof dataDir !== "string")
|
|
4372
4429
|
return null;
|
|
4373
4430
|
try {
|
|
4374
|
-
const value = JSON.parse(
|
|
4431
|
+
const value = JSON.parse(readFileSync7(join7(dataDir, "spawn-breaker", `${sessionId}.json`), "utf8"));
|
|
4375
4432
|
return typeof value === "object" && value !== null && "reason" in value && typeof value.reason === "string" ? value.reason : "capacity limit";
|
|
4376
4433
|
} catch {
|
|
4377
4434
|
return null;
|
|
4378
4435
|
}
|
|
4379
4436
|
}
|
|
4380
4437
|
function atomicWriteJson(targetPath, data) {
|
|
4381
|
-
const tmp =
|
|
4438
|
+
const tmp = join7(dirname4(targetPath), `.tmp-${randomBytes(6).toString("hex")}`);
|
|
4382
4439
|
writeFileSync2(tmp, JSON.stringify(data));
|
|
4383
4440
|
renameSync2(tmp, targetPath);
|
|
4384
4441
|
}
|
|
@@ -4393,7 +4450,7 @@ function isNonEmptyFile(path) {
|
|
|
4393
4450
|
}
|
|
4394
4451
|
function readCount(counterPath) {
|
|
4395
4452
|
try {
|
|
4396
|
-
const parsed = JSON.parse(
|
|
4453
|
+
const parsed = JSON.parse(readFileSync7(counterPath, "utf8"));
|
|
4397
4454
|
return typeof parsed === "object" && parsed !== null && "count" in parsed && typeof parsed.count === "number" && parsed.count >= 0 ? parsed.count : 0;
|
|
4398
4455
|
} catch (error) {
|
|
4399
4456
|
if (error instanceof Error)
|
|
@@ -4403,7 +4460,7 @@ function readCount(counterPath) {
|
|
|
4403
4460
|
}
|
|
4404
4461
|
function readCounts(counterPath) {
|
|
4405
4462
|
try {
|
|
4406
|
-
const parsed = JSON.parse(
|
|
4463
|
+
const parsed = JSON.parse(readFileSync7(counterPath, "utf8"));
|
|
4407
4464
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
4408
4465
|
return {};
|
|
4409
4466
|
const counts = {};
|
|
@@ -4433,11 +4490,15 @@ var LAZYCODEX_SPAWN_ROLES = new Set([
|
|
|
4433
4490
|
"momus",
|
|
4434
4491
|
"plan"
|
|
4435
4492
|
]);
|
|
4436
|
-
function spawnRoleDenial(input) {
|
|
4493
|
+
function spawnRoleDenial(input, registeredRoles = () => new Set) {
|
|
4437
4494
|
const role = typeof input === "object" && input !== null && "agent_type" in input ? input.agent_type : undefined;
|
|
4438
4495
|
if (typeof role === "string" && LAZYCODEX_SPAWN_ROLES.has(role))
|
|
4439
4496
|
return null;
|
|
4440
|
-
|
|
4497
|
+
const registered = typeof role === "string" && role.trim() !== "" ? registeredRoles() : new Set;
|
|
4498
|
+
if (typeof role === "string" && registered.has(role))
|
|
4499
|
+
return null;
|
|
4500
|
+
const known = [...new Set([...LAZYCODEX_SPAWN_ROLES, ...registered])];
|
|
4501
|
+
return `LazyCodex requires an explicit registered agent_type: ${known.join(", ")}. Received ${JSON.stringify(role) ?? "no agent_type"}. Use the matching role and fork_turns: "none" (V2) or fork_context: false (V1), unless full history is deliberately required. The hook cannot see the tool schema; if agent_type is unavailable, stop and report incompatible role routing rather than spawning a generic agent. Describing a role in message does not select its TOML.`;
|
|
4441
4502
|
}
|
|
4442
4503
|
|
|
4443
4504
|
// components/ulw-loop/src/spawn-guard.ts
|
|
@@ -4461,7 +4522,7 @@ function applySpawnGuards(payload, options = {}) {
|
|
|
4461
4522
|
if (payload.hook_event_name !== "PreToolUse" || !SPAWN_TOOL_TOKENS.has(payload.tool_name))
|
|
4462
4523
|
return "";
|
|
4463
4524
|
if (resolveToolkitSurface() === "lazycodex") {
|
|
4464
|
-
const reason = spawnRoleDenial(payload.tool_input);
|
|
4525
|
+
const reason = spawnRoleDenial(payload.tool_input, () => registeredAgentRoles(payload.cwd));
|
|
4465
4526
|
if (reason !== null)
|
|
4466
4527
|
return deny(reason);
|
|
4467
4528
|
}
|
|
@@ -4514,10 +4575,10 @@ async function runSpawnAdmissionRecorderCli(stdin, stdout) {
|
|
|
4514
4575
|
const dataDir = process.env["PLUGIN_DATA"];
|
|
4515
4576
|
if (typeof dataDir !== "string" || typeof payload["session_id"] !== "string")
|
|
4516
4577
|
return;
|
|
4517
|
-
const markerDir =
|
|
4578
|
+
const markerDir = join8(dataDir, "spawn-breaker");
|
|
4518
4579
|
try {
|
|
4519
4580
|
mkdirSync3(markerDir, { recursive: true });
|
|
4520
|
-
atomicWriteJson(
|
|
4581
|
+
atomicWriteJson(join8(markerDir, `${payload["session_id"]}.json`), {
|
|
4521
4582
|
reason: response,
|
|
4522
4583
|
at: new Date().toISOString()
|
|
4523
4584
|
});
|
|
@@ -4546,7 +4607,7 @@ async function runSpawnGuardCli(stdin, stdout) {
|
|
|
4546
4607
|
}
|
|
4547
4608
|
}
|
|
4548
4609
|
function peekFanOutBudget(stateDir) {
|
|
4549
|
-
const counterPath =
|
|
4610
|
+
const counterPath = join8(stateDir, "spawn-count.json");
|
|
4550
4611
|
const count = readCount(counterPath) + 1;
|
|
4551
4612
|
const limit = fanOutLimit();
|
|
4552
4613
|
if (count <= limit)
|
|
@@ -4554,7 +4615,7 @@ function peekFanOutBudget(stateDir) {
|
|
|
4554
4615
|
return `ulw-loop spawn fan-out cap reached (${count}/${limit}). Consolidate work into the agents already running, or raise OMO_SPAWN_FANOUT_LIMIT if this volume is intentional.`;
|
|
4555
4616
|
}
|
|
4556
4617
|
function consumeFanOutBudget(stateDir) {
|
|
4557
|
-
const counterPath =
|
|
4618
|
+
const counterPath = join8(stateDir, "spawn-count.json");
|
|
4558
4619
|
const count = readCount(counterPath) + 1;
|
|
4559
4620
|
atomicWriteJson(counterPath, { count });
|
|
4560
4621
|
const limit = fanOutLimit();
|
|
@@ -4569,7 +4630,7 @@ function consumeReviewSpawnBudget(payload, plan, stateDir) {
|
|
|
4569
4630
|
const goal = plan.goals.find((candidate) => candidate.id === plan.activeGoalId) ?? plan.goals.find((candidate) => isFinalRunCompletionCandidate(plan, candidate));
|
|
4570
4631
|
if (goal === undefined)
|
|
4571
4632
|
return null;
|
|
4572
|
-
const counterPath =
|
|
4633
|
+
const counterPath = join8(stateDir, "review-spawn-counts.json");
|
|
4573
4634
|
const limit = reviewSpawnLimit();
|
|
4574
4635
|
const counts = readCounts(counterPath);
|
|
4575
4636
|
const key = `${agentType}:${goal.id}:a${goal.attempt}`;
|
|
@@ -4594,13 +4655,13 @@ function missingGateArtifact(payload, plan) {
|
|
|
4594
4655
|
const attemptDir = ulwLoopAttemptEvidenceDir(goal.id, goal.attempt, scope);
|
|
4595
4656
|
for (const name of requiredArtifacts) {
|
|
4596
4657
|
const relative = `${attemptDir}/${name}`;
|
|
4597
|
-
if (!isNonEmptyFile(
|
|
4658
|
+
if (!isNonEmptyFile(join8(payload.cwd, relative)))
|
|
4598
4659
|
return relative;
|
|
4599
4660
|
}
|
|
4600
4661
|
return null;
|
|
4601
4662
|
}
|
|
4602
4663
|
const manualQa = `.omo/evidence/${goal.id}-manual-qa.md`;
|
|
4603
|
-
return isNonEmptyFile(
|
|
4664
|
+
return isNonEmptyFile(join8(payload.cwd, manualQa)) ? null : manualQa;
|
|
4604
4665
|
}
|
|
4605
4666
|
function isGateReviewerSpawn(toolInput) {
|
|
4606
4667
|
const agentType = reviewAgentType(toolInput);
|
|
@@ -4684,8 +4745,8 @@ function readPlan(repoRoot, sessionId) {
|
|
|
4684
4745
|
}
|
|
4685
4746
|
|
|
4686
4747
|
// components/ulw-loop/src/stop-resume-hook.ts
|
|
4687
|
-
import { existsSync as existsSync8, readFileSync as
|
|
4688
|
-
import { isAbsolute as isAbsolute3, join as
|
|
4748
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "node:fs";
|
|
4749
|
+
import { isAbsolute as isAbsolute3, join as join9, resolve as resolve6, sep as sep3 } from "node:path";
|
|
4689
4750
|
var RESUME_CAP = 2;
|
|
4690
4751
|
var CONTEXT_PRESSURE_MARKERS2 = [
|
|
4691
4752
|
"context compacted",
|
|
@@ -4753,8 +4814,8 @@ function consumeResumeBudgetLocked(lockPath, stateDir, goalId) {
|
|
|
4753
4814
|
}
|
|
4754
4815
|
function consumeResumeBudget(stateDir, goalId) {
|
|
4755
4816
|
const ledgerLineCount = readLedgerAt(stateDir).length;
|
|
4756
|
-
const counterPath =
|
|
4757
|
-
const stuckPath =
|
|
4817
|
+
const counterPath = resolve6(stateDir, `auto-resume-${goalId}.json`);
|
|
4818
|
+
const stuckPath = resolve6(stateDir, `auto-resume-${goalId}.stuck`);
|
|
4758
4819
|
if (!isInsideDir(stateDir, counterPath) || !isInsideDir(stateDir, stuckPath))
|
|
4759
4820
|
return false;
|
|
4760
4821
|
const previous = readCounter(counterPath);
|
|
@@ -4768,7 +4829,7 @@ function consumeResumeBudget(stateDir, goalId) {
|
|
|
4768
4829
|
return true;
|
|
4769
4830
|
}
|
|
4770
4831
|
function isInsideDir(dir, candidate) {
|
|
4771
|
-
return candidate.startsWith(
|
|
4832
|
+
return candidate.startsWith(resolve6(dir) + sep3);
|
|
4772
4833
|
}
|
|
4773
4834
|
function renderResumeDirective(plan, goal, sessionId) {
|
|
4774
4835
|
const normalized = normalizeUlwLoopSessionId(sessionId);
|
|
@@ -4796,7 +4857,7 @@ function readCounter(counterPath) {
|
|
|
4796
4857
|
try {
|
|
4797
4858
|
if (!existsSync8(counterPath))
|
|
4798
4859
|
return null;
|
|
4799
|
-
const parsed = JSON.parse(
|
|
4860
|
+
const parsed = JSON.parse(readFileSync8(counterPath, "utf8"));
|
|
4800
4861
|
if (typeof parsed["count"] !== "number" || typeof parsed["ledgerLineCount"] !== "number")
|
|
4801
4862
|
return null;
|
|
4802
4863
|
return { count: parsed["count"], ledgerLineCount: parsed["ledgerLineCount"] };
|
|
@@ -4808,7 +4869,7 @@ function readCounter(counterPath) {
|
|
|
4808
4869
|
}
|
|
4809
4870
|
function boulderContinuationWillFire(cwd, sessionId) {
|
|
4810
4871
|
try {
|
|
4811
|
-
const raw = JSON.parse(
|
|
4872
|
+
const raw = JSON.parse(readFileSync8(join9(cwd, ".omo", "boulder.json"), "utf8"));
|
|
4812
4873
|
const works = raw["works"];
|
|
4813
4874
|
const entries = typeof works === "object" && works !== null ? Object.values(works) : [raw];
|
|
4814
4875
|
return entries.some((work) => {
|
|
@@ -4827,7 +4888,7 @@ function boulderContinuationWillFire(cwd, sessionId) {
|
|
|
4827
4888
|
}
|
|
4828
4889
|
function transcriptShowsContextPressure(transcriptPath) {
|
|
4829
4890
|
try {
|
|
4830
|
-
const transcript =
|
|
4891
|
+
const transcript = readFileSync8(transcriptPath, "utf8").toLowerCase();
|
|
4831
4892
|
return CONTEXT_PRESSURE_MARKERS2.some((marker) => transcript.includes(marker));
|
|
4832
4893
|
} catch (error) {
|
|
4833
4894
|
if (error instanceof Error)
|
|
@@ -4839,12 +4900,12 @@ function boulderPlanHasChecklist(cwd, entry) {
|
|
|
4839
4900
|
const activePlan = entry["active_plan"];
|
|
4840
4901
|
if (typeof activePlan !== "string" || activePlan.trim().length === 0)
|
|
4841
4902
|
return false;
|
|
4842
|
-
const planPath = isAbsolute3(activePlan) ? activePlan :
|
|
4903
|
+
const planPath = isAbsolute3(activePlan) ? activePlan : join9(cwd, activePlan);
|
|
4843
4904
|
const worktree = entry["worktree_path"];
|
|
4844
|
-
const candidates = typeof worktree === "string" && worktree.trim().length > 0 && !isAbsolute3(activePlan) ? [
|
|
4905
|
+
const candidates = typeof worktree === "string" && worktree.trim().length > 0 && !isAbsolute3(activePlan) ? [join9(isAbsolute3(worktree) ? worktree : join9(cwd, worktree), activePlan), planPath] : [planPath];
|
|
4845
4906
|
for (const candidate of candidates) {
|
|
4846
4907
|
try {
|
|
4847
|
-
return
|
|
4908
|
+
return readFileSync8(candidate, "utf8").split(/\r?\n/).some((line) => line.startsWith("- [ ] ") || line.startsWith("- [x] ") || line.startsWith("- [X] "));
|
|
4848
4909
|
} catch (error) {
|
|
4849
4910
|
if (!(error instanceof Error))
|
|
4850
4911
|
throw error;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function registeredAgentRoles(cwd: string, env?: NodeJS.ProcessEnv): ReadonlySet<string>;
|