@prestyj/cli 5.32.0 → 5.32.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/assets/skills/task-list/SKILL.md +39 -0
- package/dist/app-sidecar/app-settings.js +11 -5
- package/dist/app-sidecar/app-settings.js.map +1 -1
- package/dist/app-sidecar/auth-routes.js +10 -3
- package/dist/app-sidecar/auth-routes.js.map +1 -1
- package/dist/app-sidecar/git-target.d.ts +15 -2
- package/dist/app-sidecar/git-target.js +24 -10
- package/dist/app-sidecar/git-target.js.map +1 -1
- package/dist/app-sidecar/goal-routes.js +8 -0
- package/dist/app-sidecar/goal-routes.js.map +1 -1
- package/dist/app-sidecar/history-routes.js +7 -0
- package/dist/app-sidecar/history-routes.js.map +1 -1
- package/dist/app-sidecar/history.d.ts +5 -0
- package/dist/app-sidecar/history.js.map +1 -1
- package/dist/app-sidecar/integration-routes.js +4 -0
- package/dist/app-sidecar/integration-routes.js.map +1 -1
- package/dist/app-sidecar/mcp-routes.js +1 -0
- package/dist/app-sidecar/mcp-routes.js.map +1 -1
- package/dist/app-sidecar/mcp-rows.d.ts +2 -0
- package/dist/app-sidecar/mcp-rows.js +4 -1
- package/dist/app-sidecar/mcp-rows.js.map +1 -1
- package/dist/app-sidecar/prompt-routes.js +67 -1
- package/dist/app-sidecar/prompt-routes.js.map +1 -1
- package/dist/app-sidecar/route-context.d.ts +2 -0
- package/dist/app-sidecar/run-control-routes.js +43 -3
- package/dist/app-sidecar/run-control-routes.js.map +1 -1
- package/dist/app-sidecar/session-routes.js +10 -0
- package/dist/app-sidecar/session-routes.js.map +1 -1
- package/dist/app-sidecar/task-routes.js +2 -0
- package/dist/app-sidecar/task-routes.js.map +1 -1
- package/dist/app-sidecar/trophy-manager.d.ts +15 -0
- package/dist/app-sidecar/trophy-manager.js +71 -0
- package/dist/app-sidecar/trophy-manager.js.map +1 -0
- package/dist/app-sidecar/workspace-routes.js +1 -0
- package/dist/app-sidecar/workspace-routes.js.map +1 -1
- package/dist/app-sidecar.js +157 -30
- package/dist/app-sidecar.js.map +1 -1
- package/dist/cli/auth.js +22 -1
- package/dist/cli/auth.js.map +1 -1
- package/dist/cli/mcp.js +6 -1
- package/dist/cli/mcp.js.map +1 -1
- package/dist/cli.js +13 -9
- package/dist/cli.js.map +1 -1
- package/dist/config.js +4 -2
- package/dist/config.js.map +1 -1
- package/dist/core/action-threats.d.ts +68 -0
- package/dist/core/action-threats.js +894 -0
- package/dist/core/action-threats.js.map +1 -0
- package/dist/core/agent-notifications.d.ts +18 -0
- package/dist/core/agent-notifications.js +35 -2
- package/dist/core/agent-notifications.js.map +1 -1
- package/dist/core/agent-session/verification-tracker.d.ts +21 -0
- package/dist/core/agent-session/verification-tracker.js +39 -1
- package/dist/core/agent-session/verification-tracker.js.map +1 -1
- package/dist/core/agent-session.d.ts +166 -2
- package/dist/core/agent-session.js +478 -22
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/blocked-packages-file.d.ts +33 -0
- package/dist/core/blocked-packages-file.js +274 -0
- package/dist/core/blocked-packages-file.js.map +1 -0
- package/dist/core/compaction/policy.d.ts +2 -2
- package/dist/core/compaction/policy.js +86 -43
- package/dist/core/compaction/policy.js.map +1 -1
- package/dist/core/compaction/token-estimator.d.ts +4 -1
- package/dist/core/compaction/token-estimator.js +5 -2
- package/dist/core/compaction/token-estimator.js.map +1 -1
- package/dist/core/event-bus.d.ts +10 -0
- package/dist/core/event-bus.js +2 -0
- package/dist/core/event-bus.js.map +1 -1
- package/dist/core/idle-wake.d.ts +62 -0
- package/dist/core/idle-wake.js +103 -0
- package/dist/core/idle-wake.js.map +1 -0
- package/dist/core/injection-detect.d.ts +2 -1
- package/dist/core/injection-detect.js +13 -4
- package/dist/core/injection-detect.js.map +1 -1
- package/dist/core/installed-threats.d.ts +23 -0
- package/dist/core/installed-threats.js +71 -0
- package/dist/core/installed-threats.js.map +1 -0
- package/dist/core/mcp/catalog-cache.d.ts +2 -0
- package/dist/core/mcp/catalog-cache.js +4 -0
- package/dist/core/mcp/catalog-cache.js.map +1 -1
- package/dist/core/mcp/client.d.ts +8 -0
- package/dist/core/mcp/client.js +37 -3
- package/dist/core/mcp/client.js.map +1 -1
- package/dist/core/mcp/deferred-catalog.d.ts +12 -1
- package/dist/core/mcp/deferred-catalog.js +37 -10
- package/dist/core/mcp/deferred-catalog.js.map +1 -1
- package/dist/core/mcp/index.d.ts +2 -0
- package/dist/core/mcp/index.js +1 -0
- package/dist/core/mcp/index.js.map +1 -1
- package/dist/core/mcp/tool-guard.d.ts +58 -0
- package/dist/core/mcp/tool-guard.js +215 -0
- package/dist/core/mcp/tool-guard.js.map +1 -0
- package/dist/core/network-guard.d.ts +0 -20
- package/dist/core/network-guard.js +6 -0
- package/dist/core/network-guard.js.map +1 -1
- package/dist/core/package-cooldown.d.ts +71 -0
- package/dist/core/package-cooldown.js +301 -0
- package/dist/core/package-cooldown.js.map +1 -0
- package/dist/core/package-threats.d.ts +77 -3
- package/dist/core/package-threats.js +1250 -78
- package/dist/core/package-threats.js.map +1 -1
- package/dist/core/process-manager.d.ts +54 -12
- package/dist/core/process-manager.js +233 -56
- package/dist/core/process-manager.js.map +1 -1
- package/dist/core/prompt-commands.js +1 -0
- package/dist/core/prompt-commands.js.map +1 -1
- package/dist/core/session-manager.d.ts +3 -1
- package/dist/core/session-manager.js +2 -1
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -0
- package/dist/core/settings-manager.js +4 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/shell-threats.d.ts +14 -1
- package/dist/core/shell-threats.js +73 -24
- package/dist/core/shell-threats.js.map +1 -1
- package/dist/core/skill-scan.d.ts +112 -0
- package/dist/core/skill-scan.js +781 -0
- package/dist/core/skill-scan.js.map +1 -0
- package/dist/core/skills.d.ts +33 -0
- package/dist/core/skills.js +87 -16
- package/dist/core/skills.js.map +1 -1
- package/dist/core/slash-commands.d.ts +5 -1
- package/dist/core/slash-commands.js +24 -9
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/steering.d.ts +7 -0
- package/dist/core/steering.js +13 -0
- package/dist/core/steering.js.map +1 -1
- package/dist/core/style-packs/index.js +1 -1
- package/dist/core/style-packs/index.js.map +1 -1
- package/dist/core/style-packs/packs.js +2 -2
- package/dist/core/subagent-manager.js +5 -4
- package/dist/core/subagent-manager.js.map +1 -1
- package/dist/core/threat-feed.d.ts +65 -0
- package/dist/core/threat-feed.js +329 -0
- package/dist/core/threat-feed.js.map +1 -0
- package/dist/core/trophies/catalog-more.d.ts +2 -0
- package/dist/core/trophies/catalog-more.js +857 -0
- package/dist/core/trophies/catalog-more.js.map +1 -0
- package/dist/core/trophies/catalog.d.ts +4 -0
- package/dist/core/trophies/catalog.js +649 -0
- package/dist/core/trophies/catalog.js.map +1 -0
- package/dist/core/trophies/engine.d.ts +70 -0
- package/dist/core/trophies/engine.js +351 -0
- package/dist/core/trophies/engine.js.map +1 -0
- package/dist/core/trophies/store.d.ts +17 -0
- package/dist/core/trophies/store.js +113 -0
- package/dist/core/trophies/store.js.map +1 -0
- package/dist/core/trophies/types.d.ts +119 -0
- package/dist/core/trophies/types.js +78 -0
- package/dist/core/trophies/types.js.map +1 -0
- package/dist/core/verification-evidence.d.ts +10 -6
- package/dist/core/verification-evidence.js +190 -11
- package/dist/core/verification-evidence.js.map +1 -1
- package/dist/goals-agent/goals-agent.js +3 -2
- package/dist/goals-agent/goals-agent.js.map +1 -1
- package/dist/interactive.js +16 -5
- package/dist/interactive.js.map +1 -1
- package/dist/modes/acp-mode.js +8 -1
- package/dist/modes/acp-mode.js.map +1 -1
- package/dist/modes/agent-home-mode.js +13 -1
- package/dist/modes/agent-home-mode.js.map +1 -1
- package/dist/modes/rpc-mode.d.ts +2 -1
- package/dist/modes/rpc-mode.js +15 -3
- package/dist/modes/rpc-mode.js.map +1 -1
- package/dist/modes/serve-mode.js +13 -1
- package/dist/modes/serve-mode.js.map +1 -1
- package/dist/system-prompt.d.ts +1 -0
- package/dist/system-prompt.js +12 -8
- package/dist/system-prompt.js.map +1 -1
- package/dist/test-support/slow-git.d.ts +1 -0
- package/dist/test-support/slow-git.js +21 -0
- package/dist/test-support/slow-git.js.map +1 -0
- package/dist/tools/background-watch-lint.d.ts +18 -0
- package/dist/tools/background-watch-lint.js +102 -0
- package/dist/tools/background-watch-lint.js.map +1 -0
- package/dist/tools/bash.d.ts +15 -4
- package/dist/tools/bash.js +72 -47
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/checklist.d.ts +3 -3
- package/dist/tools/compress.js +45 -0
- package/dist/tools/compress.js.map +1 -1
- package/dist/tools/edit.js +5 -0
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/generate-image.d.ts +29 -6
- package/dist/tools/generate-image.js +325 -151
- package/dist/tools/generate-image.js.map +1 -1
- package/dist/tools/grep.d.ts +14 -0
- package/dist/tools/grep.js +36 -2
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/index.d.ts +5 -1
- package/dist/tools/index.js +7 -4
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/skill.d.ts +2 -2
- package/dist/tools/skill.js +8 -1
- package/dist/tools/skill.js.map +1 -1
- package/dist/tools/subagent-shared.js +4 -1
- package/dist/tools/subagent-shared.js.map +1 -1
- package/dist/tools/task-output.js +11 -1
- package/dist/tools/task-output.js.map +1 -1
- package/dist/tools/write.js +8 -0
- package/dist/tools/write.js.map +1 -1
- package/dist/ui/components/ModelSelector.d.ts +10 -0
- package/dist/ui/components/ModelSelector.js +51 -5
- package/dist/ui/components/ModelSelector.js.map +1 -1
- package/dist/ui/components/ToolExecution.js +2 -2
- package/dist/ui/components/ToolExecution.js.map +1 -1
- package/dist/ui/mcp.d.ts +2 -0
- package/dist/ui/mcp.js.map +1 -1
- package/dist/ui/terminal-history.js +2 -1
- package/dist/ui/terminal-history.js.map +1 -1
- package/dist/ui/tool-line-summary.js +3 -1
- package/dist/ui/tool-line-summary.js.map +1 -1
- package/dist/utils/git.d.ts +12 -3
- package/dist/utils/git.js +21 -8
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/image.d.ts +14 -0
- package/dist/utils/image.js +23 -0
- package/dist/utils/image.js.map +1 -1
- package/package.json +4 -4
package/dist/app-sidecar.js
CHANGED
|
@@ -11,11 +11,12 @@
|
|
|
11
11
|
* The agent spine (@prestyj/ai → @prestyj/agent → @prestyj/core) and every tool are reused
|
|
12
12
|
* unchanged via AgentSession — this file is only a network seam.
|
|
13
13
|
*/
|
|
14
|
+
import { startThreatFeedSync } from "./core/threat-feed.js";
|
|
14
15
|
import http from "node:http";
|
|
15
16
|
import fs from "node:fs/promises";
|
|
16
17
|
import os from "node:os";
|
|
17
18
|
import path from "node:path";
|
|
18
|
-
import { randomUUID } from "node:crypto";
|
|
19
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
19
20
|
import { environmentSecrets, formatChatError, redactValue } from "@prestyj/ai";
|
|
20
21
|
import { createAppErrorPayload } from "./app-error.js";
|
|
21
22
|
import { runSubagentWorkerMode } from "./modes/subagent-worker-mode.js";
|
|
@@ -23,13 +24,14 @@ import { setStreamDiagnostic } from "@prestyj/agent";
|
|
|
23
24
|
import { AgentSession } from "./core/agent-session.js";
|
|
24
25
|
import { RunLifecycle } from "./core/run-lifecycle.js";
|
|
25
26
|
import { RunClaim } from "./core/run-claim.js";
|
|
27
|
+
import { IdleWakeController } from "./core/idle-wake.js";
|
|
26
28
|
import { IMPLEMENT_PLAN_PROMPT as SHARED_IMPLEMENT_PLAN_PROMPT, PLAN_SUBMITTED_FOR_REVIEW, shouldOfferPendingPlan, } from "./core/plan-handoff.js";
|
|
27
29
|
import { createChatAgent, parseChatAgentId, switchChatAgent, } from "./chat-agents/index.js";
|
|
28
30
|
import { createMotionAgentSession } from "./motion-agent/motion-agent.js";
|
|
29
31
|
import { buildJiwaTools, JiwaStore } from "./chat-agents/jiwa.js";
|
|
30
32
|
import { buildMemoryTools, MemoryStore } from "./chat-agents/memory.js";
|
|
31
33
|
import { buildNolanSystemPrompt, buildNolanAutopilotSystemPrompt } from "./core/nolan-prompt.js";
|
|
32
|
-
import { countAssistantMessages } from "./core/autopilot-gate.js";
|
|
34
|
+
import { countAssistantMessages, extractTurnToolCalls, } from "./core/autopilot-gate.js";
|
|
33
35
|
import { describeRunVerification, describeTurnVerification } from "./core/run-status.js";
|
|
34
36
|
import { validateNolanModelPref, effectiveNolanModel, } from "./core/nolan-model.js";
|
|
35
37
|
import { AuthStorage } from "./core/auth-storage.js";
|
|
@@ -39,7 +41,7 @@ import { listAllEndpoints, syncEndpointCredentials } from "./core/local-endpoint
|
|
|
39
41
|
import { AUTH_PROVIDERS, authPriorityNote, describeAuthMethods, } from "./core/auth-providers.js";
|
|
40
42
|
import { ensureAppDirs, loadSavedSettings } from "./config.js";
|
|
41
43
|
import { SettingsManager } from "./core/settings-manager.js";
|
|
42
|
-
import { getModel, getDefaultThinkingLevel, getContextWindow, clearRuntimeModels, registerRuntimeModels, } from "./core/model-registry.js";
|
|
44
|
+
import { getModel, withoutPaidTier, getDefaultThinkingLevel, getContextWindow, clearRuntimeModels, registerRuntimeModels, } from "./core/model-registry.js";
|
|
43
45
|
import { resolveStartOrFallback } from "./core/resolve-start.js";
|
|
44
46
|
import { getGitBranch, getGitDirtyFileCount, isGitRepo } from "./utils/git.js";
|
|
45
47
|
import { scoreProjectHealth } from "./core/project-health-score.js";
|
|
@@ -62,6 +64,8 @@ import { parseWorkspaceMode, } from "./app-sidecar/session-types.js";
|
|
|
62
64
|
import { ALL_PROVIDERS, loadProjectModelPrefs, loadNolanModelPref, loadAutopilot, } from "./app-sidecar/app-settings.js";
|
|
63
65
|
import { NOLAN_ALLOWED_TOOLS } from "./app-sidecar/nolan-context.js";
|
|
64
66
|
import { createProgressManager } from "./app-sidecar/progress-manager.js";
|
|
67
|
+
import { createTrophyManager } from "./app-sidecar/trophy-manager.js";
|
|
68
|
+
import { localTrophyDate } from "./core/trophies/engine.js";
|
|
65
69
|
import { handleSessionRoutes } from "./app-sidecar/session-routes.js";
|
|
66
70
|
import { handleGoalRoutes } from "./app-sidecar/goal-routes.js";
|
|
67
71
|
import { readGitState, resolveGitCwd } from "./app-sidecar/git-target.js";
|
|
@@ -78,6 +82,8 @@ import { handleMcpRoutes } from "./app-sidecar/mcp-routes.js";
|
|
|
78
82
|
import { createHfPullManager } from "./app-sidecar/hf-pull-manager.js";
|
|
79
83
|
import { keepAwake } from "./app-sidecar/keep-awake.js";
|
|
80
84
|
import { createAutopilotRunner } from "./app-sidecar/autopilot-runner.js";
|
|
85
|
+
/** Limit for re-reading git state in the background when git missed the usual 2 s. */
|
|
86
|
+
const SLOW_GIT_READ_TIMEOUT_MS = 15_000;
|
|
81
87
|
async function main() {
|
|
82
88
|
// Hidden persistent-worker dispatch must win before strict JSON/server parsing.
|
|
83
89
|
if (process.argv.includes("--subagent-worker")) {
|
|
@@ -161,6 +167,9 @@ async function main() {
|
|
|
161
167
|
// Sweep recoverable full tool outputs (~/.ezcoder/tool-output/) older than 48h.
|
|
162
168
|
// Fire-and-forget: cleanup must never delay or break startup.
|
|
163
169
|
void cleanupToolOutputs().catch(() => { });
|
|
170
|
+
// Daily malicious-package feed (OSV MAL-, npm + PyPI): deferred, background,
|
|
171
|
+
// fail-silent; at most one sync a day (core/threat-feed.ts).
|
|
172
|
+
startThreatFeedSync();
|
|
164
173
|
const auth = new AuthStorage(paths.authFile);
|
|
165
174
|
await auth.load();
|
|
166
175
|
// Every window's session lives here as an in-process object, keyed by the id
|
|
@@ -215,6 +224,20 @@ async function main() {
|
|
|
215
224
|
for (const ctx of sessions.values())
|
|
216
225
|
ctx.broadcast("progress", { ...snapshot, origin: ctx.id === originId });
|
|
217
226
|
});
|
|
227
|
+
// Trophies — daemon-level like progress. XP totals (commits, streak, level)
|
|
228
|
+
// come from the progress snapshot rather than being counted twice.
|
|
229
|
+
const trophies = await createTrophyManager(() => {
|
|
230
|
+
const p = progress.snapshot();
|
|
231
|
+
return {
|
|
232
|
+
prompts: p.totals.prompts,
|
|
233
|
+
commits: p.totals.commits,
|
|
234
|
+
linesShipped: p.totals.linesShipped,
|
|
235
|
+
level: p.level,
|
|
236
|
+
};
|
|
237
|
+
}, (snapshot, originId) => {
|
|
238
|
+
for (const ctx of sessions.values())
|
|
239
|
+
ctx.broadcast("trophies", { ...snapshot, origin: ctx.id === originId });
|
|
240
|
+
});
|
|
218
241
|
const { subscriptionUsage } = createUsageService(auth);
|
|
219
242
|
// git status / GitHub counts / CI pollers, one per repo however many windows
|
|
220
243
|
// have it open (each window used to run its own).
|
|
@@ -280,6 +303,7 @@ async function main() {
|
|
|
280
303
|
auth,
|
|
281
304
|
paths,
|
|
282
305
|
progress,
|
|
306
|
+
trophies,
|
|
283
307
|
memoryStore,
|
|
284
308
|
jiwaStore,
|
|
285
309
|
broadcastAll,
|
|
@@ -363,6 +387,11 @@ async function main() {
|
|
|
363
387
|
daemonJson(res, 200, progress.snapshot());
|
|
364
388
|
return;
|
|
365
389
|
}
|
|
390
|
+
// Trophies are app-wide too: earned anywhere, shown in every window.
|
|
391
|
+
if (method === "GET" && url === "/trophies") {
|
|
392
|
+
daemonJson(res, 200, trophies.snapshot());
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
366
395
|
// Subscription quota is account-wide, not project/session-specific. OAuth
|
|
367
396
|
// tokens stay in this daemon; only the active provider's normalized snapshot
|
|
368
397
|
// reaches the webview.
|
|
@@ -455,7 +484,7 @@ async function main() {
|
|
|
455
484
|
* logger, PATH, shared auth file, and radio live at the daemon level.
|
|
456
485
|
*/
|
|
457
486
|
async function createSession(deps, opts) {
|
|
458
|
-
const { auth, progress, memoryStore, jiwaStore, broadcastAll, oauthInFlightProviders, repoPolls, sessionIdsAt, busyAt, } = deps;
|
|
487
|
+
const { auth, progress, trophies, memoryStore, jiwaStore, broadcastAll, oauthInFlightProviders, repoPolls, sessionIdsAt, busyAt, } = deps;
|
|
459
488
|
const paths = deps.paths;
|
|
460
489
|
const mode = opts.mode;
|
|
461
490
|
let chatAgent = opts.chatAgent;
|
|
@@ -643,6 +672,7 @@ async function createSession(deps, opts) {
|
|
|
643
672
|
if (started) {
|
|
644
673
|
goal = { dir: current.dir, record: started };
|
|
645
674
|
broadcast("goal_state", { id: started.id, phase: goalPhase(started) });
|
|
675
|
+
void trophies.count("goalsStarted", opts.id);
|
|
646
676
|
}
|
|
647
677
|
};
|
|
648
678
|
const askUserTool = createAskUserTool(async (request) => {
|
|
@@ -887,7 +917,8 @@ async function createSession(deps, opts) {
|
|
|
887
917
|
// follows the active model. A Goal window reads git from its attached
|
|
888
918
|
// project folder, not the goal folder (see resolveGitCwd), and re-targets
|
|
889
919
|
// when the goal's folders change (`retargetGit`).
|
|
890
|
-
|
|
920
|
+
const initialTarget = await resolveGitCwd(cwd, goal?.record ?? null);
|
|
921
|
+
let gitCwd = initialTarget.dir;
|
|
891
922
|
const initialGit = await readGitState(gitCwd);
|
|
892
923
|
let gitBranch = initialGit.branch;
|
|
893
924
|
let gitIsRepo = initialGit.isRepo;
|
|
@@ -1114,13 +1145,19 @@ async function createSession(deps, opts) {
|
|
|
1114
1145
|
session.eventBus.on("model_change", (d) => broadcast("model_change", d));
|
|
1115
1146
|
session.eventBus.on("hook", (d) => broadcast("hook", d));
|
|
1116
1147
|
session.eventBus.on("diagnostics", (d) => broadcast("diagnostics", d));
|
|
1148
|
+
session.eventBus.on("security_notice", (d) => broadcast("security_notice", d));
|
|
1149
|
+
session.eventBus.on("security_block", (d) => broadcast("security_block", d));
|
|
1117
1150
|
// Fires BEFORE the candidate final answer streams. The webview holds assistant
|
|
1118
1151
|
// text back while armed, so an injected follow-up supersedes a draft that was never
|
|
1119
1152
|
// painted instead of deleting one the user already started reading.
|
|
1120
1153
|
session.eventBus.on("hook_armed", (d) => broadcast("hook_armed", d));
|
|
1121
1154
|
session.eventBus.on("subagent_state", (d) => broadcast("subagent_state", d));
|
|
1122
1155
|
session.eventBus.on("compaction_start", (d) => broadcast("compaction_start", d));
|
|
1123
|
-
session.eventBus.on("compaction_end", (d) =>
|
|
1156
|
+
session.eventBus.on("compaction_end", (d) => {
|
|
1157
|
+
broadcast("compaction_end", d);
|
|
1158
|
+
if (d.compacted)
|
|
1159
|
+
void trophies.count("compactions", opts.id);
|
|
1160
|
+
});
|
|
1124
1161
|
// Cold-prompt-cache notice: push the fresh TTL anchor + context size whenever
|
|
1125
1162
|
// a run or compaction settles. Expiry itself is time-based, so the webview
|
|
1126
1163
|
// also re-reads `cacheExpiry` from /state when the user returns or types.
|
|
@@ -1184,6 +1221,9 @@ async function createSession(deps, opts) {
|
|
|
1184
1221
|
// Bumped by /cancel — a run whose cancel generation changed mid-flight was
|
|
1185
1222
|
// canceled and earns no XP.
|
|
1186
1223
|
let cancelGeneration = 0;
|
|
1224
|
+
// Wakes an idle session for its own failed background work; set up once the
|
|
1225
|
+
// run machinery below exists. Null outside the coding agent.
|
|
1226
|
+
let idleWake = null;
|
|
1187
1227
|
// Autopilot (auto-review) toggle for THIS window's project. Loaded from
|
|
1188
1228
|
// ezcoder-app.json on boot; flipped via POST /autopilot. When on, POST /prompt runs
|
|
1189
1229
|
// runAutopilotCycle after the user's turn settles — Nolan auto-reviews the work
|
|
@@ -1314,20 +1354,25 @@ async function createSession(deps, opts) {
|
|
|
1314
1354
|
});
|
|
1315
1355
|
nolanModelOverride = null;
|
|
1316
1356
|
}
|
|
1317
|
-
/** The model Nolan uses next turn: the pin when set, else EZ Coder's.
|
|
1357
|
+
/** The model Nolan uses next turn: the pin when set, else EZ Coder's. Nolan never
|
|
1358
|
+
* follows GG onto a paid tier: he runs in the background, unwatched. */
|
|
1318
1359
|
function nolanCurrentModel() {
|
|
1319
1360
|
if (nolanModelOverride)
|
|
1320
1361
|
return nolanModelOverride;
|
|
1321
1362
|
const st = session.getState();
|
|
1322
|
-
return { provider: st.provider, model: st.model };
|
|
1363
|
+
return { provider: st.provider, model: withoutPaidTier(st.model) };
|
|
1323
1364
|
}
|
|
1324
1365
|
/** Footer payload: Nolan's effective model + whether it's a pin. Merged into
|
|
1325
1366
|
* /state, the SSE ready frame, and every nolan_model_change broadcast. */
|
|
1326
1367
|
function nolanStatePayload() {
|
|
1327
1368
|
const st = session.getState();
|
|
1328
|
-
return effectiveNolanModel(nolanModelOverride, {
|
|
1369
|
+
return effectiveNolanModel(nolanModelOverride, {
|
|
1370
|
+
provider: st.provider,
|
|
1371
|
+
model: withoutPaidTier(st.model),
|
|
1372
|
+
});
|
|
1329
1373
|
}
|
|
1330
|
-
async function syncNolanModel(provider,
|
|
1374
|
+
async function syncNolanModel(provider, requested) {
|
|
1375
|
+
const model = withoutPaidTier(requested);
|
|
1331
1376
|
if (nolanRunning) {
|
|
1332
1377
|
pendingNolanModel = { provider, model };
|
|
1333
1378
|
return;
|
|
@@ -1398,7 +1443,9 @@ async function createSession(deps, opts) {
|
|
|
1398
1443
|
let nolanAutoSession = null;
|
|
1399
1444
|
let nolanAutoAbort = new AbortController();
|
|
1400
1445
|
let pendingNolanAutoModel = null;
|
|
1401
|
-
async function syncNolanAutoModel(provider,
|
|
1446
|
+
async function syncNolanAutoModel(provider, requested) {
|
|
1447
|
+
// The autopilot reviewer runs every turn, unwatched: never on a paid tier.
|
|
1448
|
+
const model = withoutPaidTier(requested);
|
|
1402
1449
|
if (autopilotReviewing) {
|
|
1403
1450
|
pendingNolanAutoModel = { provider, model };
|
|
1404
1451
|
return;
|
|
@@ -1443,6 +1490,8 @@ async function createSession(deps, opts) {
|
|
|
1443
1490
|
}
|
|
1444
1491
|
function abortOwnedWork() {
|
|
1445
1492
|
cancelGeneration++;
|
|
1493
|
+
// Stop means "stop acting on your own" — that includes background alerts.
|
|
1494
|
+
idleWake?.noteStop();
|
|
1446
1495
|
abort.abort();
|
|
1447
1496
|
// An MCP tool call parked on user input is not cancelled by the signal —
|
|
1448
1497
|
// the promise lives in the bridge. Release it, or the aborted turn's tool
|
|
@@ -1487,6 +1536,7 @@ async function createSession(deps, opts) {
|
|
|
1487
1536
|
const runStartedAt = Date.now();
|
|
1488
1537
|
const cancelGenAtStart = cancelGeneration;
|
|
1489
1538
|
const assistantsBeforeRun = countAssistantMessages(session.getMessages());
|
|
1539
|
+
const messagesBeforeRun = session.getMessages().length;
|
|
1490
1540
|
let runSucceeded = false;
|
|
1491
1541
|
broadcast("run_start", {
|
|
1492
1542
|
text: label,
|
|
@@ -1521,7 +1571,25 @@ async function createSession(deps, opts) {
|
|
|
1521
1571
|
cancelGeneration === cancelGenAtStart &&
|
|
1522
1572
|
countAssistantMessages(session.getMessages()) > assistantsBeforeRun) {
|
|
1523
1573
|
// Fire-and-forget — XP must never delay or break run teardown.
|
|
1524
|
-
|
|
1574
|
+
// Trophies follow the award so they see this run's commits and level.
|
|
1575
|
+
const st = session.getState();
|
|
1576
|
+
const runFacts = {
|
|
1577
|
+
mode,
|
|
1578
|
+
chatAgent: mode === "chat" ? chatAgent : undefined,
|
|
1579
|
+
goal: goal !== null,
|
|
1580
|
+
provider: st.provider,
|
|
1581
|
+
model: st.model,
|
|
1582
|
+
tools: extractTurnToolCalls(session.getMessages(), messagesBeforeRun),
|
|
1583
|
+
durationMs: Date.now() - runStartedAt,
|
|
1584
|
+
hour: new Date().getHours(),
|
|
1585
|
+
day: new Date().getDay(),
|
|
1586
|
+
date: localTrophyDate(new Date()),
|
|
1587
|
+
// A hash, so the trophies file never holds project paths.
|
|
1588
|
+
project: createHash("sha256").update(path.resolve(cwd)).digest("hex").slice(0, 16),
|
|
1589
|
+
};
|
|
1590
|
+
void progress
|
|
1591
|
+
.awardRun(cwd, runStartedAt, opts.id)
|
|
1592
|
+
.then(() => trophies.recordRun(runFacts, opts.id));
|
|
1525
1593
|
}
|
|
1526
1594
|
// A run may have switched branches, changed files, or spawned/finished
|
|
1527
1595
|
// background tasks. Refresh the workspace extras once it settles.
|
|
@@ -1779,6 +1847,42 @@ async function createSession(deps, opts) {
|
|
|
1779
1847
|
taskRunAll = v;
|
|
1780
1848
|
},
|
|
1781
1849
|
});
|
|
1850
|
+
// ── Idle wake for background alerts ────────────────────────
|
|
1851
|
+
// A background task that fails, or a child agent that finishes, after the
|
|
1852
|
+
// agent ended its turn would otherwise wait for the user's next message.
|
|
1853
|
+
// The coding agent only: chat agents start no background work, Motion owns
|
|
1854
|
+
// its own render flow, and GG Goals drives its workers itself.
|
|
1855
|
+
if (mode !== "chat" && mode !== "motion" && !goal) {
|
|
1856
|
+
idleWake = new IdleWakeController({
|
|
1857
|
+
isBusy: () => running ||
|
|
1858
|
+
runClaim.active ||
|
|
1859
|
+
autopilotActive ||
|
|
1860
|
+
autopilotReviewing ||
|
|
1861
|
+
drainingStrandedQueue ||
|
|
1862
|
+
taskRunAll,
|
|
1863
|
+
isBlocked: () => pendingPlanPath !== null,
|
|
1864
|
+
hasPendingAlert: () => session.hasPendingBackgroundAlert(),
|
|
1865
|
+
humanPromptSeq: () => session.getHumanPromptSeq(),
|
|
1866
|
+
onSkip: (reason) => log("INFO", "app-sidecar", "background alert left for the next message", { reason }),
|
|
1867
|
+
start: async () => {
|
|
1868
|
+
// Same race guard as POST /prompt: claim synchronously or yield.
|
|
1869
|
+
if (!runClaim.claim())
|
|
1870
|
+
return;
|
|
1871
|
+
try {
|
|
1872
|
+
log("INFO", "app-sidecar", "starting a run for a background alert");
|
|
1873
|
+
await runAgent("Background task update", async () => {
|
|
1874
|
+
await session.continueFromNotifications();
|
|
1875
|
+
});
|
|
1876
|
+
await runStrandedQueue();
|
|
1877
|
+
}
|
|
1878
|
+
finally {
|
|
1879
|
+
runClaim.release();
|
|
1880
|
+
offerPendingPlan();
|
|
1881
|
+
}
|
|
1882
|
+
},
|
|
1883
|
+
});
|
|
1884
|
+
session.onBackgroundAlert(() => idleWake?.trigger());
|
|
1885
|
+
}
|
|
1782
1886
|
// ── Provider auth (login) bridge ───────────────────────────
|
|
1783
1887
|
// OAuth login functions are interactive (open a URL, sometimes prompt for a
|
|
1784
1888
|
// pasted code). We run one at a time and surface every step over SSE so the
|
|
@@ -1827,11 +1931,15 @@ async function createSession(deps, opts) {
|
|
|
1827
1931
|
? ((await auth.getCredentials(oauthKey))?.usageExhaustedUntil ?? 0)
|
|
1828
1932
|
: 0;
|
|
1829
1933
|
const oauthSidelined = hasOAuth && Date.now() < exhaustedUntil && hasApiKey;
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
: connectedMethods
|
|
1934
|
+
// Gemini resolves the API key first (model registry authStorageKeys).
|
|
1935
|
+
const keyFirst = p.value === "gemini" && hasApiKey;
|
|
1936
|
+
const activeMethod = keyFirst
|
|
1937
|
+
? "apikey"
|
|
1938
|
+
: connectedMethods.includes("oauth")
|
|
1939
|
+
? oauthSidelined
|
|
1940
|
+
? "apikey"
|
|
1941
|
+
: "oauth"
|
|
1942
|
+
: connectedMethods[0];
|
|
1835
1943
|
// `methodDetails` is the server-side lookup table behind
|
|
1836
1944
|
// `methodGuidance` — shipping both would duplicate every string on the
|
|
1837
1945
|
// wire for no consumer.
|
|
@@ -1928,31 +2036,48 @@ async function createSession(deps, opts) {
|
|
|
1928
2036
|
let gitTargetGeneration = 0;
|
|
1929
2037
|
/**
|
|
1930
2038
|
* A goal's attached folders changed: point the titlebar's git state at the
|
|
1931
|
-
* new project folder. No-op when the target is unchanged
|
|
1932
|
-
*
|
|
2039
|
+
* new project folder. No-op when the target is unchanged, unless `reread`
|
|
2040
|
+
* asks for a fresh read of the same folder; a newer call or teardown
|
|
2041
|
+
* supersedes one still reading git.
|
|
1933
2042
|
*/
|
|
1934
|
-
async function retargetGit() {
|
|
2043
|
+
async function retargetGit(options = {}) {
|
|
1935
2044
|
const generation = ++gitTargetGeneration;
|
|
1936
|
-
const
|
|
1937
|
-
|
|
2045
|
+
const read = { timeoutMs: options.timeoutMs };
|
|
2046
|
+
const { dir: next } = await resolveGitCwd(cwd, goal?.record ?? null, (dir) => isGitRepo(dir, read));
|
|
2047
|
+
if (generation !== gitTargetGeneration || repoWatchStopped)
|
|
2048
|
+
return;
|
|
2049
|
+
if (next === gitCwd && !options.reread)
|
|
1938
2050
|
return;
|
|
1939
|
-
const state = await readGitState(next);
|
|
2051
|
+
const state = await readGitState(next, read);
|
|
1940
2052
|
if (generation !== gitTargetGeneration || repoWatchStopped)
|
|
1941
2053
|
return;
|
|
1942
|
-
|
|
2054
|
+
const moved = next !== gitCwd || state.gitHubSlug !== gitHubSlug;
|
|
1943
2055
|
gitCwd = next;
|
|
1944
2056
|
gitBranch = state.branch;
|
|
1945
2057
|
gitIsRepo = state.isRepo;
|
|
1946
2058
|
gitDirtyFileCount = state.dirtyFileCount;
|
|
1947
2059
|
gitHubSlug = state.gitHubSlug;
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
2060
|
+
if (moved) {
|
|
2061
|
+
// The old repo's numbers don't apply; the new watchers fill these in.
|
|
2062
|
+
repoWatch.unsubscribe();
|
|
2063
|
+
gitHubIssues = null;
|
|
2064
|
+
gitHubPRs = null;
|
|
2065
|
+
gitHubCI = null;
|
|
2066
|
+
projectHealthScan = null;
|
|
2067
|
+
repoWatch = watchRepo(gitCwd);
|
|
2068
|
+
}
|
|
1954
2069
|
broadcast("extras", footerExtras());
|
|
1955
2070
|
}
|
|
2071
|
+
// Git missed its 2 s limit at startup (a busy machine): the titlebar would
|
|
2072
|
+
// show "not a repo" until the next run. Read again in the background with
|
|
2073
|
+
// more time; window creation doesn't wait for it.
|
|
2074
|
+
if (!initialTarget.settled || !initialGit.settled) {
|
|
2075
|
+
retargetGit({ reread: true, timeoutMs: SLOW_GIT_READ_TIMEOUT_MS }).catch((err) => {
|
|
2076
|
+
log("WARN", "app-sidecar", "slow git re-read failed", {
|
|
2077
|
+
message: err instanceof Error ? err.message : String(err),
|
|
2078
|
+
});
|
|
2079
|
+
});
|
|
2080
|
+
}
|
|
1956
2081
|
const routeCtx = {
|
|
1957
2082
|
get session() {
|
|
1958
2083
|
return session;
|
|
@@ -1983,6 +2108,7 @@ async function createSession(deps, opts) {
|
|
|
1983
2108
|
nolanStatePayload,
|
|
1984
2109
|
footerExtras,
|
|
1985
2110
|
progress,
|
|
2111
|
+
trophies,
|
|
1986
2112
|
memoryStore,
|
|
1987
2113
|
jiwaStore,
|
|
1988
2114
|
get clientSeq() {
|
|
@@ -2202,6 +2328,7 @@ async function createSession(deps, opts) {
|
|
|
2202
2328
|
// started. Otherwise they keep streaming tokens and running tools on a
|
|
2203
2329
|
// session that is being torn down.
|
|
2204
2330
|
abortOwnedWork();
|
|
2331
|
+
idleWake?.dispose();
|
|
2205
2332
|
cancelHfPull();
|
|
2206
2333
|
releaseRunAwake?.();
|
|
2207
2334
|
releaseRunAwake = null;
|