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.
- package/README.md +47 -23
- package/package.json +34 -28
- package/scripts/_test-folder-dialog.mjs +30 -0
- package/scripts/agent-parallel-smoke.mjs +388 -0
- package/scripts/agent-tag-reuse-smoke.mjs +183 -0
- package/scripts/background-task-meta-smoke.mjs +38 -0
- package/scripts/boot-smoke.mjs +52 -9
- package/scripts/build-runtime-linux.sh +348 -0
- package/scripts/build-runtime-macos.sh +217 -0
- package/scripts/build-runtime-windows.ps1 +242 -0
- package/scripts/compact-active-turn-test.mjs +68 -0
- package/scripts/compact-smoke.mjs +859 -129
- package/scripts/compact-trigger-migration-smoke.mjs +187 -0
- package/scripts/fix-brief-fn.mjs +35 -0
- package/scripts/fix-format-tool-surface.mjs +24 -0
- package/scripts/fix-tool-exec-visible.mjs +42 -0
- package/scripts/generate-runtime-manifest.mjs +166 -0
- package/scripts/hook-bus-test.mjs +330 -0
- package/scripts/lead-workflow-smoke.mjs +33 -39
- package/scripts/live-worker-smoke.mjs +43 -37
- package/scripts/llm-trace-summary.mjs +315 -0
- package/scripts/memory-meta-concurrency-test.mjs +20 -0
- package/scripts/output-style-smoke.mjs +56 -15
- package/scripts/parent-abort-link-test.mjs +44 -0
- package/scripts/patch-agent-brief.mjs +48 -0
- package/scripts/patch-app.mjs +21 -0
- package/scripts/patch-app2.mjs +18 -0
- package/scripts/patch-dist-brief.mjs +96 -0
- package/scripts/patch-tool-exec.mjs +70 -0
- package/scripts/pretool-ask-runtime-test.mjs +54 -0
- package/scripts/provider-toolcall-test.mjs +376 -0
- package/scripts/reactive-compact-persist-smoke.mjs +124 -0
- package/scripts/sanitize-tool-pairs-test.mjs +260 -0
- package/scripts/session-context-bench.mjs +205 -33
- package/scripts/session-ingest-smoke.mjs +177 -0
- package/scripts/set-effort-config-test.mjs +41 -0
- package/scripts/smoke-runtime-negative.ps1 +106 -0
- package/scripts/smoke-runtime-negative.sh +97 -0
- package/scripts/smoke.mjs +25 -0
- package/scripts/tool-result-hook-test.mjs +48 -0
- package/scripts/tool-smoke.mjs +1222 -90
- package/scripts/toolcall-args-test.mjs +150 -0
- package/scripts/tui-background-failure-smoke.mjs +73 -0
- package/scripts/usage-metrics-epoch-smoke.mjs +114 -0
- package/src/agents/debugger/AGENT.md +8 -0
- package/src/agents/explore/AGENT.md +4 -0
- package/src/agents/heavy-worker/AGENT.md +9 -3
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +8 -0
- package/src/agents/scheduler-task/AGENT.md +12 -0
- package/src/agents/scheduler-task/agent.json +6 -0
- package/src/agents/webhook-handler/AGENT.md +12 -0
- package/src/agents/webhook-handler/agent.json +6 -0
- package/src/agents/worker/AGENT.md +9 -3
- package/src/app.mjs +77 -3
- package/src/defaults/hidden-roles.json +17 -12
- package/src/headless-role.mjs +117 -0
- package/src/help.mjs +30 -0
- package/src/hooks/lib/permission-evaluator.cjs +11 -475
- package/src/lib/keychain-cjs.cjs +9 -1
- package/src/lib/mixdog-debug.cjs +0 -29
- package/src/lib/plugin-paths.cjs +1 -7
- package/src/lib/rules-builder.cjs +242 -98
- package/src/lib/text-utils.cjs +1 -1
- package/src/mixdog-session-runtime.mjs +2211 -446
- package/src/output-styles/default.md +12 -28
- package/src/output-styles/extreme-simple.md +9 -6
- package/src/output-styles/simple.md +22 -9
- package/src/repl.mjs +108 -59
- package/src/rules/agent/00-common.md +15 -0
- package/src/rules/{bridge → agent}/20-skip-protocol.md +1 -2
- package/src/rules/agent/30-explorer.md +22 -0
- package/src/rules/{bridge → agent}/40-cycle1-agent.md +7 -0
- package/src/rules/{bridge → agent}/41-cycle2-agent.md +7 -0
- package/src/rules/{bridge → agent}/42-cycle3-agent.md +7 -0
- package/src/rules/lead/01-general.md +9 -5
- package/src/rules/lead/04-workflow.md +51 -12
- package/src/rules/lead/lead-tool.md +6 -0
- package/src/rules/shared/01-tool.md +12 -1
- package/src/runtime/agent/orchestrator/activity-bus.mjs +7 -18
- package/src/runtime/agent/orchestrator/agent-owner.mjs +11 -0
- package/src/runtime/agent/orchestrator/{smart-bridge/bridge-llm.mjs → agent-runtime/agent-dispatch.mjs} +138 -111
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +94 -0
- package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/cache-strategy.mjs +32 -23
- package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/session-builder.mjs +33 -27
- package/src/runtime/agent/orchestrator/{bridge-trace.mjs → agent-trace.mjs} +132 -81
- package/src/runtime/agent/orchestrator/cache-mtime.mjs +0 -21
- package/src/runtime/agent/orchestrator/config.mjs +174 -55
- package/src/runtime/agent/orchestrator/context/collect.mjs +195 -487
- package/src/runtime/agent/orchestrator/dispatch-persist.mjs +1 -1
- package/src/runtime/agent/orchestrator/internal-roles.mjs +77 -29
- package/src/runtime/agent/orchestrator/internal-tools.mjs +5 -6
- package/src/runtime/agent/orchestrator/mcp/client.mjs +15 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +380 -268
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +131 -93
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +236 -4
- package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +49 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +58 -13
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +67 -149
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +132 -2
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +45 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +61 -116
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +25 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +79 -255
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +203 -71
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +512 -312
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +343 -518
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +6 -6
- package/src/runtime/agent/orchestrator/providers/registry.mjs +88 -51
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +289 -11
- package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +41 -8
- package/src/runtime/agent/orchestrator/session/compact.mjs +1173 -267
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +199 -36
- package/src/runtime/agent/orchestrator/session/loop.mjs +840 -612
- package/src/runtime/agent/orchestrator/session/manager.mjs +1574 -459
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +107 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +67 -29
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +2 -2
- package/src/runtime/agent/orchestrator/stall-policy.mjs +31 -16
- package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +3 -219
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +35 -8
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +19 -8
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +10 -53
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +60 -37
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +21 -2
- package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -7
- package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +1 -3
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +36 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +5 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -36
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +2 -40
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +148 -27
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +43 -75
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +90 -20
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -5
- package/src/runtime/agent/orchestrator/tools/code-graph-state.mjs +86 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +11 -11
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4106 -4019
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +33 -4
- package/src/runtime/agent/orchestrator/tools/patch.mjs +90 -6
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +6 -4
- package/src/runtime/agent/orchestrator/tools/result-compression.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +8 -1
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -4
- package/src/runtime/channels/index.mjs +149 -51
- package/src/runtime/channels/lib/cli-worker-host.mjs +1 -8
- package/src/runtime/channels/lib/config.mjs +0 -1
- package/src/runtime/channels/lib/drop-trace.mjs +1 -1
- package/src/runtime/channels/lib/executor.mjs +0 -3
- package/src/runtime/channels/lib/memory-client.mjs +0 -38
- package/src/runtime/channels/lib/output-forwarder.mjs +1 -8
- package/src/runtime/channels/lib/runtime-paths.mjs +0 -6
- package/src/runtime/channels/lib/scheduler.mjs +18 -14
- package/src/runtime/channels/lib/session-discovery.mjs +3 -6
- package/src/runtime/channels/lib/tool-format.mjs +0 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +4 -12
- package/src/runtime/channels/lib/webhook.mjs +1 -1
- package/src/runtime/channels/tool-defs.mjs +29 -29
- package/src/runtime/lib/keychain-cjs.cjs +0 -1
- package/src/runtime/memory/data/runtime-manifest.json +6 -7
- package/src/runtime/memory/index.mjs +519 -118
- package/src/runtime/memory/lib/agent-ipc.mjs +29 -12
- package/src/runtime/memory/lib/core-memory-store.mjs +2 -2
- package/src/runtime/memory/lib/embedding-model-config.mjs +55 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +31 -4
- package/src/runtime/memory/lib/embedding-worker.mjs +19 -10
- package/src/runtime/memory/lib/llm-worker-host.mjs +0 -4
- package/src/runtime/memory/lib/memory-cycle1.mjs +28 -7
- package/src/runtime/memory/lib/memory-cycle2.mjs +4 -5
- package/src/runtime/memory/lib/memory-cycle3.mjs +2 -2
- package/src/runtime/memory/lib/memory-ops-policy.mjs +2 -2
- package/src/runtime/memory/lib/memory-session-merge.mjs +38 -0
- package/src/runtime/memory/lib/memory.mjs +88 -9
- package/src/runtime/memory/lib/model-profile.mjs +1 -1
- package/src/runtime/memory/lib/pg/adapter.mjs +1 -1
- package/src/runtime/memory/lib/pg/supervisor.mjs +12 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +80 -21
- package/src/runtime/memory/lib/session-ingest.mjs +196 -0
- package/src/runtime/memory/lib/trace-store.mjs +96 -51
- package/src/runtime/memory/tool-defs.mjs +46 -37
- package/src/runtime/search/index.mjs +104 -473
- package/src/runtime/search/lib/config.mjs +0 -4
- package/src/runtime/search/lib/state.mjs +1 -15
- package/src/runtime/search/lib/web-tools.mjs +44 -25
- package/src/runtime/search/tool-defs.mjs +16 -23
- package/src/runtime/shared/abort-controller.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +4 -3
- package/src/runtime/shared/background-tasks.mjs +122 -11
- package/src/runtime/shared/child-spawn-gate.mjs +139 -0
- package/src/runtime/shared/config.mjs +7 -4
- package/src/runtime/shared/err-text.mjs +131 -4
- package/src/runtime/shared/llm/cost.mjs +2 -2
- package/src/runtime/shared/llm/http-agent.mjs +23 -7
- package/src/runtime/shared/llm/index.mjs +34 -11
- package/src/runtime/shared/llm/usage-log.mjs +4 -4
- package/src/runtime/shared/markdown-frontmatter.mjs +56 -0
- package/src/runtime/shared/singleton-owner.mjs +104 -0
- package/src/runtime/shared/tool-execution-contract.mjs +199 -20
- package/src/runtime/shared/tool-execution-contract.test.mjs +183 -0
- package/src/runtime/shared/tool-surface.mjs +624 -95
- package/src/runtime/shared/user-data-guard.mjs +0 -2
- package/src/standalone/agent-task-status.mjs +203 -0
- package/src/standalone/agent-task-status.test.mjs +76 -0
- package/src/standalone/agent-tool.mjs +1913 -0
- package/src/standalone/channel-worker.mjs +370 -14
- package/src/standalone/explore-tool.mjs +165 -70
- package/src/standalone/folder-dialog.mjs +314 -0
- package/src/standalone/hook-bus.mjs +898 -22
- package/src/standalone/memory-runtime-proxy.mjs +320 -0
- package/src/standalone/projects.mjs +226 -0
- package/src/standalone/provider-admin.mjs +41 -24
- package/src/standalone/seeds.mjs +1 -78
- package/src/standalone/usage-dashboard.mjs +96 -8
- package/src/tui/App.jsx +4800 -2140
- package/src/tui/components/AnsiText.jsx +39 -28
- package/src/tui/components/ContextPanel.jsx +14 -4
- package/src/tui/components/Markdown.jsx +43 -77
- package/src/tui/components/MarkdownTable.jsx +9 -184
- package/src/tui/components/Message.jsx +28 -11
- package/src/tui/components/Picker.jsx +95 -56
- package/src/tui/components/PromptInput.jsx +428 -239
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +27 -21
- package/src/tui/components/Spinner.jsx +67 -38
- package/src/tui/components/StatusLine.jsx +606 -38
- package/src/tui/components/TextEntryPanel.jsx +128 -9
- package/src/tui/components/ToolExecution.jsx +597 -362
- package/src/tui/components/TurnDone.jsx +3 -3
- package/src/tui/components/UsagePanel.jsx +3 -5
- package/src/tui/components/tool-output-format.mjs +499 -0
- package/src/tui/components/tool-output-format.test.mjs +312 -0
- package/src/tui/dist/index.mjs +9192 -2399
- package/src/tui/engine-runtime-notification.test.mjs +115 -0
- package/src/tui/engine-tool-result-text.test.mjs +75 -0
- package/src/tui/engine.mjs +1455 -279
- package/src/tui/figures.mjs +21 -40
- package/src/tui/index.jsx +75 -31
- package/src/tui/input-editing.mjs +25 -0
- package/src/tui/markdown/format-token.mjs +671 -69
- package/src/tui/markdown/format-token.test.mjs +312 -0
- package/src/tui/markdown/render-ansi.mjs +94 -0
- package/src/tui/markdown/render-ansi.test.mjs +108 -0
- package/src/tui/markdown/stream-fence.mjs +34 -0
- package/src/tui/markdown/stream-fence.test.mjs +26 -0
- package/src/tui/markdown/table-layout.mjs +250 -0
- package/src/tui/paste-attachments.mjs +0 -7
- package/src/tui/spinner-verbs.mjs +1 -2
- package/src/tui/statusline-ansi-bridge.mjs +172 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +159 -0
- package/src/tui/theme.mjs +756 -24
- package/src/tui/time-format.mjs +1 -1
- package/src/tui/transcript-tool-failures.mjs +67 -0
- package/src/tui/transcript-tool-failures.test.mjs +111 -0
- package/src/ui/ansi.mjs +1 -2
- package/src/ui/markdown.mjs +85 -26
- package/src/ui/markdown.test.mjs +70 -0
- package/src/ui/model-display.mjs +121 -0
- package/src/ui/session-stats.mjs +44 -0
- package/src/ui/statusline-context-label.test.mjs +15 -0
- package/src/ui/statusline.mjs +386 -178
- package/src/ui/tool-card.mjs +2 -13
- package/src/vendor/statusline/bin/statusline-lib.mjs +1 -620
- package/src/vendor/statusline/bin/statusline-route.mjs +169 -37
- package/src/vendor/statusline/bin/statusline-route.test.mjs +80 -0
- package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +3 -3
- package/src/vendor/statusline/src/gateway/claude-current.mjs +1 -1
- package/src/vendor/statusline/src/gateway/route-meta.mjs +44 -6
- package/src/vendor/statusline/src/gateway/session-routes.mjs +1 -1
- package/src/workflows/default/WORKFLOW.md +12 -5
- package/src/workflows/default/workflow.json +0 -1
- package/src/workflows/solo/WORKFLOW.md +15 -0
- package/src/workflows/solo/workflow.json +7 -0
- package/vendor/ink/build/ink.js +54 -8
- package/vendor/ink/build/output.js +6 -1
- package/src/agents/scheduler-task.md +0 -3
- package/src/agents/web-researcher/AGENT.md +0 -3
- package/src/agents/web-researcher/agent.json +0 -6
- package/src/agents/webhook-handler.md +0 -3
- package/src/hooks/lib/permission-rules.cjs +0 -170
- package/src/hooks/lib/settings-loader.cjs +0 -112
- package/src/lib/hook-pipe-path.cjs +0 -10
- package/src/rules/bridge/00-common.md +0 -5
- package/src/rules/bridge/30-explorer.md +0 -4
- package/src/rules/lead/00-tool-lead.md +0 -5
- package/src/rules/shared/00-language.md +0 -3
- package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
- package/src/runtime/agent/orchestrator/tools/mutation-content-cache.mjs +0 -67
- package/src/runtime/channels/lib/hook-pipe-server.mjs +0 -671
- package/src/runtime/memory/lib/bridge-trace-queries.mjs +0 -120
- package/src/runtime/shared/llm/pid-cleanup.mjs +0 -27
- package/src/standalone/bridge-tool.mjs +0 -1414
- 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: '
|
|
40
|
-
Object.freeze({ id: 'anthropic-oauth', name: '
|
|
41
|
-
Object.freeze({ id: 'grok-oauth', name: 'Grok', desc: '
|
|
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
|
|
83
|
-
const
|
|
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 :
|
|
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
|
-
|
|
272
|
+
manualUrl: started.manualUrl || null,
|
|
273
|
+
waitForCallback: started.waitForCallback?.then(finish),
|
|
255
274
|
cancel: started.cancel,
|
|
256
275
|
completeCode: async (code) => {
|
|
257
|
-
|
|
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(
|
|
311
|
-
const
|
|
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
|
-
|
|
351
|
+
agentHidden: true,
|
|
335
352
|
},
|
|
336
|
-
description: 'List
|
|
353
|
+
description: 'List provider auth/config status. No secrets.',
|
|
337
354
|
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
338
355
|
};
|