mixdog 0.8.1 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (299) hide show
  1. package/README.md +47 -23
  2. package/package.json +34 -28
  3. package/scripts/_test-folder-dialog.mjs +30 -0
  4. package/scripts/agent-parallel-smoke.mjs +388 -0
  5. package/scripts/agent-tag-reuse-smoke.mjs +183 -0
  6. package/scripts/background-task-meta-smoke.mjs +38 -0
  7. package/scripts/boot-smoke.mjs +52 -9
  8. package/scripts/build-runtime-linux.sh +348 -0
  9. package/scripts/build-runtime-macos.sh +217 -0
  10. package/scripts/build-runtime-windows.ps1 +242 -0
  11. package/scripts/compact-active-turn-test.mjs +68 -0
  12. package/scripts/compact-smoke.mjs +859 -129
  13. package/scripts/compact-trigger-migration-smoke.mjs +187 -0
  14. package/scripts/fix-brief-fn.mjs +35 -0
  15. package/scripts/fix-format-tool-surface.mjs +24 -0
  16. package/scripts/fix-tool-exec-visible.mjs +42 -0
  17. package/scripts/generate-runtime-manifest.mjs +166 -0
  18. package/scripts/hook-bus-test.mjs +330 -0
  19. package/scripts/lead-workflow-smoke.mjs +33 -39
  20. package/scripts/live-worker-smoke.mjs +43 -37
  21. package/scripts/llm-trace-summary.mjs +315 -0
  22. package/scripts/memory-meta-concurrency-test.mjs +20 -0
  23. package/scripts/output-style-smoke.mjs +56 -15
  24. package/scripts/parent-abort-link-test.mjs +44 -0
  25. package/scripts/patch-agent-brief.mjs +48 -0
  26. package/scripts/patch-app.mjs +21 -0
  27. package/scripts/patch-app2.mjs +18 -0
  28. package/scripts/patch-dist-brief.mjs +96 -0
  29. package/scripts/patch-tool-exec.mjs +70 -0
  30. package/scripts/pretool-ask-runtime-test.mjs +54 -0
  31. package/scripts/provider-toolcall-test.mjs +376 -0
  32. package/scripts/reactive-compact-persist-smoke.mjs +124 -0
  33. package/scripts/sanitize-tool-pairs-test.mjs +260 -0
  34. package/scripts/session-context-bench.mjs +205 -33
  35. package/scripts/session-ingest-smoke.mjs +177 -0
  36. package/scripts/set-effort-config-test.mjs +41 -0
  37. package/scripts/smoke-runtime-negative.ps1 +106 -0
  38. package/scripts/smoke-runtime-negative.sh +97 -0
  39. package/scripts/smoke.mjs +25 -0
  40. package/scripts/tool-result-hook-test.mjs +48 -0
  41. package/scripts/tool-smoke.mjs +1222 -90
  42. package/scripts/toolcall-args-test.mjs +150 -0
  43. package/scripts/tui-background-failure-smoke.mjs +73 -0
  44. package/scripts/usage-metrics-epoch-smoke.mjs +114 -0
  45. package/src/agents/debugger/AGENT.md +8 -0
  46. package/src/agents/explore/AGENT.md +4 -0
  47. package/src/agents/heavy-worker/AGENT.md +9 -3
  48. package/src/agents/maintainer/AGENT.md +4 -0
  49. package/src/agents/reviewer/AGENT.md +8 -0
  50. package/src/agents/scheduler-task/AGENT.md +12 -0
  51. package/src/agents/scheduler-task/agent.json +6 -0
  52. package/src/agents/webhook-handler/AGENT.md +12 -0
  53. package/src/agents/webhook-handler/agent.json +6 -0
  54. package/src/agents/worker/AGENT.md +9 -3
  55. package/src/app.mjs +77 -3
  56. package/src/defaults/hidden-roles.json +17 -12
  57. package/src/headless-role.mjs +117 -0
  58. package/src/help.mjs +30 -0
  59. package/src/hooks/lib/permission-evaluator.cjs +11 -475
  60. package/src/lib/keychain-cjs.cjs +9 -1
  61. package/src/lib/mixdog-debug.cjs +0 -29
  62. package/src/lib/plugin-paths.cjs +1 -7
  63. package/src/lib/rules-builder.cjs +242 -98
  64. package/src/lib/text-utils.cjs +1 -1
  65. package/src/mixdog-session-runtime.mjs +2211 -446
  66. package/src/output-styles/default.md +12 -28
  67. package/src/output-styles/extreme-simple.md +9 -6
  68. package/src/output-styles/simple.md +22 -9
  69. package/src/repl.mjs +108 -59
  70. package/src/rules/agent/00-common.md +15 -0
  71. package/src/rules/{bridge → agent}/20-skip-protocol.md +1 -2
  72. package/src/rules/agent/30-explorer.md +22 -0
  73. package/src/rules/{bridge → agent}/40-cycle1-agent.md +7 -0
  74. package/src/rules/{bridge → agent}/41-cycle2-agent.md +7 -0
  75. package/src/rules/{bridge → agent}/42-cycle3-agent.md +7 -0
  76. package/src/rules/lead/01-general.md +9 -5
  77. package/src/rules/lead/04-workflow.md +51 -12
  78. package/src/rules/lead/lead-tool.md +6 -0
  79. package/src/rules/shared/01-tool.md +12 -1
  80. package/src/runtime/agent/orchestrator/activity-bus.mjs +7 -18
  81. package/src/runtime/agent/orchestrator/agent-owner.mjs +11 -0
  82. package/src/runtime/agent/orchestrator/{smart-bridge/bridge-llm.mjs → agent-runtime/agent-dispatch.mjs} +138 -111
  83. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +94 -0
  84. package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/cache-strategy.mjs +32 -23
  85. package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/session-builder.mjs +33 -27
  86. package/src/runtime/agent/orchestrator/{bridge-trace.mjs → agent-trace.mjs} +132 -81
  87. package/src/runtime/agent/orchestrator/cache-mtime.mjs +0 -21
  88. package/src/runtime/agent/orchestrator/config.mjs +174 -55
  89. package/src/runtime/agent/orchestrator/context/collect.mjs +195 -487
  90. package/src/runtime/agent/orchestrator/dispatch-persist.mjs +1 -1
  91. package/src/runtime/agent/orchestrator/internal-roles.mjs +77 -29
  92. package/src/runtime/agent/orchestrator/internal-tools.mjs +5 -6
  93. package/src/runtime/agent/orchestrator/mcp/client.mjs +15 -9
  94. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -1
  95. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +380 -268
  96. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +131 -93
  97. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +236 -4
  98. package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +49 -0
  99. package/src/runtime/agent/orchestrator/providers/gemini.mjs +58 -13
  100. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +67 -149
  101. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +132 -2
  102. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +4 -1
  103. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +45 -0
  104. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +61 -116
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +25 -0
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +79 -255
  107. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +203 -71
  108. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +512 -312
  109. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +343 -518
  110. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +6 -6
  111. package/src/runtime/agent/orchestrator/providers/registry.mjs +88 -51
  112. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +289 -11
  113. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +41 -8
  114. package/src/runtime/agent/orchestrator/session/compact.mjs +1173 -267
  115. package/src/runtime/agent/orchestrator/session/context-utils.mjs +199 -36
  116. package/src/runtime/agent/orchestrator/session/loop.mjs +840 -612
  117. package/src/runtime/agent/orchestrator/session/manager.mjs +1574 -459
  118. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +107 -0
  119. package/src/runtime/agent/orchestrator/session/store.mjs +67 -29
  120. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +2 -2
  121. package/src/runtime/agent/orchestrator/stall-policy.mjs +31 -16
  122. package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +3 -219
  123. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +35 -8
  124. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +19 -8
  125. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +10 -53
  126. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +60 -37
  127. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +21 -2
  128. package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +1 -1
  129. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -7
  130. package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +1 -3
  131. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +36 -12
  132. package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +2 -0
  133. package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -2
  134. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +5 -12
  135. package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +1 -1
  136. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -36
  137. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +2 -40
  138. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +148 -27
  139. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +2 -2
  140. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +43 -75
  141. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +90 -20
  142. package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -5
  143. package/src/runtime/agent/orchestrator/tools/code-graph-state.mjs +86 -0
  144. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +11 -11
  145. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4106 -4019
  146. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +33 -4
  147. package/src/runtime/agent/orchestrator/tools/patch.mjs +90 -6
  148. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +6 -4
  149. package/src/runtime/agent/orchestrator/tools/result-compression.mjs +4 -4
  150. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +8 -1
  151. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -4
  152. package/src/runtime/channels/index.mjs +149 -51
  153. package/src/runtime/channels/lib/cli-worker-host.mjs +1 -8
  154. package/src/runtime/channels/lib/config.mjs +0 -1
  155. package/src/runtime/channels/lib/drop-trace.mjs +1 -1
  156. package/src/runtime/channels/lib/executor.mjs +0 -3
  157. package/src/runtime/channels/lib/memory-client.mjs +0 -38
  158. package/src/runtime/channels/lib/output-forwarder.mjs +1 -8
  159. package/src/runtime/channels/lib/runtime-paths.mjs +0 -6
  160. package/src/runtime/channels/lib/scheduler.mjs +18 -14
  161. package/src/runtime/channels/lib/session-discovery.mjs +3 -6
  162. package/src/runtime/channels/lib/tool-format.mjs +0 -1
  163. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -12
  164. package/src/runtime/channels/lib/webhook.mjs +1 -1
  165. package/src/runtime/channels/tool-defs.mjs +29 -29
  166. package/src/runtime/lib/keychain-cjs.cjs +0 -1
  167. package/src/runtime/memory/data/runtime-manifest.json +6 -7
  168. package/src/runtime/memory/index.mjs +519 -118
  169. package/src/runtime/memory/lib/agent-ipc.mjs +29 -12
  170. package/src/runtime/memory/lib/core-memory-store.mjs +2 -2
  171. package/src/runtime/memory/lib/embedding-model-config.mjs +55 -0
  172. package/src/runtime/memory/lib/embedding-provider.mjs +31 -4
  173. package/src/runtime/memory/lib/embedding-worker.mjs +19 -10
  174. package/src/runtime/memory/lib/llm-worker-host.mjs +0 -4
  175. package/src/runtime/memory/lib/memory-cycle1.mjs +28 -7
  176. package/src/runtime/memory/lib/memory-cycle2.mjs +4 -5
  177. package/src/runtime/memory/lib/memory-cycle3.mjs +2 -2
  178. package/src/runtime/memory/lib/memory-ops-policy.mjs +2 -2
  179. package/src/runtime/memory/lib/memory-session-merge.mjs +38 -0
  180. package/src/runtime/memory/lib/memory.mjs +88 -9
  181. package/src/runtime/memory/lib/model-profile.mjs +1 -1
  182. package/src/runtime/memory/lib/pg/adapter.mjs +1 -1
  183. package/src/runtime/memory/lib/pg/supervisor.mjs +12 -0
  184. package/src/runtime/memory/lib/runtime-fetcher.mjs +80 -21
  185. package/src/runtime/memory/lib/session-ingest.mjs +196 -0
  186. package/src/runtime/memory/lib/trace-store.mjs +96 -51
  187. package/src/runtime/memory/tool-defs.mjs +46 -37
  188. package/src/runtime/search/index.mjs +104 -473
  189. package/src/runtime/search/lib/config.mjs +0 -4
  190. package/src/runtime/search/lib/state.mjs +1 -15
  191. package/src/runtime/search/lib/web-tools.mjs +44 -25
  192. package/src/runtime/search/tool-defs.mjs +16 -23
  193. package/src/runtime/shared/abort-controller.mjs +1 -1
  194. package/src/runtime/shared/atomic-file.mjs +4 -3
  195. package/src/runtime/shared/background-tasks.mjs +122 -11
  196. package/src/runtime/shared/child-spawn-gate.mjs +139 -0
  197. package/src/runtime/shared/config.mjs +7 -4
  198. package/src/runtime/shared/err-text.mjs +131 -4
  199. package/src/runtime/shared/llm/cost.mjs +2 -2
  200. package/src/runtime/shared/llm/http-agent.mjs +23 -7
  201. package/src/runtime/shared/llm/index.mjs +34 -11
  202. package/src/runtime/shared/llm/usage-log.mjs +4 -4
  203. package/src/runtime/shared/markdown-frontmatter.mjs +56 -0
  204. package/src/runtime/shared/singleton-owner.mjs +104 -0
  205. package/src/runtime/shared/tool-execution-contract.mjs +199 -20
  206. package/src/runtime/shared/tool-execution-contract.test.mjs +183 -0
  207. package/src/runtime/shared/tool-surface.mjs +624 -95
  208. package/src/runtime/shared/user-data-guard.mjs +0 -2
  209. package/src/standalone/agent-task-status.mjs +203 -0
  210. package/src/standalone/agent-task-status.test.mjs +76 -0
  211. package/src/standalone/agent-tool.mjs +1913 -0
  212. package/src/standalone/channel-worker.mjs +370 -14
  213. package/src/standalone/explore-tool.mjs +165 -70
  214. package/src/standalone/folder-dialog.mjs +314 -0
  215. package/src/standalone/hook-bus.mjs +898 -22
  216. package/src/standalone/memory-runtime-proxy.mjs +320 -0
  217. package/src/standalone/projects.mjs +226 -0
  218. package/src/standalone/provider-admin.mjs +41 -24
  219. package/src/standalone/seeds.mjs +1 -78
  220. package/src/standalone/usage-dashboard.mjs +96 -8
  221. package/src/tui/App.jsx +4800 -2140
  222. package/src/tui/components/AnsiText.jsx +39 -28
  223. package/src/tui/components/ContextPanel.jsx +14 -4
  224. package/src/tui/components/Markdown.jsx +43 -77
  225. package/src/tui/components/MarkdownTable.jsx +9 -184
  226. package/src/tui/components/Message.jsx +28 -11
  227. package/src/tui/components/Picker.jsx +95 -56
  228. package/src/tui/components/PromptInput.jsx +428 -239
  229. package/src/tui/components/QueuedCommands.jsx +1 -1
  230. package/src/tui/components/SlashCommandPalette.jsx +27 -21
  231. package/src/tui/components/Spinner.jsx +67 -38
  232. package/src/tui/components/StatusLine.jsx +606 -38
  233. package/src/tui/components/TextEntryPanel.jsx +128 -9
  234. package/src/tui/components/ToolExecution.jsx +597 -362
  235. package/src/tui/components/TurnDone.jsx +3 -3
  236. package/src/tui/components/UsagePanel.jsx +3 -5
  237. package/src/tui/components/tool-output-format.mjs +499 -0
  238. package/src/tui/components/tool-output-format.test.mjs +312 -0
  239. package/src/tui/dist/index.mjs +9192 -2399
  240. package/src/tui/engine-runtime-notification.test.mjs +115 -0
  241. package/src/tui/engine-tool-result-text.test.mjs +75 -0
  242. package/src/tui/engine.mjs +1455 -279
  243. package/src/tui/figures.mjs +21 -40
  244. package/src/tui/index.jsx +75 -31
  245. package/src/tui/input-editing.mjs +25 -0
  246. package/src/tui/markdown/format-token.mjs +671 -69
  247. package/src/tui/markdown/format-token.test.mjs +312 -0
  248. package/src/tui/markdown/render-ansi.mjs +94 -0
  249. package/src/tui/markdown/render-ansi.test.mjs +108 -0
  250. package/src/tui/markdown/stream-fence.mjs +34 -0
  251. package/src/tui/markdown/stream-fence.test.mjs +26 -0
  252. package/src/tui/markdown/table-layout.mjs +250 -0
  253. package/src/tui/paste-attachments.mjs +0 -7
  254. package/src/tui/spinner-verbs.mjs +1 -2
  255. package/src/tui/statusline-ansi-bridge.mjs +172 -0
  256. package/src/tui/statusline-ansi-bridge.test.mjs +159 -0
  257. package/src/tui/theme.mjs +756 -24
  258. package/src/tui/time-format.mjs +1 -1
  259. package/src/tui/transcript-tool-failures.mjs +67 -0
  260. package/src/tui/transcript-tool-failures.test.mjs +111 -0
  261. package/src/ui/ansi.mjs +1 -2
  262. package/src/ui/markdown.mjs +85 -26
  263. package/src/ui/markdown.test.mjs +70 -0
  264. package/src/ui/model-display.mjs +121 -0
  265. package/src/ui/session-stats.mjs +44 -0
  266. package/src/ui/statusline-context-label.test.mjs +15 -0
  267. package/src/ui/statusline.mjs +386 -178
  268. package/src/ui/tool-card.mjs +2 -13
  269. package/src/vendor/statusline/bin/statusline-lib.mjs +1 -620
  270. package/src/vendor/statusline/bin/statusline-route.mjs +169 -37
  271. package/src/vendor/statusline/bin/statusline-route.test.mjs +80 -0
  272. package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +3 -3
  273. package/src/vendor/statusline/src/gateway/claude-current.mjs +1 -1
  274. package/src/vendor/statusline/src/gateway/route-meta.mjs +44 -6
  275. package/src/vendor/statusline/src/gateway/session-routes.mjs +1 -1
  276. package/src/workflows/default/WORKFLOW.md +12 -5
  277. package/src/workflows/default/workflow.json +0 -1
  278. package/src/workflows/solo/WORKFLOW.md +15 -0
  279. package/src/workflows/solo/workflow.json +7 -0
  280. package/vendor/ink/build/ink.js +54 -8
  281. package/vendor/ink/build/output.js +6 -1
  282. package/src/agents/scheduler-task.md +0 -3
  283. package/src/agents/web-researcher/AGENT.md +0 -3
  284. package/src/agents/web-researcher/agent.json +0 -6
  285. package/src/agents/webhook-handler.md +0 -3
  286. package/src/hooks/lib/permission-rules.cjs +0 -170
  287. package/src/hooks/lib/settings-loader.cjs +0 -112
  288. package/src/lib/hook-pipe-path.cjs +0 -10
  289. package/src/rules/bridge/00-common.md +0 -5
  290. package/src/rules/bridge/30-explorer.md +0 -4
  291. package/src/rules/lead/00-tool-lead.md +0 -5
  292. package/src/rules/shared/00-language.md +0 -3
  293. package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
  294. package/src/runtime/agent/orchestrator/tools/mutation-content-cache.mjs +0 -67
  295. package/src/runtime/channels/lib/hook-pipe-server.mjs +0 -671
  296. package/src/runtime/memory/lib/bridge-trace-queries.mjs +0 -120
  297. package/src/runtime/shared/llm/pid-cleanup.mjs +0 -27
  298. package/src/standalone/bridge-tool.mjs +0 -1414
  299. package/src/tui/runtime/shared/process-shutdown.mjs +0 -1
