@prestyj/cli 5.9.0 → 5.10.0
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 +1 -1
- package/assets/skills/evidence-led-ui/SKILL.md +1 -1
- package/dist/app-sidecar.js +374 -14
- package/dist/app-sidecar.js.map +1 -1
- package/dist/chat-agents/general.test.js +5 -3
- package/dist/chat-agents/general.test.js.map +1 -1
- package/dist/chat-agents/memory.d.ts.map +1 -1
- package/dist/chat-agents/memory.js +14 -5
- package/dist/chat-agents/memory.js.map +1 -1
- package/dist/chat-agents/memory.test.js +11 -1
- package/dist/chat-agents/memory.test.js.map +1 -1
- package/dist/chat-agents/shared.js +1 -1
- package/dist/chat-agents/shared.js.map +1 -1
- package/dist/chat-agents/specialists.test.js +3 -2
- package/dist/chat-agents/specialists.test.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +16 -4
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +14 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +41 -2
- package/dist/config.js.map +1 -1
- package/dist/config.test.js +61 -1
- package/dist/config.test.js.map +1 -1
- package/dist/core/agent-mcp-passthrough.test.d.ts +2 -0
- package/dist/core/agent-mcp-passthrough.test.d.ts.map +1 -0
- package/dist/core/agent-mcp-passthrough.test.js +61 -0
- package/dist/core/agent-mcp-passthrough.test.js.map +1 -0
- package/dist/core/agent-session-add-dir.test.d.ts +2 -0
- package/dist/core/agent-session-add-dir.test.d.ts.map +1 -0
- package/dist/core/agent-session-add-dir.test.js +110 -0
- package/dist/core/agent-session-add-dir.test.js.map +1 -0
- package/dist/core/agent-session-compaction.test.js +4 -7
- package/dist/core/agent-session-compaction.test.js.map +1 -1
- package/dist/core/agent-session-marker-anchors.test.d.ts +2 -0
- package/dist/core/agent-session-marker-anchors.test.d.ts.map +1 -0
- package/dist/core/agent-session-marker-anchors.test.js +147 -0
- package/dist/core/agent-session-marker-anchors.test.js.map +1 -0
- package/dist/core/agent-session-memory-tail.test.js +4 -7
- package/dist/core/agent-session-memory-tail.test.js.map +1 -1
- package/dist/core/agent-session-queue.test.js +4 -7
- package/dist/core/agent-session-queue.test.js.map +1 -1
- package/dist/core/agent-session-subagent-policy.test.js +7 -3
- package/dist/core/agent-session-subagent-policy.test.js.map +1 -1
- package/dist/core/agent-session.d.ts +42 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +93 -7
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/agent-tools-allowlist.test.js +5 -3
- package/dist/core/agent-tools-allowlist.test.js.map +1 -1
- package/dist/core/agents.d.ts +11 -0
- package/dist/core/agents.d.ts.map +1 -1
- package/dist/core/agents.js +21 -0
- package/dist/core/agents.js.map +1 -1
- package/dist/core/agents.test.d.ts +2 -0
- package/dist/core/agents.test.d.ts.map +1 -0
- package/dist/core/agents.test.js +67 -0
- package/dist/core/agents.test.js.map +1 -0
- package/dist/core/auth-providers.js +1 -1
- package/dist/core/auth-providers.js.map +1 -1
- package/dist/core/compaction/compactor.test.js +1 -1
- package/dist/core/compaction/compactor.test.js.map +1 -1
- package/dist/core/compaction/token-estimator.test.js +1 -1
- package/dist/core/compaction/token-estimator.test.js.map +1 -1
- package/dist/core/encode-cwd.d.ts +17 -0
- package/dist/core/encode-cwd.d.ts.map +1 -1
- package/dist/core/encode-cwd.js +20 -7
- package/dist/core/encode-cwd.js.map +1 -1
- package/dist/core/encode-cwd.test.js +20 -1
- package/dist/core/encode-cwd.test.js.map +1 -1
- package/dist/core/ideal-review.d.ts.map +1 -1
- package/dist/core/ideal-review.js +6 -1
- package/dist/core/ideal-review.js.map +1 -1
- package/dist/core/ideal-review.test.js +7 -2
- package/dist/core/ideal-review.test.js.map +1 -1
- package/dist/core/local-endpoint-store.d.ts +50 -0
- package/dist/core/local-endpoint-store.d.ts.map +1 -0
- package/dist/core/local-endpoint-store.js +201 -0
- package/dist/core/local-endpoint-store.js.map +1 -0
- package/dist/core/local-endpoint-store.test.d.ts +2 -0
- package/dist/core/local-endpoint-store.test.d.ts.map +1 -0
- package/dist/core/local-endpoint-store.test.js +131 -0
- package/dist/core/local-endpoint-store.test.js.map +1 -0
- package/dist/core/lsp/client.d.ts +50 -0
- package/dist/core/lsp/client.d.ts.map +1 -1
- package/dist/core/lsp/client.js +158 -13
- package/dist/core/lsp/client.js.map +1 -1
- package/dist/core/lsp/jsonrpc.d.ts +38 -1
- package/dist/core/lsp/jsonrpc.d.ts.map +1 -1
- package/dist/core/lsp/jsonrpc.js +13 -1
- package/dist/core/lsp/jsonrpc.js.map +1 -1
- package/dist/core/lsp/manager.d.ts.map +1 -1
- package/dist/core/lsp/manager.js +23 -2
- package/dist/core/lsp/manager.js.map +1 -1
- package/dist/core/lsp/manager.test.js +32 -1
- package/dist/core/lsp/manager.test.js.map +1 -1
- package/dist/core/lsp/test-support.d.ts +18 -0
- package/dist/core/lsp/test-support.d.ts.map +1 -0
- package/dist/core/lsp/test-support.js +27 -0
- package/dist/core/lsp/test-support.js.map +1 -0
- package/dist/core/lsp/uri.test.d.ts +2 -0
- package/dist/core/lsp/uri.test.d.ts.map +1 -0
- package/dist/core/lsp/uri.test.js +56 -0
- package/dist/core/lsp/uri.test.js.map +1 -0
- package/dist/core/lsp/windows.test.d.ts +2 -0
- package/dist/core/lsp/windows.test.d.ts.map +1 -0
- package/dist/core/lsp/windows.test.js +251 -0
- package/dist/core/lsp/windows.test.js.map +1 -0
- package/dist/core/mcp/resolve-stdio.d.ts +35 -0
- package/dist/core/mcp/resolve-stdio.d.ts.map +1 -1
- package/dist/core/mcp/resolve-stdio.js +80 -1
- package/dist/core/mcp/resolve-stdio.js.map +1 -1
- package/dist/core/mcp/resolve-stdio.test.js +99 -4
- package/dist/core/mcp/resolve-stdio.test.js.map +1 -1
- package/dist/core/network-guard.d.ts +47 -0
- package/dist/core/network-guard.d.ts.map +1 -0
- package/dist/core/network-guard.js +208 -0
- package/dist/core/network-guard.js.map +1 -0
- package/dist/core/network-guard.test.d.ts +2 -0
- package/dist/core/network-guard.test.d.ts.map +1 -0
- package/dist/core/network-guard.test.js +72 -0
- package/dist/core/network-guard.test.js.map +1 -0
- package/dist/core/persistent-shell.d.ts +3 -1
- package/dist/core/persistent-shell.d.ts.map +1 -1
- package/dist/core/persistent-shell.js +13 -3
- package/dist/core/persistent-shell.js.map +1 -1
- package/dist/core/process-manager-dev-server-repro.test.js +34 -12
- package/dist/core/process-manager-dev-server-repro.test.js.map +1 -1
- package/dist/core/process-manager.d.ts +1 -1
- package/dist/core/process-manager.d.ts.map +1 -1
- package/dist/core/process-manager.js +23 -13
- package/dist/core/process-manager.js.map +1 -1
- package/dist/core/project-discovery.d.ts +7 -0
- package/dist/core/project-discovery.d.ts.map +1 -1
- package/dist/core/project-discovery.js +40 -9
- package/dist/core/project-discovery.js.map +1 -1
- package/dist/core/project-discovery.test.js +102 -1
- package/dist/core/project-discovery.test.js.map +1 -1
- package/dist/core/prompt-commands.d.ts.map +1 -1
- package/dist/core/prompt-commands.js +89 -156
- package/dist/core/prompt-commands.js.map +1 -1
- package/dist/core/prompt-commands.test.js +4 -10
- package/dist/core/prompt-commands.test.js.map +1 -1
- package/dist/core/radio.test.js +4 -1
- package/dist/core/radio.test.js.map +1 -1
- package/dist/core/runtime-mode.d.ts +2 -0
- package/dist/core/runtime-mode.d.ts.map +1 -1
- package/dist/core/runtime-mode.js +4 -0
- package/dist/core/runtime-mode.js.map +1 -1
- package/dist/core/session-export.d.ts +98 -0
- package/dist/core/session-export.d.ts.map +1 -0
- package/dist/core/session-export.js +356 -0
- package/dist/core/session-export.js.map +1 -0
- package/dist/core/session-export.test.d.ts +2 -0
- package/dist/core/session-export.test.d.ts.map +1 -0
- package/dist/core/session-export.test.js +353 -0
- package/dist/core/session-export.test.js.map +1 -0
- package/dist/core/session-storage.d.ts.map +1 -1
- package/dist/core/session-storage.js +25 -1
- package/dist/core/session-storage.js.map +1 -1
- package/dist/core/session-storage.test.js +4 -1
- package/dist/core/session-storage.test.js.map +1 -1
- package/dist/core/settings-manager.d.ts +5 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +10 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/setup-history.d.ts +0 -6
- package/dist/core/setup-history.d.ts.map +1 -1
- package/dist/core/setup-history.js +2 -18
- package/dist/core/setup-history.js.map +1 -1
- package/dist/core/setup-history.test.js +11 -26
- package/dist/core/setup-history.test.js.map +1 -1
- package/dist/core/skills-routing.test.js +3 -1
- package/dist/core/skills-routing.test.js.map +1 -1
- package/dist/core/slash-commands.d.ts +18 -0
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +42 -10
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/slash-commands.test.d.ts +2 -0
- package/dist/core/slash-commands.test.d.ts.map +1 -0
- package/dist/core/slash-commands.test.js +59 -0
- package/dist/core/slash-commands.test.js.map +1 -0
- package/dist/core/speed-benchmark.test.js +9 -3
- package/dist/core/speed-benchmark.test.js.map +1 -1
- package/dist/core/subagent-manager.d.ts +1 -1
- package/dist/core/subagent-manager.d.ts.map +1 -1
- package/dist/core/subagent-manager.js +9 -0
- package/dist/core/subagent-manager.js.map +1 -1
- package/dist/core/subagent-store.test.js +14 -2
- package/dist/core/subagent-store.test.js.map +1 -1
- package/dist/core/workspace-guard.d.ts +2 -0
- package/dist/core/workspace-guard.d.ts.map +1 -1
- package/dist/core/workspace-guard.js +4 -1
- package/dist/core/workspace-guard.js.map +1 -1
- package/dist/core/workspace-guard.test.js +21 -0
- package/dist/core/workspace-guard.test.js.map +1 -1
- package/dist/modes/json-mode.d.ts +7 -0
- package/dist/modes/json-mode.d.ts.map +1 -1
- package/dist/modes/json-mode.js +1 -0
- package/dist/modes/json-mode.js.map +1 -1
- package/dist/modes/subagent-worker-mode.d.ts +2 -0
- package/dist/modes/subagent-worker-mode.d.ts.map +1 -1
- package/dist/modes/subagent-worker-mode.js.map +1 -1
- package/dist/system-prompt.d.ts +11 -1
- package/dist/system-prompt.d.ts.map +1 -1
- package/dist/system-prompt.js +39 -18
- package/dist/system-prompt.js.map +1 -1
- package/dist/system-prompt.test.js +33 -17
- package/dist/system-prompt.test.js.map +1 -1
- package/dist/test-support/fake-home.d.ts +20 -0
- package/dist/test-support/fake-home.d.ts.map +1 -0
- package/dist/test-support/fake-home.js +37 -0
- package/dist/test-support/fake-home.js.map +1 -0
- package/dist/tools/bash.d.ts +2 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +22 -5
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/bash.test.js +122 -7
- package/dist/tools/bash.test.js.map +1 -1
- package/dist/tools/edit-robustness.test.js +3 -1
- package/dist/tools/edit-robustness.test.js.map +1 -1
- package/dist/tools/find.d.ts.map +1 -1
- package/dist/tools/find.js +4 -1
- package/dist/tools/find.js.map +1 -1
- package/dist/tools/grep.d.ts +3 -1
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +135 -11
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/grep.test.js +53 -0
- package/dist/tools/grep.test.js.map +1 -1
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +3 -3
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/overflow.test.js +4 -7
- package/dist/tools/overflow.test.js.map +1 -1
- package/dist/tools/path-utils.d.ts +15 -0
- package/dist/tools/path-utils.d.ts.map +1 -1
- package/dist/tools/path-utils.js +17 -0
- package/dist/tools/path-utils.js.map +1 -1
- package/dist/tools/path-utils.test.js +7 -2
- package/dist/tools/path-utils.test.js.map +1 -1
- package/dist/tools/prompt-hints.js +1 -1
- package/dist/tools/prompt-hints.js.map +1 -1
- package/dist/tools/search-code.d.ts.map +1 -1
- package/dist/tools/search-code.js +6 -4
- package/dist/tools/search-code.js.map +1 -1
- package/dist/tools/subagent.d.ts +1 -1
- package/dist/tools/subagent.d.ts.map +1 -1
- package/dist/tools/subagent.js +8 -0
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/web-fetch.d.ts +2 -1
- package/dist/tools/web-fetch.d.ts.map +1 -1
- package/dist/tools/web-fetch.js +17 -6
- package/dist/tools/web-fetch.js.map +1 -1
- package/dist/tools/web-fetch.test.js +34 -0
- package/dist/tools/web-fetch.test.js.map +1 -1
- package/dist/tools/web-search.d.ts +2 -1
- package/dist/tools/web-search.d.ts.map +1 -1
- package/dist/tools/web-search.js +21 -5
- package/dist/tools/web-search.js.map +1 -1
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +5 -80
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/app-items.d.ts +1 -12
- package/dist/ui/app-items.d.ts.map +1 -1
- package/dist/ui/app-items.js.map +1 -1
- package/dist/ui/components/Footer.d.ts +1 -1
- package/dist/ui/components/Footer.js +2 -2
- package/dist/ui/components/Footer.js.map +1 -1
- package/dist/ui/hooks/usePixelFixFlow.d.ts +1 -2
- package/dist/ui/hooks/usePixelFixFlow.d.ts.map +1 -1
- package/dist/ui/hooks/usePixelFixFlow.js +2 -5
- package/dist/ui/hooks/usePixelFixFlow.js.map +1 -1
- package/dist/ui/login.js +1 -1
- package/dist/ui/login.js.map +1 -1
- package/dist/ui/terminal-history-status-renderers.d.ts +1 -2
- package/dist/ui/terminal-history-status-renderers.d.ts.map +1 -1
- package/dist/ui/terminal-history-status-renderers.js +2 -15
- package/dist/ui/terminal-history-status-renderers.js.map +1 -1
- package/dist/ui/terminal-history.d.ts.map +1 -1
- package/dist/ui/terminal-history.js +2 -4
- package/dist/ui/terminal-history.js.map +1 -1
- package/dist/ui/thinking-level-cycle.test.js +8 -8
- package/dist/ui/thinking-level-cycle.test.js.map +1 -1
- package/dist/ui/transcript/MiscRows.d.ts +1 -4
- package/dist/ui/transcript/MiscRows.d.ts.map +1 -1
- package/dist/ui/transcript/MiscRows.js +2 -7
- package/dist/ui/transcript/MiscRows.js.map +1 -1
- package/dist/ui/transcript/TranscriptRenderer.d.ts.map +1 -1
- package/dist/ui/transcript/TranscriptRenderer.js +1 -3
- package/dist/ui/transcript/TranscriptRenderer.js.map +1 -1
- package/dist/ui/transcript/presentation.d.ts +1 -9
- package/dist/ui/transcript/presentation.d.ts.map +1 -1
- package/dist/ui/transcript/presentation.js +0 -9
- package/dist/ui/transcript/presentation.js.map +1 -1
- package/dist/ui/transcript/spacing.d.ts +2 -2
- package/dist/ui/transcript/spacing.d.ts.map +1 -1
- package/dist/ui/transcript/spacing.js +0 -1
- package/dist/ui/transcript/spacing.js.map +1 -1
- package/dist/ui/transcript/spacing.test.js +1 -3
- package/dist/ui/transcript/spacing.test.js.map +1 -1
- package/dist/ui/tui-history-parity.test.js +2 -9
- package/dist/ui/tui-history-parity.test.js.map +1 -1
- package/dist/utils/github.d.ts +20 -0
- package/dist/utils/github.d.ts.map +1 -0
- package/dist/utils/github.js +65 -0
- package/dist/utils/github.js.map +1 -0
- package/dist/utils/github.test.d.ts +2 -0
- package/dist/utils/github.test.d.ts.map +1 -0
- package/dist/utils/github.test.js +23 -0
- package/dist/utils/github.test.js.map +1 -0
- package/dist/utils/process.d.ts +26 -3
- package/dist/utils/process.d.ts.map +1 -1
- package/dist/utils/process.js +68 -10
- package/dist/utils/process.js.map +1 -1
- package/dist/utils/process.test.d.ts +2 -0
- package/dist/utils/process.test.d.ts.map +1 -0
- package/dist/utils/process.test.js +42 -0
- package/dist/utils/process.test.js.map +1 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -69,7 +69,7 @@ Switch mid-conversation with `/model`. Not locked to anyone.
|
|
|
69
69
|
|
|
70
70
|
| Provider | Models | Auth |
|
|
71
71
|
|---|---|---|
|
|
72
|
-
| **Anthropic** | Claude Opus
|
|
72
|
+
| **Anthropic** | Claude Opus 5, Sonnet 5, Haiku 4.5 | OAuth |
|
|
73
73
|
| **OpenAI** | GPT-5.5, GPT-5.5 Pro, GPT-5.4, GPT-5.3 Codex | OAuth |
|
|
74
74
|
| **Moonshot** | Kimi K3, Kimi K2.7 Code | OAuth or API key |
|
|
75
75
|
| **Z.AI (GLM)** | GLM-5.1, GLM-4.7, GLM-4.7 Flash | API key |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: evidence-led-ui
|
|
3
|
-
description: Use for web/mobile UI
|
|
3
|
+
description: Use for broad or design-sensitive web/mobile UI work: net-new pages/screens, redesigns, design-system work, visual polish, responsive or accessibility overhauls, and UI quality review/critique. Do NOT use for small UI edits that follow existing patterns (bug fixes, minor styling tweaks, wiring, copy changes, small component additions matching neighbors); just match the surrounding code for those. Also exclude database/API schemas, CLI output, standalone image/SVG generation, and description-only tasks.
|
|
4
4
|
license: See LICENSES.md
|
|
5
5
|
compatibility: Full review requires filesystem inspection and rendered screenshots; web research and browser/device tooling are optional and unavailable checks must be reported honestly.
|
|
6
6
|
---
|
package/dist/app-sidecar.js
CHANGED
|
@@ -21,6 +21,7 @@ import { parseArgs } from "node:util";
|
|
|
21
21
|
import { environmentSecrets, formatError, redactValue } from "@prestyj/ai";
|
|
22
22
|
import { runJsonMode } from "./modes/json-mode.js";
|
|
23
23
|
import { runSubagentWorkerMode } from "./modes/subagent-worker-mode.js";
|
|
24
|
+
import { setStreamDiagnostic } from "@prestyj/agent";
|
|
24
25
|
import { AgentSession } from "./core/agent-session.js";
|
|
25
26
|
import { RunLifecycle } from "./core/run-lifecycle.js";
|
|
26
27
|
import { CHAT_AGENT_IDS, chatAgentSessionsDir, createChatAgent, parseChatAgentId, switchChatAgent, } from "./chat-agents/index.js";
|
|
@@ -33,10 +34,12 @@ import { isWorkflowCommandText, countAssistantMessages, shouldStartAutopilotCycl
|
|
|
33
34
|
import { driveAutopilotCycle, frameAutopilotInjection } from "./core/autopilot-cycle.js";
|
|
34
35
|
import { validateNolanModelPref, effectiveNolanModel, } from "./core/nolan-model.js";
|
|
35
36
|
import { normalizeAutopilotMarkersForHistory, normalizeAppMarkersForHistory, normalizeNolanTurnsForHistory, restoreUserRow, restoreAssistantTexts, autopilotMarkerCopySeed, } from "./core/session-history.js";
|
|
37
|
+
import { sessionToMarkdown, defaultExportFilename, } from "./core/session-export.js";
|
|
36
38
|
import { AuthStorage } from "./core/auth-storage.js";
|
|
37
39
|
import { cleanupToolOutputs } from "./tools/overflow.js";
|
|
38
40
|
import { readCappedBody } from "./utils/http-body.js";
|
|
39
|
-
import { fetchSubscriptionUsage, MOONSHOT_OAUTH_KEY, SubscriptionUsageError, XIAOMI_CREDITS_KEY, } from "@prestyj/core";
|
|
41
|
+
import { fetchSubscriptionUsage, MOONSHOT_OAUTH_KEY, SubscriptionUsageError, XIAOMI_CREDITS_KEY, discoverLocalModels, findProbedModel, formatLocalModelId, parseLocalModelId, probeEndpoint, toModelInfo as localModelInfo, } from "@prestyj/core";
|
|
42
|
+
import { LocalEndpointError, addCustomEndpoint, listAllEndpoints, removeCustomEndpoint, syncEndpointCredentials, } from "./core/local-endpoint-store.js";
|
|
40
43
|
import { loginAnthropic } from "./core/oauth/anthropic.js";
|
|
41
44
|
import { loginOpenAI } from "./core/oauth/openai.js";
|
|
42
45
|
import { loginGemini } from "./core/oauth/gemini.js";
|
|
@@ -44,9 +47,10 @@ import { loginKimi } from "./core/oauth/kimi.js";
|
|
|
44
47
|
import { AUTH_PROVIDERS } from "./core/auth-providers.js";
|
|
45
48
|
import { ensureAppDirs, loadSavedSettings } from "./config.js";
|
|
46
49
|
import { SettingsManager } from "./core/settings-manager.js";
|
|
47
|
-
import { getModel, getDefaultThinkingLevel, getContextWindow,
|
|
50
|
+
import { getModel, getDefaultThinkingLevel, getContextWindow, getAllModels, clearRuntimeModels, registerRuntimeModels, } from "./core/model-registry.js";
|
|
48
51
|
import { resolveStartOrFallback } from "./core/resolve-start.js";
|
|
49
52
|
import { getGitBranch, getGitDirtyFileCount, isGitRepo } from "./utils/git.js";
|
|
53
|
+
import { getGitHubOpenCounts, getGitHubRepoSlug } from "./utils/github.js";
|
|
50
54
|
import { extractPlanSteps } from "./utils/plan-steps.js";
|
|
51
55
|
import { getNextThinkingLevel, getSupportedThinkingLevels, isThinkingLevelSupported, } from "./core/thinking-level.js";
|
|
52
56
|
import { PROMPT_COMMANDS } from "./core/prompt-commands.js";
|
|
@@ -344,6 +348,11 @@ async function searchProjectFiles(cwd, rawQuery) {
|
|
|
344
348
|
* hook prompt, by its distinctive opening phrase. Returns the hook kind so the
|
|
345
349
|
* webview can render the short notice line instead of the full prompt body.
|
|
346
350
|
*/
|
|
351
|
+
/** `?tools=` on /export. Anything unrecognised (including absent) falls back to
|
|
352
|
+
* the human-facing `summary` default rather than dumping every payload. */
|
|
353
|
+
function parseToolDetail(value) {
|
|
354
|
+
return value === "none" || value === "full" ? value : "summary";
|
|
355
|
+
}
|
|
347
356
|
function detectHookKind(text) {
|
|
348
357
|
const t = text.trimStart();
|
|
349
358
|
if (t.startsWith("Ideal? Review the actual work"))
|
|
@@ -451,6 +460,7 @@ async function runJsonModeIfRequested() {
|
|
|
451
460
|
"max-turns": { type: "string" },
|
|
452
461
|
"system-prompt": { type: "string" },
|
|
453
462
|
tools: { type: "string" },
|
|
463
|
+
"mcp-servers": { type: "string" },
|
|
454
464
|
"prompt-cache-key": { type: "string" },
|
|
455
465
|
},
|
|
456
466
|
allowPositionals: true,
|
|
@@ -467,14 +477,24 @@ async function runJsonModeIfRequested() {
|
|
|
467
477
|
.filter(Boolean)
|
|
468
478
|
: [];
|
|
469
479
|
const allowedTools = parsedTools.length > 0 ? parsedTools : undefined;
|
|
480
|
+
// MCP servers the agent definition asked for. Without forwarding these, an
|
|
481
|
+
// allow-listed child connects no MCP at all and loses live code search.
|
|
482
|
+
const parsedMcpServers = values["mcp-servers"]
|
|
483
|
+
? values["mcp-servers"]
|
|
484
|
+
.split(",")
|
|
485
|
+
.map((s) => s.trim())
|
|
486
|
+
.filter(Boolean)
|
|
487
|
+
: [];
|
|
488
|
+
const allowedMcpServers = parsedMcpServers.length > 0 ? parsedMcpServers : undefined;
|
|
470
489
|
await runJsonMode({
|
|
471
490
|
message: positionals[0] ?? "",
|
|
472
491
|
provider: (values.provider ?? "anthropic"),
|
|
473
|
-
model: values.model ?? "claude-opus-
|
|
492
|
+
model: values.model ?? "claude-opus-5",
|
|
474
493
|
cwd: process.cwd(),
|
|
475
494
|
systemPrompt: values["system-prompt"],
|
|
476
495
|
maxTurns: maxTurnsRaw ? parseInt(maxTurnsRaw, 10) : undefined,
|
|
477
496
|
allowedTools,
|
|
497
|
+
allowedMcpServers,
|
|
478
498
|
promptCacheKey: values["prompt-cache-key"],
|
|
479
499
|
}).catch(async (err) => {
|
|
480
500
|
captureSidecarError(err, "app-sidecar.json-mode", {
|
|
@@ -518,6 +538,31 @@ async function main() {
|
|
|
518
538
|
// ~/.ezcoder/debug.log (initLogger truncates on each start).
|
|
519
539
|
const sidecarLog = path.join(paths.agentDir, "ezcoder-app-sidecar.log");
|
|
520
540
|
initLogger(sidecarLog);
|
|
541
|
+
// The desktop sidecar previously omitted the stream diagnostic hook used by
|
|
542
|
+
// the CLI, leaving device-specific provider stalls impossible to distinguish from
|
|
543
|
+
// event-loop starvation or a broken streaming network path. Keep routine
|
|
544
|
+
// phases lightweight; timeout phases include non-sensitive runtime context.
|
|
545
|
+
setStreamDiagnostic((phase, data) => {
|
|
546
|
+
const includeRuntime = phase === "idle_timeout_fired" ||
|
|
547
|
+
phase === "hard_timeout_fired" ||
|
|
548
|
+
phase === "stall_exhausted";
|
|
549
|
+
log("INFO", "stream", phase, {
|
|
550
|
+
...(data ?? {}),
|
|
551
|
+
...(includeRuntime
|
|
552
|
+
? {
|
|
553
|
+
platform: process.platform,
|
|
554
|
+
arch: process.arch,
|
|
555
|
+
osRelease: os.release(),
|
|
556
|
+
node: process.version,
|
|
557
|
+
logicalCpus: os.cpus().length,
|
|
558
|
+
totalMemoryMb: Math.round(os.totalmem() / 1024 / 1024),
|
|
559
|
+
freeMemoryMb: Math.round(os.freemem() / 1024 / 1024),
|
|
560
|
+
rssMb: Math.round(process.memoryUsage().rss / 1024 / 1024),
|
|
561
|
+
processUptimeSec: Math.round(process.uptime()),
|
|
562
|
+
}
|
|
563
|
+
: {}),
|
|
564
|
+
});
|
|
565
|
+
});
|
|
521
566
|
// Global last-resort guards, installed as early as the logger allows so they
|
|
522
567
|
// cover the WHOLE lifecycle — including startup/initialize, the phase the
|
|
523
568
|
// "sidecar did not start in time" bug lives in. The sidecar is a long-lived
|
|
@@ -1072,6 +1117,25 @@ async function createSession(deps, opts) {
|
|
|
1072
1117
|
// /help
|
|
1073
1118
|
.replaceAll(/see \/help/gi, "check the help menu"));
|
|
1074
1119
|
}
|
|
1120
|
+
/**
|
|
1121
|
+
* Guidance for a connection failure against the ACTIVE local endpoint, or
|
|
1122
|
+
* undefined when that isn't the situation (so the generic wording stands).
|
|
1123
|
+
* "Disable your VPN / allow us through the firewall" is useless advice for a
|
|
1124
|
+
* server running on this machine — name what the user has to restart.
|
|
1125
|
+
*/
|
|
1126
|
+
function localNetworkGuidance(source) {
|
|
1127
|
+
if (source !== "network")
|
|
1128
|
+
return undefined;
|
|
1129
|
+
const state = session.getState();
|
|
1130
|
+
if (state.provider !== "local")
|
|
1131
|
+
return undefined;
|
|
1132
|
+
const parsed = parseLocalModelId(state.model);
|
|
1133
|
+
const probe = localProbes.find((p) => p.endpoint.id === parsed?.endpointId);
|
|
1134
|
+
if (!probe)
|
|
1135
|
+
return undefined;
|
|
1136
|
+
return (`${probe.endpoint.label} stopped responding at ${probe.endpoint.baseUrl}. ` +
|
|
1137
|
+
"Start it again and retry, or pick another model.");
|
|
1138
|
+
}
|
|
1075
1139
|
// Turn any thrown value into the same clear headline/message/guidance shape
|
|
1076
1140
|
// the TUI shows (see gg-ai's formatError) instead of a bare `err.message`, log
|
|
1077
1141
|
// the full detail, and broadcast it under `type` ("error" or "nolan_error").
|
|
@@ -1081,7 +1145,7 @@ async function createSession(deps, opts) {
|
|
|
1081
1145
|
function broadcastError(type, logLabel, err) {
|
|
1082
1146
|
const f = formatError(err);
|
|
1083
1147
|
const message = f.message ? desktopGuidance(f.message) : undefined;
|
|
1084
|
-
const guidance = desktopGuidance(f.guidance);
|
|
1148
|
+
const guidance = localNetworkGuidance(f.source) ?? desktopGuidance(f.guidance);
|
|
1085
1149
|
captureSidecarError(err, `app-sidecar.${logLabel.replaceAll(" ", "-")}`, {
|
|
1086
1150
|
scope: type,
|
|
1087
1151
|
...(f.provider ? { provider: f.provider } : {}),
|
|
@@ -1197,17 +1261,116 @@ async function createSession(deps, opts) {
|
|
|
1197
1261
|
}
|
|
1198
1262
|
}
|
|
1199
1263
|
log("INFO", "app-sidecar", "session ready", { provider, model, mode, chatAgent, cwd });
|
|
1264
|
+
// ── Local models (Ollama / LM Studio / llama.cpp / vLLM) ──
|
|
1265
|
+
// Probing four HTTP endpoints must never delay readiness, so this runs in the
|
|
1266
|
+
// background (same shape as backgroundMcpConnect) and pushes a models_change
|
|
1267
|
+
// frame when it lands.
|
|
1268
|
+
let localProbes = [];
|
|
1269
|
+
async function scanLocalModels(force) {
|
|
1270
|
+
const endpoints = await listAllEndpoints();
|
|
1271
|
+
const { probes, models } = await discoverLocalModels(endpoints, { force });
|
|
1272
|
+
localProbes = probes;
|
|
1273
|
+
// Only endpoints that answered get a credential: writing one for a server
|
|
1274
|
+
// that isn't running would make `hasProviderAuth("local")` true forever.
|
|
1275
|
+
await syncEndpointCredentials(probes.filter((probe) => probe.reachable).map((probe) => probe.endpoint));
|
|
1276
|
+
clearRuntimeModels((m) => m.provider === "local");
|
|
1277
|
+
registerRuntimeModels(models);
|
|
1278
|
+
log("INFO", "app-sidecar", "local model scan", {
|
|
1279
|
+
reachable: probes.filter((p) => p.reachable).length + "/" + probes.length,
|
|
1280
|
+
models: String(models.length),
|
|
1281
|
+
});
|
|
1282
|
+
return probes;
|
|
1283
|
+
}
|
|
1284
|
+
/** Endpoint rows + models, in the shape the Local models UI renders. */
|
|
1285
|
+
function localStatePayload() {
|
|
1286
|
+
return {
|
|
1287
|
+
endpoints: localProbes.map((probe) => ({
|
|
1288
|
+
id: probe.endpoint.id,
|
|
1289
|
+
label: probe.endpoint.label,
|
|
1290
|
+
baseUrl: probe.endpoint.baseUrl,
|
|
1291
|
+
kind: probe.endpoint.kind,
|
|
1292
|
+
custom: probe.endpoint.custom === true,
|
|
1293
|
+
reachable: probe.reachable,
|
|
1294
|
+
...(probe.reason ? { reason: probe.reason } : {}),
|
|
1295
|
+
models: probe.models.map((m) => ({
|
|
1296
|
+
id: formatLocalModelId(probe.endpoint.id, m.rawId),
|
|
1297
|
+
rawId: m.rawId,
|
|
1298
|
+
contextWindow: m.contextWindow,
|
|
1299
|
+
contextWindowKnown: m.contextWindowKnown,
|
|
1300
|
+
supportsTools: m.supportsTools,
|
|
1301
|
+
supportsImages: m.supportsImages,
|
|
1302
|
+
supportsThinking: m.supportsThinking,
|
|
1303
|
+
...(m.loaded === undefined ? {} : { loaded: m.loaded }),
|
|
1304
|
+
})),
|
|
1305
|
+
})),
|
|
1306
|
+
};
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* Why `modelId` can't be selected right now, or `undefined` when it can.
|
|
1310
|
+
* Two real footguns get a clear answer instead of a mid-run provider error:
|
|
1311
|
+
* a model with no tool calling (can't drive the agent at all), and a server
|
|
1312
|
+
* that has since been shut down.
|
|
1313
|
+
*/
|
|
1314
|
+
async function localModelBlocker(modelId) {
|
|
1315
|
+
const parsed = parseLocalModelId(modelId);
|
|
1316
|
+
if (!parsed)
|
|
1317
|
+
return undefined;
|
|
1318
|
+
const endpoints = await listAllEndpoints();
|
|
1319
|
+
const endpoint = endpoints.find((e) => e.id === parsed.endpointId);
|
|
1320
|
+
if (!endpoint)
|
|
1321
|
+
return `Unknown local endpoint "${parsed.endpointId}" — re-scan for local models.`;
|
|
1322
|
+
const probe = await probeEndpoint(endpoint);
|
|
1323
|
+
// Keep the cached view honest: this probe is fresher than the last scan.
|
|
1324
|
+
localProbes = localProbes.map((p) => (p.endpoint.id === endpoint.id ? probe : p));
|
|
1325
|
+
if (!probe.reachable) {
|
|
1326
|
+
return `${endpoint.label} isn't running at ${endpoint.baseUrl}. Start it and scan again.`;
|
|
1327
|
+
}
|
|
1328
|
+
const model = probe.models.find((m) => m.rawId === parsed.rawId);
|
|
1329
|
+
if (!model) {
|
|
1330
|
+
return `${endpoint.label} no longer serves "${parsed.rawId}".`;
|
|
1331
|
+
}
|
|
1332
|
+
if (!model.supportsTools) {
|
|
1333
|
+
return `${parsed.rawId} has no tool calling, so it can't run the agent. Pick a tool-capable model.`;
|
|
1334
|
+
}
|
|
1335
|
+
registerRuntimeModels(probe.models.map((m) => localModelInfo(m, endpoint)));
|
|
1336
|
+
return undefined;
|
|
1337
|
+
}
|
|
1338
|
+
/**
|
|
1339
|
+
* A restored per-project pref can ask for thinking on a local model that
|
|
1340
|
+
* turns out not to reason (capabilities are only known after a probe). Drop
|
|
1341
|
+
* the level once we know, so the first prompt doesn't carry a
|
|
1342
|
+
* `reasoning_effort` the server rejects.
|
|
1343
|
+
*/
|
|
1344
|
+
function clampLocalThinking() {
|
|
1345
|
+
const st = session.getState();
|
|
1346
|
+
const level = session.getThinkingLevel();
|
|
1347
|
+
if (!level || st.provider !== "local")
|
|
1348
|
+
return;
|
|
1349
|
+
if (isThinkingLevelSupported(st.provider, st.model, level))
|
|
1350
|
+
return;
|
|
1351
|
+
session.setThinkingLevel(undefined);
|
|
1352
|
+
broadcast("thinking_change", {
|
|
1353
|
+
thinkingLevel: null,
|
|
1354
|
+
supportedThinkingLevels: getSupportedThinkingLevels(st.provider, st.model),
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1200
1357
|
// Workspace extras (context window, git status, background tasks). Git state
|
|
1201
1358
|
// is resolved once at startup and refreshed after every run; the context
|
|
1202
1359
|
// window follows the active model.
|
|
1203
|
-
const [initialGitBranch, initialGitIsRepo, initialDirtyFileCount] = await Promise.all([
|
|
1360
|
+
const [initialGitBranch, initialGitIsRepo, initialDirtyFileCount, initialGitHubSlug] = await Promise.all([
|
|
1204
1361
|
getGitBranch(cwd).catch(() => null),
|
|
1205
1362
|
isGitRepo(cwd).catch(() => false),
|
|
1206
1363
|
getGitDirtyFileCount(cwd).catch(() => 0),
|
|
1364
|
+
getGitHubRepoSlug(cwd).catch(() => null),
|
|
1207
1365
|
]);
|
|
1208
1366
|
let gitBranch = initialGitBranch;
|
|
1209
1367
|
let gitIsRepo = initialGitIsRepo;
|
|
1210
1368
|
let gitDirtyFileCount = initialDirtyFileCount;
|
|
1369
|
+
// Open issue/PR counts for the origin repo's GitHub slug, via the `gh` CLI's
|
|
1370
|
+
// auth. null = unknown (gh missing/unauthed, non-GitHub origin) → chips hidden.
|
|
1371
|
+
const gitHubSlug = initialGitHubSlug;
|
|
1372
|
+
let gitHubIssues = null;
|
|
1373
|
+
let gitHubPRs = null;
|
|
1211
1374
|
function currentContextWindow() {
|
|
1212
1375
|
const st = session.getState();
|
|
1213
1376
|
return getContextWindow(st.model, { provider: st.provider, accountId: st.accountId });
|
|
@@ -1220,9 +1383,41 @@ async function createSession(deps, opts) {
|
|
|
1220
1383
|
gitBranch,
|
|
1221
1384
|
isGitRepo: gitIsRepo,
|
|
1222
1385
|
gitDirtyFileCount,
|
|
1386
|
+
gitHubIssues,
|
|
1387
|
+
gitHubPRs,
|
|
1388
|
+
gitHubRepoUrl: gitHubSlug ? `https://github.com/${gitHubSlug}` : null,
|
|
1223
1389
|
tasks: session.listBackgroundProcesses(),
|
|
1390
|
+
// Roots added with /add-dir — the header shows a badge when non-empty.
|
|
1391
|
+
additionalRoots: session.getAdditionalRoots(),
|
|
1224
1392
|
};
|
|
1225
1393
|
}
|
|
1394
|
+
void scanLocalModels(false)
|
|
1395
|
+
.then(() => {
|
|
1396
|
+
clampLocalThinking();
|
|
1397
|
+
broadcast("extras", footerExtras());
|
|
1398
|
+
})
|
|
1399
|
+
.then(() => broadcast("models_change", { local: localStatePayload() }))
|
|
1400
|
+
.catch((err) => {
|
|
1401
|
+
// A discovery failure is never fatal — the user simply has no local
|
|
1402
|
+
// models. Log it; don't push an error row into the transcript.
|
|
1403
|
+
log("WARN", "app-sidecar", "local model scan failed", {
|
|
1404
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1405
|
+
});
|
|
1406
|
+
});
|
|
1407
|
+
// Refresh the GitHub counts and broadcast only on change. Transient failures
|
|
1408
|
+
// keep the last-known numbers so the chips don't flicker off on a timeout.
|
|
1409
|
+
async function refreshGitHubCounts() {
|
|
1410
|
+
if (!gitHubSlug)
|
|
1411
|
+
return;
|
|
1412
|
+
const counts = await getGitHubOpenCounts(gitHubSlug);
|
|
1413
|
+
if (!counts)
|
|
1414
|
+
return;
|
|
1415
|
+
if (counts.issues !== gitHubIssues || counts.prs !== gitHubPRs) {
|
|
1416
|
+
gitHubIssues = counts.issues;
|
|
1417
|
+
gitHubPRs = counts.prs;
|
|
1418
|
+
broadcast("extras", footerExtras());
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1226
1421
|
// tool_call_end carries no tool name (only the id), so remember each call's
|
|
1227
1422
|
// name from tool_call_start to log a useful line on completion. Mirrors the
|
|
1228
1423
|
// CLI's logging so the app sidecar's ~/.ezcoder/ezcoder-app-sidecar.log records tool
|
|
@@ -1664,6 +1859,9 @@ async function createSession(deps, opts) {
|
|
|
1664
1859
|
isGitRepo(cwd).catch(() => gitIsRepo),
|
|
1665
1860
|
getGitDirtyFileCount(cwd).catch(() => gitDirtyFileCount),
|
|
1666
1861
|
]);
|
|
1862
|
+
// A run may have opened/closed issues or PRs — refresh fire-and-forget so
|
|
1863
|
+
// teardown isn't delayed by the network. Broadcasts itself on change.
|
|
1864
|
+
void refreshGitHubCounts();
|
|
1667
1865
|
// Serialize behind any marker/tool-triggered refresh so the terminal
|
|
1668
1866
|
// progress snapshot uses the live plan file. Once every canonical step
|
|
1669
1867
|
// is complete, remove the approved plan from future system prompts and
|
|
@@ -2136,6 +2334,20 @@ async function createSession(deps, opts) {
|
|
|
2136
2334
|
gitPoll.unref?.();
|
|
2137
2335
|
};
|
|
2138
2336
|
scheduleGitPoll(5000);
|
|
2337
|
+
// GitHub issue/PR counts change outside the app (web UI, teammates), so poll
|
|
2338
|
+
// on a slow cadence. Network-bound, so keep it well under the search API's
|
|
2339
|
+
// rate budget (2 calls per tick). No-op when the origin isn't a GitHub repo.
|
|
2340
|
+
let gitHubPoll;
|
|
2341
|
+
let gitHubPollStopped = false;
|
|
2342
|
+
const scheduleGitHubPoll = (delay) => {
|
|
2343
|
+
if (gitHubPollStopped)
|
|
2344
|
+
return;
|
|
2345
|
+
gitHubPoll = setTimeout(() => {
|
|
2346
|
+
void refreshGitHubCounts().finally(() => scheduleGitHubPoll(60_000));
|
|
2347
|
+
}, delay);
|
|
2348
|
+
gitHubPoll.unref?.();
|
|
2349
|
+
};
|
|
2350
|
+
scheduleGitHubPoll(2000);
|
|
2139
2351
|
function readBody(req, res) {
|
|
2140
2352
|
return readCappedBody(req, res);
|
|
2141
2353
|
}
|
|
@@ -2386,6 +2598,30 @@ async function createSession(deps, opts) {
|
|
|
2386
2598
|
});
|
|
2387
2599
|
return;
|
|
2388
2600
|
}
|
|
2601
|
+
// Markdown transcript export for the app's download button. Serialized
|
|
2602
|
+
// here rather than in the webview because the webview's transcript model
|
|
2603
|
+
// deliberately keeps tool activity in the LiveToolPanel — exporting from
|
|
2604
|
+
// there would hand the user a coding session with the coding missing.
|
|
2605
|
+
// `?name=1` asks for the suggested filename only (the save dialog needs it
|
|
2606
|
+
// before there is a path), so the markdown never crosses IPC twice.
|
|
2607
|
+
if (method === "GET" && (url === "/export" || url.startsWith("/export?"))) {
|
|
2608
|
+
const query = new URLSearchParams(url.slice(url.indexOf("?") + 1));
|
|
2609
|
+
const st = session.getState();
|
|
2610
|
+
const filename = defaultExportFilename(mode);
|
|
2611
|
+
if (query.get("name") === "1") {
|
|
2612
|
+
json(res, 200, { filename });
|
|
2613
|
+
return;
|
|
2614
|
+
}
|
|
2615
|
+
const markdown = sessionToMarkdown({
|
|
2616
|
+
mode,
|
|
2617
|
+
cwd,
|
|
2618
|
+
provider: st.provider,
|
|
2619
|
+
model: st.model,
|
|
2620
|
+
...(st.sessionId ? { sessionId: st.sessionId } : {}),
|
|
2621
|
+
}, session.getMessages(), { toolDetail: parseToolDetail(query.get("tools")) });
|
|
2622
|
+
json(res, 200, { filename, markdown });
|
|
2623
|
+
return;
|
|
2624
|
+
}
|
|
2389
2625
|
if (method === "GET" && url === "/history") {
|
|
2390
2626
|
// Reconstruct the transcript from persisted messages so resume is 1:1 with
|
|
2391
2627
|
// the live SSE stream. Walks ALL message types (not just user/assistant):
|
|
@@ -2705,16 +2941,34 @@ async function createSession(deps, opts) {
|
|
|
2705
2941
|
description: c.description,
|
|
2706
2942
|
source: "built-in",
|
|
2707
2943
|
}));
|
|
2944
|
+
// Desktop-safe registry actions belong in the same picker as workflows.
|
|
2945
|
+
// Most registry commands have dedicated app controls or TUI-only flows;
|
|
2946
|
+
// multi-root management has no other affordance, so expose only these.
|
|
2947
|
+
const workspaceActions = [
|
|
2948
|
+
{
|
|
2949
|
+
name: "add-dir",
|
|
2950
|
+
aliases: ["adddir"],
|
|
2951
|
+
description: "Add another project folder to this workspace",
|
|
2952
|
+
source: "built-in",
|
|
2953
|
+
},
|
|
2954
|
+
{
|
|
2955
|
+
name: "remove-dir",
|
|
2956
|
+
aliases: ["removedir"],
|
|
2957
|
+
description: "Remove an added project folder from this workspace",
|
|
2958
|
+
source: "built-in",
|
|
2959
|
+
},
|
|
2960
|
+
];
|
|
2708
2961
|
const custom = (await loadCustomCommands(cwd))
|
|
2709
|
-
// A custom command can't shadow a built-in name.
|
|
2710
|
-
.filter((c) => !PROMPT_COMMANDS.some((b) => b.name === c.name)
|
|
2962
|
+
// A custom command can't shadow a built-in name or app action.
|
|
2963
|
+
.filter((c) => !PROMPT_COMMANDS.some((b) => b.name === c.name) &&
|
|
2964
|
+
!workspaceActions.some((action) => action.name === c.name))
|
|
2711
2965
|
.map((c) => ({
|
|
2712
2966
|
name: c.name,
|
|
2713
2967
|
aliases: [],
|
|
2714
2968
|
description: c.description,
|
|
2715
2969
|
source: "custom",
|
|
2716
2970
|
}));
|
|
2717
|
-
json(res, 200, { commands: [...builtins, ...custom] });
|
|
2971
|
+
json(res, 200, { commands: [...workspaceActions, ...builtins, ...custom] });
|
|
2718
2972
|
})();
|
|
2719
2973
|
return;
|
|
2720
2974
|
}
|
|
@@ -3173,12 +3427,30 @@ async function createSession(deps, opts) {
|
|
|
3173
3427
|
loggedIn.push(p);
|
|
3174
3428
|
}
|
|
3175
3429
|
// Just the names, grouped by provider in registry order — the UI shows
|
|
3176
|
-
// a clean multi-column list of model ids.
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
provider
|
|
3181
|
-
|
|
3430
|
+
// a clean multi-column list of model ids. Local models come from the
|
|
3431
|
+
// runtime registry (populated by the background scan) and are always
|
|
3432
|
+
// listed: their "login" is the endpoint answering a probe.
|
|
3433
|
+
const models = getAllModels()
|
|
3434
|
+
.filter((m) => m.provider === "local" || loggedIn.includes(m.provider))
|
|
3435
|
+
.map((m) => {
|
|
3436
|
+
if (m.provider !== "local") {
|
|
3437
|
+
return { id: m.id, name: m.name, provider: m.provider };
|
|
3438
|
+
}
|
|
3439
|
+
const probed = findProbedModel(localProbes, m.id);
|
|
3440
|
+
return {
|
|
3441
|
+
id: m.id,
|
|
3442
|
+
name: m.name,
|
|
3443
|
+
provider: m.provider,
|
|
3444
|
+
local: true,
|
|
3445
|
+
endpoint: probed?.endpoint.label ?? parseLocalModelId(m.id)?.endpointId,
|
|
3446
|
+
// A local model that can't call tools can't run the agent — the UI
|
|
3447
|
+
// renders it disabled rather than hiding it, so the user learns why.
|
|
3448
|
+
supportsTools: probed?.model.supportsTools ?? true,
|
|
3449
|
+
contextWindow: m.contextWindow,
|
|
3450
|
+
contextWindowKnown: probed?.model.contextWindowKnown ?? false,
|
|
3451
|
+
supportsThinking: m.supportsThinking,
|
|
3452
|
+
};
|
|
3453
|
+
});
|
|
3182
3454
|
json(res, 200, { models });
|
|
3183
3455
|
})();
|
|
3184
3456
|
return;
|
|
@@ -3207,6 +3479,13 @@ async function createSession(deps, opts) {
|
|
|
3207
3479
|
// Pick up any natively-written keys (desktop app) before resolving the
|
|
3208
3480
|
// new model's credentials, so switching to a just-added provider works.
|
|
3209
3481
|
await auth.reload();
|
|
3482
|
+
if (target.provider === "local") {
|
|
3483
|
+
const problem = await localModelBlocker(target.id);
|
|
3484
|
+
if (problem) {
|
|
3485
|
+
json(res, 409, { error: problem });
|
|
3486
|
+
return;
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3210
3489
|
await session.switchModel(target.provider, target.id);
|
|
3211
3490
|
// Nolan follows EZ Coder's model only while un-pinned; a user-set Nolan
|
|
3212
3491
|
// override survives EZ Coder model switches untouched.
|
|
@@ -3632,6 +3911,84 @@ async function createSession(deps, opts) {
|
|
|
3632
3911
|
});
|
|
3633
3912
|
return;
|
|
3634
3913
|
}
|
|
3914
|
+
// ── Local models ──────────────────────────────────────
|
|
3915
|
+
// GET returns the last scan (cheap, no probing) so opening the modal is
|
|
3916
|
+
// instant; POST /local/scan is the explicit refresh.
|
|
3917
|
+
if (method === "GET" && url === "/local") {
|
|
3918
|
+
json(res, 200, localStatePayload());
|
|
3919
|
+
return;
|
|
3920
|
+
}
|
|
3921
|
+
if (method === "POST" && url === "/local/scan") {
|
|
3922
|
+
void scanLocalModels(true)
|
|
3923
|
+
.then(() => {
|
|
3924
|
+
broadcast("models_change", { local: localStatePayload() });
|
|
3925
|
+
json(res, 200, localStatePayload());
|
|
3926
|
+
})
|
|
3927
|
+
.catch((err) => {
|
|
3928
|
+
broadcastError("error", "local model scan failed", err);
|
|
3929
|
+
json(res, 500, { error: "Local model scan failed — see the sidecar log." });
|
|
3930
|
+
});
|
|
3931
|
+
return;
|
|
3932
|
+
}
|
|
3933
|
+
if (method === "POST" && url === "/local/endpoints") {
|
|
3934
|
+
void readBody(req, res).then(async (raw) => {
|
|
3935
|
+
if (raw === null)
|
|
3936
|
+
return;
|
|
3937
|
+
let body;
|
|
3938
|
+
try {
|
|
3939
|
+
body = JSON.parse(raw);
|
|
3940
|
+
}
|
|
3941
|
+
catch {
|
|
3942
|
+
json(res, 400, { error: "invalid JSON body" });
|
|
3943
|
+
return;
|
|
3944
|
+
}
|
|
3945
|
+
try {
|
|
3946
|
+
const endpoint = await addCustomEndpoint({
|
|
3947
|
+
baseUrl: body.baseUrl ?? "",
|
|
3948
|
+
...(body.label ? { label: body.label } : {}),
|
|
3949
|
+
...(body.apiKey ? { apiKey: body.apiKey } : {}),
|
|
3950
|
+
});
|
|
3951
|
+
await scanLocalModels(true);
|
|
3952
|
+
broadcast("models_change", { local: localStatePayload() });
|
|
3953
|
+
json(res, 200, {
|
|
3954
|
+
endpoint: { id: endpoint.id, label: endpoint.label },
|
|
3955
|
+
...localStatePayload(),
|
|
3956
|
+
});
|
|
3957
|
+
}
|
|
3958
|
+
catch (err) {
|
|
3959
|
+
// Validation errors are the user's typo, not a system fault — 400 with
|
|
3960
|
+
// the exact reason, and nothing in the transcript.
|
|
3961
|
+
if (err instanceof LocalEndpointError) {
|
|
3962
|
+
json(res, 400, { error: err.message });
|
|
3963
|
+
return;
|
|
3964
|
+
}
|
|
3965
|
+
broadcastError("error", "add local endpoint failed", err);
|
|
3966
|
+
json(res, 500, { error: "Could not save the endpoint — see the sidecar log." });
|
|
3967
|
+
}
|
|
3968
|
+
});
|
|
3969
|
+
return;
|
|
3970
|
+
}
|
|
3971
|
+
if (method === "DELETE" && url.startsWith("/local/endpoints/")) {
|
|
3972
|
+
const id = decodeURIComponent(url.slice("/local/endpoints/".length));
|
|
3973
|
+
void (async () => {
|
|
3974
|
+
try {
|
|
3975
|
+
await removeCustomEndpoint(id);
|
|
3976
|
+
clearRuntimeModels((m) => m.provider === "local" && parseLocalModelId(m.id)?.endpointId === id);
|
|
3977
|
+
await scanLocalModels(true);
|
|
3978
|
+
broadcast("models_change", { local: localStatePayload() });
|
|
3979
|
+
json(res, 200, localStatePayload());
|
|
3980
|
+
}
|
|
3981
|
+
catch (err) {
|
|
3982
|
+
if (err instanceof LocalEndpointError) {
|
|
3983
|
+
json(res, 400, { error: err.message });
|
|
3984
|
+
return;
|
|
3985
|
+
}
|
|
3986
|
+
broadcastError("error", "remove local endpoint failed", err);
|
|
3987
|
+
json(res, 500, { error: "Could not remove the endpoint — see the sidecar log." });
|
|
3988
|
+
}
|
|
3989
|
+
})();
|
|
3990
|
+
return;
|
|
3991
|
+
}
|
|
3635
3992
|
// ── Telegram config (mirrors `ezcoder telegram`) ─────────
|
|
3636
3993
|
if (method === "GET" && url === "/telegram") {
|
|
3637
3994
|
void loadTelegramConfig().then((cfg) => {
|
|
@@ -3919,6 +4276,9 @@ async function createSession(deps, opts) {
|
|
|
3919
4276
|
gitPollStopped = true;
|
|
3920
4277
|
if (gitPoll)
|
|
3921
4278
|
clearTimeout(gitPoll);
|
|
4279
|
+
gitHubPollStopped = true;
|
|
4280
|
+
if (gitHubPoll)
|
|
4281
|
+
clearTimeout(gitHubPoll);
|
|
3922
4282
|
// Stop the Telegram serve loop + dispose its per-chat sessions.
|
|
3923
4283
|
if (serveController)
|
|
3924
4284
|
await serveController.stop().catch(() => { });
|