@duckmind/dm-darwin-x64 0.13.6 → 0.13.8

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 (78) hide show
  1. package/dm +0 -0
  2. package/extensions/.dm-extensions.json +26 -2
  3. package/extensions/dm-phone/README.md +23 -0
  4. package/extensions/dm-phone/index.ts +12 -0
  5. package/extensions/dm-phone/node_modules/.package-lock.json +29 -0
  6. package/extensions/dm-phone/node_modules/ws/LICENSE +20 -0
  7. package/extensions/dm-phone/node_modules/ws/README.md +548 -0
  8. package/extensions/dm-phone/node_modules/ws/browser.js +8 -0
  9. package/extensions/dm-phone/node_modules/ws/index.js +22 -0
  10. package/extensions/dm-phone/node_modules/ws/lib/buffer-util.js +131 -0
  11. package/extensions/dm-phone/node_modules/ws/lib/constants.js +19 -0
  12. package/extensions/dm-phone/node_modules/ws/lib/event-target.js +292 -0
  13. package/extensions/dm-phone/node_modules/ws/lib/extension.js +203 -0
  14. package/extensions/dm-phone/node_modules/ws/lib/limiter.js +55 -0
  15. package/extensions/dm-phone/node_modules/ws/lib/permessage-deflate.js +528 -0
  16. package/extensions/dm-phone/node_modules/ws/lib/receiver.js +706 -0
  17. package/extensions/dm-phone/node_modules/ws/lib/sender.js +602 -0
  18. package/extensions/dm-phone/node_modules/ws/lib/stream.js +161 -0
  19. package/extensions/dm-phone/node_modules/ws/lib/subprotocol.js +62 -0
  20. package/extensions/dm-phone/node_modules/ws/lib/validation.js +152 -0
  21. package/extensions/dm-phone/node_modules/ws/lib/websocket-server.js +554 -0
  22. package/extensions/dm-phone/node_modules/ws/lib/websocket.js +1393 -0
  23. package/extensions/dm-phone/node_modules/ws/package.json +70 -0
  24. package/extensions/dm-phone/node_modules/ws/wrapper.mjs +21 -0
  25. package/extensions/dm-phone/package-lock.json +66 -0
  26. package/extensions/dm-phone/package.json +35 -0
  27. package/extensions/dm-phone/phone-session-pool.ts +8 -0
  28. package/extensions/dm-phone/public/app/attachments.js +233 -0
  29. package/extensions/dm-phone/public/app/autocomplete-controller.js +81 -0
  30. package/extensions/dm-phone/public/app/autocomplete.js +135 -0
  31. package/extensions/dm-phone/public/app/bindings.js +178 -0
  32. package/extensions/dm-phone/public/app/command-catalog.js +76 -0
  33. package/extensions/dm-phone/public/app/commands.js +370 -0
  34. package/extensions/dm-phone/public/app/constants.js +60 -0
  35. package/extensions/dm-phone/public/app/formatters.js +131 -0
  36. package/extensions/dm-phone/public/app/handlers.js +442 -0
  37. package/extensions/dm-phone/public/app/main.js +6 -0
  38. package/extensions/dm-phone/public/app/markdown.js +105 -0
  39. package/extensions/dm-phone/public/app/messages.js +418 -0
  40. package/extensions/dm-phone/public/app/sheet-actions.js +113 -0
  41. package/extensions/dm-phone/public/app/sheet-navigation.js +19 -0
  42. package/extensions/dm-phone/public/app/sheets-view.js +272 -0
  43. package/extensions/dm-phone/public/app/state.js +95 -0
  44. package/extensions/dm-phone/public/app/tool-rendering.js +562 -0
  45. package/extensions/dm-phone/public/app/transport.js +176 -0
  46. package/extensions/dm-phone/public/app/ui.js +409 -0
  47. package/extensions/dm-phone/public/app.js +1 -0
  48. package/extensions/dm-phone/public/icon.svg +15 -0
  49. package/extensions/dm-phone/public/index.html +147 -0
  50. package/extensions/dm-phone/public/manifest.webmanifest +17 -0
  51. package/extensions/dm-phone/public/styles.css +1139 -0
  52. package/extensions/dm-phone/public/sw.js +78 -0
  53. package/extensions/dm-phone/src/extension/phone-args.ts +121 -0
  54. package/extensions/dm-phone/src/extension/phone-paths.ts +250 -0
  55. package/extensions/dm-phone/src/extension/phone-quota.ts +188 -0
  56. package/extensions/dm-phone/src/extension/phone-runtime.ts +154 -0
  57. package/extensions/dm-phone/src/extension/phone-server-runtime.ts +1217 -0
  58. package/extensions/dm-phone/src/extension/phone-sessions.ts +139 -0
  59. package/extensions/dm-phone/src/extension/phone-static.ts +30 -0
  60. package/extensions/dm-phone/src/extension/phone-tailscale.ts +148 -0
  61. package/extensions/dm-phone/src/extension/phone-theme.ts +85 -0
  62. package/extensions/dm-phone/src/extension/register-phone-child-extension.ts +112 -0
  63. package/extensions/dm-phone/src/extension/register-phone-extension.ts +106 -0
  64. package/extensions/dm-phone/src/extension/types.ts +73 -0
  65. package/extensions/dm-phone/src/session-pool/parent-session-worker.ts +881 -0
  66. package/extensions/dm-phone/src/session-pool/session-pool.ts +470 -0
  67. package/extensions/dm-phone/src/session-pool/session-worker.ts +734 -0
  68. package/extensions/dm-phone/src/session-pool/types.ts +105 -0
  69. package/extensions/dm-phone/src/session-pool/utils.ts +23 -0
  70. package/extensions/dm-subagents/agent-management.ts +15 -6
  71. package/extensions/dm-subagents/agent-manager-detail.ts +12 -2
  72. package/extensions/dm-subagents/agent-manager-edit.ts +75 -23
  73. package/extensions/dm-subagents/agent-manager-list.ts +9 -2
  74. package/extensions/dm-subagents/agent-manager.ts +199 -11
  75. package/extensions/dm-subagents/agents.ts +315 -20
  76. package/extensions/dm-ultrathink/README.md +5 -0
  77. package/extensions/dm-ultrathink/src/naming.ts +75 -3
  78. package/package.json +1 -1
