@prestyj/cli 5.8.0 → 5.9.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/assets/skills/evidence-led-ui/LICENSES.md +12 -0
- package/assets/skills/evidence-led-ui/SKILL.md +14 -6
- package/assets/skills/evidence-led-ui/references/anti-defaults.md +9 -1
- package/assets/skills/evidence-led-ui/references/craft-rulings.md +33 -0
- package/assets/skills/evidence-led-ui/references/production-contract.md +41 -21
- package/assets/skills/evidence-led-ui/references/provenance.md +7 -5
- package/assets/skills/evidence-led-ui/references/quality-rubric.md +10 -10
- package/dist/app-sidecar-sessions.d.ts +8 -0
- package/dist/app-sidecar-sessions.d.ts.map +1 -0
- package/dist/app-sidecar-sessions.js +31 -0
- package/dist/app-sidecar-sessions.js.map +1 -0
- package/dist/app-sidecar-sessions.test.d.ts +2 -0
- package/dist/app-sidecar-sessions.test.d.ts.map +1 -0
- package/dist/app-sidecar-sessions.test.js +83 -0
- package/dist/app-sidecar-sessions.test.js.map +1 -0
- package/dist/app-sidecar.js +250 -107
- package/dist/app-sidecar.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +43 -26
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +9 -0
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-compaction.test.js +19 -0
- package/dist/core/agent-session-compaction.test.js.map +1 -1
- package/dist/core/agent-session.d.ts +4 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +84 -12
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/agents.d.ts +1 -1
- package/dist/core/agents.d.ts.map +1 -1
- package/dist/core/agents.js +4 -1
- package/dist/core/agents.js.map +1 -1
- package/dist/core/auth-storage.d.ts +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +1 -1
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/compaction/compactor.d.ts.map +1 -1
- package/dist/core/compaction/compactor.js +20 -11
- package/dist/core/compaction/compactor.js.map +1 -1
- package/dist/core/compaction/compactor.test.js +16 -3
- package/dist/core/compaction/compactor.test.js.map +1 -1
- package/dist/core/event-bus.d.ts +5 -0
- package/dist/core/event-bus.d.ts.map +1 -1
- package/dist/core/event-bus.js +6 -0
- package/dist/core/event-bus.js.map +1 -1
- package/dist/core/goal-controller.test.js +3 -3
- package/dist/core/goal-controller.test.js.map +1 -1
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/input-normalization.test.d.ts +2 -0
- package/dist/core/input-normalization.test.d.ts.map +1 -0
- package/dist/core/input-normalization.test.js +75 -0
- package/dist/core/input-normalization.test.js.map +1 -0
- package/dist/core/loop-breaker.d.ts +38 -1
- package/dist/core/loop-breaker.d.ts.map +1 -1
- package/dist/core/loop-breaker.js +84 -2
- package/dist/core/loop-breaker.js.map +1 -1
- package/dist/core/loop-breaker.test.js +108 -1
- package/dist/core/loop-breaker.test.js.map +1 -1
- package/dist/core/message-images.d.ts +12 -0
- package/dist/core/message-images.d.ts.map +1 -0
- package/dist/core/message-images.js +52 -0
- package/dist/core/message-images.js.map +1 -0
- package/dist/core/message-images.test.d.ts +2 -0
- package/dist/core/message-images.test.d.ts.map +1 -0
- package/dist/core/message-images.test.js +62 -0
- package/dist/core/message-images.test.js.map +1 -0
- package/dist/core/project-discovery.d.ts +1 -1
- package/dist/core/project-discovery.d.ts.map +1 -1
- package/dist/core/project-discovery.js +105 -53
- package/dist/core/project-discovery.js.map +1 -1
- package/dist/core/project-discovery.test.js +29 -0
- package/dist/core/project-discovery.test.js.map +1 -1
- package/dist/core/session-manager.d.ts +27 -7
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +257 -124
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-storage.d.ts +57 -0
- package/dist/core/session-storage.d.ts.map +1 -0
- package/dist/core/session-storage.js +568 -0
- package/dist/core/session-storage.js.map +1 -0
- package/dist/core/session-storage.test.d.ts +2 -0
- package/dist/core/session-storage.test.d.ts.map +1 -0
- package/dist/core/session-storage.test.js +269 -0
- package/dist/core/session-storage.test.js.map +1 -0
- package/dist/core/settings-manager.d.ts +2 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +7 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/sidecar-error-reporter.d.ts +12 -0
- package/dist/core/sidecar-error-reporter.d.ts.map +1 -0
- package/dist/core/sidecar-error-reporter.js +57 -0
- package/dist/core/sidecar-error-reporter.js.map +1 -0
- package/dist/core/sidecar-error-reporter.test.d.ts +2 -0
- package/dist/core/sidecar-error-reporter.test.d.ts.map +1 -0
- package/dist/core/sidecar-error-reporter.test.js +61 -0
- package/dist/core/sidecar-error-reporter.test.js.map +1 -0
- package/dist/core/skills.d.ts +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +4 -1
- package/dist/core/skills.js.map +1 -1
- package/dist/core/subagent-manager.d.ts +7 -0
- package/dist/core/subagent-manager.d.ts.map +1 -1
- package/dist/core/subagent-manager.js +26 -2
- package/dist/core/subagent-manager.js.map +1 -1
- package/dist/core/subagent-manager.test.js +34 -0
- package/dist/core/subagent-manager.test.js.map +1 -1
- package/dist/core/verify-commands.d.ts.map +1 -1
- package/dist/core/verify-commands.js +1 -6
- package/dist/core/verify-commands.js.map +1 -1
- package/dist/core/verify-commands.test.js +1 -5
- package/dist/core/verify-commands.test.js.map +1 -1
- package/dist/core/workspace-guard.d.ts +38 -0
- package/dist/core/workspace-guard.d.ts.map +1 -0
- package/dist/core/workspace-guard.js +112 -0
- package/dist/core/workspace-guard.js.map +1 -0
- package/dist/core/workspace-guard.test.d.ts +2 -0
- package/dist/core/workspace-guard.test.d.ts.map +1 -0
- package/dist/core/workspace-guard.test.js +86 -0
- package/dist/core/workspace-guard.test.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/json-mode.d.ts.map +1 -1
- package/dist/modes/json-mode.js +9 -0
- package/dist/modes/json-mode.js.map +1 -1
- package/dist/modes/subagent-worker-mode.d.ts.map +1 -1
- package/dist/modes/subagent-worker-mode.js +11 -0
- package/dist/modes/subagent-worker-mode.js.map +1 -1
- package/dist/session.d.ts +4 -4
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +38 -101
- package/dist/session.js.map +1 -1
- package/dist/session.test.d.ts +2 -0
- package/dist/session.test.d.ts.map +1 -0
- package/dist/session.test.js +35 -0
- package/dist/session.test.js.map +1 -0
- package/dist/system-prompt.d.ts +12 -0
- package/dist/system-prompt.d.ts.map +1 -1
- package/dist/system-prompt.js +86 -19
- package/dist/system-prompt.js.map +1 -1
- package/dist/system-prompt.test.js +132 -8
- package/dist/system-prompt.test.js.map +1 -1
- package/dist/tools/bash.d.ts +3 -2
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +37 -6
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/bash.test.js +34 -1
- package/dist/tools/bash.test.js.map +1 -1
- package/dist/tools/edit.d.ts +2 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +7 -1
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/edit.test.js +7 -0
- package/dist/tools/edit.test.js.map +1 -1
- package/dist/tools/exit-plan.d.ts.map +1 -1
- package/dist/tools/exit-plan.js +15 -4
- package/dist/tools/exit-plan.js.map +1 -1
- package/dist/tools/exit-plan.test.d.ts +2 -0
- package/dist/tools/exit-plan.test.d.ts.map +1 -0
- package/dist/tools/exit-plan.test.js +72 -0
- package/dist/tools/exit-plan.test.js.map +1 -0
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +8 -3
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/grep.test.js +6 -0
- package/dist/tools/grep.test.js.map +1 -1
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +3 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/screenshot.js +1 -1
- package/dist/tools/screenshot.js.map +1 -1
- package/dist/tools/skill.d.ts.map +1 -1
- package/dist/tools/skill.js +4 -2
- package/dist/tools/skill.js.map +1 -1
- package/dist/tools/source-path.d.ts.map +1 -1
- package/dist/tools/source-path.js +9 -0
- package/dist/tools/source-path.js.map +1 -1
- package/dist/tools/subagent-shared.d.ts +1 -1
- package/dist/tools/subagent-shared.d.ts.map +1 -1
- package/dist/tools/subagent-shared.js +4 -1
- package/dist/tools/subagent-shared.js.map +1 -1
- package/dist/tools/subagent-shared.test.js +21 -1
- package/dist/tools/subagent-shared.test.js.map +1 -1
- package/dist/tools/web-search.d.ts.map +1 -1
- package/dist/tools/web-search.js +13 -2
- package/dist/tools/web-search.js.map +1 -1
- package/dist/tools/write.d.ts +2 -1
- package/dist/tools/write.d.ts.map +1 -1
- package/dist/tools/write.js +7 -1
- package/dist/tools/write.js.map +1 -1
- package/dist/tools/write.test.js +12 -0
- package/dist/tools/write.test.js.map +1 -1
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +17 -5
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/app-items.d.ts +10 -0
- package/dist/ui/app-items.d.ts.map +1 -1
- package/dist/ui/app-items.js +10 -0
- package/dist/ui/app-items.js.map +1 -1
- package/dist/ui/components/ActivityIndicator.d.ts.map +1 -1
- package/dist/ui/components/ActivityIndicator.js +1 -0
- package/dist/ui/components/ActivityIndicator.js.map +1 -1
- package/dist/ui/components/Footer.test.js +1 -1
- package/dist/ui/hooks/useAgentLoop.d.ts +5 -2
- package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
- package/dist/ui/hooks/useAgentLoop.js +49 -8
- package/dist/ui/hooks/useAgentLoop.js.map +1 -1
- package/dist/ui/hooks/useTranscriptHistory.d.ts +8 -5
- package/dist/ui/hooks/useTranscriptHistory.d.ts.map +1 -1
- package/dist/ui/hooks/useTranscriptHistory.js +2 -30
- package/dist/ui/hooks/useTranscriptHistory.js.map +1 -1
- package/dist/utils/http-body.d.ts +19 -0
- package/dist/utils/http-body.d.ts.map +1 -0
- package/dist/utils/http-body.js +58 -0
- package/dist/utils/http-body.js.map +1 -0
- package/dist/utils/http-body.test.d.ts +2 -0
- package/dist/utils/http-body.test.d.ts.map +1 -0
- package/dist/utils/http-body.test.js +68 -0
- package/dist/utils/http-body.test.js.map +1 -0
- package/dist/utils/text.d.ts +9 -0
- package/dist/utils/text.d.ts.map +1 -0
- package/dist/utils/text.js +11 -0
- package/dist/utils/text.js.map +1 -0
- package/package.json +5 -5
package/dist/app-sidecar.js
CHANGED
|
@@ -23,7 +23,7 @@ import { runJsonMode } from "./modes/json-mode.js";
|
|
|
23
23
|
import { runSubagentWorkerMode } from "./modes/subagent-worker-mode.js";
|
|
24
24
|
import { AgentSession } from "./core/agent-session.js";
|
|
25
25
|
import { RunLifecycle } from "./core/run-lifecycle.js";
|
|
26
|
-
import { CHAT_AGENT_IDS, chatAgentSessionsDir, createChatAgent, parseChatAgentId,
|
|
26
|
+
import { CHAT_AGENT_IDS, chatAgentSessionsDir, createChatAgent, parseChatAgentId, switchChatAgent, } from "./chat-agents/index.js";
|
|
27
27
|
import { buildJiwaTools, JiwaStore } from "./chat-agents/jiwa.js";
|
|
28
28
|
import { buildMemoryTools, MemoryStore } from "./chat-agents/memory.js";
|
|
29
29
|
import { buildNolanSystemPrompt, buildNolanAutopilotSystemPrompt } from "./core/nolan-prompt.js";
|
|
@@ -35,6 +35,7 @@ import { validateNolanModelPref, effectiveNolanModel, } from "./core/nolan-model
|
|
|
35
35
|
import { normalizeAutopilotMarkersForHistory, normalizeAppMarkersForHistory, normalizeNolanTurnsForHistory, restoreUserRow, restoreAssistantTexts, autopilotMarkerCopySeed, } from "./core/session-history.js";
|
|
36
36
|
import { AuthStorage } from "./core/auth-storage.js";
|
|
37
37
|
import { cleanupToolOutputs } from "./tools/overflow.js";
|
|
38
|
+
import { readCappedBody } from "./utils/http-body.js";
|
|
38
39
|
import { fetchSubscriptionUsage, MOONSHOT_OAUTH_KEY, SubscriptionUsageError, XIAOMI_CREDITS_KEY, } from "@prestyj/core";
|
|
39
40
|
import { loginAnthropic } from "./core/oauth/anthropic.js";
|
|
40
41
|
import { loginOpenAI } from "./core/oauth/openai.js";
|
|
@@ -43,19 +44,20 @@ import { loginKimi } from "./core/oauth/kimi.js";
|
|
|
43
44
|
import { AUTH_PROVIDERS } from "./core/auth-providers.js";
|
|
44
45
|
import { ensureAppDirs, loadSavedSettings } from "./config.js";
|
|
45
46
|
import { SettingsManager } from "./core/settings-manager.js";
|
|
46
|
-
import { getModel,
|
|
47
|
+
import { getModel, getDefaultThinkingLevel, getContextWindow, MODELS, } from "./core/model-registry.js";
|
|
47
48
|
import { resolveStartOrFallback } from "./core/resolve-start.js";
|
|
48
49
|
import { getGitBranch, getGitDirtyFileCount, isGitRepo } from "./utils/git.js";
|
|
49
50
|
import { extractPlanSteps } from "./utils/plan-steps.js";
|
|
50
51
|
import { getNextThinkingLevel, getSupportedThinkingLevels, isThinkingLevelSupported, } from "./core/thinking-level.js";
|
|
51
52
|
import { PROMPT_COMMANDS } from "./core/prompt-commands.js";
|
|
52
53
|
import { loadCustomCommands } from "./core/custom-commands.js";
|
|
53
|
-
import { discoverProjects
|
|
54
|
+
import { discoverProjects } from "./core/project-discovery.js";
|
|
55
|
+
import { listSidecarSessions } from "./app-sidecar-sessions.js";
|
|
54
56
|
import { loadTasksSync, saveTasksSync, pruneDoneTasksSync, getNextPendingTask, markTaskInProgress, } from "./core/task-store.js";
|
|
55
57
|
import { initLogger, log } from "./core/logger.js";
|
|
56
58
|
import { RADIO_STATIONS, getCurrentStation, getRadioVolume, playRadio, setRadioVolume, stopRadio, } from "./core/radio.js";
|
|
57
59
|
import { enrichProcessPath } from "./core/shell-path.js";
|
|
58
|
-
import { downscaleForPreview, validateVisionImage } from "./utils/image.js";
|
|
60
|
+
import { downscaleForPreview, shrinkToFit, validateVisionImage } from "./utils/image.js";
|
|
59
61
|
import { startServeMode } from "./modes/serve-mode.js";
|
|
60
62
|
import { loadTelegramConfig, saveTelegramConfig, verifyBotToken } from "./core/telegram-config.js";
|
|
61
63
|
import { loadServers, addServer, removeServer, getServer, parseMcpAddCommand, MCPClientManager, McpOAuthStore, } from "./core/mcp/index.js";
|
|
@@ -64,6 +66,7 @@ import { loadProgress, peekProgress, updateProgress } from "./core/progress/stor
|
|
|
64
66
|
import { awardPrompt, awardCommits } from "./core/progress/engine.js";
|
|
65
67
|
import { detectNewCommits, repoKey } from "./core/progress/git-xp.js";
|
|
66
68
|
import { rebuildFromSessions } from "./core/progress/rebuild.js";
|
|
69
|
+
import { captureSidecarError, flushSidecarErrors, shouldCaptureToolFailure, shouldCaptureUsagePollingError, wrapSidecarHandler, } from "./core/sidecar-error-reporter.js";
|
|
67
70
|
const ALL_PROVIDERS = [
|
|
68
71
|
// US
|
|
69
72
|
"anthropic",
|
|
@@ -169,6 +172,7 @@ async function persistModelSelection(settingsFile, provider, model) {
|
|
|
169
172
|
await sm.set("defaultModel", model);
|
|
170
173
|
}
|
|
171
174
|
catch (err) {
|
|
175
|
+
captureSidecarError(err, "app-sidecar.settings.persist-model");
|
|
172
176
|
log("WARN", "app-sidecar", "failed to persist model selection", { err: String(err) });
|
|
173
177
|
}
|
|
174
178
|
}
|
|
@@ -185,6 +189,7 @@ async function persistThinkingLevel(settingsFile, level) {
|
|
|
185
189
|
await sm.set("thinkingLevel", level);
|
|
186
190
|
}
|
|
187
191
|
catch (err) {
|
|
192
|
+
captureSidecarError(err, "app-sidecar.settings.persist-thinking");
|
|
188
193
|
log("WARN", "app-sidecar", "failed to persist thinking level", { err: String(err) });
|
|
189
194
|
}
|
|
190
195
|
}
|
|
@@ -202,19 +207,28 @@ async function prepareAttachments(cwd, attachments) {
|
|
|
202
207
|
const fileName = `${Date.now().toString(36)}-${safe}`;
|
|
203
208
|
const filePath = path.join(dir, fileName);
|
|
204
209
|
const buf = Buffer.from(a.data, "base64");
|
|
205
|
-
// Validate image attachments before they become native image content
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
//
|
|
210
|
+
// Validate and cap image attachments before they become native image content
|
|
211
|
+
// blocks. Anthropic applies a 2000 px per-dimension cap once conversation
|
|
212
|
+
// history contains more than 20 images, so every attachment must be safe for
|
|
213
|
+
// later turns too. Keep the original on disk, but send the resized bytes.
|
|
214
|
+
// Corrupt or unsupported images become plain files so they cannot reject the
|
|
215
|
+
// entire provider request.
|
|
211
216
|
let prepared = { ...a };
|
|
212
217
|
if (a.kind === "image") {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
218
|
+
try {
|
|
219
|
+
const resized = await shrinkToFit(buf, a.mediaType);
|
|
220
|
+
const validatedType = await validateVisionImage(resized.buffer);
|
|
221
|
+
prepared = validatedType
|
|
222
|
+
? {
|
|
223
|
+
...a,
|
|
224
|
+
mediaType: validatedType,
|
|
225
|
+
data: resized.buffer.toString("base64"),
|
|
226
|
+
}
|
|
227
|
+
: { ...a, kind: "file" };
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
217
230
|
prepared = { ...a, kind: "file" };
|
|
231
|
+
}
|
|
218
232
|
}
|
|
219
233
|
try {
|
|
220
234
|
await fs.writeFile(filePath, buf);
|
|
@@ -227,6 +241,9 @@ async function prepareAttachments(cwd, attachments) {
|
|
|
227
241
|
return out;
|
|
228
242
|
}
|
|
229
243
|
const FILE_SEARCH_LIMIT = 20;
|
|
244
|
+
// Upper bound on files walked per search (baseline #8 memory cap). Far above the
|
|
245
|
+
// 20-result output limit, so relevance/recency ranking is unaffected in practice.
|
|
246
|
+
const FILE_SEARCH_SCAN_CAP = 50_000;
|
|
230
247
|
/** Score a candidate path against a lowercased query. Higher is better; a
|
|
231
248
|
* negative score means "no match". Basename hits beat path hits; prefix beats
|
|
232
249
|
* substring; shorter paths break ties. */
|
|
@@ -279,8 +296,13 @@ async function searchProjectFiles(cwd, rawQuery) {
|
|
|
279
296
|
}
|
|
280
297
|
const ig = ignore.default().add(gitignore);
|
|
281
298
|
// `stats: true` gives mtime without a second stat pass, so the empty-query
|
|
282
|
-
// "recent files" path is a single walk.
|
|
283
|
-
|
|
299
|
+
// "recent files" path is a single walk. Stream + hard scan cap (baseline #8):
|
|
300
|
+
// collecting the full result array retained ~0.9 MB per 1k files (18.5 MB at
|
|
301
|
+
// 20k), unbounded. Bail after FILE_SEARCH_SCAN_CAP entries so a giant repo
|
|
302
|
+
// can't balloon RSS; the newest/most-relevant matches still surface because
|
|
303
|
+
// the cap is far above the 20-result output limit.
|
|
304
|
+
const entries = [];
|
|
305
|
+
const scanStream = fg.default.stream("**/*", {
|
|
284
306
|
cwd,
|
|
285
307
|
dot: false,
|
|
286
308
|
onlyFiles: true,
|
|
@@ -289,6 +311,17 @@ async function searchProjectFiles(cwd, rawQuery) {
|
|
|
289
311
|
followSymbolicLinks: false,
|
|
290
312
|
stats: true,
|
|
291
313
|
});
|
|
314
|
+
for await (const entry of scanStream) {
|
|
315
|
+
entries.push(entry);
|
|
316
|
+
if (entries.length >= FILE_SEARCH_SCAN_CAP) {
|
|
317
|
+
scanStream.destroy();
|
|
318
|
+
log("DEBUG", "app-sidecar", "file search scan cap hit", {
|
|
319
|
+
cap: String(FILE_SEARCH_SCAN_CAP),
|
|
320
|
+
cwd,
|
|
321
|
+
});
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
292
325
|
const files = entries.filter((e) => !ig.ignores(e.path));
|
|
293
326
|
if (!query) {
|
|
294
327
|
return files
|
|
@@ -443,7 +476,11 @@ async function runJsonModeIfRequested() {
|
|
|
443
476
|
maxTurns: maxTurnsRaw ? parseInt(maxTurnsRaw, 10) : undefined,
|
|
444
477
|
allowedTools,
|
|
445
478
|
promptCacheKey: values["prompt-cache-key"],
|
|
446
|
-
}).catch((err) => {
|
|
479
|
+
}).catch(async (err) => {
|
|
480
|
+
captureSidecarError(err, "app-sidecar.json-mode", {
|
|
481
|
+
provider: String(values.provider ?? "anthropic"),
|
|
482
|
+
});
|
|
483
|
+
await flushSidecarErrors();
|
|
447
484
|
process.stderr.write((err instanceof Error ? err.message : String(err)) + "\n");
|
|
448
485
|
process.exit(1);
|
|
449
486
|
});
|
|
@@ -452,13 +489,8 @@ async function runJsonModeIfRequested() {
|
|
|
452
489
|
// ── Daemon-level HTTP helpers (shared by the session-management routes) ─────
|
|
453
490
|
// The per-session route table has its own local copies; these serve the
|
|
454
491
|
// daemon's own POST /session / DELETE /session routes.
|
|
455
|
-
function daemonReadBody(req) {
|
|
456
|
-
return
|
|
457
|
-
const chunks = [];
|
|
458
|
-
req.on("data", (c) => chunks.push(c));
|
|
459
|
-
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
|
|
460
|
-
req.on("error", reject);
|
|
461
|
-
});
|
|
492
|
+
function daemonReadBody(req, res) {
|
|
493
|
+
return readCappedBody(req, res);
|
|
462
494
|
}
|
|
463
495
|
function daemonJson(res, status, body) {
|
|
464
496
|
res.writeHead(status, {
|
|
@@ -544,18 +576,24 @@ async function main() {
|
|
|
544
576
|
});
|
|
545
577
|
const usageCache = new Map();
|
|
546
578
|
const usageRequests = new Map();
|
|
547
|
-
// 429 backoff:
|
|
548
|
-
//
|
|
549
|
-
//
|
|
579
|
+
// 429 backoff: quota endpoints are auxiliary UI data. Honor Retry-After when
|
|
580
|
+
// provided; otherwise retain the unavailable snapshot for 30 minutes. Clamp
|
|
581
|
+
// the provider value so a malformed header can neither hammer the endpoint
|
|
582
|
+
// nor suppress usage data forever.
|
|
550
583
|
const usageRateLimitedUntil = new Map();
|
|
551
|
-
const
|
|
584
|
+
const USAGE_RATE_LIMIT_FALLBACK_BACKOFF_MS = 30 * 60_000;
|
|
585
|
+
const USAGE_RATE_LIMIT_MIN_BACKOFF_MS = 60_000;
|
|
586
|
+
const USAGE_RATE_LIMIT_MAX_BACKOFF_MS = 24 * 60 * 60_000;
|
|
552
587
|
async function fetchUsageProvider(provider) {
|
|
553
|
-
const displayName = provider === "anthropic" ? "Anthropic" : "Codex";
|
|
554
|
-
|
|
588
|
+
const displayName = provider === "anthropic" ? "Anthropic" : provider === "openai" ? "Codex" : "Kimi";
|
|
589
|
+
// Kimi plan usage is tracked on the OAuth credential specifically — the
|
|
590
|
+
// Moonshot platform API key is metered per-token, not per plan window.
|
|
591
|
+
const authKey = provider === "moonshot" ? MOONSHOT_OAUTH_KEY : provider;
|
|
592
|
+
if (!(await auth.hasProviderAuth(authKey))) {
|
|
555
593
|
return { provider, displayName, connected: false, windows: [], fetchedAt: Date.now() };
|
|
556
594
|
}
|
|
557
595
|
try {
|
|
558
|
-
let credentials = await auth.resolveCredentials(
|
|
596
|
+
let credentials = await auth.resolveCredentials(authKey);
|
|
559
597
|
try {
|
|
560
598
|
const snapshot = {
|
|
561
599
|
...(await fetchSubscriptionUsage(provider, credentials)),
|
|
@@ -568,7 +606,7 @@ async function main() {
|
|
|
568
606
|
// A provider can revoke an access token before its stored expiry. Refresh
|
|
569
607
|
// once on 401, matching inference auth recovery, then retry the usage call.
|
|
570
608
|
if (error instanceof SubscriptionUsageError && error.status === 401) {
|
|
571
|
-
credentials = await auth.resolveCredentials(
|
|
609
|
+
credentials = await auth.resolveCredentials(authKey, { forceRefresh: true });
|
|
572
610
|
const snapshot = {
|
|
573
611
|
...(await fetchSubscriptionUsage(provider, credentials)),
|
|
574
612
|
connected: true,
|
|
@@ -581,11 +619,20 @@ async function main() {
|
|
|
581
619
|
}
|
|
582
620
|
catch (error) {
|
|
583
621
|
const message = error instanceof Error ? error.message : String(error);
|
|
584
|
-
|
|
622
|
+
if (shouldCaptureUsagePollingError(error)) {
|
|
623
|
+
captureSidecarError(error, "app-sidecar.usage.fetch", { provider });
|
|
624
|
+
}
|
|
625
|
+
let backoffMs;
|
|
585
626
|
if (error instanceof SubscriptionUsageError && error.status === 429) {
|
|
586
|
-
|
|
627
|
+
backoffMs = Math.min(USAGE_RATE_LIMIT_MAX_BACKOFF_MS, Math.max(USAGE_RATE_LIMIT_MIN_BACKOFF_MS, error.retryAfterMs ?? USAGE_RATE_LIMIT_FALLBACK_BACKOFF_MS));
|
|
628
|
+
usageRateLimitedUntil.set(provider, Date.now() + backoffMs);
|
|
587
629
|
}
|
|
588
|
-
|
|
630
|
+
log("WARN", "app-sidecar", "subscription usage fetch failed", {
|
|
631
|
+
provider,
|
|
632
|
+
message,
|
|
633
|
+
...(backoffMs !== undefined && { backoffMs: String(backoffMs) }),
|
|
634
|
+
});
|
|
635
|
+
const connected = await auth.hasProviderAuth(authKey);
|
|
589
636
|
return {
|
|
590
637
|
provider,
|
|
591
638
|
displayName,
|
|
@@ -640,7 +687,7 @@ async function main() {
|
|
|
640
687
|
return null;
|
|
641
688
|
}
|
|
642
689
|
}
|
|
643
|
-
const server = http.createServer((req, res) => {
|
|
690
|
+
const server = http.createServer(wrapSidecarHandler((req, res) => {
|
|
644
691
|
const url = req.url ?? "/";
|
|
645
692
|
const method = req.method ?? "GET";
|
|
646
693
|
// CORS preflight — the webview origin differs from 127.0.0.1.
|
|
@@ -656,7 +703,9 @@ async function main() {
|
|
|
656
703
|
// ── Daemon-level routes (session lifecycle) ──────────────────────────
|
|
657
704
|
// Create a session for a window: { mode?, cwd, sessionPath? } → { sessionId }.
|
|
658
705
|
if (method === "POST" && url === "/session") {
|
|
659
|
-
void daemonReadBody(req).then(async (raw) => {
|
|
706
|
+
void daemonReadBody(req, res).then(async (raw) => {
|
|
707
|
+
if (raw === null)
|
|
708
|
+
return;
|
|
660
709
|
let body = {};
|
|
661
710
|
try {
|
|
662
711
|
body = raw ? JSON.parse(raw) : {};
|
|
@@ -684,6 +733,7 @@ async function main() {
|
|
|
684
733
|
}
|
|
685
734
|
catch (err) {
|
|
686
735
|
const message = err instanceof Error ? err.message : String(err);
|
|
736
|
+
captureSidecarError(err, "app-sidecar.session.create");
|
|
687
737
|
log("ERROR", "app-sidecar", "session create failed", { message });
|
|
688
738
|
daemonJson(res, 500, { error: message });
|
|
689
739
|
}
|
|
@@ -714,12 +764,13 @@ async function main() {
|
|
|
714
764
|
if (method === "GET" && (url === "/usage" || url.startsWith("/usage?"))) {
|
|
715
765
|
void (async () => {
|
|
716
766
|
const provider = new URL(url, `http://${host}`).searchParams.get("provider");
|
|
717
|
-
if (provider !== "anthropic" && provider !== "openai") {
|
|
767
|
+
if (provider !== "anthropic" && provider !== "openai" && provider !== "moonshot") {
|
|
718
768
|
daemonJson(res, 400, { error: "unsupported usage provider" });
|
|
719
769
|
return;
|
|
720
770
|
}
|
|
721
771
|
daemonJson(res, 200, await subscriptionUsage(provider));
|
|
722
772
|
})().catch((error) => {
|
|
773
|
+
captureSidecarError(error, "app-sidecar.usage.request");
|
|
723
774
|
log("ERROR", "app-sidecar", "subscription usage request failed", {
|
|
724
775
|
message: error instanceof Error ? error.message : String(error),
|
|
725
776
|
});
|
|
@@ -735,7 +786,7 @@ async function main() {
|
|
|
735
786
|
return;
|
|
736
787
|
}
|
|
737
788
|
ctx.handle(req, res, url, method);
|
|
738
|
-
});
|
|
789
|
+
}, "app-sidecar.http"));
|
|
739
790
|
server.listen(port, host, () => {
|
|
740
791
|
const addr = server.address();
|
|
741
792
|
// The Rust shell reads this line to learn the daemon port.
|
|
@@ -752,6 +803,8 @@ async function main() {
|
|
|
752
803
|
// Radio playback is app-wide (one stream across all windows), so it stops
|
|
753
804
|
// at the daemon level, not per session.
|
|
754
805
|
stopRadio();
|
|
806
|
+
// Close the ~/.ezcoder progress fs.watch handle (baseline #8 leak fix).
|
|
807
|
+
progress.dispose();
|
|
755
808
|
await Promise.all([...sessions.values()].map((c) => c.dispose().catch(() => { })));
|
|
756
809
|
server.close();
|
|
757
810
|
process.exit(0);
|
|
@@ -880,6 +933,7 @@ async function createProgressManager(agentDir, broadcastAll) {
|
|
|
880
933
|
broadcastAll(buildSnapshot(updated), originId);
|
|
881
934
|
}
|
|
882
935
|
catch (err) {
|
|
936
|
+
captureSidecarError(err, "app-sidecar.progress.award");
|
|
883
937
|
log("DEBUG", "app-sidecar", "progress award failed", {
|
|
884
938
|
message: err instanceof Error ? err.message : String(err),
|
|
885
939
|
});
|
|
@@ -888,6 +942,7 @@ async function createProgressManager(agentDir, broadcastAll) {
|
|
|
888
942
|
// Watch ~/.ezcoder (dir watch survives the atomic tmp+rename) for progress.json
|
|
889
943
|
// writes from other daemon processes; debounce, reload read-only, dedupe by nonce.
|
|
890
944
|
let watchDebounce = null;
|
|
945
|
+
let progressWatcher = null;
|
|
891
946
|
try {
|
|
892
947
|
const watcher = fsWatch(agentDir, (_event, filename) => {
|
|
893
948
|
if (filename !== "progress.json")
|
|
@@ -909,13 +964,30 @@ async function createProgressManager(agentDir, broadcastAll) {
|
|
|
909
964
|
}, 150);
|
|
910
965
|
});
|
|
911
966
|
watcher.unref();
|
|
967
|
+
progressWatcher = watcher;
|
|
912
968
|
}
|
|
913
969
|
catch (err) {
|
|
970
|
+
captureSidecarError(err, "app-sidecar.progress.watch");
|
|
914
971
|
log("DEBUG", "app-sidecar", "progress watch unavailable", {
|
|
915
972
|
message: err instanceof Error ? err.message : String(err),
|
|
916
973
|
});
|
|
917
974
|
}
|
|
918
|
-
|
|
975
|
+
// Dispose closes the fs.watch handle (baseline #8: it was previously never
|
|
976
|
+
// closed — a per-daemon leak) and clears any pending debounce timer.
|
|
977
|
+
function dispose() {
|
|
978
|
+
if (watchDebounce) {
|
|
979
|
+
clearTimeout(watchDebounce);
|
|
980
|
+
watchDebounce = null;
|
|
981
|
+
}
|
|
982
|
+
try {
|
|
983
|
+
progressWatcher?.close();
|
|
984
|
+
}
|
|
985
|
+
catch {
|
|
986
|
+
// Already closed / never opened — nothing to do.
|
|
987
|
+
}
|
|
988
|
+
progressWatcher = null;
|
|
989
|
+
}
|
|
990
|
+
return { snapshot, awardRun, dispose };
|
|
919
991
|
}
|
|
920
992
|
/**
|
|
921
993
|
* Build one in-process agent session: its AgentSession, SSE client set, event
|
|
@@ -934,6 +1006,10 @@ async function createSession(deps, opts) {
|
|
|
934
1006
|
// parsing); the daemon owns the real listen host.
|
|
935
1007
|
const host = "127.0.0.1";
|
|
936
1008
|
const saved = loadSavedSettings(paths.settingsFile);
|
|
1009
|
+
// Native login/logout and other live sessions share auth.json. Refresh the
|
|
1010
|
+
// daemon-level snapshot before choosing this session's provider so a project
|
|
1011
|
+
// never boots against credentials that were just replaced or disconnected.
|
|
1012
|
+
await auth.load();
|
|
937
1013
|
// Per-project model/thinking prefs win over the shared global settings.json:
|
|
938
1014
|
// each window (one project cwd) restores its own selection instead of every
|
|
939
1015
|
// window reading the same single global slot that the last writer clobbered
|
|
@@ -952,9 +1028,14 @@ async function createSession(deps, opts) {
|
|
|
952
1028
|
});
|
|
953
1029
|
}
|
|
954
1030
|
// Per-project thinking prefs win over the global settings.json fallback.
|
|
1031
|
+
// With no saved level, the default follows the active credential's endpoint:
|
|
1032
|
+
// Kimi K3 on the OAuth coding endpoint starts at its declared default (high),
|
|
1033
|
+
// matching the official kimi-code CLI's plan-usage profile.
|
|
955
1034
|
const thinkEnabled = projectPrefs?.thinkingEnabled ?? saved.thinkingEnabled;
|
|
956
1035
|
const thinkingLevel = thinkEnabled
|
|
957
|
-
? (projectPrefs?.thinkingLevel ??
|
|
1036
|
+
? (projectPrefs?.thinkingLevel ??
|
|
1037
|
+
saved.thinkingLevel ??
|
|
1038
|
+
getDefaultThinkingLevel(model, { baseUrl: auth.getStoredBaseUrl(provider) }))
|
|
958
1039
|
: undefined;
|
|
959
1040
|
// ── SSE fan-out (declared before the session so plan callbacks can use it) ─
|
|
960
1041
|
const clients = new Set();
|
|
@@ -1001,6 +1082,11 @@ async function createSession(deps, opts) {
|
|
|
1001
1082
|
const f = formatError(err);
|
|
1002
1083
|
const message = f.message ? desktopGuidance(f.message) : undefined;
|
|
1003
1084
|
const guidance = desktopGuidance(f.guidance);
|
|
1085
|
+
captureSidecarError(err, `app-sidecar.${logLabel.replaceAll(" ", "-")}`, {
|
|
1086
|
+
scope: type,
|
|
1087
|
+
...(f.provider ? { provider: f.provider } : {}),
|
|
1088
|
+
...(f.statusCode != null ? { status: String(f.statusCode) } : {}),
|
|
1089
|
+
});
|
|
1004
1090
|
log("ERROR", "app-sidecar", logLabel, {
|
|
1005
1091
|
headline: f.headline,
|
|
1006
1092
|
source: f.source,
|
|
@@ -1058,6 +1144,7 @@ async function createSession(deps, opts) {
|
|
|
1058
1144
|
chatAgent = nextAgent;
|
|
1059
1145
|
broadcast("chat_agent_change", { chatAgent: nextAgent });
|
|
1060
1146
|
await session.persistAppMarker("agent_handoff", { chatAgent: nextAgent }).catch((error) => {
|
|
1147
|
+
captureSidecarError(error, "app-sidecar.chat-agent.persist-handoff");
|
|
1061
1148
|
log("WARN", "app-sidecar", "agent handoff marker persist failed", {
|
|
1062
1149
|
message: error instanceof Error ? error.message : String(error),
|
|
1063
1150
|
});
|
|
@@ -1184,6 +1271,7 @@ async function createSession(deps, opts) {
|
|
|
1184
1271
|
.catch(() => false)
|
|
1185
1272
|
.then(() => syncApprovedPlanProgress(generation))
|
|
1186
1273
|
.catch((error) => {
|
|
1274
|
+
captureSidecarError(error, "app-sidecar.plan.refresh-progress");
|
|
1187
1275
|
log("WARN", "app-sidecar", "plan progress refresh failed", {
|
|
1188
1276
|
message: error instanceof Error ? error.message : String(error),
|
|
1189
1277
|
});
|
|
@@ -1238,6 +1326,11 @@ async function createSession(deps, opts) {
|
|
|
1238
1326
|
session.eventBus.on("tool_call_end", (d) => {
|
|
1239
1327
|
const name = toolCallNames.get(d.toolCallId) ?? "unknown";
|
|
1240
1328
|
toolCallNames.delete(d.toolCallId);
|
|
1329
|
+
if (d.isError && shouldCaptureToolFailure(name, d.result)) {
|
|
1330
|
+
// Expected model-correctable validation failures stay in the local log and
|
|
1331
|
+
// conversation. Unexpected failures are reported without private result data.
|
|
1332
|
+
captureSidecarError(new Error(`Tool ${name} failed`), `tool.${name}`, { tool: name });
|
|
1333
|
+
}
|
|
1241
1334
|
log(d.isError ? "ERROR" : "INFO", "tool", `Tool call ended: ${name}`, {
|
|
1242
1335
|
id: d.toolCallId,
|
|
1243
1336
|
durationMs: String(d.durationMs),
|
|
@@ -1264,6 +1357,9 @@ async function createSession(deps, opts) {
|
|
|
1264
1357
|
session.eventBus.on("server_tool_call", (d) => broadcast("server_tool_call", d));
|
|
1265
1358
|
session.eventBus.on("turn_end", (d) => broadcast("turn_end", d));
|
|
1266
1359
|
session.eventBus.on("agent_done", (d) => broadcast("agent_done", d));
|
|
1360
|
+
// Non-clean stop (max_tokens/refusal/provider error) — info-style frame so
|
|
1361
|
+
// the webview can warn instead of presenting truncated output as complete.
|
|
1362
|
+
session.eventBus.on("truncated", (d) => broadcast("truncated", d));
|
|
1267
1363
|
session.eventBus.on("error", (d) => {
|
|
1268
1364
|
broadcastError("error", "agent error", d.error);
|
|
1269
1365
|
});
|
|
@@ -1584,6 +1680,7 @@ async function createSession(deps, opts) {
|
|
|
1584
1680
|
catch (error) {
|
|
1585
1681
|
// Keep tracking when prompt cleanup fails; hiding the widget here
|
|
1586
1682
|
// would claim completion while the approved-plan contract remained.
|
|
1683
|
+
captureSidecarError(error, "app-sidecar.plan.cleanup");
|
|
1587
1684
|
log("WARN", "app-sidecar", "completed plan cleanup failed", {
|
|
1588
1685
|
message: error instanceof Error ? error.message : String(error),
|
|
1589
1686
|
});
|
|
@@ -2039,13 +2136,8 @@ async function createSession(deps, opts) {
|
|
|
2039
2136
|
gitPoll.unref?.();
|
|
2040
2137
|
};
|
|
2041
2138
|
scheduleGitPoll(5000);
|
|
2042
|
-
function readBody(req) {
|
|
2043
|
-
return
|
|
2044
|
-
const chunks = [];
|
|
2045
|
-
req.on("data", (c) => chunks.push(c));
|
|
2046
|
-
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
|
|
2047
|
-
req.on("error", reject);
|
|
2048
|
-
});
|
|
2139
|
+
function readBody(req, res) {
|
|
2140
|
+
return readCappedBody(req, res);
|
|
2049
2141
|
}
|
|
2050
2142
|
function json(res, status, body) {
|
|
2051
2143
|
const payload = JSON.stringify(redactValue(body, { secrets: environmentSecrets(process.env) }));
|
|
@@ -2083,7 +2175,10 @@ async function createSession(deps, opts) {
|
|
|
2083
2175
|
void memoryStore
|
|
2084
2176
|
.snapshot()
|
|
2085
2177
|
.then((snapshot) => json(res, 200, snapshot))
|
|
2086
|
-
.catch((error) =>
|
|
2178
|
+
.catch((error) => {
|
|
2179
|
+
captureSidecarError(error, "app-sidecar.memory.snapshot");
|
|
2180
|
+
json(res, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
2181
|
+
});
|
|
2087
2182
|
return;
|
|
2088
2183
|
}
|
|
2089
2184
|
if (method === "DELETE" && url.startsWith("/memories/")) {
|
|
@@ -2096,14 +2191,20 @@ async function createSession(deps, opts) {
|
|
|
2096
2191
|
.forget(id)
|
|
2097
2192
|
.then(() => memoryStore.snapshot())
|
|
2098
2193
|
.then((snapshot) => json(res, 200, snapshot))
|
|
2099
|
-
.catch((error) =>
|
|
2194
|
+
.catch((error) => {
|
|
2195
|
+
captureSidecarError(error, "app-sidecar.memory.forget");
|
|
2196
|
+
json(res, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
2197
|
+
});
|
|
2100
2198
|
return;
|
|
2101
2199
|
}
|
|
2102
2200
|
if (method === "GET" && url === "/jiwa") {
|
|
2103
2201
|
void jiwaStore
|
|
2104
2202
|
.snapshot()
|
|
2105
2203
|
.then((snapshot) => json(res, 200, snapshot))
|
|
2106
|
-
.catch((error) =>
|
|
2204
|
+
.catch((error) => {
|
|
2205
|
+
captureSidecarError(error, "app-sidecar.jiwa.snapshot");
|
|
2206
|
+
json(res, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
2207
|
+
});
|
|
2107
2208
|
return;
|
|
2108
2209
|
}
|
|
2109
2210
|
if (method === "DELETE" && url.startsWith("/jiwa/")) {
|
|
@@ -2116,7 +2217,10 @@ async function createSession(deps, opts) {
|
|
|
2116
2217
|
.forget(id)
|
|
2117
2218
|
.then(() => jiwaStore.snapshot())
|
|
2118
2219
|
.then((snapshot) => json(res, 200, snapshot))
|
|
2119
|
-
.catch((error) =>
|
|
2220
|
+
.catch((error) => {
|
|
2221
|
+
captureSidecarError(error, "app-sidecar.jiwa.forget");
|
|
2222
|
+
json(res, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
2223
|
+
});
|
|
2120
2224
|
return;
|
|
2121
2225
|
}
|
|
2122
2226
|
if (method === "GET" && (url === "/events" || url.startsWith("/events?"))) {
|
|
@@ -2174,7 +2278,9 @@ async function createSession(deps, opts) {
|
|
|
2174
2278
|
return;
|
|
2175
2279
|
}
|
|
2176
2280
|
if (method === "POST" && url === "/settings") {
|
|
2177
|
-
void readBody(req).then(async (raw) => {
|
|
2281
|
+
void readBody(req, res).then(async (raw) => {
|
|
2282
|
+
if (raw === null)
|
|
2283
|
+
return;
|
|
2178
2284
|
let projectsRoot;
|
|
2179
2285
|
try {
|
|
2180
2286
|
projectsRoot = JSON.parse(raw).projectsRoot ?? "";
|
|
@@ -2197,7 +2303,9 @@ async function createSession(deps, opts) {
|
|
|
2197
2303
|
return;
|
|
2198
2304
|
}
|
|
2199
2305
|
if (method === "POST" && url === "/create-project") {
|
|
2200
|
-
void readBody(req).then(async (raw) => {
|
|
2306
|
+
void readBody(req, res).then(async (raw) => {
|
|
2307
|
+
if (raw === null)
|
|
2308
|
+
return;
|
|
2201
2309
|
let name;
|
|
2202
2310
|
try {
|
|
2203
2311
|
name = JSON.parse(raw).name ?? "";
|
|
@@ -2229,6 +2337,7 @@ async function createSession(deps, opts) {
|
|
|
2229
2337
|
json(res, 200, { path: dir });
|
|
2230
2338
|
}
|
|
2231
2339
|
catch (err) {
|
|
2340
|
+
captureSidecarError(err, "app-sidecar.project.create");
|
|
2232
2341
|
json(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
2233
2342
|
}
|
|
2234
2343
|
});
|
|
@@ -2239,6 +2348,7 @@ async function createSession(deps, opts) {
|
|
|
2239
2348
|
void discoverProjects()
|
|
2240
2349
|
.then((projects) => json(res, 200, { projects }))
|
|
2241
2350
|
.catch((err) => {
|
|
2351
|
+
captureSidecarError(err, "app-sidecar.projects.discover");
|
|
2242
2352
|
log("ERROR", "app-sidecar", "discoverProjects failed", {
|
|
2243
2353
|
message: err instanceof Error ? err.message : String(err),
|
|
2244
2354
|
});
|
|
@@ -2253,37 +2363,14 @@ async function createSession(deps, opts) {
|
|
|
2253
2363
|
return;
|
|
2254
2364
|
}
|
|
2255
2365
|
const requestedAgent = new URL(url, `http://${host}`).searchParams.get("chatAgent");
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
return agentSessions.map((item) => ({ ...item, chatAgent: agentId }));
|
|
2260
|
-
}))
|
|
2261
|
-
.then(async (groups) => {
|
|
2262
|
-
const dated = await Promise.all(groups.flat().map(async (item) => ({
|
|
2263
|
-
item,
|
|
2264
|
-
mtime: await fs
|
|
2265
|
-
.stat(item.path)
|
|
2266
|
-
.then((stat) => stat.mtimeMs)
|
|
2267
|
-
.catch(() => 0),
|
|
2268
|
-
})));
|
|
2269
|
-
const recent = dated
|
|
2270
|
-
.sort((left, right) => right.mtime - left.mtime)
|
|
2271
|
-
.slice(0, 5)
|
|
2272
|
-
.map(({ item }) => item);
|
|
2273
|
-
json(res, 200, { sessions: recent });
|
|
2274
|
-
})
|
|
2275
|
-
.catch(() => json(res, 200, { sessions: [] }));
|
|
2276
|
-
return;
|
|
2277
|
-
}
|
|
2278
|
-
// The picker may be served by a sidecar currently running in Chat mode.
|
|
2279
|
-
// An omitted chatAgent always means coding history; chat callers identify
|
|
2280
|
-
// their namespace explicitly (one agent or "all").
|
|
2281
|
-
const sessionsDir = requestedAgent
|
|
2282
|
-
? sessionsDirForChatAgent(paths.sessionsDir, requestedAgent)
|
|
2283
|
-
: paths.sessionsDir;
|
|
2284
|
-
void listRecentSessions(target, 5, sessionsDir)
|
|
2366
|
+
// An omitted chatAgent means coding history; chat callers identify one
|
|
2367
|
+
// agent or request the combined, recency-sorted "all" listing.
|
|
2368
|
+
void listSidecarSessions(target, requestedAgent, paths.sessionsDir)
|
|
2285
2369
|
.then((sessions) => json(res, 200, { sessions }))
|
|
2286
|
-
.catch(() =>
|
|
2370
|
+
.catch((error) => {
|
|
2371
|
+
captureSidecarError(error, "app-sidecar.sessions.list");
|
|
2372
|
+
json(res, 200, { sessions: [] });
|
|
2373
|
+
});
|
|
2287
2374
|
return;
|
|
2288
2375
|
}
|
|
2289
2376
|
if (method === "GET" && url.startsWith("/files")) {
|
|
@@ -2291,6 +2378,7 @@ async function createSession(deps, opts) {
|
|
|
2291
2378
|
void searchProjectFiles(cwd, q)
|
|
2292
2379
|
.then((files) => json(res, 200, { files }))
|
|
2293
2380
|
.catch((err) => {
|
|
2381
|
+
captureSidecarError(err, "app-sidecar.files.search");
|
|
2294
2382
|
log("ERROR", "app-sidecar", "searchProjectFiles failed", {
|
|
2295
2383
|
message: err instanceof Error ? err.message : String(err),
|
|
2296
2384
|
});
|
|
@@ -2631,7 +2719,9 @@ async function createSession(deps, opts) {
|
|
|
2631
2719
|
return;
|
|
2632
2720
|
}
|
|
2633
2721
|
if (method === "POST" && url === "/prompt") {
|
|
2634
|
-
void readBody(req).then(async (raw) => {
|
|
2722
|
+
void readBody(req, res).then(async (raw) => {
|
|
2723
|
+
if (raw === null)
|
|
2724
|
+
return;
|
|
2635
2725
|
let text;
|
|
2636
2726
|
let attachments;
|
|
2637
2727
|
let meta;
|
|
@@ -2758,7 +2848,9 @@ async function createSession(deps, opts) {
|
|
|
2758
2848
|
json(res, 404, { error: "Nolan is not available in EZ Chat." });
|
|
2759
2849
|
return;
|
|
2760
2850
|
}
|
|
2761
|
-
void readBody(req).then(async (raw) => {
|
|
2851
|
+
void readBody(req, res).then(async (raw) => {
|
|
2852
|
+
if (raw === null)
|
|
2853
|
+
return;
|
|
2762
2854
|
let text;
|
|
2763
2855
|
try {
|
|
2764
2856
|
text = JSON.parse(raw).text ?? "";
|
|
@@ -2817,7 +2909,9 @@ async function createSession(deps, opts) {
|
|
|
2817
2909
|
json(res, 404, { error: "Autopilot is not available in EZ Chat." });
|
|
2818
2910
|
return;
|
|
2819
2911
|
}
|
|
2820
|
-
void readBody(req).then(async (raw) => {
|
|
2912
|
+
void readBody(req, res).then(async (raw) => {
|
|
2913
|
+
if (raw === null)
|
|
2914
|
+
return;
|
|
2821
2915
|
let enabled;
|
|
2822
2916
|
try {
|
|
2823
2917
|
enabled = Boolean(JSON.parse(raw).enabled);
|
|
@@ -2838,7 +2932,9 @@ async function createSession(deps, opts) {
|
|
|
2838
2932
|
return;
|
|
2839
2933
|
}
|
|
2840
2934
|
if (method === "POST" && url === "/enhance") {
|
|
2841
|
-
void readBody(req).then(async (raw) => {
|
|
2935
|
+
void readBody(req, res).then(async (raw) => {
|
|
2936
|
+
if (raw === null)
|
|
2937
|
+
return;
|
|
2842
2938
|
let text;
|
|
2843
2939
|
try {
|
|
2844
2940
|
text = JSON.parse(raw).text ?? "";
|
|
@@ -2859,6 +2955,7 @@ async function createSession(deps, opts) {
|
|
|
2859
2955
|
}
|
|
2860
2956
|
catch (err) {
|
|
2861
2957
|
const message = err instanceof Error ? err.message : String(err);
|
|
2958
|
+
captureSidecarError(err, "app-sidecar.prompt-enhancer");
|
|
2862
2959
|
log("ERROR", "app-sidecar", "enhance failed", { message });
|
|
2863
2960
|
json(res, 500, { error: message });
|
|
2864
2961
|
}
|
|
@@ -2886,7 +2983,9 @@ async function createSession(deps, opts) {
|
|
|
2886
2983
|
return;
|
|
2887
2984
|
}
|
|
2888
2985
|
if (method === "POST" && url === "/radio/volume") {
|
|
2889
|
-
void readBody(req).then((raw) => {
|
|
2986
|
+
void readBody(req, res).then((raw) => {
|
|
2987
|
+
if (raw === null)
|
|
2988
|
+
return;
|
|
2890
2989
|
let volume;
|
|
2891
2990
|
try {
|
|
2892
2991
|
volume = Number(JSON.parse(raw).volume);
|
|
@@ -2909,7 +3008,9 @@ async function createSession(deps, opts) {
|
|
|
2909
3008
|
return;
|
|
2910
3009
|
}
|
|
2911
3010
|
if (method === "POST" && url === "/radio") {
|
|
2912
|
-
void readBody(req).then((raw) => {
|
|
3011
|
+
void readBody(req, res).then((raw) => {
|
|
3012
|
+
if (raw === null)
|
|
3013
|
+
return;
|
|
2913
3014
|
let station;
|
|
2914
3015
|
try {
|
|
2915
3016
|
station = JSON.parse(raw).station ?? "";
|
|
@@ -2933,7 +3034,9 @@ async function createSession(deps, opts) {
|
|
|
2933
3034
|
return;
|
|
2934
3035
|
}
|
|
2935
3036
|
if (method === "POST" && url === "/tasks/run") {
|
|
2936
|
-
void readBody(req).then((raw) => {
|
|
3037
|
+
void readBody(req, res).then((raw) => {
|
|
3038
|
+
if (raw === null)
|
|
3039
|
+
return;
|
|
2937
3040
|
let id;
|
|
2938
3041
|
let all;
|
|
2939
3042
|
try {
|
|
@@ -2955,7 +3058,9 @@ async function createSession(deps, opts) {
|
|
|
2955
3058
|
return;
|
|
2956
3059
|
}
|
|
2957
3060
|
if (method === "POST" && url === "/tasks/add") {
|
|
2958
|
-
void readBody(req).then((raw) => {
|
|
3061
|
+
void readBody(req, res).then((raw) => {
|
|
3062
|
+
if (raw === null)
|
|
3063
|
+
return;
|
|
2959
3064
|
let title;
|
|
2960
3065
|
let prompt;
|
|
2961
3066
|
try {
|
|
@@ -2987,7 +3092,9 @@ async function createSession(deps, opts) {
|
|
|
2987
3092
|
return;
|
|
2988
3093
|
}
|
|
2989
3094
|
if (method === "POST" && url === "/tasks/update") {
|
|
2990
|
-
void readBody(req).then((raw) => {
|
|
3095
|
+
void readBody(req, res).then((raw) => {
|
|
3096
|
+
if (raw === null)
|
|
3097
|
+
return;
|
|
2991
3098
|
let id;
|
|
2992
3099
|
let status;
|
|
2993
3100
|
let title;
|
|
@@ -3032,7 +3139,9 @@ async function createSession(deps, opts) {
|
|
|
3032
3139
|
return;
|
|
3033
3140
|
}
|
|
3034
3141
|
if (method === "POST" && url === "/tasks/delete") {
|
|
3035
|
-
void readBody(req).then((raw) => {
|
|
3142
|
+
void readBody(req, res).then((raw) => {
|
|
3143
|
+
if (raw === null)
|
|
3144
|
+
return;
|
|
3036
3145
|
let id;
|
|
3037
3146
|
try {
|
|
3038
3147
|
id = JSON.parse(raw).id ?? "";
|
|
@@ -3075,7 +3184,9 @@ async function createSession(deps, opts) {
|
|
|
3075
3184
|
return;
|
|
3076
3185
|
}
|
|
3077
3186
|
if (method === "POST" && url === "/model") {
|
|
3078
|
-
void readBody(req).then(async (raw) => {
|
|
3187
|
+
void readBody(req, res).then(async (raw) => {
|
|
3188
|
+
if (raw === null)
|
|
3189
|
+
return;
|
|
3079
3190
|
let modelId;
|
|
3080
3191
|
try {
|
|
3081
3192
|
modelId = JSON.parse(raw).model ?? "";
|
|
@@ -3147,7 +3258,9 @@ async function createSession(deps, opts) {
|
|
|
3147
3258
|
// to BOTH Nolan sessions (chat + autopilot reviewer); a switch landing while
|
|
3148
3259
|
// either is mid-run defers via the pending-model mechanics.
|
|
3149
3260
|
if (method === "POST" && url === "/ken/model") {
|
|
3150
|
-
void readBody(req).then(async (raw) => {
|
|
3261
|
+
void readBody(req, res).then(async (raw) => {
|
|
3262
|
+
if (raw === null)
|
|
3263
|
+
return;
|
|
3151
3264
|
let modelId;
|
|
3152
3265
|
try {
|
|
3153
3266
|
const parsed = JSON.parse(raw).model;
|
|
@@ -3191,7 +3304,9 @@ async function createSession(deps, opts) {
|
|
|
3191
3304
|
return;
|
|
3192
3305
|
}
|
|
3193
3306
|
if (method === "POST" && url === "/kill") {
|
|
3194
|
-
void readBody(req).then(async (raw) => {
|
|
3307
|
+
void readBody(req, res).then(async (raw) => {
|
|
3308
|
+
if (raw === null)
|
|
3309
|
+
return;
|
|
3195
3310
|
let id;
|
|
3196
3311
|
try {
|
|
3197
3312
|
id = JSON.parse(raw).id ?? "";
|
|
@@ -3269,6 +3384,7 @@ async function createSession(deps, opts) {
|
|
|
3269
3384
|
drained,
|
|
3270
3385
|
});
|
|
3271
3386
|
})().catch((error) => {
|
|
3387
|
+
captureSidecarError(error, "app-sidecar.run.cancel");
|
|
3272
3388
|
broadcast("cancel_failed", { error: "cancel_failed", runState: runLifecycle.state });
|
|
3273
3389
|
json(res, 500, {
|
|
3274
3390
|
error: "cancel_failed",
|
|
@@ -3296,6 +3412,7 @@ async function createSession(deps, opts) {
|
|
|
3296
3412
|
json(res, 200, { ok: true });
|
|
3297
3413
|
})
|
|
3298
3414
|
.catch((err) => {
|
|
3415
|
+
captureSidecarError(err, "app-sidecar.session.new");
|
|
3299
3416
|
json(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
3300
3417
|
});
|
|
3301
3418
|
return;
|
|
@@ -3310,7 +3427,9 @@ async function createSession(deps, opts) {
|
|
|
3310
3427
|
// 3. session_reset tells the webview to clear its transcript; it then runs
|
|
3311
3428
|
// the "implement it now" prompt in the clean session.
|
|
3312
3429
|
if (method === "POST" && url === "/plan/accept") {
|
|
3313
|
-
void readBody(req).then(async (raw) => {
|
|
3430
|
+
void readBody(req, res).then(async (raw) => {
|
|
3431
|
+
if (raw === null)
|
|
3432
|
+
return;
|
|
3314
3433
|
let planPath;
|
|
3315
3434
|
try {
|
|
3316
3435
|
planPath = JSON.parse(raw).planPath || undefined;
|
|
@@ -3350,6 +3469,7 @@ async function createSession(deps, opts) {
|
|
|
3350
3469
|
json(res, 200, { ok: true, planTotal });
|
|
3351
3470
|
}
|
|
3352
3471
|
catch (err) {
|
|
3472
|
+
captureSidecarError(err, "app-sidecar.plan.accept");
|
|
3353
3473
|
json(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
3354
3474
|
}
|
|
3355
3475
|
});
|
|
@@ -3361,7 +3481,9 @@ async function createSession(deps, opts) {
|
|
|
3361
3481
|
return;
|
|
3362
3482
|
}
|
|
3363
3483
|
if (method === "POST" && url === "/auth/apikey") {
|
|
3364
|
-
void readBody(req).then(async (raw) => {
|
|
3484
|
+
void readBody(req, res).then(async (raw) => {
|
|
3485
|
+
if (raw === null)
|
|
3486
|
+
return;
|
|
3365
3487
|
let provider = "";
|
|
3366
3488
|
let key;
|
|
3367
3489
|
let variant;
|
|
@@ -3404,7 +3526,9 @@ async function createSession(deps, opts) {
|
|
|
3404
3526
|
return;
|
|
3405
3527
|
}
|
|
3406
3528
|
if (method === "POST" && url === "/auth/oauth/start") {
|
|
3407
|
-
void readBody(req).then((raw) => {
|
|
3529
|
+
void readBody(req, res).then((raw) => {
|
|
3530
|
+
if (raw === null)
|
|
3531
|
+
return;
|
|
3408
3532
|
let provider = "";
|
|
3409
3533
|
try {
|
|
3410
3534
|
provider = JSON.parse(raw).provider ?? "";
|
|
@@ -3446,6 +3570,7 @@ async function createSession(deps, opts) {
|
|
|
3446
3570
|
broadcast("auth_done", { provider });
|
|
3447
3571
|
}
|
|
3448
3572
|
catch (err) {
|
|
3573
|
+
captureSidecarError(err, "app-sidecar.auth.oauth", { provider });
|
|
3449
3574
|
broadcast("auth_error", {
|
|
3450
3575
|
provider,
|
|
3451
3576
|
message: err instanceof Error ? err.message : String(err),
|
|
@@ -3460,7 +3585,9 @@ async function createSession(deps, opts) {
|
|
|
3460
3585
|
return;
|
|
3461
3586
|
}
|
|
3462
3587
|
if (method === "POST" && url === "/auth/oauth/code") {
|
|
3463
|
-
void readBody(req).then((raw) => {
|
|
3588
|
+
void readBody(req, res).then((raw) => {
|
|
3589
|
+
if (raw === null)
|
|
3590
|
+
return;
|
|
3464
3591
|
let code;
|
|
3465
3592
|
try {
|
|
3466
3593
|
code = JSON.parse(raw).code ?? "";
|
|
@@ -3480,7 +3607,9 @@ async function createSession(deps, opts) {
|
|
|
3480
3607
|
return;
|
|
3481
3608
|
}
|
|
3482
3609
|
if (method === "POST" && url === "/auth/logout") {
|
|
3483
|
-
void readBody(req).then(async (raw) => {
|
|
3610
|
+
void readBody(req, res).then(async (raw) => {
|
|
3611
|
+
if (raw === null)
|
|
3612
|
+
return;
|
|
3484
3613
|
let provider;
|
|
3485
3614
|
try {
|
|
3486
3615
|
provider = JSON.parse(raw).provider ?? "";
|
|
@@ -3519,7 +3648,9 @@ async function createSession(deps, opts) {
|
|
|
3519
3648
|
return;
|
|
3520
3649
|
}
|
|
3521
3650
|
if (method === "POST" && url === "/telegram") {
|
|
3522
|
-
void readBody(req).then(async (raw) => {
|
|
3651
|
+
void readBody(req, res).then(async (raw) => {
|
|
3652
|
+
if (raw === null)
|
|
3653
|
+
return;
|
|
3523
3654
|
let botTokenInput;
|
|
3524
3655
|
let userIdInput;
|
|
3525
3656
|
try {
|
|
@@ -3587,6 +3718,7 @@ async function createSession(deps, opts) {
|
|
|
3587
3718
|
}
|
|
3588
3719
|
catch (err) {
|
|
3589
3720
|
serveController = null;
|
|
3721
|
+
captureSidecarError(err, "app-sidecar.serve.start", { provider: st.provider });
|
|
3590
3722
|
json(res, 400, { error: err instanceof Error ? err.message : String(err) });
|
|
3591
3723
|
}
|
|
3592
3724
|
})();
|
|
@@ -3613,6 +3745,7 @@ async function createSession(deps, opts) {
|
|
|
3613
3745
|
void buildMcpRows(targetCwd)
|
|
3614
3746
|
.then((servers) => json(res, 200, { servers }))
|
|
3615
3747
|
.catch((err) => {
|
|
3748
|
+
captureSidecarError(err, "app-sidecar.mcp.list");
|
|
3616
3749
|
log("ERROR", "app-sidecar", "buildMcpRows failed", {
|
|
3617
3750
|
message: err instanceof Error ? err.message : String(err),
|
|
3618
3751
|
});
|
|
@@ -3621,7 +3754,9 @@ async function createSession(deps, opts) {
|
|
|
3621
3754
|
return;
|
|
3622
3755
|
}
|
|
3623
3756
|
if (method === "POST" && url === "/mcp/add") {
|
|
3624
|
-
void readBody(req).then(async (raw) => {
|
|
3757
|
+
void readBody(req, res).then(async (raw) => {
|
|
3758
|
+
if (raw === null)
|
|
3759
|
+
return;
|
|
3625
3760
|
let line;
|
|
3626
3761
|
let scopeValue;
|
|
3627
3762
|
let bodyCwd;
|
|
@@ -3669,6 +3804,7 @@ async function createSession(deps, opts) {
|
|
|
3669
3804
|
});
|
|
3670
3805
|
}
|
|
3671
3806
|
catch (err) {
|
|
3807
|
+
captureSidecarError(err, "app-sidecar.mcp.add", { server: config.name });
|
|
3672
3808
|
json(res, 500, {
|
|
3673
3809
|
error: err instanceof Error ? err.message : String(err),
|
|
3674
3810
|
});
|
|
@@ -3677,7 +3813,9 @@ async function createSession(deps, opts) {
|
|
|
3677
3813
|
return;
|
|
3678
3814
|
}
|
|
3679
3815
|
if (method === "POST" && url === "/mcp/remove") {
|
|
3680
|
-
void readBody(req).then(async (raw) => {
|
|
3816
|
+
void readBody(req, res).then(async (raw) => {
|
|
3817
|
+
if (raw === null)
|
|
3818
|
+
return;
|
|
3681
3819
|
let name;
|
|
3682
3820
|
let scopeValue;
|
|
3683
3821
|
let bodyCwd;
|
|
@@ -3714,7 +3852,9 @@ async function createSession(deps, opts) {
|
|
|
3714
3852
|
// `mcp_auth_error`. Responds 202 immediately and runs the flow in the
|
|
3715
3853
|
// background (the browser round-trip can take a while).
|
|
3716
3854
|
if (method === "POST" && url === "/mcp/login") {
|
|
3717
|
-
void readBody(req).then(async (raw) => {
|
|
3855
|
+
void readBody(req, res).then(async (raw) => {
|
|
3856
|
+
if (raw === null)
|
|
3857
|
+
return;
|
|
3718
3858
|
let name;
|
|
3719
3859
|
let scopeValue;
|
|
3720
3860
|
let bodyCwd;
|
|
@@ -3758,6 +3898,7 @@ async function createSession(deps, opts) {
|
|
|
3758
3898
|
}
|
|
3759
3899
|
}
|
|
3760
3900
|
catch (err) {
|
|
3901
|
+
captureSidecarError(err, "app-sidecar.mcp.login", { server: name });
|
|
3761
3902
|
broadcast("mcp_auth_error", {
|
|
3762
3903
|
name,
|
|
3763
3904
|
message: err instanceof Error ? err.message : String(err),
|
|
@@ -3804,7 +3945,9 @@ async function createSession(deps, opts) {
|
|
|
3804
3945
|
dispose,
|
|
3805
3946
|
};
|
|
3806
3947
|
}
|
|
3807
|
-
main().catch((err) => {
|
|
3948
|
+
main().catch(async (err) => {
|
|
3949
|
+
captureSidecarError(err, "app-sidecar.main", { severity: "fatal" });
|
|
3950
|
+
await flushSidecarErrors();
|
|
3808
3951
|
const message = err instanceof Error ? err.message : String(err);
|
|
3809
3952
|
process.stderr.write(`GG_APP_FATAL ${message}\n`);
|
|
3810
3953
|
process.exit(1);
|