@prestyj/cli 5.30.2 → 5.30.3
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/dist/app-sidecar/app-settings.d.ts +6 -2
- package/dist/app-sidecar/app-settings.js +39 -20
- package/dist/app-sidecar/app-settings.js.map +1 -1
- package/dist/app-sidecar/git-target.d.ts +17 -0
- package/dist/app-sidecar/git-target.js +31 -0
- package/dist/app-sidecar/git-target.js.map +1 -0
- package/dist/app-sidecar/goal-routes.d.ts +26 -0
- package/dist/app-sidecar/goal-routes.js +445 -0
- package/dist/app-sidecar/goal-routes.js.map +1 -0
- package/dist/app-sidecar/history-routes.d.ts +13 -0
- package/dist/app-sidecar/history-routes.js +36 -25
- package/dist/app-sidecar/history-routes.js.map +1 -1
- package/dist/app-sidecar/mcp-routes.js +1 -2
- package/dist/app-sidecar/mcp-routes.js.map +1 -1
- package/dist/app-sidecar/route-context.d.ts +12 -0
- package/dist/app-sidecar/run-control-routes.js +2 -1
- package/dist/app-sidecar/run-control-routes.js.map +1 -1
- package/dist/app-sidecar/session-routes.js +41 -0
- package/dist/app-sidecar/session-routes.js.map +1 -1
- package/dist/app-sidecar/session-types.d.ts +2 -0
- package/dist/app-sidecar/workspace-routes.js +13 -95
- package/dist/app-sidecar/workspace-routes.js.map +1 -1
- package/dist/app-sidecar.js +213 -60
- package/dist/app-sidecar.js.map +1 -1
- package/dist/cli/shared.js +1 -1
- package/dist/cli/shared.js.map +1 -1
- package/dist/core/acceptance-checks.js +17 -4
- package/dist/core/acceptance-checks.js.map +1 -1
- package/dist/core/agent-notifications.d.ts +1 -1
- package/dist/core/agent-session.d.ts +83 -5
- package/dist/core/agent-session.js +174 -59
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/ask-user.d.ts +11 -0
- package/dist/core/ask-user.js +6 -1
- package/dist/core/ask-user.js.map +1 -1
- package/dist/core/auth-providers.d.ts +2 -80
- package/dist/core/auth-providers.js +4 -183
- package/dist/core/auth-providers.js.map +1 -1
- package/dist/core/cache-diagnostics.d.ts +1 -1
- package/dist/core/cache-diagnostics.js +2 -1
- package/dist/core/cache-diagnostics.js.map +1 -1
- package/dist/core/compaction/tool-result-pruner.d.ts +10 -0
- package/dist/core/compaction/tool-result-pruner.js +12 -0
- package/dist/core/compaction/tool-result-pruner.js.map +1 -1
- package/dist/core/context-files.d.ts +8 -0
- package/dist/core/context-files.js +46 -0
- package/dist/core/context-files.js.map +1 -0
- package/dist/core/env-delta.d.ts +9 -1
- package/dist/core/env-delta.js +20 -0
- package/dist/core/env-delta.js.map +1 -1
- package/dist/core/goals-store.d.ts +208 -0
- package/dist/core/goals-store.js +521 -0
- package/dist/core/goals-store.js.map +1 -0
- package/dist/core/local-endpoint-store.js +15 -11
- package/dist/core/local-endpoint-store.js.map +1 -1
- package/dist/core/project-discovery.d.ts +7 -0
- package/dist/core/project-discovery.js +9 -3
- package/dist/core/project-discovery.js.map +1 -1
- package/dist/core/radio.d.ts +2 -3
- package/dist/core/radio.js.map +1 -1
- package/dist/core/session-manager.d.ts +7 -1
- package/dist/core/session-manager.js +17 -7
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-storage.d.ts +3 -0
- package/dist/core/session-storage.js +8 -2
- package/dist/core/session-storage.js.map +1 -1
- package/dist/core/steroids.d.ts +8 -0
- package/dist/core/steroids.js +19 -3
- package/dist/core/steroids.js.map +1 -1
- package/dist/core/subagent-manager.d.ts +51 -0
- package/dist/core/subagent-manager.js +103 -16
- package/dist/core/subagent-manager.js.map +1 -1
- package/dist/core/subagent-store.js +2 -1
- package/dist/core/subagent-store.js.map +1 -1
- package/dist/goals-agent/goal-team.d.ts +2 -0
- package/dist/goals-agent/goal-team.js +83 -0
- package/dist/goals-agent/goal-team.js.map +1 -0
- package/dist/goals-agent/goals-agent.d.ts +90 -0
- package/dist/goals-agent/goals-agent.js +246 -0
- package/dist/goals-agent/goals-agent.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/interactive.js +4 -3
- package/dist/interactive.js.map +1 -1
- package/dist/modes/subagent-worker-mode.d.ts +14 -0
- package/dist/modes/subagent-worker-mode.js +32 -1
- package/dist/modes/subagent-worker-mode.js.map +1 -1
- package/dist/system-prompt.d.ts +8 -0
- package/dist/system-prompt.js +21 -9
- package/dist/system-prompt.js.map +1 -1
- package/dist/tools/bash-read-evidence.d.ts +13 -0
- package/dist/tools/bash-read-evidence.js +48 -1
- package/dist/tools/bash-read-evidence.js.map +1 -1
- package/dist/tools/index.d.ts +7 -1
- package/dist/tools/index.js +10 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/read-tracker.d.ts +6 -12
- package/dist/tools/read-tracker.js +21 -36
- package/dist/tools/read-tracker.js.map +1 -1
- package/dist/tools/read.js +2 -2
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/subagent-control.js +22 -3
- package/dist/tools/subagent-control.js.map +1 -1
- package/dist/tools/write.js +110 -8
- package/dist/tools/write.js.map +1 -1
- package/dist/ui/components/ActivityIndicator.d.ts +5 -5
- package/dist/ui/components/InputArea.d.ts +2 -2
- package/dist/ui/components/InputArea.js +3 -3
- package/dist/ui/components/InputArea.js.map +1 -1
- package/dist/ui/components/ToolExecution.d.ts +2 -2
- package/dist/ui/hooks/useContextCompaction.js +38 -31
- package/dist/ui/hooks/useContextCompaction.js.map +1 -1
- package/dist/utils/format.d.ts +0 -4
- package/dist/utils/format.js +0 -7
- package/dist/utils/format.js.map +1 -1
- package/package.json +4 -4
package/dist/app-sidecar.js
CHANGED
|
@@ -42,7 +42,6 @@ import { SettingsManager } from "./core/settings-manager.js";
|
|
|
42
42
|
import { getModel, getDefaultThinkingLevel, getContextWindow, clearRuntimeModels, registerRuntimeModels, } from "./core/model-registry.js";
|
|
43
43
|
import { resolveStartOrFallback } from "./core/resolve-start.js";
|
|
44
44
|
import { getGitBranch, getGitDirtyFileCount, isGitRepo } from "./utils/git.js";
|
|
45
|
-
import { getGitHubRepoSlug } from "./utils/github.js";
|
|
46
45
|
import { scoreProjectHealth } from "./core/project-health-score.js";
|
|
47
46
|
import { createRepoPolls } from "./app-sidecar/repo-polls.js";
|
|
48
47
|
import { extractPlanSteps } from "./utils/plan-steps.js";
|
|
@@ -55,7 +54,7 @@ import { installTerminationHandlers } from "./core/shutdown.js";
|
|
|
55
54
|
import { stopRadio } from "./core/radio.js";
|
|
56
55
|
import { enrichProcessPath } from "./core/shell-path.js";
|
|
57
56
|
import { createElicitationBridge } from "./core/mcp/index.js";
|
|
58
|
-
import { askSoftDeadlineMs, createAskUserBridge, deliverLateAnswer } from "./core/ask-user.js";
|
|
57
|
+
import { askSoftDeadlineMs, createAskUserBridge, deliverLateAnswer, } from "./core/ask-user.js";
|
|
59
58
|
import { createAskUserTool } from "./tools/ask-user.js";
|
|
60
59
|
import { createUsageService } from "./app-sidecar/usage.js";
|
|
61
60
|
import { daemonReadBody, daemonJson, json } from "./app-sidecar/http.js";
|
|
@@ -65,6 +64,10 @@ import { ALL_PROVIDERS, loadProjectModelPrefs, loadNolanModelPref, loadAutopilot
|
|
|
65
64
|
import { NOLAN_ALLOWED_TOOLS } from "./app-sidecar/nolan-context.js";
|
|
66
65
|
import { createProgressManager } from "./app-sidecar/progress-manager.js";
|
|
67
66
|
import { handleSessionRoutes } from "./app-sidecar/session-routes.js";
|
|
67
|
+
import { handleGoalRoutes } from "./app-sidecar/goal-routes.js";
|
|
68
|
+
import { readGitState, resolveGitCwd } from "./app-sidecar/git-target.js";
|
|
69
|
+
import { goalPhase, goalsRoot, isGoalDir, startGoalOnAnswer, readGoal, } from "./core/goals-store.js";
|
|
70
|
+
import { createGoalsAgentSession, goalCritterPick, loadGoalBriefExtras, } from "./goals-agent/goals-agent.js";
|
|
68
71
|
import { handleWorkspaceRoutes } from "./app-sidecar/workspace-routes.js";
|
|
69
72
|
import { handleHistoryRoutes } from "./app-sidecar/history-routes.js";
|
|
70
73
|
import { handlePromptRoutes } from "./app-sidecar/prompt-routes.js";
|
|
@@ -177,6 +180,18 @@ async function main() {
|
|
|
177
180
|
for (const ctx of sessions.values())
|
|
178
181
|
ctx.broadcast(type, data);
|
|
179
182
|
};
|
|
183
|
+
const sessionIdsAt = (cwd) => {
|
|
184
|
+
const target = path.resolve(cwd);
|
|
185
|
+
return [...sessions.values()]
|
|
186
|
+
.filter((ctx) => path.resolve(ctx.cwd) === target)
|
|
187
|
+
.map((ctx) => ctx.id)
|
|
188
|
+
.sort();
|
|
189
|
+
};
|
|
190
|
+
/** Whether any live window session in `cwd` is working right now. */
|
|
191
|
+
const busyAt = (cwd) => {
|
|
192
|
+
const target = path.resolve(cwd);
|
|
193
|
+
return [...sessions.values()].some((ctx) => path.resolve(ctx.cwd) === target && ctx.isBusy());
|
|
194
|
+
};
|
|
180
195
|
// Providers currently mid-OAuth in some window. Daemon-level so two windows
|
|
181
196
|
// cannot race two browser flows for the same provider into one auth file.
|
|
182
197
|
const oauthInFlightProviders = new Set();
|
|
@@ -271,6 +286,8 @@ async function main() {
|
|
|
271
286
|
broadcastAll,
|
|
272
287
|
oauthInFlightProviders,
|
|
273
288
|
repoPolls,
|
|
289
|
+
sessionIdsAt,
|
|
290
|
+
busyAt,
|
|
274
291
|
}, { id, mode, chatAgent, cwd: sessionCwd, sessionPath });
|
|
275
292
|
sessions.set(id, ctx);
|
|
276
293
|
log("INFO", "app-sidecar", "session created", {
|
|
@@ -279,7 +296,13 @@ async function main() {
|
|
|
279
296
|
chatAgent,
|
|
280
297
|
cwd: sessionCwd,
|
|
281
298
|
});
|
|
282
|
-
|
|
299
|
+
// The conversation file too, so the app can reopen this exact
|
|
300
|
+
// session if the daemon restarts (a new session has no path yet
|
|
301
|
+
// on the app's side).
|
|
302
|
+
daemonJson(res, 200, {
|
|
303
|
+
sessionId: id,
|
|
304
|
+
sessionPath: ctx.session.getState().sessionPath ?? null,
|
|
305
|
+
});
|
|
283
306
|
}
|
|
284
307
|
catch (err) {
|
|
285
308
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -433,7 +456,7 @@ async function main() {
|
|
|
433
456
|
* logger, PATH, shared auth file, and radio live at the daemon level.
|
|
434
457
|
*/
|
|
435
458
|
async function createSession(deps, opts) {
|
|
436
|
-
const { auth, progress, memoryStore, jiwaStore, broadcastAll, oauthInFlightProviders, repoPolls, } = deps;
|
|
459
|
+
const { auth, progress, memoryStore, jiwaStore, broadcastAll, oauthInFlightProviders, repoPolls, sessionIdsAt, busyAt, } = deps;
|
|
437
460
|
const paths = deps.paths;
|
|
438
461
|
const mode = opts.mode;
|
|
439
462
|
let chatAgent = opts.chatAgent;
|
|
@@ -442,6 +465,10 @@ async function createSession(deps, opts) {
|
|
|
442
465
|
// root; create it on first use so a fresh install can start a video at once.
|
|
443
466
|
if (mode === "motion")
|
|
444
467
|
await fs.mkdir(cwd, { recursive: true });
|
|
468
|
+
// A coding session whose cwd is a Goal folder is that goal's coordinator.
|
|
469
|
+
let goal = mode === "code" && isGoalDir(goalsRoot(paths.agentDir), cwd)
|
|
470
|
+
? await readGoal(cwd).then((record) => (record ? { dir: cwd, record } : null))
|
|
471
|
+
: null;
|
|
445
472
|
// Base host for parsing request-URL query params (value is irrelevant to
|
|
446
473
|
// parsing); the daemon owns the real listen host.
|
|
447
474
|
const host = "127.0.0.1";
|
|
@@ -587,7 +614,9 @@ async function createSession(deps, opts) {
|
|
|
587
614
|
onClosed: (ids) => broadcast("ask_user_closed", { ids }),
|
|
588
615
|
onLateAnswer: (late) => {
|
|
589
616
|
log("INFO", "app-sidecar", "ask_user late answer queued", { id: late.prompt.id });
|
|
590
|
-
|
|
617
|
+
// A late "Start the goal now" starts the goal before EZ Goals hears it,
|
|
618
|
+
// so the run it wakes into may already hand out work.
|
|
619
|
+
void startGoalFrom({ action: "answer", answers: late.answers }, late.prompt.questions).then(() => deliverLateAnswer(late, {
|
|
591
620
|
queueMessage: (text) => session.queueMessage(text),
|
|
592
621
|
isBusy: () => running || runClaim.active || autopilotActive,
|
|
593
622
|
onQueued: () => broadcast("queued", {
|
|
@@ -595,13 +624,44 @@ async function createSession(deps, opts) {
|
|
|
595
624
|
messages: session.listQueuedMessages(),
|
|
596
625
|
}),
|
|
597
626
|
startIdleRun: () => void runStrandedQueue(),
|
|
598
|
-
});
|
|
627
|
+
}));
|
|
599
628
|
},
|
|
600
629
|
});
|
|
601
|
-
|
|
630
|
+
// A goal starts when the user picks "Start the goal now" from EZ Goals'
|
|
631
|
+
// question: answered while it waits, or late, after its deadline (see
|
|
632
|
+
// onLateAnswer). Recorded once, and announced so the window can switch to
|
|
633
|
+
// the goal's world view. Never throws: a failed write leaves it briefing.
|
|
634
|
+
const startGoalFrom = async (result, questions) => {
|
|
635
|
+
const current = goal;
|
|
636
|
+
if (!current)
|
|
637
|
+
return;
|
|
638
|
+
const started = await startGoalOnAnswer(current.dir, current.record, result, new Date(), questions).catch((err) => {
|
|
639
|
+
log("WARN", "app-sidecar", "couldn't record goal start", {
|
|
640
|
+
error: err instanceof Error ? err.message : String(err),
|
|
641
|
+
});
|
|
642
|
+
return null;
|
|
643
|
+
});
|
|
644
|
+
if (started) {
|
|
645
|
+
goal = { dir: current.dir, record: started };
|
|
646
|
+
broadcast("goal_state", { id: started.id, phase: goalPhase(started) });
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
const askUserTool = createAskUserTool(async (request) => {
|
|
650
|
+
const result = await asks.park(request);
|
|
651
|
+
await startGoalFrom(result, request.questions);
|
|
652
|
+
return result;
|
|
653
|
+
});
|
|
602
654
|
// The session file path to resume (passed by the daemon's POST /session);
|
|
603
|
-
// empty/unset starts a fresh session.
|
|
604
|
-
|
|
655
|
+
// empty/unset starts a fresh session. A file that's gone (retention cleaned
|
|
656
|
+
// it up, or its goal was deleted) starts fresh too: failing instead left
|
|
657
|
+
// the window with no session at all, so not even its goal list loaded.
|
|
658
|
+
const resumeSessionPath = opts.sessionPath &&
|
|
659
|
+
(await fs.access(opts.sessionPath).then(() => true, () => false))
|
|
660
|
+
? opts.sessionPath
|
|
661
|
+
: undefined;
|
|
662
|
+
if (opts.sessionPath && !resumeSessionPath) {
|
|
663
|
+
log("WARN", "app-sidecar", "session file gone, starting fresh", { cwd });
|
|
664
|
+
}
|
|
605
665
|
let abort = new AbortController();
|
|
606
666
|
const baseSessionOptions = {
|
|
607
667
|
provider,
|
|
@@ -648,6 +708,19 @@ async function createSession(deps, opts) {
|
|
|
648
708
|
additionalTools: [askUserTool],
|
|
649
709
|
});
|
|
650
710
|
}
|
|
711
|
+
else if (goal) {
|
|
712
|
+
// A Goal window talks to EZ Goals, the orchestrator, not the coding agent.
|
|
713
|
+
session = createGoalsAgentSession({
|
|
714
|
+
...baseSessionOptions,
|
|
715
|
+
goal,
|
|
716
|
+
// Memory and the attached projects' rules, read once for this session.
|
|
717
|
+
extras: await loadGoalBriefExtras(goal),
|
|
718
|
+
// Read live: the goal turns active mid-session when the user starts it.
|
|
719
|
+
isActive: () => Boolean(goal?.record.startedAt),
|
|
720
|
+
getChildModel: () => goalCritterPick(goal?.record),
|
|
721
|
+
additionalTools: [askUserTool],
|
|
722
|
+
});
|
|
723
|
+
}
|
|
651
724
|
else {
|
|
652
725
|
session = new AgentSession({
|
|
653
726
|
...baseSessionOptions,
|
|
@@ -686,6 +759,18 @@ async function createSession(deps, opts) {
|
|
|
686
759
|
});
|
|
687
760
|
}
|
|
688
761
|
await session.initialize();
|
|
762
|
+
// A goal's attached folders join its workspace so the coordinator can read
|
|
763
|
+
// them. A resumed session may already carry them; `addDirectory` skips those.
|
|
764
|
+
if (goal) {
|
|
765
|
+
for (const item of goal.record.context) {
|
|
766
|
+
if (item.kind !== "folder")
|
|
767
|
+
continue;
|
|
768
|
+
const added = await session.addDirectory(item.path);
|
|
769
|
+
if (!added.ok && !added.error.startsWith("Already in the workspace")) {
|
|
770
|
+
log("WARN", "app-sidecar", "goal context folder unavailable", { error: added.error });
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
689
774
|
if (mode === "chat") {
|
|
690
775
|
const restoredAgent = [...session.getAppMarkers()]
|
|
691
776
|
.reverse()
|
|
@@ -799,20 +884,18 @@ async function createSession(deps, opts) {
|
|
|
799
884
|
});
|
|
800
885
|
}
|
|
801
886
|
// Workspace extras (context window, git status, background tasks). Git state
|
|
802
|
-
// is resolved
|
|
803
|
-
//
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
let
|
|
811
|
-
let gitIsRepo = initialGitIsRepo;
|
|
812
|
-
let gitDirtyFileCount = initialDirtyFileCount;
|
|
887
|
+
// is resolved at startup and refreshed after every run; the context window
|
|
888
|
+
// follows the active model. A Goal window reads git from its attached
|
|
889
|
+
// project folder, not the goal folder (see resolveGitCwd), and re-targets
|
|
890
|
+
// when the goal's folders change (`retargetGit`).
|
|
891
|
+
let gitCwd = await resolveGitCwd(cwd, goal?.record ?? null);
|
|
892
|
+
const initialGit = await readGitState(gitCwd);
|
|
893
|
+
let gitBranch = initialGit.branch;
|
|
894
|
+
let gitIsRepo = initialGit.isRepo;
|
|
895
|
+
let gitDirtyFileCount = initialGit.dirtyFileCount;
|
|
813
896
|
// Open issue/PR counts for the origin repo's GitHub slug, via the `gh` CLI's
|
|
814
897
|
// auth. null = unknown (gh missing/unauthed, non-GitHub origin) → chips hidden.
|
|
815
|
-
|
|
898
|
+
let gitHubSlug = initialGit.gitHubSlug;
|
|
816
899
|
let gitHubIssues = null;
|
|
817
900
|
let gitHubPRs = null;
|
|
818
901
|
let gitHubCI = null;
|
|
@@ -1444,15 +1527,15 @@ async function createSession(deps, opts) {
|
|
|
1444
1527
|
// A run may have switched branches, changed files, or spawned/finished
|
|
1445
1528
|
// background tasks. Refresh the workspace extras once it settles.
|
|
1446
1529
|
[gitBranch, gitIsRepo, gitDirtyFileCount] = await Promise.all([
|
|
1447
|
-
getGitBranch(
|
|
1448
|
-
isGitRepo(
|
|
1449
|
-
getGitDirtyFileCount(
|
|
1530
|
+
getGitBranch(gitCwd).catch(() => gitBranch),
|
|
1531
|
+
isGitRepo(gitCwd).catch(() => gitIsRepo),
|
|
1532
|
+
getGitDirtyFileCount(gitCwd).catch(() => gitDirtyFileCount),
|
|
1450
1533
|
]);
|
|
1451
1534
|
// A run may have opened/closed issues or PRs — refresh fire-and-forget so
|
|
1452
1535
|
// teardown isn't delayed by the network. Broadcasts itself on change.
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1536
|
+
repoWatch.gitHubCounts?.refresh();
|
|
1537
|
+
repoWatch.ci.refresh();
|
|
1538
|
+
repoWatch.health?.refresh();
|
|
1456
1539
|
// Serialize behind any marker/tool-triggered refresh so the terminal
|
|
1457
1540
|
// progress snapshot uses the live plan file. Once every canonical step
|
|
1458
1541
|
// is complete, remove the approved plan from future system prompts and
|
|
@@ -1795,39 +1878,82 @@ async function createSession(deps, opts) {
|
|
|
1795
1878
|
// (they change outside the agent: editor saves, commits, teammates). Branch/
|
|
1796
1879
|
// repo state already refreshes after agent runs. The pollers are shared with
|
|
1797
1880
|
// every other window on the same repo; each window broadcasts only changes.
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
gitDirtyFileCount = next;
|
|
1803
|
-
broadcast("extras", footerExtras());
|
|
1804
|
-
});
|
|
1805
|
-
// Failed checks publish nothing, so the chips keep their last-known numbers
|
|
1806
|
-
// instead of flickering off on a timeout. None when origin isn't GitHub.
|
|
1807
|
-
const gitHubCountsPoll = gitHubSlug
|
|
1808
|
-
? repoPolls.gitHubCounts.subscribe(gitHubSlug, (counts) => {
|
|
1809
|
-
if (counts.issues === gitHubIssues && counts.prs === gitHubPRs)
|
|
1881
|
+
function watchRepo(dir) {
|
|
1882
|
+
const repoKey = path.resolve(dir);
|
|
1883
|
+
const dirtyFiles = repoPolls.dirtyFiles.subscribe(repoKey, (next) => {
|
|
1884
|
+
if (next === gitDirtyFileCount)
|
|
1810
1885
|
return;
|
|
1811
|
-
|
|
1812
|
-
gitHubPRs = counts.prs;
|
|
1886
|
+
gitDirtyFileCount = next;
|
|
1813
1887
|
broadcast("extras", footerExtras());
|
|
1814
|
-
})
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1888
|
+
});
|
|
1889
|
+
// Failed checks publish nothing, so the chips keep their last-known numbers
|
|
1890
|
+
// instead of flickering off on a timeout. None when origin isn't GitHub.
|
|
1891
|
+
const gitHubCounts = gitHubSlug
|
|
1892
|
+
? repoPolls.gitHubCounts.subscribe(gitHubSlug, (counts) => {
|
|
1893
|
+
if (counts.issues === gitHubIssues && counts.prs === gitHubPRs)
|
|
1894
|
+
return;
|
|
1895
|
+
gitHubIssues = counts.issues;
|
|
1896
|
+
gitHubPRs = counts.prs;
|
|
1897
|
+
broadcast("extras", footerExtras());
|
|
1898
|
+
})
|
|
1899
|
+
: null;
|
|
1900
|
+
const ci = repoPolls.gitHubCI.subscribe(repoKey, (next) => {
|
|
1901
|
+
if (JSON.stringify(next) === JSON.stringify(gitHubCI))
|
|
1826
1902
|
return;
|
|
1827
|
-
|
|
1903
|
+
gitHubCI = next;
|
|
1828
1904
|
broadcast("extras", footerExtras());
|
|
1829
|
-
})
|
|
1830
|
-
|
|
1905
|
+
});
|
|
1906
|
+
// Project Health is a code-workspace signal; chat and motion windows skip the scan.
|
|
1907
|
+
const health = mode === "code"
|
|
1908
|
+
? repoPolls.projectHealth.subscribe(repoKey, (next) => {
|
|
1909
|
+
if (JSON.stringify(next) === JSON.stringify(projectHealthScan))
|
|
1910
|
+
return;
|
|
1911
|
+
projectHealthScan = next;
|
|
1912
|
+
broadcast("extras", footerExtras());
|
|
1913
|
+
})
|
|
1914
|
+
: null;
|
|
1915
|
+
return {
|
|
1916
|
+
gitHubCounts,
|
|
1917
|
+
ci,
|
|
1918
|
+
health,
|
|
1919
|
+
unsubscribe() {
|
|
1920
|
+
dirtyFiles.unsubscribe();
|
|
1921
|
+
gitHubCounts?.unsubscribe();
|
|
1922
|
+
ci.unsubscribe();
|
|
1923
|
+
health?.unsubscribe();
|
|
1924
|
+
},
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
let repoWatch = watchRepo(gitCwd);
|
|
1928
|
+
let repoWatchStopped = false;
|
|
1929
|
+
let gitTargetGeneration = 0;
|
|
1930
|
+
/**
|
|
1931
|
+
* A goal's attached folders changed: point the titlebar's git state at the
|
|
1932
|
+
* new project folder. No-op when the target is unchanged; a newer call or
|
|
1933
|
+
* teardown supersedes one still reading git.
|
|
1934
|
+
*/
|
|
1935
|
+
async function retargetGit() {
|
|
1936
|
+
const generation = ++gitTargetGeneration;
|
|
1937
|
+
const next = await resolveGitCwd(cwd, goal?.record ?? null);
|
|
1938
|
+
if (generation !== gitTargetGeneration || repoWatchStopped || next === gitCwd)
|
|
1939
|
+
return;
|
|
1940
|
+
const state = await readGitState(next);
|
|
1941
|
+
if (generation !== gitTargetGeneration || repoWatchStopped)
|
|
1942
|
+
return;
|
|
1943
|
+
repoWatch.unsubscribe();
|
|
1944
|
+
gitCwd = next;
|
|
1945
|
+
gitBranch = state.branch;
|
|
1946
|
+
gitIsRepo = state.isRepo;
|
|
1947
|
+
gitDirtyFileCount = state.dirtyFileCount;
|
|
1948
|
+
gitHubSlug = state.gitHubSlug;
|
|
1949
|
+
// The old repo's numbers don't apply; the new watchers fill these in.
|
|
1950
|
+
gitHubIssues = null;
|
|
1951
|
+
gitHubPRs = null;
|
|
1952
|
+
gitHubCI = null;
|
|
1953
|
+
projectHealthScan = null;
|
|
1954
|
+
repoWatch = watchRepo(gitCwd);
|
|
1955
|
+
broadcast("extras", footerExtras());
|
|
1956
|
+
}
|
|
1831
1957
|
const routeCtx = {
|
|
1832
1958
|
get session() {
|
|
1833
1959
|
return session;
|
|
@@ -1879,6 +2005,23 @@ async function createSession(deps, opts) {
|
|
|
1879
2005
|
cwd,
|
|
1880
2006
|
asks,
|
|
1881
2007
|
runClaim,
|
|
2008
|
+
sessionId: opts.id,
|
|
2009
|
+
sessionIdsAt,
|
|
2010
|
+
busyAt,
|
|
2011
|
+
get goal() {
|
|
2012
|
+
return goal;
|
|
2013
|
+
},
|
|
2014
|
+
set goal(v) {
|
|
2015
|
+
const foldersBefore = JSON.stringify(goal?.record.context ?? []);
|
|
2016
|
+
goal = v;
|
|
2017
|
+
if (JSON.stringify(goal?.record.context ?? []) !== foldersBefore) {
|
|
2018
|
+
retargetGit().catch((err) => {
|
|
2019
|
+
log("WARN", "app-sidecar", "goal git retarget failed", {
|
|
2020
|
+
message: err instanceof Error ? err.message : String(err),
|
|
2021
|
+
});
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
2024
|
+
},
|
|
1882
2025
|
broadcast,
|
|
1883
2026
|
get scheduledRunActive() {
|
|
1884
2027
|
return scheduledRunActive;
|
|
@@ -2036,6 +2179,8 @@ async function createSession(deps, opts) {
|
|
|
2036
2179
|
return;
|
|
2037
2180
|
if (handleWorkspaceRoutes(routeCtx, req, res, url, method))
|
|
2038
2181
|
return;
|
|
2182
|
+
if (handleGoalRoutes(routeCtx, req, res, url, method))
|
|
2183
|
+
return;
|
|
2039
2184
|
if (handleHistoryRoutes(routeCtx, req, res, url, method))
|
|
2040
2185
|
return;
|
|
2041
2186
|
if (handlePromptRoutes(routeCtx, req, res, url, method))
|
|
@@ -2053,6 +2198,12 @@ async function createSession(deps, opts) {
|
|
|
2053
2198
|
json(res, 404, { error: "not found" });
|
|
2054
2199
|
}
|
|
2055
2200
|
async function dispose() {
|
|
2201
|
+
// A window closed (or switched project) mid-run: stop the run itself, any
|
|
2202
|
+
// run-all sweep or autopilot turn, and a model download this window
|
|
2203
|
+
// started. Otherwise they keep streaming tokens and running tools on a
|
|
2204
|
+
// session that is being torn down.
|
|
2205
|
+
abortOwnedWork();
|
|
2206
|
+
cancelHfPull();
|
|
2056
2207
|
releaseRunAwake?.();
|
|
2057
2208
|
releaseRunAwake = null;
|
|
2058
2209
|
releaseSubagentAwake?.();
|
|
@@ -2062,10 +2213,8 @@ async function createSession(deps, opts) {
|
|
|
2062
2213
|
tasksPollStopped = true;
|
|
2063
2214
|
if (tasksPoll)
|
|
2064
2215
|
clearTimeout(tasksPoll);
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
ciPoll.unsubscribe();
|
|
2068
|
-
healthPoll?.unsubscribe();
|
|
2216
|
+
repoWatchStopped = true;
|
|
2217
|
+
repoWatch.unsubscribe();
|
|
2069
2218
|
// Stop the Telegram serve loop + dispose its per-chat sessions.
|
|
2070
2219
|
if (serveController)
|
|
2071
2220
|
await serveController.stop().catch(() => { });
|
|
@@ -2096,6 +2245,10 @@ async function createSession(deps, opts) {
|
|
|
2096
2245
|
handle,
|
|
2097
2246
|
dispose,
|
|
2098
2247
|
stopBackgroundProcesses,
|
|
2248
|
+
isBusy: () => running ||
|
|
2249
|
+
runClaim.active ||
|
|
2250
|
+
autopilotActive ||
|
|
2251
|
+
session.listSubAgents().some((w) => w.state === "running" || w.state === "starting"),
|
|
2099
2252
|
};
|
|
2100
2253
|
}
|
|
2101
2254
|
main().catch(async (err) => {
|