@@ -15,6 +15,31 @@ export type AgentScope = "user" | "project" | "both";
15
15
 
16
16
  export type AgentSource = "builtin" | "user" | "project";
17
17
 
18
+ export interface BuiltinAgentOverrideBase {
19
+ model?: string;
20
+ fallbackModels?: string[];
21
+ thinking?: string;
22
+ systemPrompt: string;
23
+ skills?: string[];
24
+ tools?: string[];
25
+ mcpDirectTools?: string[];
26
+ }
27
+
28
+ export interface BuiltinAgentOverrideConfig {
29
+ model?: string | false;
30
+ fallbackModels?: string[] | false;
31
+ thinking?: string | false;
32
+ systemPrompt?: string;
33
+ skills?: string[] | false;
34
+ tools?: string[] | false;
35
+ }
36
+
37
+ export interface BuiltinAgentOverrideInfo {
38
+ scope: "user" | "project";
39
+ path: string;
40
+ base: BuiltinAgentOverrideBase;
41
+ }
42
+
18
43
  export interface AgentConfig {
19
44
  name: string;
20
45
  description: string;
@@ -28,13 +53,13 @@ export interface AgentConfig {
28
53
  filePath: string;
29
54
  skills?: string[];
30
55
  extensions?: string[];
31
- // Chain behavior fields
32
56
  output?: string;
33
57
  defaultReads?: string[];
34
58
  defaultProgress?: boolean;
35
59
  interactive?: boolean;
36
60
  maxSubagentDepth?: number;
37
61
  extraFields?: Record<string, string>;
62
+ override?: BuiltinAgentOverrideInfo;
38
63
  }
39
64
 
40
65
  export interface ChainStepConfig {
@@ -61,6 +86,266 @@ export interface AgentDiscoveryResult {
61
86
  projectAgentsDir: string | null;
62
87
  }
63
88
 
89
+ function splitToolList(rawTools: string[] | undefined): { tools?: string[]; mcpDirectTools?: string[] } {
90
+ const mcpDirectTools: string[] = [];
91
+ const tools: string[] = [];
92
+ for (const tool of rawTools ?? []) {
93
+ if (tool.startsWith("mcp:")) {
94
+ mcpDirectTools.push(tool.slice(4));
95
+ } else {
96
+ tools.push(tool);
97
+ }
98
+ }
99
+ return {
100
+ ...(tools.length > 0 ? { tools } : {}),
101
+ ...(mcpDirectTools.length > 0 ? { mcpDirectTools } : {}),
102
+ };
103
+ }
104
+
105
+ function joinToolList(config: Pick<AgentConfig, "tools" | "mcpDirectTools">): string[] | undefined {
106
+ const joined = [
107
+ ...(config.tools ?? []),
108
+ ...(config.mcpDirectTools ?? []).map((tool) => `mcp:${tool}`),
109
+ ];
110
+ return joined.length > 0 ? joined : undefined;
111
+ }
112
+
113
+ function arraysEqual(a: string[] | undefined, b: string[] | undefined): boolean {
114
+ if (!a && !b) return true;
115
+ if (!a || !b) return false;
116
+ if (a.length !== b.length) return false;
117
+ for (let i = 0; i < a.length; i++) {
118
+ if (a[i] !== b[i]) return false;
119
+ }
120
+ return true;
121
+ }
122
+
123
+ function cloneOverrideBase(agent: AgentConfig): BuiltinAgentOverrideBase {
124
+ return {
125
+ model: agent.model,
126
+ fallbackModels: agent.fallbackModels ? [...agent.fallbackModels] : undefined,
127
+ thinking: agent.thinking,
128
+ systemPrompt: agent.systemPrompt,
129
+ skills: agent.skills ? [...agent.skills] : undefined,
130
+ tools: agent.tools ? [...agent.tools] : undefined,
131
+ mcpDirectTools: agent.mcpDirectTools ? [...agent.mcpDirectTools] : undefined,
132
+ };
133
+ }
134
+
135
+ function cloneOverrideValue(override: BuiltinAgentOverrideConfig): BuiltinAgentOverrideConfig {
136
+ return {
137
+ ...(override.model !== undefined ? { model: override.model } : {}),
138
+ ...(override.fallbackModels !== undefined
139
+ ? { fallbackModels: override.fallbackModels === false ? false : [...override.fallbackModels] }
140
+ : {}),
141
+ ...(override.thinking !== undefined ? { thinking: override.thinking } : {}),
142
+ ...(override.systemPrompt !== undefined ? { systemPrompt: override.systemPrompt } : {}),
143
+ ...(override.skills !== undefined ? { skills: override.skills === false ? false : [...override.skills] } : {}),
144
+ ...(override.tools !== undefined ? { tools: override.tools === false ? false : [...override.tools] } : {}),
145
+ };
146
+ }
147
+
148
+ function findNearestProjectRoot(cwd: string): string | null {
149
+ let currentDir = cwd;
150
+ while (true) {
151
+ if (isDirectory(path.join(currentDir, ".dm")) || isDirectory(path.join(currentDir, ".agents"))) {
152
+ return currentDir;
153
+ }
154
+
155
+ const parentDir = path.dirname(currentDir);
156
+ if (parentDir === currentDir) return null;
157
+ currentDir = parentDir;
158
+ }
159
+ }
160
+
161
+ export function getUserAgentSettingsPath(): string {
162
+ return path.join(os.homedir(), ".dm", "agent", "settings.json");
163
+ }
164
+
165
+ export function getProjectAgentSettingsPath(cwd: string): string | null {
166
+ const projectRoot = findNearestProjectRoot(cwd);
167
+ return projectRoot ? path.join(projectRoot, ".dm", "settings.json") : null;
168
+ }
169
+
170
+ function readSettingsFileStrict(filePath: string): Record<string, unknown> {
171
+ if (!fs.existsSync(filePath)) return {};
172
+ let parsed: unknown;
173
+ try {
174
+ parsed = JSON.parse(fs.readFileSync(filePath, "utf-8"));
175
+ } catch (error) {
176
+ const message = error instanceof Error ? error.message : String(error);
177
+ throw new Error(`Failed to parse settings file '${filePath}': ${message}`, { cause: error });
178
+ }
179
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
180
+ throw new Error(`Settings file '${filePath}' must contain a JSON object.`);
181
+ }
182
+ return parsed as Record<string, unknown>;
183
+ }
184
+
185
+ function writeSettingsFile(filePath: string, settings: Record<string, unknown>): void {
186
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
187
+ fs.writeFileSync(filePath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
188
+ }
189
+
190
+ function parseStringArrayOrFalse(value: unknown): string[] | false | undefined {
191
+ if (value === false) return false;
192
+ if (!Array.isArray(value)) return undefined;
193
+ const items = value.filter((item): item is string => typeof item === "string").map((item) => item.trim()).filter(Boolean);
194
+ return items;
195
+ }
196
+
197
+ function parseBuiltinOverrideEntry(value: unknown): BuiltinAgentOverrideConfig | undefined {
198
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
199
+ const input = value as Record<string, unknown>;
200
+ const override: BuiltinAgentOverrideConfig = {};
201
+
202
+ if (typeof input.model === "string" || input.model === false) override.model = input.model;
203
+ if (typeof input.thinking === "string" || input.thinking === false) override.thinking = input.thinking;
204
+ if (typeof input.systemPrompt === "string") override.systemPrompt = input.systemPrompt;
205
+
206
+ const fallbackModels = parseStringArrayOrFalse(input.fallbackModels);
207
+ if (fallbackModels !== undefined) override.fallbackModels = fallbackModels;
208
+
209
+ const skills = parseStringArrayOrFalse(input.skills);
210
+ if (skills !== undefined) override.skills = skills;
211
+
212
+ const tools = parseStringArrayOrFalse(input.tools);
213
+ if (tools !== undefined) override.tools = tools;
214
+
215
+ return Object.keys(override).length > 0 ? override : undefined;
216
+ }
217
+
218
+ function readBuiltinOverrides(filePath: string | null): Record<string, BuiltinAgentOverrideConfig> {
219
+ if (!filePath || !fs.existsSync(filePath)) return {};
220
+ const settings = readSettingsFileStrict(filePath);
221
+ const subagents = settings.subagents;
222
+ if (!subagents || typeof subagents !== "object" || Array.isArray(subagents)) return {};
223
+ const agentOverrides = (subagents as Record<string, unknown>).agentOverrides;
224
+ if (!agentOverrides || typeof agentOverrides !== "object" || Array.isArray(agentOverrides)) return {};
225
+
226
+ const parsed: Record<string, BuiltinAgentOverrideConfig> = {};
227
+ for (const [name, value] of Object.entries(agentOverrides)) {
228
+ const override = parseBuiltinOverrideEntry(value);
229
+ if (override) parsed[name] = override;
230
+ }
231
+ return parsed;
232
+ }
233
+
234
+ function applyBuiltinOverride(
235
+ agent: AgentConfig,
236
+ override: BuiltinAgentOverrideConfig,
237
+ meta: { scope: "user" | "project"; path: string },
238
+ ): AgentConfig {
239
+ const next: AgentConfig = {
240
+ ...agent,
241
+ override: { ...meta, base: cloneOverrideBase(agent) },
242
+ };
243
+
244
+ if (override.model !== undefined) next.model = override.model === false ? undefined : override.model;
245
+ if (override.fallbackModels !== undefined) {
246
+ next.fallbackModels = override.fallbackModels === false ? undefined : [...override.fallbackModels];
247
+ }
248
+ if (override.thinking !== undefined) next.thinking = override.thinking === false ? undefined : override.thinking;
249
+ if (override.systemPrompt !== undefined) next.systemPrompt = override.systemPrompt;
250
+ if (override.skills !== undefined) next.skills = override.skills === false ? undefined : [...override.skills];
251
+ if (override.tools !== undefined) {
252
+ const { tools, mcpDirectTools } = splitToolList(override.tools === false ? [] : override.tools);
253
+ next.tools = tools;
254
+ next.mcpDirectTools = mcpDirectTools;
255
+ }
256
+
257
+ return next;
258
+ }
259
+
260
+ function applyBuiltinOverrides(
261
+ builtinAgents: AgentConfig[],
262
+ userOverrides: Record<string, BuiltinAgentOverrideConfig>,
263
+ projectOverrides: Record<string, BuiltinAgentOverrideConfig>,
264
+ userSettingsPath: string,
265
+ projectSettingsPath: string | null,
266
+ ): AgentConfig[] {
267
+ return builtinAgents.map((agent) => {
268
+ const projectOverride = projectOverrides[agent.name];
269
+ if (projectOverride && projectSettingsPath) {
270
+ return applyBuiltinOverride(agent, projectOverride, { scope: "project", path: projectSettingsPath });
271
+ }
272
+
273
+ const userOverride = userOverrides[agent.name];
274
+ if (userOverride) {
275
+ return applyBuiltinOverride(agent, userOverride, { scope: "user", path: userSettingsPath });
276
+ }
277
+
278
+ return agent;
279
+ });
280
+ }
281
+
282
+ export function buildBuiltinOverrideConfig(
283
+ base: BuiltinAgentOverrideBase,
284
+ draft: Pick<AgentConfig, "model" | "fallbackModels" | "thinking" | "systemPrompt" | "skills" | "tools" | "mcpDirectTools">,
285
+ ): BuiltinAgentOverrideConfig | undefined {
286
+ const override: BuiltinAgentOverrideConfig = {};
287
+
288
+ if (draft.model !== base.model) override.model = draft.model ?? false;
289
+ if (!arraysEqual(draft.fallbackModels, base.fallbackModels)) override.fallbackModels = draft.fallbackModels ? [...draft.fallbackModels] : false;
290
+ if (draft.thinking !== base.thinking) override.thinking = draft.thinking ?? false;
291
+ if (draft.systemPrompt !== base.systemPrompt) override.systemPrompt = draft.systemPrompt;
292
+ if (!arraysEqual(draft.skills, base.skills)) override.skills = draft.skills ? [...draft.skills] : false;
293
+
294
+ const baseTools = joinToolList(base);
295
+ const draftTools = joinToolList(draft);
296
+ if (!arraysEqual(draftTools, baseTools)) override.tools = draftTools ? [...draftTools] : false;
297
+
298
+ return Object.keys(override).length > 0 ? override : undefined;
299
+ }
300
+
301
+ export function saveBuiltinAgentOverride(
302
+ cwd: string,
303
+ name: string,
304
+ scope: "user" | "project",
305
+ override: BuiltinAgentOverrideConfig,
306
+ ): string {
307
+ const filePath = scope === "project" ? getProjectAgentSettingsPath(cwd) : getUserAgentSettingsPath();
308
+ if (!filePath) throw new Error("Project override is not available here. No project config root was found.");
309
+
310
+ const settings = readSettingsFileStrict(filePath);
311
+ const subagents = settings.subagents && typeof settings.subagents === "object" && !Array.isArray(settings.subagents)
312
+ ? { ...(settings.subagents as Record<string, unknown>) }
313
+ : {};
314
+ const agentOverrides = subagents.agentOverrides && typeof subagents.agentOverrides === "object" && !Array.isArray(subagents.agentOverrides)
315
+ ? { ...(subagents.agentOverrides as Record<string, unknown>) }
316
+ : {};
317
+
318
+ agentOverrides[name] = cloneOverrideValue(override);
319
+ subagents.agentOverrides = agentOverrides;
320
+ settings.subagents = subagents;
321
+ writeSettingsFile(filePath, settings);
322
+ return filePath;
323
+ }
324
+
325
+ export function removeBuiltinAgentOverride(cwd: string, name: string, scope: "user" | "project"): string {
326
+ const filePath = scope === "project" ? getProjectAgentSettingsPath(cwd) : getUserAgentSettingsPath();
327
+ if (!filePath) throw new Error("Project override is not available here. No project config root was found.");
328
+ if (!fs.existsSync(filePath)) return filePath;
329
+
330
+ const settings = readSettingsFileStrict(filePath);
331
+ const subagents = settings.subagents;
332
+ if (!subagents || typeof subagents !== "object" || Array.isArray(subagents)) return filePath;
333
+ const nextSubagents = { ...(subagents as Record<string, unknown>) };
334
+ const agentOverrides = nextSubagents.agentOverrides;
335
+ if (!agentOverrides || typeof agentOverrides !== "object" || Array.isArray(agentOverrides)) return filePath;
336
+
337
+ const nextOverrides = { ...(agentOverrides as Record<string, unknown>) };
338
+ delete nextOverrides[name];
339
+ if (Object.keys(nextOverrides).length > 0) nextSubagents.agentOverrides = nextOverrides;
340
+ else delete nextSubagents.agentOverrides;
341
+
342
+ if (Object.keys(nextSubagents).length > 0) settings.subagents = nextSubagents;
343
+ else delete settings.subagents;
344
+
345
+ writeSettingsFile(filePath, settings);
346
+ return filePath;
347
+ }
348
+
64
349
  function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
65
350
  const agents: AgentConfig[] = [];
66
351
 
@@ -111,7 +396,6 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
111
396
  }
112
397
  }
