@saadnvd1/agent-os 0.3.637 → 0.3.644
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/app/api/bus/branches/route.ts +25 -0
- package/app/api/bus/resurrect/route.test.ts +3 -6
- package/app/api/sessions/[id]/pr/route.ts +3 -3
- package/app/api/sessions/[id]/setup/route.ts +3 -3
- package/app/api/tasks/[id]/approve-plan/route.ts +3 -4
- package/app/api/tasks/[id]/arm/route.ts +6 -5
- package/app/api/tasks/[id]/chat-facts/route.ts +2 -2
- package/app/api/tasks/[id]/chat-tail/route.ts +2 -2
- package/app/api/tasks/[id]/checks/route.ts +1 -1
- package/app/api/tasks/[id]/diff/route.ts +1 -1
- package/app/api/tasks/[id]/done/route.ts +2 -2
- package/app/api/tasks/[id]/drop/route.ts +6 -2
- package/app/api/tasks/[id]/export/route.ts +3 -5
- package/app/api/tasks/[id]/merge/route.ts +3 -3
- package/app/api/tasks/[id]/message/route.ts +2 -2
- package/app/api/tasks/[id]/model/route.ts +5 -1
- package/app/api/tasks/[id]/move/route.ts +5 -1
- package/app/api/tasks/[id]/moved/route.ts +6 -8
- package/app/api/tasks/[id]/plan/route.ts +4 -6
- package/app/api/tasks/[id]/plan-edits/route.ts +6 -5
- package/app/api/tasks/[id]/priority/route.ts +4 -1
- package/app/api/tasks/[id]/resume/route.ts +3 -4
- package/app/api/tasks/[id]/stop/route.ts +2 -2
- package/app/api/tasks/route.ts +1 -1
- package/app/api/worktree-sweep/route.test.ts +40 -0
- package/app/api/worktree-sweep/route.ts +31 -0
- package/app/api/worktree-sweep/run/route.ts +26 -0
- package/components/Health/HealthPanel.tsx +4 -1
- package/components/Health/SweepRuns.tsx +125 -0
- package/components/Merge/GlobalMergeSection.tsx +2 -0
- package/components/Merge/ProjectMergeSection.tsx +2 -0
- package/components/Merge/WorktreeSweepSection.tsx +125 -0
- package/components/Settings/NoApprovalsSection.tsx +1 -1
- package/components/Settings/SettingsDialog.tsx +1 -1
- package/data/worktree-sweep/index.ts +56 -0
- package/lib/approvals/enable.ts +1 -1
- package/lib/away/report.ts +5 -5
- package/lib/away/run.ts +2 -2
- package/lib/bookmarks/store.ts +3 -3
- package/lib/bus/held-reply.ts +2 -2
- package/lib/bus/index.ts +3 -3
- package/lib/chat/compact-context.ts +1 -1
- package/lib/chat/hold.ts +3 -3
- package/lib/chat/refresh.ts +1 -1
- package/lib/chat/registry.ts +3 -3
- package/lib/chat/runner.ts +1 -1
- package/lib/chat/worker/main.ts +1 -1
- package/lib/db/backup.ts +49 -0
- package/lib/db/index.ts +5 -1
- package/lib/db/lumifyhub.ts +8 -9
- package/lib/db/machine-scope.ts +12 -0
- package/lib/db/migrations/20261015T1733_gate_loop_guard.ts +40 -0
- package/lib/db/migrations/20261015T1800_split_sessions_tables.ts +172 -0
- package/lib/db/migrations/20261015T1830_drop_moved_session_columns.ts +173 -0
- package/lib/db/migrations/20261015T1900_worktree_sweep.ts +30 -0
- package/lib/db/migrations/README.md +11 -0
- package/lib/db/queries.ts +16 -31
- package/lib/db/session-parts/columns.ts +186 -0
- package/lib/db/session-parts/fill.ts +33 -0
- package/lib/db/session-parts/index.ts +10 -0
- package/lib/db/session-parts/pauses.ts +11 -0
- package/lib/db/session-parts/placement.ts +11 -0
- package/lib/db/session-parts/plans.ts +31 -0
- package/lib/db/session-parts/prs.ts +31 -0
- package/lib/db/session-parts/rows.ts +69 -0
- package/lib/db/session-parts/tasks.ts +27 -0
- package/lib/db/session-parts/view.ts +50 -0
- package/lib/db/session-parts/write.ts +61 -0
- package/lib/done/archive.ts +2 -2
- package/lib/done/auto-archive.ts +1 -1
- package/lib/done/bulk.ts +1 -1
- package/lib/done/index.ts +8 -14
- package/lib/done/remote.ts +9 -20
- package/lib/health/evidence.ts +1 -1
- package/lib/health/facts.ts +1 -1
- package/lib/health/pieces.ts +1 -1
- package/lib/hosts/peer-actions.ts +2 -2
- package/lib/hosts/peer-mirror.ts +2 -2
- package/lib/hosts/peer-sync.ts +69 -32
- package/lib/learning-cards/input.ts +1 -1
- package/lib/learning-cards/store.ts +1 -1
- package/lib/models/plan-step.ts +10 -24
- package/lib/models/resume.ts +5 -7
- package/lib/notify/index.ts +1 -1
- package/lib/notify/push/events.ts +1 -1
- package/lib/notify/push/index.ts +1 -1
- package/lib/orchestrator/act.ts +1 -1
- package/lib/orchestrator/armed.test-helpers.ts +228 -0
- package/lib/orchestrator/armed.ts +18 -16
- package/lib/orchestrator/ask-approvals.ts +1 -1
- package/lib/orchestrator/ask-heads.ts +1 -1
- package/lib/orchestrator/ask-settle.ts +1 -1
- package/lib/orchestrator/ask-verdict.ts +111 -0
- package/lib/orchestrator/asks.ts +26 -0
- package/lib/orchestrator/brakes.ts +1 -1
- package/lib/orchestrator/brief.ts +2 -1
- package/lib/orchestrator/digest.ts +2 -2
- package/lib/orchestrator/escalate.ts +25 -12
- package/lib/orchestrator/external-gates.ts +1 -0
- package/lib/orchestrator/external-pr.ts +2 -2
- package/lib/orchestrator/facts.ts +4 -28
- package/lib/orchestrator/gates.ts +94 -25
- package/lib/orchestrator/held-asks.ts +8 -11
- package/lib/orchestrator/held-review.ts +8 -4
- package/lib/orchestrator/home.ts +2 -2
- package/lib/orchestrator/loop-guard.ts +145 -0
- package/lib/orchestrator/merge-approvals.ts +2 -2
- package/lib/orchestrator/overview.ts +1 -1
- package/lib/orchestrator/pr-wake.ts +3 -5
- package/lib/orchestrator/questions.ts +1 -1
- package/lib/orchestrator/quick-store.ts +1 -1
- package/lib/orchestrator/quick.ts +11 -8
- package/lib/orchestrator/read.ts +1 -1
- package/lib/orchestrator/rebase.ts +8 -11
- package/lib/orchestrator/reconcile.ts +2 -2
- package/lib/orchestrator/review-prompt.ts +2 -0
- package/lib/orchestrator/review.ts +14 -4
- package/lib/orchestrator/signoff.test-helpers.ts +322 -0
- package/lib/orchestrator/signoff.ts +49 -11
- package/lib/orchestrator/targets.ts +1 -1
- package/lib/orchestrator/task-state.ts +4 -2
- package/lib/orchestrator/testing.ts +11 -14
- package/lib/orchestrator/watcher.ts +4 -0
- package/lib/plans/features.ts +1 -1
- package/lib/plans/release.ts +1 -1
- package/lib/ports.ts +1 -1
- package/lib/recipes/test-fixtures.ts +2 -3
- package/lib/resurrect/find.ts +1 -1
- package/lib/resurrect/index.ts +44 -20
- package/lib/resurrect/swept.ts +61 -0
- package/lib/schedules/store.ts +1 -1
- package/lib/session-audit/sources.ts +3 -3
- package/lib/session-logs/index.ts +1 -1
- package/lib/session-titles.ts +1 -1
- package/lib/sessions/setup-progress.ts +1 -1
- package/lib/sessions/stop.ts +1 -1
- package/lib/sessions/worktree-setup.ts +2 -1
- package/lib/simulator/index.ts +1 -1
- package/lib/simulator/task-devices.ts +1 -1
- package/lib/stacks/board.ts +1 -1
- package/lib/stacks/land.ts +1 -1
- package/lib/stacks/restack.ts +1 -1
- package/lib/stacks/start.ts +2 -2
- package/lib/stacks/testing.ts +17 -16
- package/lib/stacks/tick.ts +1 -1
- package/lib/stacks/view.ts +1 -1
- package/lib/status/incremental.ts +1 -1
- package/lib/tasks/contract/actions.ts +35 -0
- package/lib/tasks/contract/authorize.ts +82 -0
- package/lib/tasks/contract/client.ts +296 -0
- package/lib/tasks/{held-remote.ts → contract/held-calls.ts} +35 -66
- package/lib/tasks/contract/index.ts +28 -0
- package/lib/tasks/{remote-plan-path.ts → contract/plan-path.ts} +2 -2
- package/lib/tasks/{remote-plan.ts → contract/plan.ts} +76 -110
- package/lib/tasks/contract/routes.ts +26 -0
- package/lib/tasks/contract/start.ts +122 -0
- package/lib/tasks/contract/sync.ts +126 -0
- package/lib/tasks/contract/types.ts +41 -0
- package/lib/tasks/{remote-views.ts → contract/views.ts} +11 -18
- package/lib/tasks/finish.ts +30 -23
- package/lib/tasks/held-replies.ts +7 -13
- package/lib/tasks/held.ts +14 -18
- package/lib/tasks/import.ts +19 -15
- package/lib/tasks/index.ts +34 -22
- package/lib/tasks/launch-gate.ts +1 -1
- package/lib/tasks/move-carry.ts +15 -7
- package/lib/tasks/move-flow.ts +29 -36
- package/lib/tasks/move-guard.ts +1 -1
- package/lib/tasks/move-recover.ts +6 -25
- package/lib/tasks/move-testing.ts +16 -7
- package/lib/tasks/move.ts +23 -23
- package/lib/tasks/moved-away.ts +1 -1
- package/lib/tasks/pause-hold.ts +13 -5
- package/lib/tasks/pause.ts +19 -23
- package/lib/tasks/plan-view.ts +1 -1
- package/lib/tasks/pr-activity.ts +11 -2
- package/lib/tasks/prune-branches.ts +193 -0
- package/lib/tasks/prune-scope.ts +22 -0
- package/lib/tasks/queue-placement.ts +1 -1
- package/lib/tasks/queue-rows.ts +4 -4
- package/lib/tasks/queue-start.ts +5 -3
- package/lib/tasks/queue-tick.ts +2 -2
- package/lib/tasks/queue.ts +14 -6
- package/lib/tasks/remote-branch.ts +170 -0
- package/lib/tasks/session.ts +7 -4
- package/lib/tasks/start.ts +4 -4
- package/lib/tasks/starter.ts +8 -11
- package/lib/tasks/testing.ts +13 -7
- package/lib/usage/calls.ts +1 -1
- package/lib/usage/compare.ts +1 -1
- package/lib/usage/quality.ts +1 -1
- package/lib/usage/turns.ts +1 -1
- package/lib/voice/vocab.ts +1 -1
- package/lib/worktree-sweep/commits.ts +65 -0
- package/lib/worktree-sweep/findOrphans.ts +139 -0
- package/lib/worktree-sweep/findSettled.ts +112 -0
- package/lib/worktree-sweep/index.ts +10 -0
- package/lib/worktree-sweep/judge.ts +31 -0
- package/lib/worktree-sweep/panes.ts +18 -0
- package/lib/worktree-sweep/runSweep.ts +243 -0
- package/lib/worktree-sweep/settings.ts +83 -0
- package/lib/worktree-sweep/start.ts +64 -0
- package/lib/worktree-sweep/state.ts +36 -0
- package/lib/worktree-sweep/store.ts +96 -0
- package/lib/worktree-sweep/types.ts +53 -0
- package/package.json +1 -1
- package/scripts/backfill-session-logs.ts +1 -1
- package/scripts/screenshots/seed.ts +19 -18
- package/server.ts +3 -0
- package/lib/tasks/held-routes.ts +0 -33
- package/lib/tasks/remote.ts +0 -255
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Button } from "@/components/ui/button";
|
|
4
|
+
import { useRunSweep, useWorktreeSweep } from "@/data/worktree-sweep";
|
|
5
|
+
import { compactTimeAgo, fromSqliteTime } from "@/lib/session-meta";
|
|
6
|
+
import { formatBytes } from "@/lib/share/view";
|
|
7
|
+
import type { SweepRun } from "@/lib/worktree-sweep/types";
|
|
8
|
+
|
|
9
|
+
const KIND: Record<string, string> = {
|
|
10
|
+
settled: "settled session",
|
|
11
|
+
orphan: "orphan folder",
|
|
12
|
+
"test-leak": "test leftover",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const name = (p: string) => p.split("/").pop();
|
|
16
|
+
|
|
17
|
+
function Run({ run }: { run: SweepRun }) {
|
|
18
|
+
const skipped = run.details?.skipped ?? [];
|
|
19
|
+
const removed = run.details?.removed ?? [];
|
|
20
|
+
return (
|
|
21
|
+
<li className="border-border/60 border-b px-3 py-1 last:border-b-0">
|
|
22
|
+
<details>
|
|
23
|
+
<summary className="flex min-h-11 cursor-pointer flex-wrap items-center gap-x-3 gap-y-1 text-sm">
|
|
24
|
+
<span className="font-medium">
|
|
25
|
+
{run.dryRun ? "Dry run, would remove" : "Removed"} {run.removed}
|
|
26
|
+
</span>
|
|
27
|
+
<span className="text-muted-foreground tabular-nums">
|
|
28
|
+
{formatBytes(run.freedBytes)}
|
|
29
|
+
</span>
|
|
30
|
+
<span className="text-muted-foreground">{run.skipped} skipped</span>
|
|
31
|
+
<span className="text-muted-foreground ml-auto text-xs">
|
|
32
|
+
{run.finishedAt
|
|
33
|
+
? compactTimeAgo(fromSqliteTime(run.finishedAt))
|
|
34
|
+
: "running"}
|
|
35
|
+
</span>
|
|
36
|
+
</summary>
|
|
37
|
+
{run.error && (
|
|
38
|
+
<p role="alert" className="text-destructive mt-2 text-sm">
|
|
39
|
+
{run.error}
|
|
40
|
+
</p>
|
|
41
|
+
)}
|
|
42
|
+
{skipped.length > 0 && (
|
|
43
|
+
<div className="mt-2 space-y-1 pb-2">
|
|
44
|
+
<p className="text-sm font-medium">Skipped</p>
|
|
45
|
+
<ul className="text-muted-foreground space-y-1 text-xs">
|
|
46
|
+
{skipped.slice(0, 200).map((s) => (
|
|
47
|
+
<li key={s.path} className="break-all">
|
|
48
|
+
{name(s.path)}: {s.reason}
|
|
49
|
+
</li>
|
|
50
|
+
))}
|
|
51
|
+
</ul>
|
|
52
|
+
</div>
|
|
53
|
+
)}
|
|
54
|
+
{removed.length > 0 && (
|
|
55
|
+
<div className="mt-2 space-y-1 pb-2">
|
|
56
|
+
<p className="text-sm font-medium">Removed</p>
|
|
57
|
+
<ul className="text-muted-foreground space-y-1 text-xs">
|
|
58
|
+
{removed.slice(0, 200).map((r) => (
|
|
59
|
+
<li key={r.path} className="break-all">
|
|
60
|
+
{name(r.path)} ({KIND[r.kind]}, {formatBytes(r.bytes)})
|
|
61
|
+
</li>
|
|
62
|
+
))}
|
|
63
|
+
</ul>
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
66
|
+
</details>
|
|
67
|
+
</li>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Each worktree clean-up run: what it removed, freed and skipped, and why.
|
|
72
|
+
export function SweepRuns() {
|
|
73
|
+
const { data, isPending, error } = useWorktreeSweep();
|
|
74
|
+
const run = useRunSweep();
|
|
75
|
+
return (
|
|
76
|
+
<div className="space-y-3">
|
|
77
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
78
|
+
<p className="text-muted-foreground min-w-0 flex-1 text-sm">
|
|
79
|
+
Settled worktrees older than {data?.days ?? data?.defaultDays ?? 7}{" "}
|
|
80
|
+
days are removed every 6 hours. Branches and conversations stay.
|
|
81
|
+
</p>
|
|
82
|
+
<Button
|
|
83
|
+
variant="outline"
|
|
84
|
+
className="min-h-11 md:min-h-8"
|
|
85
|
+
disabled={run.isPending}
|
|
86
|
+
onClick={() => run.mutate(true)}
|
|
87
|
+
>
|
|
88
|
+
Dry run
|
|
89
|
+
</Button>
|
|
90
|
+
<Button
|
|
91
|
+
className="min-h-11 md:min-h-8"
|
|
92
|
+
disabled={run.isPending}
|
|
93
|
+
onClick={() => run.mutate(false)}
|
|
94
|
+
>
|
|
95
|
+
Run now
|
|
96
|
+
</Button>
|
|
97
|
+
</div>
|
|
98
|
+
{run.error && (
|
|
99
|
+
<p role="alert" className="text-destructive text-sm">
|
|
100
|
+
{run.error.message}
|
|
101
|
+
</p>
|
|
102
|
+
)}
|
|
103
|
+
{error && (
|
|
104
|
+
<p role="alert" className="text-destructive text-sm">
|
|
105
|
+
{error.message}
|
|
106
|
+
</p>
|
|
107
|
+
)}
|
|
108
|
+
{isPending && (
|
|
109
|
+
<div className="bg-muted/40 h-24 animate-pulse rounded-xl" />
|
|
110
|
+
)}
|
|
111
|
+
{data && data.runs.length === 0 && (
|
|
112
|
+
<p className="text-muted-foreground text-sm">
|
|
113
|
+
No run yet. The first one starts 10 minutes after the server does.
|
|
114
|
+
</p>
|
|
115
|
+
)}
|
|
116
|
+
{data && data.runs.length > 0 && (
|
|
117
|
+
<ul className="border-border/60 overflow-hidden rounded-xl border">
|
|
118
|
+
{data.runs.map((r) => (
|
|
119
|
+
<Run key={r.id} run={r} />
|
|
120
|
+
))}
|
|
121
|
+
</ul>
|
|
122
|
+
)}
|
|
123
|
+
</div>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from "@/lib/tasks/merge-methods";
|
|
12
12
|
import { MergeApprovalToggle } from "./MergeApprovalToggle";
|
|
13
13
|
import { MergeSettingsFields } from "./MergeSettingsFields";
|
|
14
|
+
import { GlobalSweepSection } from "./WorktreeSweepSection";
|
|
14
15
|
|
|
15
16
|
// "rebase, keeps worktree"
|
|
16
17
|
function describe(s: MergeSettings): string {
|
|
@@ -85,6 +86,7 @@ export function GlobalMergeSection() {
|
|
|
85
86
|
)}
|
|
86
87
|
</div>
|
|
87
88
|
{data && <Overrides overrides={data.overrides} />}
|
|
89
|
+
<GlobalSweepSection />
|
|
88
90
|
</div>
|
|
89
91
|
);
|
|
90
92
|
}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
useUpdateProjectMergeSettings,
|
|
6
6
|
} from "@/data/merge";
|
|
7
7
|
import { MergeSettingsFields } from "./MergeSettingsFields";
|
|
8
|
+
import { ProjectSweepSection } from "./WorktreeSweepSection";
|
|
8
9
|
|
|
9
10
|
// A project's own merge settings, saved as they change. Unset ones inherit
|
|
10
11
|
// from agentos.json, then the global settings.
|
|
@@ -42,6 +43,7 @@ export function ProjectMergeSection({
|
|
|
42
43
|
<p className="text-destructive text-sm">{update.error.message}</p>
|
|
43
44
|
)}
|
|
44
45
|
<p className="text-muted-foreground text-xs">Saved as you change them.</p>
|
|
46
|
+
<ProjectSweepSection projectId={projectId} />
|
|
45
47
|
</div>
|
|
46
48
|
);
|
|
47
49
|
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { Input } from "@/components/ui/input";
|
|
5
|
+
import { useSetSweepDays, useWorktreeSweep } from "@/data/worktree-sweep";
|
|
6
|
+
import { compactTimeAgo, fromSqliteTime } from "@/lib/session-meta";
|
|
7
|
+
import { formatBytes } from "@/lib/share/view";
|
|
8
|
+
|
|
9
|
+
// Days as the box shows them; empty means "inherit".
|
|
10
|
+
function DaysInput({
|
|
11
|
+
value,
|
|
12
|
+
placeholder,
|
|
13
|
+
disabled,
|
|
14
|
+
onSave,
|
|
15
|
+
}: {
|
|
16
|
+
value: number | null;
|
|
17
|
+
placeholder: string;
|
|
18
|
+
disabled: boolean;
|
|
19
|
+
onSave: (days: number | null) => void;
|
|
20
|
+
}) {
|
|
21
|
+
const [text, setText] = useState(value === null ? "" : String(value));
|
|
22
|
+
const commit = () => {
|
|
23
|
+
const raw = text.trim();
|
|
24
|
+
const next = raw === "" ? null : Number(raw);
|
|
25
|
+
if (next === value || (next !== null && !Number.isInteger(next))) return;
|
|
26
|
+
onSave(next);
|
|
27
|
+
};
|
|
28
|
+
return (
|
|
29
|
+
<Input
|
|
30
|
+
aria-label="Days before a settled worktree is removed"
|
|
31
|
+
inputMode="numeric"
|
|
32
|
+
value={text}
|
|
33
|
+
placeholder={placeholder}
|
|
34
|
+
disabled={disabled}
|
|
35
|
+
onChange={(e) => setText(e.target.value)}
|
|
36
|
+
onBlur={commit}
|
|
37
|
+
onKeyDown={(e) => e.key === "Enter" && e.currentTarget.blur()}
|
|
38
|
+
className="h-11 w-24 text-right tabular-nums md:h-9"
|
|
39
|
+
/>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function LastRun() {
|
|
44
|
+
const { data } = useWorktreeSweep();
|
|
45
|
+
const last = data?.runs.find((r) => r.finishedAt && !r.dryRun);
|
|
46
|
+
if (!last) return null;
|
|
47
|
+
return (
|
|
48
|
+
<p className="text-muted-foreground text-xs">
|
|
49
|
+
Last run {compactTimeAgo(fromSqliteTime(last.finishedAt!))}: removed{" "}
|
|
50
|
+
{last.removed}, {formatBytes(last.freedBytes)} freed, {last.skipped}{" "}
|
|
51
|
+
skipped. Details in Health > Clean-up.
|
|
52
|
+
</p>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// How long a settled worktree stays on disk. The branch and the conversation
|
|
57
|
+
// stay; restoring the session rebuilds it.
|
|
58
|
+
export function GlobalSweepSection() {
|
|
59
|
+
const { data, isPending, isError, error } = useWorktreeSweep();
|
|
60
|
+
const save = useSetSweepDays();
|
|
61
|
+
return (
|
|
62
|
+
<div className="space-y-2">
|
|
63
|
+
<h3 className="text-sm font-medium">Worktree clean-up</h3>
|
|
64
|
+
<p className="text-muted-foreground text-sm">
|
|
65
|
+
Remove the worktree of a merged, dropped, done or archived session this
|
|
66
|
+
many days after it settled, when nothing in it would be lost. 0 turns it
|
|
67
|
+
off.
|
|
68
|
+
</p>
|
|
69
|
+
{isPending && (
|
|
70
|
+
<div className="bg-muted/40 h-11 animate-pulse rounded-lg" />
|
|
71
|
+
)}
|
|
72
|
+
{isError && <p className="text-destructive text-sm">{error.message}</p>}
|
|
73
|
+
{data && (
|
|
74
|
+
<div className="flex items-center gap-3">
|
|
75
|
+
<DaysInput
|
|
76
|
+
key={data.days ?? "none"}
|
|
77
|
+
value={data.days}
|
|
78
|
+
placeholder={String(data.defaultDays)}
|
|
79
|
+
disabled={save.isPending}
|
|
80
|
+
onSave={(days) => save.mutate({ days })}
|
|
81
|
+
/>
|
|
82
|
+
<span className="text-muted-foreground text-sm">days</span>
|
|
83
|
+
</div>
|
|
84
|
+
)}
|
|
85
|
+
{data && data.overrides.length > 0 && (
|
|
86
|
+
<p className="text-muted-foreground text-xs">
|
|
87
|
+
Own number:{" "}
|
|
88
|
+
{data.overrides.map((o) => `${o.name} (${o.days})`).join(", ")}
|
|
89
|
+
</p>
|
|
90
|
+
)}
|
|
91
|
+
{save.isError && (
|
|
92
|
+
<p className="text-destructive text-sm">{save.error.message}</p>
|
|
93
|
+
)}
|
|
94
|
+
<LastRun />
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function ProjectSweepSection({ projectId }: { projectId: string }) {
|
|
100
|
+
const { data } = useWorktreeSweep();
|
|
101
|
+
const save = useSetSweepDays();
|
|
102
|
+
const own =
|
|
103
|
+
data?.overrides.find((o) => o.projectId === projectId)?.days ?? null;
|
|
104
|
+
const inherited = data?.days ?? data?.defaultDays ?? 7;
|
|
105
|
+
return (
|
|
106
|
+
<div className="space-y-2">
|
|
107
|
+
<h3 className="text-sm font-medium">Worktree clean-up</h3>
|
|
108
|
+
<div className="flex items-center gap-3">
|
|
109
|
+
<DaysInput
|
|
110
|
+
key={own ?? "none"}
|
|
111
|
+
value={own}
|
|
112
|
+
placeholder={String(inherited)}
|
|
113
|
+
disabled={!data || save.isPending}
|
|
114
|
+
onSave={(days) => save.mutate({ days, projectId })}
|
|
115
|
+
/>
|
|
116
|
+
<span className="text-muted-foreground text-sm">
|
|
117
|
+
days after a session settles (empty: {inherited}, 0: off)
|
|
118
|
+
</span>
|
|
119
|
+
</div>
|
|
120
|
+
{save.isError && (
|
|
121
|
+
<p className="text-destructive text-sm">{save.error.message}</p>
|
|
122
|
+
)}
|
|
123
|
+
</div>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
@@ -8,7 +8,7 @@ import { useApprovalsModeQuery, useSetApprovalsMode } from "@/data/approvals";
|
|
|
8
8
|
// What it switches off, said plainly before it can be switched on.
|
|
9
9
|
export const NO_APPROVALS_WARNING = [
|
|
10
10
|
"Every ask is approved the moment it is raised, hard lines included: public posts and messages, money, irreversible or destructive changes, credentials.",
|
|
11
|
-
"A PR
|
|
11
|
+
"A PR stuck failing its gates, with no CI, or too big to review still merges.",
|
|
12
12
|
"Brakes stop holding new starts, and plan-only tasks go on to implement without waiting for a plan approval.",
|
|
13
13
|
"Every agent AgentOS starts runs with its permission prompts off, and prompts already waiting are allowed.",
|
|
14
14
|
];
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
2
|
+
import type { SweepState } from "@/lib/worktree-sweep/state";
|
|
3
|
+
|
|
4
|
+
export const sweepKeys = { all: ["worktree-sweep"] as const };
|
|
5
|
+
|
|
6
|
+
async function json<T>(res: Response): Promise<T> {
|
|
7
|
+
const data = await res.json();
|
|
8
|
+
if (!res.ok) throw new Error(data.error || "Request failed");
|
|
9
|
+
return data;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useWorktreeSweep(enabled = true) {
|
|
13
|
+
return useQuery({
|
|
14
|
+
queryKey: sweepKeys.all,
|
|
15
|
+
queryFn: async () => json<SweepState>(await fetch("/api/worktree-sweep")),
|
|
16
|
+
enabled,
|
|
17
|
+
refetchInterval: 60_000,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function useSetSweepDays() {
|
|
22
|
+
const queryClient = useQueryClient();
|
|
23
|
+
return useMutation({
|
|
24
|
+
mutationFn: async (v: { days: number | null; projectId?: string }) =>
|
|
25
|
+
json<SweepState>(
|
|
26
|
+
await fetch("/api/worktree-sweep", {
|
|
27
|
+
method: "PUT",
|
|
28
|
+
headers: { "Content-Type": "application/json" },
|
|
29
|
+
body: JSON.stringify(v),
|
|
30
|
+
})
|
|
31
|
+
),
|
|
32
|
+
onSuccess: (data) => queryClient.setQueryData(sweepKeys.all, data),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function useRunSweep() {
|
|
37
|
+
const queryClient = useQueryClient();
|
|
38
|
+
return useMutation({
|
|
39
|
+
mutationFn: async (dryRun: boolean) =>
|
|
40
|
+
json<{ started: boolean }>(
|
|
41
|
+
await fetch("/api/worktree-sweep/run", {
|
|
42
|
+
method: "POST",
|
|
43
|
+
headers: { "Content-Type": "application/json" },
|
|
44
|
+
body: JSON.stringify({ dryRun }),
|
|
45
|
+
})
|
|
46
|
+
),
|
|
47
|
+
onSuccess: () => {
|
|
48
|
+
// The run is in the background; look again as it goes.
|
|
49
|
+
for (const ms of [1500, 8000, 30000])
|
|
50
|
+
setTimeout(
|
|
51
|
+
() => queryClient.invalidateQueries({ queryKey: sweepKeys.all }),
|
|
52
|
+
ms
|
|
53
|
+
);
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
package/lib/approvals/enable.ts
CHANGED
|
@@ -29,7 +29,7 @@ async function sweep(): Promise<void> {
|
|
|
29
29
|
}
|
|
30
30
|
const plans = db
|
|
31
31
|
.prepare(
|
|
32
|
-
`SELECT id, host_id, plan_waits_for_caller FROM
|
|
32
|
+
`SELECT id, host_id, plan_waits_for_caller FROM session_rows WHERE plan_phase = 'awaiting' AND task_status = 'running'`
|
|
33
33
|
)
|
|
34
34
|
.all() as {
|
|
35
35
|
id: string;
|
package/lib/away/report.ts
CHANGED
|
@@ -92,7 +92,7 @@ export function inFlightWhy(t: {
|
|
|
92
92
|
}): string {
|
|
93
93
|
const escalated = t.failures.filter((f) => f.escalated_at);
|
|
94
94
|
if (escalated.length)
|
|
95
|
-
return `with you: the ${escalated.map((f) => f.gate).join(" and ")} gate
|
|
95
|
+
return `with you: the ${escalated.map((f) => f.gate).join(" and ")} gate kept failing (the loop guard)`;
|
|
96
96
|
if (t.askKinds.length) return `waiting on your answer (${t.askKinds[0]})`;
|
|
97
97
|
const flagged = t.flagged
|
|
98
98
|
? `; flagged to the orchestrator ${t.flagged}x while you were away`
|
|
@@ -137,7 +137,7 @@ function merged(
|
|
|
137
137
|
const rows = db
|
|
138
138
|
.prepare(
|
|
139
139
|
`SELECT s.id, s.name, p.name AS project, p.id AS project_id, s.pr_number, s.pr_url
|
|
140
|
-
FROM
|
|
140
|
+
FROM session_rows s JOIN projects p ON p.id = s.project_id
|
|
141
141
|
WHERE p.workspace_id = ? AND s.task_status = 'merged'
|
|
142
142
|
AND s.merged_at >= ? AND s.merged_at <= ?
|
|
143
143
|
ORDER BY s.merged_at, s.id`
|
|
@@ -178,7 +178,7 @@ function inFlight(workspaceId: string, awayId: number): InFlightItem[] {
|
|
|
178
178
|
.prepare(
|
|
179
179
|
`SELECT s.id, s.name, p.name AS project, s.pr_number, s.pr_url,
|
|
180
180
|
s.merge_armed_at AS armed_at
|
|
181
|
-
FROM
|
|
181
|
+
FROM session_rows s JOIN projects p ON p.id = s.project_id
|
|
182
182
|
WHERE p.workspace_id = ? AND s.role IS NULL AND s.archived_at IS NULL
|
|
183
183
|
AND s.task_status = 'running'
|
|
184
184
|
AND EXISTS (SELECT 1 FROM away_actions a WHERE a.away_id = ?
|
|
@@ -224,7 +224,7 @@ function failed(workspaceId: string, away: AwayRow, end: string) {
|
|
|
224
224
|
const deploys = db
|
|
225
225
|
.prepare(
|
|
226
226
|
`SELECT a.detail, s.name, s.pr_number, s.pr_url FROM away_actions a
|
|
227
|
-
LEFT JOIN
|
|
227
|
+
LEFT JOIN session_rows s ON s.id = a.subject
|
|
228
228
|
WHERE a.away_id = ? AND a.workspace_id = ? AND a.kind = 'deploy_failed'
|
|
229
229
|
ORDER BY a.id`
|
|
230
230
|
)
|
|
@@ -236,7 +236,7 @@ function failed(workspaceId: string, away: AwayRow, end: string) {
|
|
|
236
236
|
}[];
|
|
237
237
|
const ended = db
|
|
238
238
|
.prepare(
|
|
239
|
-
`SELECT s.name, s.task_status, s.pr_number, s.pr_url FROM
|
|
239
|
+
`SELECT s.name, s.task_status, s.pr_number, s.pr_url FROM session_rows s
|
|
240
240
|
JOIN projects p ON p.id = s.project_id
|
|
241
241
|
WHERE p.workspace_id = ? AND s.task_status IN ('dropped', 'done')
|
|
242
242
|
AND s.task_ended_at >= ? AND s.task_ended_at <= ?
|
package/lib/away/run.ts
CHANGED
|
@@ -69,7 +69,7 @@ function orchestrated(): { workspaceId: string; orchestratorId: string }[] {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
const sessionRow = (id: string) =>
|
|
72
|
-
db.prepare(`SELECT * FROM
|
|
72
|
+
db.prepare(`SELECT * FROM session_rows WHERE id = ?`).get(id) as
|
|
73
73
|
| Session
|
|
74
74
|
| undefined;
|
|
75
75
|
|
|
@@ -330,7 +330,7 @@ async function checkDeploys(
|
|
|
330
330
|
const found: DeployFinding[] = [];
|
|
331
331
|
const merged = db
|
|
332
332
|
.prepare(
|
|
333
|
-
`SELECT s.* FROM
|
|
333
|
+
`SELECT s.* FROM session_rows s JOIN projects p ON p.id = s.project_id
|
|
334
334
|
WHERE p.workspace_id = ? AND s.task_status = 'merged' AND s.merged_at >= ?
|
|
335
335
|
AND NOT EXISTS (SELECT 1 FROM away_actions a
|
|
336
336
|
WHERE a.away_id = ? AND a.key = 'deploy:' || s.id)`
|
package/lib/bookmarks/store.ts
CHANGED
|
@@ -57,7 +57,7 @@ type Joined = Row & {
|
|
|
57
57
|
// The bookmark with its chat's state as of now.
|
|
58
58
|
const SELECT = `
|
|
59
59
|
SELECT b.*, s.id AS s_id, s.task_status, s.archived_at
|
|
60
|
-
FROM chat_bookmarks b LEFT JOIN
|
|
60
|
+
FROM chat_bookmarks b LEFT JOIN session_rows s ON s.id = b.session_id`;
|
|
61
61
|
|
|
62
62
|
function toBookmark(r: Joined): Bookmark {
|
|
63
63
|
return {
|
|
@@ -141,7 +141,7 @@ export function createBookmark(input: {
|
|
|
141
141
|
throw new BookmarkError("scope must be 'item' or 'turn'", 400);
|
|
142
142
|
const note = cleanNote(input.note);
|
|
143
143
|
const session = db
|
|
144
|
-
.prepare(`SELECT * FROM
|
|
144
|
+
.prepare(`SELECT * FROM session_rows WHERE id = ?`)
|
|
145
145
|
.get(sessionId) as Session | undefined;
|
|
146
146
|
if (!session) throw new BookmarkError("Session not found", 404);
|
|
147
147
|
const existing = db
|
|
@@ -308,7 +308,7 @@ export function insertMovedBookmarks(
|
|
|
308
308
|
): void {
|
|
309
309
|
if (!rows.length) return;
|
|
310
310
|
const session = db
|
|
311
|
-
.prepare(`SELECT * FROM
|
|
311
|
+
.prepare(`SELECT * FROM session_rows WHERE id = ?`)
|
|
312
312
|
.get(sessionId) as Session | undefined;
|
|
313
313
|
if (!session) return;
|
|
314
314
|
const workspace = sessionWorkspace(session);
|
package/lib/bus/held-reply.ts
CHANGED
|
@@ -68,7 +68,7 @@ export function pendingReplies(device: string, limit = 50): PendingReply[] {
|
|
|
68
68
|
db
|
|
69
69
|
.prepare(
|
|
70
70
|
`SELECT m.id, m.from_id AS fromId, m.body, m.created_at AS createdAt
|
|
71
|
-
FROM bus_messages m JOIN
|
|
71
|
+
FROM bus_messages m JOIN session_rows s ON s.id = m.from_id
|
|
72
72
|
WHERE m.relay_state = 'pending' AND s.held_by_device = ?
|
|
73
73
|
ORDER BY m.id LIMIT ?`
|
|
74
74
|
)
|
|
@@ -81,7 +81,7 @@ export function ackReplies(device: string, ids: number[]): void {
|
|
|
81
81
|
const mark = db.prepare(
|
|
82
82
|
`UPDATE bus_messages SET relay_state = 'relayed', delivered_at = datetime('now')
|
|
83
83
|
WHERE id = ? AND relay_state = 'pending'
|
|
84
|
-
AND from_id IN (SELECT id FROM
|
|
84
|
+
AND from_id IN (SELECT id FROM session_rows WHERE held_by_device = ?)`
|
|
85
85
|
);
|
|
86
86
|
db.transaction(() => {
|
|
87
87
|
for (const id of ids) mark.run(id, device);
|
package/lib/bus/index.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { deliverToPane, type Delivery } from "./delivery";
|
|
|
15
15
|
import { resolveRef, wasNames, type Candidate } from "./resolve";
|
|
16
16
|
import { tmuxPane } from "./tmux-pane";
|
|
17
17
|
import { pauseHoldOf, pausedNote } from "../tasks/pause-hold";
|
|
18
|
-
import { isHeldRemote, relayToHeldTask } from "../tasks/held-
|
|
18
|
+
import { isHeldRemote, relayToHeldTask } from "../tasks/contract/held-calls";
|
|
19
19
|
import {
|
|
20
20
|
assertTakesMessages,
|
|
21
21
|
findQueuedTarget,
|
|
@@ -43,7 +43,7 @@ export interface Peer {
|
|
|
43
43
|
function liveSessions(): Session[] {
|
|
44
44
|
return db
|
|
45
45
|
.prepare(
|
|
46
|
-
`SELECT * FROM
|
|
46
|
+
`SELECT * FROM session_rows WHERE (task_status IS NULL OR task_status = 'running')
|
|
47
47
|
AND archived_at IS NULL
|
|
48
48
|
ORDER BY updated_at DESC`
|
|
49
49
|
)
|
|
@@ -63,7 +63,7 @@ function candidates(sessions: Session[]): Candidate[] {
|
|
|
63
63
|
|
|
64
64
|
const sessionById = (id: string | null | undefined) =>
|
|
65
65
|
id
|
|
66
|
-
? (db.prepare(`SELECT * FROM
|
|
66
|
+
? (db.prepare(`SELECT * FROM session_rows WHERE id = ?`).get(id) as
|
|
67
67
|
| Session
|
|
68
68
|
| undefined)
|
|
69
69
|
: undefined;
|
|
@@ -28,7 +28,7 @@ async function gitStatus(cwd: string): Promise<string> {
|
|
|
28
28
|
export async function compactContext(sessionId: string): Promise<string> {
|
|
29
29
|
const session = db
|
|
30
30
|
.prepare(
|
|
31
|
-
`SELECT working_directory, task_prompt, role FROM
|
|
31
|
+
`SELECT working_directory, task_prompt, role FROM session_rows WHERE id = ?`
|
|
32
32
|
)
|
|
33
33
|
.get(sessionId) as
|
|
34
34
|
| Pick<Session, "working_directory" | "task_prompt" | "role">
|
package/lib/chat/hold.ts
CHANGED
|
@@ -15,10 +15,10 @@ import { db } from "../db";
|
|
|
15
15
|
|
|
16
16
|
// The SQL form, for queries that take the session id as @sid.
|
|
17
17
|
export const HELD_SQL = `(
|
|
18
|
-
EXISTS (SELECT 1 FROM
|
|
18
|
+
EXISTS (SELECT 1 FROM session_rows WHERE id = @sid AND task_status = 'moving')
|
|
19
19
|
OR EXISTS (SELECT 1 FROM stack_items i JOIN stacks s ON s.id = i.stack_id
|
|
20
20
|
WHERE i.session_id = @sid AND s.status = 'landing')
|
|
21
|
-
OR EXISTS (SELECT 1 FROM
|
|
21
|
+
OR EXISTS (SELECT 1 FROM session_rows WHERE id = @sid
|
|
22
22
|
AND pause_state IN ('pausing', 'paused'))
|
|
23
23
|
)`;
|
|
24
24
|
|
|
@@ -58,7 +58,7 @@ const holdRow = (sessionId: string) =>
|
|
|
58
58
|
`SELECT task_status, moved_to, quick_of, pause_state, paused_until,
|
|
59
59
|
EXISTS (SELECT 1 FROM stack_items i JOIN stacks s ON s.id = i.stack_id
|
|
60
60
|
WHERE i.session_id = @sid AND s.status = 'landing') AS landing
|
|
61
|
-
FROM
|
|
61
|
+
FROM session_rows WHERE id = @sid`
|
|
62
62
|
)
|
|
63
63
|
.get({ sid: sessionId }) as HoldRow | undefined;
|
|
64
64
|
|
package/lib/chat/refresh.ts
CHANGED
|
@@ -163,7 +163,7 @@ export function autoCompactEnv(sessionId: string): Record<string, string> {
|
|
|
163
163
|
const row = db
|
|
164
164
|
.prepare(
|
|
165
165
|
`SELECT role, task_status, plan_phase, started_by, model_provider
|
|
166
|
-
FROM
|
|
166
|
+
FROM session_rows WHERE id = ?`
|
|
167
167
|
)
|
|
168
168
|
.get(sessionId) as
|
|
169
169
|
| {
|
package/lib/chat/registry.ts
CHANGED
|
@@ -112,9 +112,9 @@ export function emit(sessionId: string, m: ChatServerMessage): void {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
export function getSession(sessionId: string): Session {
|
|
115
|
-
const s = db
|
|
116
|
-
|
|
117
|
-
| undefined;
|
|
115
|
+
const s = db
|
|
116
|
+
.prepare(`SELECT * FROM session_rows WHERE id = ?`)
|
|
117
|
+
.get(sessionId) as Session | undefined;
|
|
118
118
|
if (!s) throw new Error("Session not found");
|
|
119
119
|
return s;
|
|
120
120
|
}
|
package/lib/chat/runner.ts
CHANGED
|
@@ -549,7 +549,7 @@ async function resumeCutOff(sessionId: string): Promise<void> {
|
|
|
549
549
|
.prepare(
|
|
550
550
|
`SELECT s.name, s.view, s.archived_at, s.task_status, s.role,
|
|
551
551
|
COALESCE(s.workspace_id, p.workspace_id) AS workspace_id
|
|
552
|
-
FROM
|
|
552
|
+
FROM session_rows s LEFT JOIN projects p ON p.id = s.project_id
|
|
553
553
|
WHERE s.id = ?`
|
|
554
554
|
)
|
|
555
555
|
.get(sessionId) as
|
package/lib/chat/worker/main.ts
CHANGED
|
@@ -30,7 +30,7 @@ async function main(sessionId: string) {
|
|
|
30
30
|
const { ChatHost } = await import("./host");
|
|
31
31
|
|
|
32
32
|
const row = db
|
|
33
|
-
.prepare(`SELECT * FROM
|
|
33
|
+
.prepare(`SELECT * FROM session_rows WHERE id = ?`)
|
|
34
34
|
.get(sessionId) as Session | undefined;
|
|
35
35
|
if (!row) {
|
|
36
36
|
console.error(`No session ${sessionId}`);
|
package/lib/db/backup.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type Database from "better-sqlite3";
|
|
2
|
+
import Sqlite from "better-sqlite3";
|
|
3
|
+
import { randomBytes } from "crypto";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import os from "os";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { refuseRealStateUnderTest } from "../test-home-guard";
|
|
8
|
+
|
|
9
|
+
export const backupsDir = (): string =>
|
|
10
|
+
refuseRealStateUnderTest(path.join(os.homedir(), ".agent-os", "backups"));
|
|
11
|
+
|
|
12
|
+
// A full copy of the database (VACUUM INTO) under ~/.agent-os/backups, taken
|
|
13
|
+
// before a destructive migration. Throws, and the migration must stop, unless
|
|
14
|
+
// the copy exists and holds every session. The message never says "already
|
|
15
|
+
// exists": the migration runner reads that as applied.
|
|
16
|
+
export function backupDatabase(
|
|
17
|
+
db: Database.Database,
|
|
18
|
+
label: string,
|
|
19
|
+
dir: string = backupsDir()
|
|
20
|
+
): string {
|
|
21
|
+
const stamp = new Date().toISOString().replace(/[-:]/g, "").slice(0, 15);
|
|
22
|
+
const file = path.join(
|
|
23
|
+
dir,
|
|
24
|
+
`agent-os-${stamp}-${process.pid}-${randomBytes(3).toString("hex")}-${label}.db`
|
|
25
|
+
);
|
|
26
|
+
try {
|
|
27
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
28
|
+
db.prepare(`VACUUM INTO ?`).run(file);
|
|
29
|
+
fs.chmodSync(file, 0o600);
|
|
30
|
+
const copy = new Sqlite(file, { readonly: true });
|
|
31
|
+
try {
|
|
32
|
+
const count = (sql: Database.Database) =>
|
|
33
|
+
(
|
|
34
|
+
sql.prepare(`SELECT COUNT(*) AS n FROM sessions`).get() as {
|
|
35
|
+
n: number;
|
|
36
|
+
}
|
|
37
|
+
).n;
|
|
38
|
+
if (count(copy) !== count(db)) throw new Error("session count differs");
|
|
39
|
+
} finally {
|
|
40
|
+
copy.close();
|
|
41
|
+
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Backup before ${label} failed (${file}): ${reason.replace(/already exists/gi, "is present")}. Nothing was changed.`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
return file;
|
|
49
|
+
}
|
package/lib/db/index.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import Database from "better-sqlite3";
|
|
2
|
-
import path from "path";
|
|
3
2
|
import fs from "fs";
|
|
4
3
|
import { createSchema } from "./schema";
|
|
5
4
|
import { runMigrations } from "./migrations";
|
|
6
5
|
import { dbPath } from "./path";
|
|
7
6
|
|
|
7
|
+
import { machineDb } from "./machine-scope";
|
|
8
|
+
|
|
8
9
|
// Re-export types and queries
|
|
9
10
|
export * from "./types";
|
|
10
11
|
export { queries, sessionListColumns } from "./queries";
|
|
11
12
|
export * from "./lumifyhub";
|
|
12
13
|
export * from "./lumifyhub-docs";
|
|
13
14
|
export * from "./stacks";
|
|
15
|
+
export * from "./session-parts";
|
|
14
16
|
|
|
15
17
|
const DB_PATH = dbPath();
|
|
16
18
|
const LOCK_PATH = DB_PATH + ".init-lock";
|
|
@@ -89,6 +91,8 @@ export function initDb(): Database.Database {
|
|
|
89
91
|
let _db: Database.Database | null = null;
|
|
90
92
|
|
|
91
93
|
export function getDb(): Database.Database {
|
|
94
|
+
const scoped = machineDb.getStore();
|
|
95
|
+
if (scoped) return scoped;
|
|
92
96
|
if (!_db) {
|
|
93
97
|
_db = initDb();
|
|
94
98
|
}
|