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.
Files changed (101) hide show
  1. package/dist/cli/index.js +45 -18
  2. package/dist/cli-node/index.js +45 -18
  3. package/dist/features/background-agent/fallback-provider-model-filter.d.ts +12 -0
  4. package/dist/features/background-agent/manager.d.ts +1 -0
  5. package/dist/features/background-agent/session-stopped-on-error.d.ts +9 -0
  6. package/dist/features/background-agent/types.d.ts +2 -0
  7. package/dist/index.js +173 -29
  8. package/dist/oh-my-opencode.schema.json +0 -107
  9. package/dist/shared/posthog.d.ts +1 -0
  10. package/dist/skills/browser/SKILL.md +26 -2
  11. package/dist/skills/browser/references/commands.md +10 -1
  12. package/dist/skills/browser/references/install.md +7 -0
  13. package/dist/skills/browser/runtime/omowright/manifest.json +1 -1
  14. package/dist/skills/browser/scripts/browser-engine-guard.mjs +506 -0
  15. package/dist/skills/browser/scripts/omowright.mjs +29 -2
  16. package/dist/tui.js +16 -16
  17. package/package.json +14 -14
  18. package/packages/lsp-core/src/post-edit/not-installed.test.ts +2 -2
  19. package/packages/lsp-core/src/post-edit/orchestration.test.ts +13 -13
  20. package/packages/lsp-core/src/post-edit/orchestration.ts +9 -3
  21. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  22. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  23. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  24. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  25. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  26. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  27. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  28. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  29. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  30. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +4 -4
  31. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +33 -19
  32. package/packages/omo-codex/plugin/components/lsp/dist/codex-hook.js +27 -17
  33. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  34. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  35. package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +36 -18
  36. package/packages/omo-codex/plugin/components/lsp/test/codex-hook-not-installed.test.ts +203 -0
  37. package/packages/omo-codex/plugin/components/rules/CHANGELOG.md +1 -0
  38. package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-5.5.md +1 -1
  39. package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-5.6.md +1 -1
  40. package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-6.md +2 -2
  41. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  42. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  43. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  44. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  45. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  46. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  47. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  48. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  49. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  50. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  51. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +89 -28
  52. package/packages/omo-codex/plugin/components/ulw-loop/dist/registered-agent-roles.d.ts +1 -0
  53. package/packages/omo-codex/plugin/components/ulw-loop/dist/registered-agent-roles.js +61 -0
  54. package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.js +2 -1
  55. package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-role-guard.d.ts +1 -1
  56. package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-role-guard.js +6 -2
  57. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  58. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  59. package/packages/omo-codex/plugin/components/ulw-loop/src/registered-agent-roles.ts +60 -0
  60. package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-guard.ts +2 -1
  61. package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-role-guard.ts +8 -2
  62. package/packages/omo-codex/plugin/components/ulw-loop/test/spawn-role-matrix.test.ts +1 -0
  63. package/packages/omo-codex/plugin/components/ulw-loop/test/spawn-role-registered.test.ts +128 -0
  64. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  65. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  66. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  67. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  68. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  69. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  70. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  71. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  72. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  73. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  74. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  75. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  76. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  77. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  78. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  79. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  80. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  81. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  82. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  83. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  84. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  85. package/packages/omo-codex/plugin/package-lock.json +12 -12
  86. package/packages/omo-codex/plugin/package.json +1 -1
  87. package/packages/omo-codex/plugin/skills/browser/SKILL.md +26 -2
  88. package/packages/omo-codex/plugin/skills/browser/references/commands.md +10 -1
  89. package/packages/omo-codex/plugin/skills/browser/references/install.md +7 -0
  90. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
  91. package/packages/omo-codex/plugin/skills/browser/scripts/browser-engine-guard.mjs +506 -0
  92. package/packages/omo-codex/plugin/skills/browser/scripts/omowright.mjs +29 -2
  93. package/packages/omo-codex/plugin/test/aggregate-plugin-fixture.mjs +4 -3
  94. package/packages/omo-codex/plugin/test/aggregate.test.mjs +2 -2
  95. package/packages/omo-codex/scripts/install-dist/install-local.mjs +2 -2
  96. package/packages/shared-skills/skills/browser/SKILL.md +26 -2
  97. package/packages/shared-skills/skills/browser/references/commands.md +10 -1
  98. package/packages/shared-skills/skills/browser/references/install.md +7 -0
  99. package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
  100. package/packages/shared-skills/skills/browser/scripts/browser-engine-guard.mjs +506 -0
  101. 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. Whatever closes in a handful of calls is yours, and a follow-up on delegated work is yours to take back, not forward. Only a sizeable track independent of your own earns a subagent. Spawn those tracks together in the background, each brief stating its output, allowed edit paths, observable stop condition, and evidence to return. 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.
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.12) Loading Project Rules"
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.12) Loading Project Rules"
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.12) Matching Project Rules"
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.12) Resetting Project Rule Cache"
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-rules",
3
- "version": "5.1.12",
3
+ "version": "5.1.14",
4
4
  "description": "Codex plugin that injects project rule files into model context through lifecycle hooks.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.12.1",