113
398
 
114
- // Parse defaultReads as comma-separated list (like tools)
115
399
  const defaultReads = frontmatter.defaultReads
116
400
  ?.split(",")
117
401
  .map((f) => f.trim())
@@ -155,7 +439,6 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
155
439
  filePath,
156
440
  skills: skills && skills.length > 0 ? skills : undefined,
157
441
  extensions,
158
- // Chain behavior fields
159
442
  output: frontmatter.output,
160
443
  defaultReads: defaultReads && defaultReads.length > 0 ? defaultReads : undefined,
161
444
  defaultProgress: frontmatter.defaultProgress === "true",
@@ -216,18 +499,12 @@ function isDirectory(p: string): boolean {
216
499
  }
217
500
 
218
501
  function findNearestProjectAgentsDir(cwd: string): string | null {
219
- let currentDir = cwd;
220
- while (true) {
221
- const candidateAlt = path.join(currentDir, ".agents");
222
- if (isDirectory(candidateAlt)) return candidateAlt;
223
-
224
- const candidate = path.join(currentDir, ".dm", "agents");
225
- if (isDirectory(candidate)) return candidate;
226
-
227
- const parentDir = path.dirname(currentDir);
228
- if (parentDir === currentDir) return null;
229
- currentDir = parentDir;
230
- }
502
+ const projectRoot = findNearestProjectRoot(cwd);
503
+ if (!projectRoot) return null;
504
+ const candidateAlt = path.join(projectRoot, ".agents");
505
+ if (isDirectory(candidateAlt)) return candidateAlt;
506
+ const candidate = path.join(projectRoot, ".dm", "agents");
507
+ return isDirectory(candidate) ? candidate : null;
231
508
  }