@@ -0,0 +1,320 @@
1
+ import { fork } from 'node:child_process';
2
+ import { appendFileSync, mkdirSync, readFileSync } from 'node:fs';
3
+ import http from 'node:http';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import { tmpdir } from 'node:os';
6
+ import { pathToFileURL } from 'node:url';
7
+ import { claimSingletonOwner, readSingletonOwner, releaseSingletonOwner } from '../runtime/shared/singleton-owner.mjs';
8
+
9
+ function logLine(path, line) {
10
+ try {
11
+ mkdirSync(dirname(path), { recursive: true });
12
+ appendFileSync(path, `[${new Date().toISOString()}] ${line}\n`);
13
+ } catch {}
14
+ }
15
+
16
+ function runtimeRoot() {
17
+ return process.env.MIXDOG_RUNTIME_ROOT ? resolve(process.env.MIXDOG_RUNTIME_ROOT) : join(tmpdir(), 'mixdog');
18
+ }
19
+
20
+ function activeInstancePath() {
21
+ return join(runtimeRoot(), 'active-instance.json');
22
+ }
23
+
24
+ function readActiveInstance() {
25
+ try {
26
+ return JSON.parse(readFileSync(activeInstancePath(), 'utf8'));
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+
32
+ function parsePort(value) {
33
+ const port = Number(value);
34
+ return Number.isInteger(port) && port > 0 && port < 65536 ? port : null;
35
+ }
36
+
37
+ function parsePid(value) {
38
+ const pid = Number(value);
39
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
40
+ }
41
+
42
+ function isPidAlive(pid) {
43
+ const n = parsePid(pid);
44
+ if (!n) return false;
45
+ try {
46
+ process.kill(n, 0);
47
+ return true;
48
+ } catch (error) {
49
+ return error?.code === 'EPERM';
50
+ }
51
+ }
52
+
53
+ function delay(ms) {
54
+ return new Promise(resolve => setTimeout(resolve, ms));
55
+ }
56
+
57
+ function requestJson({ port, method = 'GET', path = '/', body = null, timeoutMs = 10_000, headers = {} }) {
58
+ return new Promise((resolvePromise, reject) => {
59
+ const payload = body == null ? null : JSON.stringify(body);
60
+ const req = http.request({
61
+ hostname: '127.0.0.1',
62
+ port,
63
+ path,
64
+ method,
65
+ headers: {
66
+ ...headers,
67
+ ...(payload
68
+ ? { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) }
69
+ : {}),
70
+ },
71
+ timeout: timeoutMs,
72
+ }, (res) => {
73
+ let data = '';
74
+ res.setEncoding('utf8');
75
+ res.on('data', chunk => { data += chunk; });
76
+ res.on('end', () => {
77
+ let parsed = null;
78
+ try { parsed = data ? JSON.parse(data) : null; } catch {}
79
+ if (res.statusCode && res.statusCode >= 400) {
80
+ const message = parsed?.error
81
+ || parsed?.content?.[0]?.text
82
+ || data
83
+ || `HTTP ${res.statusCode}`;
84
+ const error = new Error(message);
85
+ error.statusCode = res.statusCode;
86
+ reject(error);
87
+ return;
88
+ }
89
+ resolvePromise(parsed ?? { raw: data });
90
+ });
91
+ });
92
+ req.on('error', reject);
93
+ req.on('timeout', () => {
94
+ req.destroy();
95
+ reject(new Error(`memory proxy request timed out: ${method} ${path}`));
96
+ });
97
+ if (payload) req.write(payload);
98
+ req.end();
99
+ });
100
+ }
101
+
102
+ export function createStandaloneMemoryRuntime({
103
+ entry,
104
+ dataDir,
105
+ cwd = process.cwd(),
106
+ } = {}) {
107
+ if (!entry) throw new Error('memory runtime entry is required');
108
+ if (!dataDir) throw new Error('memory runtime dataDir is required');
109
+
110
+ const logPath = join(dataDir, 'memory-runtime-proxy.log');
111
+ const ownerPath = join(dataDir, 'memory-runtime-owner.json');
112
+ const singletonEnabled = process.env.MIXDOG_MEMORY_SINGLETON !== '0';
113
+ const idleTtlMs = Math.max(0, Number(process.env.MIXDOG_MEMORY_IDLE_TTL_MS) || 10 * 60_000);
114
+ let portCache = null;
115
+ let startPromise = null;
116
+ let child = null;
117
+ let nextCallId = 1;
118
+
119
+ async function findLivePort({ allowStarting = false } = {}) {
120
+ const active = readActiveInstance();
121
+ const port = parsePort(active?.memory_port);
122
+ if (!port) return null;
123
+ const ownerPid = parsePid(active?.memory_server_pid);
124
+ if (ownerPid && !isPidAlive(ownerPid)) return null;
125
+ try {
126
+ const health = await requestJson({ port, path: '/health', timeoutMs: allowStarting ? 2000 : 500 });
127
+ if (health?.status === 'ok' || (allowStarting && health?.status === 'starting')) {
128
+ portCache = port;
129
+ return port;
130
+ }
131
+ } catch {}
132
+ return null;
133
+ }
134
+
135
+ async function waitForPort(timeoutMs) {
136
+ const deadline = Date.now() + timeoutMs;
137
+ let lastError = null;
138
+ while (Date.now() < deadline) {
139
+ const port = await findLivePort({ allowStarting: true });
140
+ if (port) {
141
+ try {
142
+ const health = await requestJson({ port, path: '/health', timeoutMs: 1500 });
143
+ if (health?.status === 'ok') return port;
144
+ } catch (error) {
145
+ lastError = error;
146
+ }
147
+ }
148
+ await delay(100);
149
+ }
150
+ throw lastError || new Error('memory runtime did not become ready');
151
+ }
152
+
153
+ function claimOwner() {
154
+ if (!singletonEnabled) return { owned: true, owner: { pid: process.pid } };
155
+ return claimSingletonOwner(ownerPath, {
156
+ kind: 'memory-runtime-daemon',
157
+ pid: process.pid,
158
+ meta: { cwd, clientPid: process.pid },
159
+ });
160
+ }
161
+
162
+ function releaseOwnerIfSelf() {
163
+ if (!singletonEnabled) return;
164
+ releaseSingletonOwner(ownerPath, process.pid);
165
+ }
166
+
167
+ async function start() {
168
+ if (portCache) {
169
+ const port = await findLivePort();
170
+ if (port) return { running: true, port, mode: 'http-proxy' };
171
+ portCache = null;
172
+ }
173
+ const existing = await findLivePort();
174
+ if (existing) return { running: true, port: existing, mode: 'http-proxy' };
175
+ if (startPromise) {
176
+ const port = await startPromise;
177
+ return { running: true, port, mode: 'http-proxy' };
178
+ }
179
+
180
+ startPromise = (async () => {
181
+ const claim = claimOwner();
182
+ if (!claim.owned) {
183
+ const owner = readSingletonOwner(ownerPath);
184
+ if (owner.alive) {
185
+ const live = await waitForPort(30_000);
186
+ return live;
187
+ }
188
+ releaseOwnerIfSelf();
189
+ }
190
+
191
+ child = fork(entry, [], {
192
+ cwd,
193
+ stdio: ['ignore', 'ignore', 'pipe', 'ipc'],
194
+ detached: true,
195
+ env: {
196
+ ...process.env,
197
+ MIXDOG_DATA_DIR: dataDir,
198
+ MIXDOG_WORKER_MODE: '1',
199
+ MIXDOG_STANDALONE: '1',
200
+ MIXDOG_SERVER_PID: '',
201
+ MIXDOG_OWNER_LEAD_PID: String(process.pid),
202
+ MIXDOG_MEMORY_SECONDARY: '0',
203
+ MIXDOG_PG_ATTACH_ONLY: '0',
204
+ MIXDOG_MEMORY_DISABLE_CYCLES: process.env.MIXDOG_MEMORY_DISABLE_CYCLES ?? '0',
205
+ MIXDOG_MEMORY_DISABLE_LLM_WORKER: process.env.MIXDOG_MEMORY_DISABLE_LLM_WORKER ?? '0',
206
+ MIXDOG_QUIET_SESSION_LOG: process.env.MIXDOG_QUIET_SESSION_LOG ?? '1',
207
+ MIXDOG_MEMORY_DAEMON: '1',
208
+ MIXDOG_MEMORY_IDLE_TTL_MS: String(idleTtlMs),
209
+ },
210
+ windowsHide: true,
211
+ });
212
+ const childPid = child.pid;
213
+ if (singletonEnabled && childPid) {
214
+ releaseSingletonOwner(ownerPath, process.pid);
215
+ claimSingletonOwner(ownerPath, {
216
+ kind: 'memory-runtime-daemon',
217
+ pid: childPid,
218
+ meta: { cwd, launcherPid: process.pid },
219
+ });
220
+ }
221
+ child.stderr?.on('data', chunk => {
222
+ const text = String(chunk || '').trimEnd();
223
+ if (text) logLine(logPath, text);
224
+ });
225
+ child.on('exit', () => {
226
+ if (singletonEnabled && childPid) releaseSingletonOwner(ownerPath, childPid);
227
+ if (child?.pid === childPid) child = null;
228
+ portCache = null;
229
+ });
230
+
231
+ const ready = new Promise((resolveReady, rejectReady) => {
232
+ const timer = setTimeout(() => rejectReady(new Error('memory worker ready timeout')), 60_000);
233
+ child.once('message', (msg) => {
234
+ clearTimeout(timer);
235
+ if (msg?.degraded || msg?.error) rejectReady(new Error(msg.error || 'memory worker degraded'));
236
+ else resolveReady(msg);
237
+ });
238
+ child.once('error', (error) => {
239
+ clearTimeout(timer);
240
+ rejectReady(error);
241
+ });
242
+ child.once('exit', (code, signal) => {
243
+ clearTimeout(timer);
244
+ rejectReady(new Error(`memory worker exited before ready (${signal || code || 'unknown'})`));
245
+ });
246
+ });
247
+
248
+ await ready;
249
+ const port = await waitForPort(15_000);
250
+ try { child.disconnect?.(); } catch {}
251
+ try { child.unref?.(); } catch {}
252
+ try { child.stderr?.unref?.(); } catch {}
253
+ return port;
254
+ })().finally(() => {
255
+ startPromise = null;
256
+ });
257
+
258
+ const port = await startPromise;
259
+ return { running: true, port, mode: 'http-proxy' };
260
+ }
261
+
262
+ async function handleToolCall(name, args = {}) {
263
+ await start();
264
+ const port = portCache || await findLivePort({ allowStarting: true });
265
+ if (!port) throw new Error('memory runtime is not available');
266
+ const callId = `mem_${process.pid}_${nextCallId++}`;
267
+ return await requestJson({
268
+ port,
269
+ method: 'POST',
270
+ path: '/api/tool',
271
+ body: { name, arguments: args || {} },
272
+ timeoutMs: Math.max(1000, Number(process.env.MIXDOG_MEMORY_TOOL_TIMEOUT_MS) || 180_000),
273
+ headers: { 'X-Mixdog-Call-Id': callId },
274
+ });
275
+ }
276
+
277
+ async function buildSessionCoreMemoryPayload(sessionCwd) {
278
+ await start();
279
+ const port = portCache || await findLivePort({ allowStarting: true });
280
+ if (!port) throw new Error('memory runtime is not available');
281
+ return await requestJson({
282
+ port,
283
+ method: 'POST',
284
+ path: '/session-start/core-memory',
285
+ body: { cwd: sessionCwd || cwd },
286
+ timeoutMs: 30_000,
287
+ });
288
+ }
289
+
290
+ async function stop() {
291
+ // Detach only. The daemon owns its own idle lifetime; CLI shutdown must not
292
+ // tear it down out from under another tab.
293
+ try { child?.disconnect?.(); } catch {}
294
+ try { child?.unref?.(); } catch {}
295
+ child = null;
296
+ return true;
297
+ }
298
+
299
+ async function status() {
300
+ const port = await findLivePort();
301
+ const owner = readSingletonOwner(ownerPath);
302
+ return {
303
+ running: Boolean(port),
304
+ port,
305
+ mode: 'http-proxy',
306
+ ownerPid: parsePid(owner.owner?.pid),
307
+ ownerAlive: owner.alive,
308
+ };
309
+ }
310
+
311
+ return {
312
+ init: start,
313
+ start,
314
+ stop,
315
+ status,
316
+ handleToolCall,
317
+ buildSessionCoreMemoryPayload,
318
+ moduleUrl: pathToFileURL(entry).href,
319
+ };
320
+ }
@@ -0,0 +1,226 @@
1
+ /**
2
+ * standalone/projects.mjs — user-registered project (cwd) store.
3
+ *
4
+ * Projects are working directories the user explicitly registers (Codex-style).
5
+ * There is NO git scanning and NO recent-history auto-import; only entries the
6
+ * user creates appear here. Persisted as JSON at <MIXDOG_HOME>/projects.json.
7
+ *
8
+ * Data shape: { projects: [{ name, path, addedAt, lastSelectedAt? }] }
9
+ */
10
+ import { homedir } from 'node:os';
11
+ import { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';
12
+ import { basename, dirname, isAbsolute, join, resolve } from 'node:path';
13
+
14
+ const MIXDOG_HOME = process.env.MIXDOG_HOME || join(homedir(), '.mixdog');
15
+ const PROJECTS_FILE = join(MIXDOG_HOME, 'projects.json');
16
+
17
+ /** Resolve a raw path to an absolute path against the user's cwd. */
18
+ function toAbsolute(rawPath) {
19
+ const text = String(rawPath || '').trim();
20
+ if (!text) return '';
21
+ return isAbsolute(text) ? resolve(text) : resolve(process.cwd(), text);
22
+ }
23
+
24
+ /** Normalize a path for dedupe comparison (case-insensitive on Windows). */
25
+ function normalizeKey(absPath) {
26
+ const text = String(absPath || '');
27
+ return process.platform === 'win32' ? text.replace(/[\\/]+$/, '').toLowerCase() : text.replace(/\/+$/, '');
28
+ }
29
+
30
+ function readStore() {
31
+ try {
32
+ if (!existsSync(PROJECTS_FILE)) return { projects: [] };
33
+ const raw = readFileSync(PROJECTS_FILE, 'utf8');
34
+ const parsed = JSON.parse(raw);
35
+ const projects = Array.isArray(parsed?.projects) ? parsed.projects : [];
36
+ return {
37
+ projects: projects
38
+ .filter((entry) => entry && typeof entry.path === 'string' && entry.path.trim())
39
+ .map((entry) => ({
40
+ name: String(entry.name || basename(entry.path) || entry.path),
41
+ path: String(entry.path),
42
+ addedAt: Number(entry.addedAt) || 0,
43
+ ...(Number(entry.lastSelectedAt) > 0
44
+ ? { lastSelectedAt: Number(entry.lastSelectedAt) }
45
+ : {}),
46
+ })),
47
+ };
48
+ } catch {
49
+ return { projects: [] };
50
+ }
51
+ }
52
+
53
+ function writeStore(store) {
54
+ try {
55
+ mkdirSync(dirname(PROJECTS_FILE), { recursive: true });
56
+ const tmp = `${PROJECTS_FILE}.${process.pid}.tmp`;
57
+ writeFileSync(tmp, `${JSON.stringify(store, null, 2)}\n`, 'utf8');
58
+ renameSync(tmp, PROJECTS_FILE);
59
+ } catch {
60
+ /* best-effort persistence */
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Idempotently ensure `<absPath>/.mixdog/project.id` exists so the project
66
+ * selection store and memory project_id classification (resolveProjectId)
67
+ * agree. Rules:
68
+ * - Never overwrite an existing marker (user may have set 'common' or a
69
+ * custom id — respect it).
70
+ * - Only act when absPath is a real directory on disk; never create the
71
+ * project folder itself (addProject does NOT create the directory).
72
+ * - Marker value = provided name, else basename(absPath). Skip when the
73
+ * value is empty or 'common' (resolveProjectId would force COMMON).
74
+ * - Best-effort: any failure is swallowed so registration/selection never
75
+ * breaks.
76
+ */
77
+ function ensureProjectIdMarker(absPath, name) {
78
+ try {
79
+ if (!absPath) return;
80
+ // Only when the project folder actually exists as a directory on disk.
81
+ try {
82
+ if (!statSync(absPath).isDirectory()) return;
83
+ } catch {
84
+ return; // missing / unstattable → silently skip
85
+ }
86
+ const markerPath = join(absPath, '.mixdog', 'project.id');
87
+ if (existsSync(markerPath)) return; // preserve existing value
88
+ const value = String(name || basename(absPath) || '').trim();
89
+ if (!value || value.toLowerCase() === 'common') return;
90
+ const markerDir = join(absPath, '.mixdog');
91
+ mkdirSync(markerDir, { recursive: true });
92
+ const tmp = `${markerPath}.${process.pid}.tmp`;
93
+ writeFileSync(tmp, `${value}\n`, 'utf8');
94
+ renameSync(tmp, markerPath);
95
+ } catch {
96
+ /* best-effort marker; never throw */
97
+ }
98
+ }
99
+
100
+ function projectRecency(entry) {
101
+ const last = Number(entry?.lastSelectedAt);
102
+ if (Number.isFinite(last) && last > 0) return last;
103
+ return Number(entry?.addedAt) || 0;
104
+ }
105
+
106
+ function compareProjects(a, b) {
107
+ const byRecency = projectRecency(b) - projectRecency(a);
108
+ if (byRecency !== 0) return byRecency;
109
+ const byName = String(a.name).localeCompare(String(b.name));
110
+ if (byName !== 0) return byName;
111
+ return String(a.path).localeCompare(String(b.path));
112
+ }
113
+
114
+ /** List registered projects (most recently selected first; addedAt fallback). */
115
+ export function listProjects() {
116
+ const { projects } = readStore();
117
+ return projects.slice().sort(compareProjects);
118
+ }
119
+
120
+ /**
121
+ * Register a project by path. Derives the display name from the last path
122
+ * segment, dedupes by normalized absolute path, and persists. Does NOT create
123
+ * the directory on disk.
124
+ */
125
+ export function addProject(rawPath) {
126
+ const absPath = toAbsolute(rawPath);
127
+ if (!absPath) return null;
128
+ const store = readStore();
129
+ const key = normalizeKey(absPath);
130
+ const existing = store.projects.find((entry) => normalizeKey(entry.path) === key);
131
+ if (existing) {
132
+ ensureProjectIdMarker(absPath, existing.name);
133
+ return existing;
134
+ }
135
+ const entry = {
136
+ name: basename(absPath) || absPath,
137
+ path: absPath,
138
+ addedAt: Date.now(),
139
+ };
140
+ store.projects.push(entry);
141
+ writeStore(store);
142
+ ensureProjectIdMarker(absPath, entry.name);
143
+ return entry;
144
+ }
145
+
146
+ /**
147
+ * Mark an existing registered project as selected (updates lastSelectedAt).
148
+ * Does not register missing paths; use addProject for that. Returns the updated
149
+ * entry, or null when no matching project exists.
150
+ */
151
+ export function touchProjectSelected(rawPath) {
152
+ const absPath = toAbsolute(rawPath);
153
+ if (!absPath) return null;
154
+ const store = readStore();
155
+ const key = normalizeKey(absPath);
156
+ const entry = store.projects.find((item) => normalizeKey(item.path) === key);
157
+ if (!entry) return null;
158
+ entry.lastSelectedAt = Date.now();
159
+ writeStore(store);
160
+ ensureProjectIdMarker(absPath, entry.name);
161
+ return entry;
162
+ }
163
+
164
+ /** Remove a registered project by path. Does NOT touch the directory on disk. */
165
+ export function removeProject(rawPath) {
166
+ const absPath = toAbsolute(rawPath);
167
+ if (!absPath) return false;
168
+ const store = readStore();
169
+ const key = normalizeKey(absPath);
170
+ const next = store.projects.filter((entry) => normalizeKey(entry.path) !== key);
171
+ if (next.length === store.projects.length) return false;
172
+ writeStore({ projects: next });
173
+ return true;
174
+ }
175
+
176
+ /**
177
+ * Rename a registered project (display name only; the path is unchanged).
178
+ * A blank name resets the name back to the path's folder basename. Returns the
179
+ * updated entry, or null when no matching project exists.
180
+ */
181
+ export function renameProject(rawPath, nextName) {
182
+ const absPath = toAbsolute(rawPath);
183
+ if (!absPath) return null;
184
+ const store = readStore();
185
+ const key = normalizeKey(absPath);
186
+ const entry = store.projects.find((item) => normalizeKey(item.path) === key);
187
+ if (!entry) return null;
188
+ const trimmed = String(nextName || '').trim();
189
+ entry.name = trimmed || basename(absPath) || absPath;
190
+ writeStore(store);
191
+ return entry;
192
+ }
193
+
194
+ /** Whether a path currently exists on disk. */
195
+ export function pathExists(rawPath) {
196
+ const absPath = toAbsolute(rawPath);
197
+ return !!absPath && existsSync(absPath);
198
+ }
199
+
200
+ /** Whether a path exists AND is a directory (tolerant of stat errors). */
201
+ export function isDirectory(rawPath) {
202
+ const absPath = toAbsolute(rawPath);
203
+ if (!absPath) return false;
204
+ try {
205
+ return statSync(absPath).isDirectory();
206
+ } catch {
207
+ return false;
208
+ }
209
+ }
210
+
211
+ /** Create a directory (recursive). Returns the absolute path, or '' on failure. */
212
+ export function ensureDir(rawPath) {
213
+ const absPath = toAbsolute(rawPath);
214
+ if (!absPath) return '';
215
+ try {
216
+ mkdirSync(absPath, { recursive: true });
217
+ return absPath;
218
+ } catch {
219
+ return '';
220
+ }
221
+ }
222
+
223
+ /** Resolve a raw path to absolute (exported for callers that need it). */
224
+ export function resolveProjectPath(rawPath) {
225
+ return toAbsolute(rawPath);
226
+ }
@@ -7,12 +7,14 @@ import {
7
7
  saveSecret,
8
8
  } from '../runtime/shared/config.mjs';
9
9
  import {
10
+ beginOAuthLogin as beginAnthropicOAuthLogin,
10
11
  describeAnthropicOAuthCredentials,
11
12
  forgetAnthropicOAuthCredentials,
12
13
  hasAnthropicOAuthCredentials,
13
14
  loginOAuth as loginAnthropicOAuth,
14
15
  } from '../runtime/agent/orchestrator/providers/anthropic-oauth.mjs';
15
16
  import {
17
+ beginOAuthLogin as beginOpenAIOAuthLogin,
16
18
  describeOpenAIOAuthCredentials,
17
19
  forgetOpenAIOAuthCredentials,
18
20
  hasOpenAIOAuthCredentials,
@@ -36,9 +38,9 @@ export const API_PROVIDERS = Object.freeze([
36
38
  ]);
37
39
 
38
40
  export const OAUTH_PROVIDERS = Object.freeze([
39
- Object.freeze({ id: 'openai-oauth', name: 'Codex', desc: '~/.codex/auth.json', has: hasOpenAIOAuthCredentials, describe: describeOpenAIOAuthCredentials, forget: forgetOpenAIOAuthCredentials, login: loginOpenAIOAuth }),
40
- Object.freeze({ id: 'anthropic-oauth', name: 'Claude Code', desc: 'Mixdog OAuth credentials', has: hasAnthropicOAuthCredentials, describe: describeAnthropicOAuthCredentials, forget: forgetAnthropicOAuthCredentials, login: loginAnthropicOAuth }),
41
- Object.freeze({ id: 'grok-oauth', name: 'Grok', desc: '~/.grok/auth.json or browser OAuth (Grok Build)', has: hasGrokOAuthCredentials, describe: describeGrokOAuthCredentials, forget: forgetGrokOAuthCredentials, begin: beginGrokOAuthLogin, login: loginGrokOAuth }),
41
+ Object.freeze({ id: 'openai-oauth', name: 'OpenAI OAuth', desc: 'Mixdog OAuth credentials', has: hasOpenAIOAuthCredentials, describe: describeOpenAIOAuthCredentials, forget: forgetOpenAIOAuthCredentials, begin: beginOpenAIOAuthLogin, login: loginOpenAIOAuth }),
42
+ Object.freeze({ id: 'anthropic-oauth', name: 'Anthropic OAuth', desc: 'Mixdog OAuth credentials', has: hasAnthropicOAuthCredentials, describe: describeAnthropicOAuthCredentials, forget: forgetAnthropicOAuthCredentials, begin: beginAnthropicOAuthLogin, login: loginAnthropicOAuth }),
43
+ Object.freeze({ id: 'grok-oauth', name: 'Grok', desc: 'Mixdog OAuth credentials (Grok Build)', has: hasGrokOAuthCredentials, describe: describeGrokOAuthCredentials, forget: forgetGrokOAuthCredentials, begin: beginGrokOAuthLogin, login: loginGrokOAuth }),
42
44
  ]);
43
45
 
44
46
  export const LOCAL_PROVIDERS = Object.freeze([
@@ -72,15 +74,17 @@ function updateConfigProvider(cfgMod, providerId, patch) {
72
74
  return cfgMod.loadConfig();
73
75
  }
74
76
 
75
- export async function providerSetup(config = {}) {
77
+ export async function providerSetup(config = {}, options = {}) {
76
78
  const providers = config.providers || {};
79
+ const detectLocal = options?.detectLocal !== false;
80
+ const checkSecrets = options?.checkSecrets !== false;
77
81
  const api = API_PROVIDERS.map((p) => {
78
82
  const configured = providers[p.id] || {};
79
83
  const envName = AGENT_PROVIDER_ENV[p.id] || p.env;
80
84
  const env = Boolean(envName && process.env[envName]);
81
85
  const configuredEnabled = configured.enabled === true;
82
- const authenticated = configuredEnabled || env || Boolean(getAgentApiKey(p.id));
83
- const stored = configuredEnabled && !env ? true : authenticated && !env;
86
+ const stored = checkSecrets ? hasStoredSecret(SECRET_ACCOUNTS.agentApiKey(p.id)) : false;
87
+ const authenticated = env || stored || (checkSecrets ? Boolean(getAgentApiKey(p.id)) : configuredEnabled);
84
88
  return {
85
89
  ...p,
86
90
  group: 'api',
@@ -90,17 +94,23 @@ export async function providerSetup(config = {}) {
90
94
  stored,
91
95
  env,
92
96
  envName,
93
- status: stored ? 'Set' : env ? 'Env' : 'Off',
94
- detail: stored ? 'stored in keychain' : env ? envName : p.env,
97
+ status: stored ? 'Set' : env ? 'Env' : authenticated ? 'Set' : configuredEnabled ? 'No Key' : 'Off',
98
+ detail: stored ? 'stored in keychain' : env ? envName : authenticated ? 'runtime credential' : envName,
95
99
  };
96
100
  });
97
101
 
98
102
  const oauth = OAUTH_PROVIDERS.map((p) => {
99
- const auth = typeof p.describe === 'function'
100
- ? p.describe()
101
- : { authenticated: Boolean(p.has()), status: Boolean(p.has()) ? 'Set' : 'Not Set', detail: p.desc };
102
- const authenticated = Boolean(auth.authenticated);
103
103
  const configured = providers[p.id] || {};
104
+ const auth = checkSecrets
105
+ ? (typeof p.describe === 'function'
106
+ ? p.describe()
107
+ : { authenticated: Boolean(p.has()), status: Boolean(p.has()) ? 'Set' : 'Not Set', detail: p.desc })
108
+ : {
109
+ authenticated: configured.enabled === true,
110
+ status: configured.enabled === true ? 'Enabled' : 'Not Set',
111
+ detail: p.desc,
112
+ };
113
+ const authenticated = Boolean(auth.authenticated);
104
114
  return {
105
115
  id: p.id,
106
116
  name: p.name,
@@ -118,7 +128,7 @@ export async function providerSetup(config = {}) {
118
128
  const local = await Promise.all(LOCAL_PROVIDERS.map(async (p) => {
119
129
  const configured = providers[p.id] || {};
120
130
  const baseURL = configured.baseURL || p.url;
121
- const detected = await detectLocalProvider(baseURL);
131
+ const detected = detectLocal ? await detectLocalProvider(baseURL) : false;
122
132
  const enabled = configured.enabled === true;
123
133
  return {
124
134
  id: p.id,
@@ -247,19 +257,23 @@ export async function beginOAuthProviderLogin(cfgMod, provider) {
247
257
  if (!oauth) throw new Error(`unknown OAuth provider "${id}"`);
248
258
  if (typeof oauth.begin !== 'function') throw new Error(`${id} does not support interactive code login`);
249
259
  const started = await oauth.begin();
260
+ const finish = async (result) => {
261
+ if (!result) return result;
262
+ const auth = typeof oauth.describe === 'function'
263
+ ? oauth.describe()
264
+ : { authenticated: Boolean(oauth.has()), status: Boolean(oauth.has()) ? 'Set' : 'Not Set' };
265
+ updateConfigProvider(cfgMod, id, { enabled: Boolean(auth.authenticated) });
266
+ return { provider: id, type: 'oauth', authenticated: Boolean(auth.authenticated), status: auth.status || null, result };
267
+ };
250
268
  return {
251
269
  provider: id,
252
270
  type: 'oauth',
253
271
  url: started.url,
254
- waitForCallback: started.waitForCallback,
272
+ manualUrl: started.manualUrl || null,
273
+ waitForCallback: started.waitForCallback?.then(finish),
255
274
  cancel: started.cancel,
256
275
  completeCode: async (code) => {
257
- const result = await started.completeCode(code);
258
- const auth = typeof oauth.describe === 'function'
259
- ? oauth.describe()
260
- : { authenticated: Boolean(oauth.has()), status: Boolean(oauth.has()) ? 'Set' : 'Not Set' };
261
- updateConfigProvider(cfgMod, id, { enabled: Boolean(auth.authenticated) });
262
- return { provider: id, type: 'oauth', authenticated: Boolean(auth.authenticated), status: auth.status || null, result };
276
+ return await finish(await started.completeCode(code));
263
277
  },
264
278
  };
265
279
  }
@@ -307,18 +321,21 @@ export function setLocalProvider(cfgMod, provider, { enabled, baseURL } = {}) {
307
321
  return { provider: id, type: 'local', enabled: enabled === true, baseURL: nextBaseURL };
308
322
  }
309
323
 
310
- export function forgetProviderAuth(provider) {
311
- const id = String(provider || '').trim();
324
+ export function forgetProviderAuth(cfgModOrProvider, maybeProvider) {
325
+ const cfgMod = maybeProvider === undefined ? null : cfgModOrProvider;
326
+ const id = String(maybeProvider === undefined ? cfgModOrProvider : maybeProvider || '').trim();
312
327
  const oauth = OAUTH_BY_ID.get(id);
313
328
  if (oauth) {
314
329
  if (typeof oauth.forget !== 'function') throw new Error(`forget is not supported for OAuth provider ${id}`);
315
330
  const result = oauth.forget();
331
+ if (cfgMod) updateConfigProvider(cfgMod, id, { enabled: false });
316
332
  return { provider: id, type: 'oauth', forgotten: true, removed: Boolean(result?.removed) };
317
333
  }
318
334
  if (!API_PROVIDER_IDS.has(id)) {
319
335
  throw new Error(`unknown provider "${id}"`);
320
336
  }
321
337
  deleteSecret(SECRET_ACCOUNTS.agentApiKey(id));
338
+ if (cfgMod) updateConfigProvider(cfgMod, id, { enabled: false });
322
339
  return { provider: id, type: 'api-key', forgotten: true };
323
340
  }
324
341
 
@@ -331,8 +348,8 @@ export const PROVIDER_STATUS_TOOL = {
331
348
  destructiveHint: false,
332
349
  idempotentHint: true,
333
350
  openWorldHint: false,
334
- bridgeHidden: true,
351
+ agentHidden: true,
335
352
  },
336
- description: 'List mixdog provider authentication/configuration status. This never returns secret values.',
353
+ description: 'List provider auth/config status. No secrets.',
337
354
  inputSchema: { type: 'object', properties: {}, additionalProperties: false },
338
355
  };