@@ -8,7 +8,7 @@
8
8
  "type": "command",
9
9
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-tool-use",
10
10
  "timeout": 10,
11
- "statusMessage": "(OmO 5.1.12) Checking Thread Title Hygiene"
11
+ "statusMessage": "(OmO 5.1.14) Checking Thread Title Hygiene"
12
12
  }
13
13
  ]
14
14
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sisyphuslabs/codex-teammode",
3
- "version": "5.1.12",
3
+ "version": "5.1.14",
4
4
  "description": "Codex team-mode hook component that keeps background thread titles descriptive after create_thread.",
5
5
  "type": "module",
6
6
  "private": true,
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook session-start",
9
9
  "timeout": 5,
10
- "statusMessage": "(OmO 5.1.12) Recording Session Telemetry"
10
+ "statusMessage": "(OmO 5.1.14) Recording Session Telemetry"
11
11
  }
12
12
  ]
13
13
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-telemetry",
3
- "version": "5.1.12",
3
+ "version": "5.1.14",
4
4
  "description": "Codex plugin component that emits omo-codex anonymous daily-active telemetry on SessionStart.",
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}/dist/cli.js\" hook user-prompt-submit",
9
9
  "timeout": 5,
10
- "statusMessage": "(OmO 5.1.12) Checking Ultrawork Trigger"
10
+ "statusMessage": "(OmO 5.1.14) Checking Ultrawork Trigger"
11
11
  }
12
12
  ]
13
13
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-ultrawork",
3
- "version": "5.1.12",
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.12) Checking Ulw-Execute Continuation"
10
+ "statusMessage": "(OmO 5.1.14) Checking Ulw-Execute Continuation"
11
11
  }
12
12
  ]
13
13
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-ulw-execute-continuation",
3
- "version": "5.1.12",
3
+ "version": "5.1.14",
4
4
  "description": "Codex Stop hook continuation injector for omo-codex ulw-execute plans.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.12.1",
@@ -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 join7 } from "node:path";
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 readFileSync6, renameSync as renameSync2, statSync as statSync3, writeFileSync as writeFileSync2 } from "node:fs";
4368
- import { dirname as dirname3, join as join6 } from "node:path";
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(readFileSync6(join6(dataDir, "spawn-breaker", `${sessionId}.json`), "utf8"));
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 = join6(dirname3(targetPath), `.tmp-${randomBytes(6).toString("hex")}`);
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(readFileSync6(counterPath, "utf8"));
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(readFileSync6(counterPath, "utf8"));
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
- return `LazyCodex requires an explicit registered agent_type: ${[...LAZYCODEX_SPAWN_ROLES].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.`;
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 = join7(dataDir, "spawn-breaker");
4578
+ const markerDir = join8(dataDir, "spawn-breaker");
4518
4579
  try {
4519
4580
  mkdirSync3(markerDir, { recursive: true });
4520
- atomicWriteJson(join7(markerDir, `${payload["session_id"]}.json`), {
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 = join7(stateDir, "spawn-count.json");
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 = join7(stateDir, "spawn-count.json");
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 = join7(stateDir, "review-spawn-counts.json");
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(join7(payload.cwd, relative)))
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(join7(payload.cwd, manualQa)) ? null : manualQa;
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 readFileSync7, writeFileSync as writeFileSync3 } from "node:fs";
4688
- import { isAbsolute as isAbsolute3, join as join8, resolve as resolve5, sep as sep3 } from "node:path";
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 = resolve5(stateDir, `auto-resume-${goalId}.json`);
4757
- const stuckPath = resolve5(stateDir, `auto-resume-${goalId}.stuck`);
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(resolve5(dir) + sep3);
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(readFileSync7(counterPath, "utf8"));
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(readFileSync7(join8(cwd, ".omo", "boulder.json"), "utf8"));
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 = readFileSync7(transcriptPath, "utf8").toLowerCase();
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 : join8(cwd, 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) ? [join8(isAbsolute3(worktree) ? worktree : join8(cwd, worktree), activePlan), planPath] : [planPath];
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 readFileSync7(candidate, "utf8").split(/\r?\n/).some((line) => line.startsWith("- [ ] ") || line.startsWith("- [x] ") || line.startsWith("- [X] "));
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>;