232
509
 
233
510
  const BUILTIN_AGENTS_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), "agents");
@@ -236,8 +513,16 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
236
513
  const userDirOld = path.join(os.homedir(), ".dm", "agent", "agents");
237
514
  const userDirNew = path.join(os.homedir(), ".agents");
238
515
  const projectAgentsDir = findNearestProjectAgentsDir(cwd);
239
-
240
- const builtinAgents = loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin");
516
+ const userSettingsPath = getUserAgentSettingsPath();
517
+ const projectSettingsPath = getProjectAgentSettingsPath(cwd);
518
+
519
+ const builtinAgents = applyBuiltinOverrides(
520
+ loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin"),
521
+ scope === "project" ? {} : readBuiltinOverrides(userSettingsPath),
522
+ scope === "user" ? {} : readBuiltinOverrides(projectSettingsPath),
523
+ userSettingsPath,
524
+ projectSettingsPath,
525
+ );
241
526
 
242
527
  const userAgentsOld = scope === "project" ? [] : loadAgentsFromDir(userDirOld, "user");
243
528
  const userAgentsNew = scope === "project" ? [] : loadAgentsFromDir(userDirNew, "user");
@@ -256,12 +541,22 @@ export function discoverAgentsAll(cwd: string): {
256
541
  chains: ChainConfig[];
257
542
  userDir: string;
258
543
  projectDir: string | null;
544
+ userSettingsPath: string;
545
+ projectSettingsPath: string | null;
259
546
  } {
260
547
  const userDirOld = path.join(os.homedir(), ".dm", "agent", "agents");
261
548
  const userDirNew = path.join(os.homedir(), ".agents");
262
549
  const projectDir = findNearestProjectAgentsDir(cwd);
263
-
264
- const builtin = loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin");
550
+ const userSettingsPath = getUserAgentSettingsPath();
551
+ const projectSettingsPath = getProjectAgentSettingsPath(cwd);
552
+
553
+ const builtin = applyBuiltinOverrides(
554
+ loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin"),
555
+ readBuiltinOverrides(userSettingsPath),
556
+ readBuiltinOverrides(projectSettingsPath),
557
+ userSettingsPath,
558
+ projectSettingsPath,
559
+ );
265
560
  const user = [
266
561
  ...loadAgentsFromDir(userDirOld, "user"),
267
562
  ...loadAgentsFromDir(userDirNew, "user"),
@@ -275,5 +570,5 @@ export function discoverAgentsAll(cwd: string): {
275
570
 
276
571
  const userDir = fs.existsSync(userDirNew) ? userDirNew : userDirOld;
277
572
 
278
- return { builtin, user, project, chains, userDir, projectDir };
573
+ return { builtin, user, project, chains, userDir, projectDir, userSettingsPath, projectSettingsPath };
279
574
  }
@@ -13,6 +13,11 @@ Bundled DM extension for multi-pass review loops and oracle-assisted review sess
13
13
  - Global config path: `~/.dm/ultrathink.json`
14
14
  - Config env override: `DM_ULTRATHINK_CONFIG_PATH`
15
15
 
16
+ ## Naming-model catalogs
17
+
18
+ - DuckMind/OpenRouter sessions show `duckmind/free`, `duckmind/auto`, `duckmind/lite`, `duckmind/smart`, `duckmind/deep`
19
+ - OpenAI Codex / dm-multicodex sessions show `openai-codex/gpt-5.1`, `openai-codex/gpt-5.1-codex-max`, `openai-codex/gpt-5.1-codex-mini`, `openai-codex/gpt-5.2`, `openai-codex/gpt-5.2-codex`
20
+
16
21
  ## Bundled with dm
17
22
 
18
23
  No separate install step is required. The extension is loaded from DM's bundled extension catalog.
@@ -61,6 +61,7 @@ type DuckMindPreset = {
61
61
 
62
62
  const DUCKMIND_PROVIDER = "duckmind";
63
63
  const OPENROUTER_PROVIDER = "openrouter";
64
+ const OPENAI_CODEX_PROVIDER = "openai-codex";
64
65
  const DUCKMIND_PRESETS: readonly DuckMindPreset[] = [
65
66
  {
66
67
  alias: "free",
@@ -94,6 +95,34 @@ const DUCKMIND_PRESETS: readonly DuckMindPreset[] = [
94
95
  },
95
96
  ];
96
97
 
98
+ const OPENAI_CODEX_NAMING_MODELS = [
99
+ {
100
+ modelId: "gpt-5.1",
101
+ fallbackModelId: "gpt-5.1-codex-mini",
102
+ displayName: "GPT-5.1",
103
+ },
104
+ {
105
+ modelId: "gpt-5.1-codex-max",
106
+ fallbackModelId: "gpt-5.1-codex-mini",
107
+ displayName: "GPT-5.1 Codex Max",
108
+ },
109
+ {
110
+ modelId: "gpt-5.1-codex-mini",
111
+ fallbackModelId: "gpt-5.1-codex-mini",
112
+ displayName: "GPT-5.1 Codex Mini",
113
+ },
114
+ {
115
+ modelId: "gpt-5.2",
116
+ fallbackModelId: "gpt-5.1-codex-mini",
117
+ displayName: "GPT-5.2",
118
+ },
119
+ {
120
+ modelId: "gpt-5.2-codex",
121
+ fallbackModelId: "gpt-5.1-codex-mini",
122
+ displayName: "GPT-5.2 Codex",
123
+ },
124
+ ] as const;
125
+
97
126
  let testOverrides: NamingTestOverrides | undefined;
98
127
 
99
128
  export function setNamingTestOverrides(overrides?: NamingTestOverrides): void {
@@ -208,6 +237,25 @@ function buildDuckMindNamingChoices(availableModels: Model<any>[]): NamingModelC
208
237
  });
209
238
  }
210
239
 
240
+ function buildOpenAICodexNamingChoices(availableModels: Model<any>[]): NamingModelChoice[] {
241
+ const codexModels = availableModels.filter((model) => model.provider === OPENAI_CODEX_PROVIDER);
242
+ return OPENAI_CODEX_NAMING_MODELS.flatMap((entry) => {
243
+ const model = pickOpenRouterModel(codexModels, entry.modelId, entry.fallbackModelId, entry.displayName);
244
+ if (!model) {
245
+ return [];
246
+ }
247
+ return [
248
+ {
249
+ label: `${OPENAI_CODEX_PROVIDER}/${entry.modelId}`,
250
+ config: {
251
+ provider: OPENAI_CODEX_PROVIDER,
252
+ modelId: entry.modelId,
253
+ },
254
+ },
255
+ ];
256
+ });
257
+ }
258
+
211
259
  function resolveDuckMindAliasModel(availableModels: Model<any>[], config: NamingModelConfig): Model<any> | undefined {
212
260
  const normalizedProvider = config.provider.trim().toLowerCase();
213
261
  const normalizedModelId = config.modelId.trim().toLowerCase();
@@ -231,9 +279,31 @@ function resolveDuckMindAliasModel(availableModels: Model<any>[], config: Naming
231
279
  );
232
280
  }
233
281
 
282
+ function resolveOpenAICodexAliasModel(availableModels: Model<any>[], config: NamingModelConfig): Model<any> | undefined {
283
+ const normalizedProvider = config.provider.trim().toLowerCase();
284
+ const normalizedModelId = config.modelId.trim().toLowerCase();
285
+ if (normalizedProvider !== OPENAI_CODEX_PROVIDER) {
286
+ return undefined;
287
+ }
288
+
289
+ const preset = OPENAI_CODEX_NAMING_MODELS.find((entry) => normalizedModelId === entry.modelId.toLowerCase());
290
+ if (!preset) {
291
+ return undefined;
292
+ }
293
+
294
+ return pickOpenRouterModel(
295
+ availableModels.filter((model) => model.provider === OPENAI_CODEX_PROVIDER),
296
+ preset.modelId,
297
+ preset.fallbackModelId,
298
+ preset.displayName,
299
+ );
300
+ }
301
+
234
302
  async function resolveNamingModel(ctx: ExtensionContext, config: NamingModelConfig): Promise<{ model: Model<any>; apiKey: string }> {
303
+ const availableModels = ctx.modelRegistry.getAvailable();
235
304
  const model =
236
- resolveDuckMindAliasModel(ctx.modelRegistry.getAvailable(), config) ??
305
+ resolveDuckMindAliasModel(availableModels, config) ??
306
+ resolveOpenAICodexAliasModel(availableModels, config) ??
237
307
  ctx.modelRegistry.find(config.provider, config.modelId);
238
308
  if (!model) {
239
309
  throw new Error(`Ultrathink naming model ${config.provider}/${config.modelId} is not available in DM.`);
@@ -320,10 +390,12 @@ export async function ensureNamingModel(
320
390
  const choices =
321
391
  ctx.model?.provider === OPENROUTER_PROVIDER || ctx.model?.provider === DUCKMIND_PROVIDER
322
392
  ? buildDuckMindNamingChoices(availableModels)
323
- : availableModels.map(buildDefaultNamingChoice);
393
+ : ctx.model?.provider === OPENAI_CODEX_PROVIDER
394
+ ? buildOpenAICodexNamingChoices(availableModels)
395
+ : availableModels.map(buildDefaultNamingChoice);
324
396
 
325
397
  if (choices.length === 0) {
326
- throw new Error("Ultrathink could not find any available DM models to use for branch and commit naming.");
398
+ throw new Error("Ultrathink could not find any available naming models to use for branch and commit naming.");
327
399
  }
328
400
 
329
401
  const selection = await ctx.ui.select(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duckmind/dm-darwin-x64",
3
- "version": "0.13.6",
3
+ "version": "0.13.8",
4
4
  "description": "DuckMind (dm) binary payload for darwin x64",
5
5
  "license": "MIT",
6
6
  "os": [