@gmickel/gno 2.4.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -6
- package/assets/skill/README.md +5 -1
- package/assets/skill/SKILL.md +98 -1
- package/assets/skill/cli-reference.md +140 -0
- package/assets/skill/examples.md +45 -0
- package/assets/skill/mcp-reference.md +62 -0
- package/assets/skill/recipes/capture-and-file.md +6 -0
- package/assets/skill/recipes/memory-file-decision.md +6 -0
- package/assets/skill/recipes/memory-supersede-fact.md +5 -0
- package/assets/skill/recipes/session-evidence-lookup.md +98 -0
- package/assets/spa-production.json.gz +0 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v2.4.0.zip → gno-browser-clipper-v2.6.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.6.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +2 -1
- package/spec/cli.md +358 -24
- package/spec/compiled-context.md +68 -0
- package/spec/mcp.md +223 -2
- package/spec/output-schemas/capture-receipt.schema.json +3 -0
- package/spec/output-schemas/compiled-context-check.schema.json +44 -0
- package/spec/output-schemas/compiled-context-file.schema.json +165 -0
- package/spec/output-schemas/compiled-context-preview.schema.json +142 -0
- package/spec/output-schemas/mcp-capture-result.schema.json +3 -0
- package/spec/output-schemas/memory-remember.schema.json +8 -2
- package/spec/output-schemas/request-status.schema.json +113 -0
- package/spec/output-schemas/sessions-automation-run.schema.json +46 -0
- package/spec/output-schemas/sessions-discovery.schema.json +38 -0
- package/spec/output-schemas/sessions-import-receipt.schema.json +156 -0
- package/spec/output-schemas/sessions-status.schema.json +432 -0
- package/src/app/compiled-context-files.ts +361 -0
- package/src/app/compiled-context.ts +240 -0
- package/src/app/context-surface.ts +7 -2
- package/src/cli/commands/ask.ts +14 -2
- package/src/cli/commands/audit.ts +4 -0
- package/src/cli/commands/capture.ts +55 -96
- package/src/cli/commands/context-compiled.ts +136 -0
- package/src/cli/commands/daemon.ts +41 -0
- package/src/cli/commands/ls.ts +3 -0
- package/src/cli/commands/memory.ts +12 -3
- package/src/cli/commands/request-status.ts +59 -0
- package/src/cli/commands/reset.ts +39 -5
- package/src/cli/commands/sessions.ts +713 -0
- package/src/cli/commands/shared.ts +14 -1
- package/src/cli/errors.ts +10 -3
- package/src/cli/program.ts +458 -1
- package/src/cli/session-binding.ts +49 -0
- package/src/config/types.ts +8 -0
- package/src/core/capture-publish.ts +239 -0
- package/src/core/capture-sync.ts +3 -0
- package/src/core/compiled-context.ts +254 -0
- package/src/core/context-budget.ts +2 -10
- package/src/core/file-lock.ts +22 -5
- package/src/core/folder-setup-planning.ts +2 -1
- package/src/core/memory-remember.ts +233 -122
- package/src/core/memory-types.ts +11 -0
- package/src/core/network-boundary-inventory.ts +16 -0
- package/src/core/request-receipts.ts +671 -0
- package/src/core/setup-receipt.ts +27 -20
- package/src/core/typed-metadata.ts +4 -0
- package/src/core/validation.ts +9 -2
- package/src/core/windows-private-path.ts +96 -0
- package/src/index.ts +11 -2
- package/src/ingestion/compiled-context.ts +15 -0
- package/src/ingestion/sync.ts +40 -8
- package/src/ingestion/walker.ts +5 -4
- package/src/llm/nodeLlamaCpp/simulator-install.ts +6 -2
- package/src/mcp/context.ts +8 -0
- package/src/mcp/http-egress.ts +15 -3
- package/src/mcp/http-transport.ts +2 -0
- package/src/mcp/retrieval-warnings.ts +24 -0
- package/src/mcp/tools/ask.ts +14 -1
- package/src/mcp/tools/capture.ts +87 -83
- package/src/mcp/tools/context.ts +46 -2
- package/src/mcp/tools/index.ts +119 -7
- package/src/mcp/tools/memory-remember.ts +7 -0
- package/src/mcp/tools/memory-shared.ts +7 -1
- package/src/mcp/tools/query.ts +3 -1
- package/src/mcp/tools/request-status.ts +73 -0
- package/src/mcp/tools/search.ts +3 -1
- package/src/mcp/tools/sessions.ts +208 -0
- package/src/mcp/tools/vsearch.ts +3 -1
- package/src/sdk/client.ts +252 -89
- package/src/sdk/index.ts +13 -0
- package/src/sdk/types.ts +82 -3
- package/src/serve/capture-service.ts +98 -32
- package/src/serve/compiled-context.ts +84 -0
- package/src/serve/config-sync.ts +3 -2
- package/src/serve/public/app.tsx +15 -1
- package/src/serve/public/components/CaptureModal.tsx +26 -8
- package/src/serve/public/components/sessions/AutomationPanel.tsx +800 -0
- package/src/serve/public/components/sessions/ImportReceipt.tsx +238 -0
- package/src/serve/public/components/sessions/SessionSearch.tsx +286 -0
- package/src/serve/public/components/sessions/SourcesPanel.tsx +541 -0
- package/src/serve/public/components/sessions/api.ts +40 -0
- package/src/serve/public/components/sessions/snippet.tsx +53 -0
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/hooks/use-api.ts +10 -2
- package/src/serve/public/lib/request-intent.ts +69 -0
- package/src/serve/public/lib/workspace-actions.ts +12 -1
- package/src/serve/public/lib/workspace-tabs.ts +4 -0
- package/src/serve/public/pages/CompiledContext.tsx +364 -0
- package/src/serve/public/pages/Dashboard.tsx +17 -0
- package/src/serve/public/pages/DocView.tsx +15 -1
- package/src/serve/public/pages/DocumentEditor.tsx +139 -96
- package/src/serve/public/pages/Sessions.tsx +350 -0
- package/src/serve/resident-runtime.ts +43 -3
- package/src/serve/routes/api.ts +476 -147
- package/src/serve/routes/sessions.ts +766 -0
- package/src/serve/security.ts +9 -0
- package/src/serve/server.ts +248 -3
- package/src/serve/session-automation.ts +146 -0
- package/src/serve/spa-production-build.ts +6 -5
- package/src/sessions/archive.ts +348 -0
- package/src/sessions/automation-state.ts +444 -0
- package/src/sessions/automation-status.ts +239 -0
- package/src/sessions/automation.ts +1169 -0
- package/src/sessions/binding.ts +105 -0
- package/src/sessions/claude-hook.ts +240 -0
- package/src/sessions/config.ts +176 -0
- package/src/sessions/format.ts +191 -0
- package/src/sessions/import-child-env.ts +8 -0
- package/src/sessions/import-child.ts +152 -0
- package/src/sessions/parsers/claude-code.ts +259 -0
- package/src/sessions/parsers/codex.ts +303 -0
- package/src/sessions/parsers/hermes.ts +248 -0
- package/src/sessions/parsers/openclaw.ts +496 -0
- package/src/sessions/parsers/shared.ts +184 -0
- package/src/sessions/sanitize.ts +222 -0
- package/src/sessions/service.ts +1533 -0
- package/src/sessions/setup.ts +477 -0
- package/src/sessions/sources.ts +518 -0
- package/src/sessions/state.ts +118 -0
- package/src/sessions/types.ts +457 -0
- package/src/store/sqlite/adapter.ts +68 -16
- package/src/store/sqlite/scoped-index.ts +9 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.4.0.zip.sha256 +0 -1
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ArchiveIcon,
|
|
3
|
+
InfoIcon,
|
|
4
|
+
Loader2Icon,
|
|
5
|
+
MessagesSquareIcon,
|
|
6
|
+
} from "lucide-react";
|
|
7
|
+
import {
|
|
8
|
+
type FormEvent,
|
|
9
|
+
useCallback,
|
|
10
|
+
useEffect,
|
|
11
|
+
useRef,
|
|
12
|
+
useState,
|
|
13
|
+
} from "react";
|
|
14
|
+
|
|
15
|
+
import type {
|
|
16
|
+
SessionImportReceipt,
|
|
17
|
+
SessionsStatus,
|
|
18
|
+
} from "../components/sessions/api";
|
|
19
|
+
|
|
20
|
+
import { sessionsApi } from "../components/sessions/api";
|
|
21
|
+
import { AutomationPanel } from "../components/sessions/AutomationPanel";
|
|
22
|
+
import { SessionSearch } from "../components/sessions/SessionSearch";
|
|
23
|
+
import { SourcesPanel } from "../components/sessions/SourcesPanel";
|
|
24
|
+
import { Badge } from "../components/ui/badge";
|
|
25
|
+
import { Button } from "../components/ui/button";
|
|
26
|
+
import { Card, CardContent } from "../components/ui/card";
|
|
27
|
+
import { Input } from "../components/ui/input";
|
|
28
|
+
import { fetchServerCapabilities } from "../lib/server-capabilities";
|
|
29
|
+
|
|
30
|
+
interface PageProps {
|
|
31
|
+
navigate: (to: string | number) => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Codes meaning "this instance is not a usable session archive". */
|
|
35
|
+
const UNBOUND_CODES = new Set([
|
|
36
|
+
"SESSIONS_NOT_CONFIGURED",
|
|
37
|
+
"SESSIONS_BINDING_MISMATCH",
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
const INIT_COMMAND =
|
|
41
|
+
"gno --config <archive.yml> --index sessions sessions init --archive <dir> --collection <name>";
|
|
42
|
+
const SERVE_COMMAND = "gno --config <archive.yml> --index sessions serve";
|
|
43
|
+
|
|
44
|
+
function CommandLine({ children }: { children: string }) {
|
|
45
|
+
return (
|
|
46
|
+
<code className="block whitespace-pre-wrap break-all rounded-md border border-border/60 bg-muted/40 px-3 py-2 font-mono text-xs">
|
|
47
|
+
{children}
|
|
48
|
+
</code>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function InitForm({ onDone }: { onDone: () => Promise<void> }) {
|
|
53
|
+
const [archive, setArchive] = useState("");
|
|
54
|
+
const [collection, setCollection] = useState("sessions");
|
|
55
|
+
const [error, setError] = useState<string | null>(null);
|
|
56
|
+
const [saving, setSaving] = useState(false);
|
|
57
|
+
|
|
58
|
+
const submit = async (event: FormEvent<HTMLFormElement>) => {
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
setSaving(true);
|
|
61
|
+
const result = await sessionsApi("/api/sessions/init", {
|
|
62
|
+
method: "POST",
|
|
63
|
+
body: JSON.stringify({
|
|
64
|
+
archive: archive.trim(),
|
|
65
|
+
collection: collection.trim(),
|
|
66
|
+
}),
|
|
67
|
+
});
|
|
68
|
+
setSaving(false);
|
|
69
|
+
if (result.error) {
|
|
70
|
+
setError(result.error);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
setError(null);
|
|
74
|
+
await onDone();
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<form
|
|
79
|
+
aria-label="Create session archive"
|
|
80
|
+
className="grid gap-3 sm:grid-cols-2"
|
|
81
|
+
onSubmit={(event) => void submit(event)}
|
|
82
|
+
>
|
|
83
|
+
<label className="grid gap-1 text-sm" htmlFor="sessions-init-archive">
|
|
84
|
+
Archive directory (absolute path)
|
|
85
|
+
<Input
|
|
86
|
+
id="sessions-init-archive"
|
|
87
|
+
onChange={(event) => setArchive(event.currentTarget.value)}
|
|
88
|
+
placeholder="/home/me/agent-archive"
|
|
89
|
+
required
|
|
90
|
+
value={archive}
|
|
91
|
+
/>
|
|
92
|
+
</label>
|
|
93
|
+
<label className="grid gap-1 text-sm" htmlFor="sessions-init-collection">
|
|
94
|
+
First archive collection
|
|
95
|
+
<Input
|
|
96
|
+
id="sessions-init-collection"
|
|
97
|
+
onChange={(event) => setCollection(event.currentTarget.value)}
|
|
98
|
+
pattern="[a-z0-9][a-z0-9_-]{0,63}"
|
|
99
|
+
required
|
|
100
|
+
value={collection}
|
|
101
|
+
/>
|
|
102
|
+
</label>
|
|
103
|
+
{error && (
|
|
104
|
+
<p
|
|
105
|
+
className="break-words text-destructive text-sm sm:col-span-2"
|
|
106
|
+
role="alert"
|
|
107
|
+
>
|
|
108
|
+
{error}
|
|
109
|
+
</p>
|
|
110
|
+
)}
|
|
111
|
+
<div className="sm:col-span-2">
|
|
112
|
+
<Button disabled={saving} type="submit">
|
|
113
|
+
{saving && <Loader2Icon className="animate-spin" />}
|
|
114
|
+
Create archive for this instance
|
|
115
|
+
</Button>
|
|
116
|
+
<p className="mt-2 text-muted-foreground text-xs">
|
|
117
|
+
Only works when this server was started with a dedicated archive
|
|
118
|
+
config and a named index; the default config is refused.
|
|
119
|
+
</p>
|
|
120
|
+
</div>
|
|
121
|
+
</form>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export default function Sessions({ navigate }: PageProps) {
|
|
126
|
+
const [status, setStatus] = useState<SessionsStatus | null>(null);
|
|
127
|
+
const [unbound, setUnbound] = useState<string | null>(null);
|
|
128
|
+
const [error, setError] = useState<string | null>(null);
|
|
129
|
+
const [loading, setLoading] = useState(true);
|
|
130
|
+
const [localClient, setLocalClient] = useState(false);
|
|
131
|
+
const [busy, setBusy] = useState<string | null>(null);
|
|
132
|
+
const [receipts, setReceipts] = useState<
|
|
133
|
+
Record<string, SessionImportReceipt>
|
|
134
|
+
>({});
|
|
135
|
+
// Bumped when an action (import/remove) fails so focus moves to the alert
|
|
136
|
+
// instead of dropping to <body> from the disabled button.
|
|
137
|
+
const [actionErrorCount, setActionErrorCount] = useState(0);
|
|
138
|
+
const errorRef = useRef<HTMLDivElement>(null);
|
|
139
|
+
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
if (actionErrorCount > 0) errorRef.current?.focus();
|
|
142
|
+
}, [actionErrorCount]);
|
|
143
|
+
|
|
144
|
+
const loadStatus = useCallback(async () => {
|
|
145
|
+
const result = await sessionsApi<SessionsStatus>("/api/sessions/status");
|
|
146
|
+
setLoading(false);
|
|
147
|
+
if (result.data) {
|
|
148
|
+
setStatus(result.data);
|
|
149
|
+
setUnbound(null);
|
|
150
|
+
setError(null);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (result.sessionsCode && UNBOUND_CODES.has(result.sessionsCode)) {
|
|
154
|
+
setStatus(null);
|
|
155
|
+
setUnbound(result.error);
|
|
156
|
+
setError(null);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
// A transient failure (for example during background polling) keeps the
|
|
160
|
+
// last good status on screen, so run monitoring and controls survive.
|
|
161
|
+
setError(result.error);
|
|
162
|
+
}, []);
|
|
163
|
+
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
void loadStatus();
|
|
166
|
+
void fetchServerCapabilities().then(({ data }) => {
|
|
167
|
+
setLocalClient(data?.localClient === true);
|
|
168
|
+
});
|
|
169
|
+
}, [loadStatus]);
|
|
170
|
+
|
|
171
|
+
const runImport = async (sourceId: string, dryRun: boolean) => {
|
|
172
|
+
setBusy(`${sourceId}:${dryRun ? "preview" : "import"}`);
|
|
173
|
+
const result = await sessionsApi<SessionImportReceipt>(
|
|
174
|
+
"/api/sessions/import",
|
|
175
|
+
{
|
|
176
|
+
method: "POST",
|
|
177
|
+
body: JSON.stringify({ sourceId, dryRun }),
|
|
178
|
+
}
|
|
179
|
+
);
|
|
180
|
+
setBusy(null);
|
|
181
|
+
if (!result.data) {
|
|
182
|
+
setError(result.error);
|
|
183
|
+
setActionErrorCount((count) => count + 1);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const receipt = result.data;
|
|
187
|
+
setError(null);
|
|
188
|
+
setReceipts((current) => ({ ...current, [sourceId]: receipt }));
|
|
189
|
+
if (!dryRun) await loadStatus();
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const removeSource = async (sourceId: string) => {
|
|
193
|
+
setBusy(`${sourceId}:remove`);
|
|
194
|
+
const result = await sessionsApi(
|
|
195
|
+
`/api/sessions/sources/${encodeURIComponent(sourceId)}`,
|
|
196
|
+
{ method: "DELETE" }
|
|
197
|
+
);
|
|
198
|
+
setBusy(null);
|
|
199
|
+
if (result.error) {
|
|
200
|
+
setError(result.error);
|
|
201
|
+
setActionErrorCount((count) => count + 1);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
setReceipts((current) => {
|
|
205
|
+
const next = { ...current };
|
|
206
|
+
delete next[sourceId];
|
|
207
|
+
return next;
|
|
208
|
+
});
|
|
209
|
+
await loadStatus();
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const archiveCollections = status?.collections.map((c) => c.name) ?? [];
|
|
213
|
+
|
|
214
|
+
return (
|
|
215
|
+
<div className="min-h-screen">
|
|
216
|
+
<main className="mx-auto max-w-5xl space-y-8 px-4 py-6 sm:p-8">
|
|
217
|
+
<header className="flex flex-wrap items-start justify-between gap-4">
|
|
218
|
+
<div className="min-w-0 max-w-3xl space-y-3">
|
|
219
|
+
<div className="flex items-center gap-2 text-primary">
|
|
220
|
+
<ArchiveIcon aria-hidden="true" className="size-4" />
|
|
221
|
+
<span className="font-medium text-sm tracking-wide uppercase">
|
|
222
|
+
Agent session archive
|
|
223
|
+
</span>
|
|
224
|
+
</div>
|
|
225
|
+
<h1 className="font-semibold text-2xl tracking-tight sm:text-3xl">
|
|
226
|
+
Agent sessions
|
|
227
|
+
</h1>
|
|
228
|
+
{status && (
|
|
229
|
+
<p className="flex flex-wrap items-center gap-2 text-sm">
|
|
230
|
+
<span className="text-muted-foreground">Archive index</span>
|
|
231
|
+
<Badge className="font-mono" variant="secondary">
|
|
232
|
+
{status.index}
|
|
233
|
+
</Badge>
|
|
234
|
+
<span className="text-muted-foreground">
|
|
235
|
+
· {status.collections.length} collection
|
|
236
|
+
{status.collections.length === 1 ? "" : "s"}
|
|
237
|
+
</span>
|
|
238
|
+
</p>
|
|
239
|
+
)}
|
|
240
|
+
<p className="text-muted-foreground text-sm">
|
|
241
|
+
Import is manual by default. Nothing watches your agents or
|
|
242
|
+
imports on its own unless you switch on a hook or schedule below.
|
|
243
|
+
</p>
|
|
244
|
+
</div>
|
|
245
|
+
<Button onClick={() => navigate("/")} variant="outline">
|
|
246
|
+
Back to Dashboard
|
|
247
|
+
</Button>
|
|
248
|
+
</header>
|
|
249
|
+
|
|
250
|
+
<aside className="flex gap-3 rounded-lg border border-border/60 bg-muted/30 p-4 text-sm">
|
|
251
|
+
<InfoIcon
|
|
252
|
+
aria-hidden="true"
|
|
253
|
+
className="mt-0.5 size-4 shrink-0 text-primary"
|
|
254
|
+
/>
|
|
255
|
+
<p className="min-w-0">
|
|
256
|
+
This archive index is separate from your curated index. Broad search
|
|
257
|
+
in a curated instance does not include archived sessions unless you
|
|
258
|
+
explicitly register an archive collection in that index. Human turns
|
|
259
|
+
are what you wrote; assistant turns are agent output — suggestions,
|
|
260
|
+
not your decisions.
|
|
261
|
+
</p>
|
|
262
|
+
</aside>
|
|
263
|
+
|
|
264
|
+
{error && (
|
|
265
|
+
<Card className="border-destructive bg-destructive/10">
|
|
266
|
+
<CardContent
|
|
267
|
+
className="break-words py-4 text-destructive outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
|
|
268
|
+
ref={errorRef}
|
|
269
|
+
role="alert"
|
|
270
|
+
tabIndex={-1}
|
|
271
|
+
>
|
|
272
|
+
{error}
|
|
273
|
+
</CardContent>
|
|
274
|
+
</Card>
|
|
275
|
+
)}
|
|
276
|
+
|
|
277
|
+
{loading && (
|
|
278
|
+
<p className="flex items-center gap-2 text-muted-foreground">
|
|
279
|
+
<Loader2Icon className="size-4 animate-spin" />
|
|
280
|
+
Loading archive status…
|
|
281
|
+
</p>
|
|
282
|
+
)}
|
|
283
|
+
|
|
284
|
+
{unbound !== null && (
|
|
285
|
+
<section
|
|
286
|
+
aria-labelledby="sessions-unbound-heading"
|
|
287
|
+
className="space-y-4 rounded-lg border border-dashed border-border/70 p-4 sm:p-6"
|
|
288
|
+
>
|
|
289
|
+
<div className="flex items-center gap-2">
|
|
290
|
+
<MessagesSquareIcon
|
|
291
|
+
aria-hidden="true"
|
|
292
|
+
className="size-5 text-primary"
|
|
293
|
+
/>
|
|
294
|
+
<h2
|
|
295
|
+
className="font-semibold text-xl"
|
|
296
|
+
id="sessions-unbound-heading"
|
|
297
|
+
>
|
|
298
|
+
This server is not a session archive
|
|
299
|
+
</h2>
|
|
300
|
+
</div>
|
|
301
|
+
<p className="break-words text-muted-foreground text-sm">
|
|
302
|
+
{unbound}
|
|
303
|
+
</p>
|
|
304
|
+
<p className="text-sm">
|
|
305
|
+
Session archives live in their own config file and named index.
|
|
306
|
+
Create one, then start a server for it:
|
|
307
|
+
</p>
|
|
308
|
+
<CommandLine>{INIT_COMMAND}</CommandLine>
|
|
309
|
+
<CommandLine>{SERVE_COMMAND}</CommandLine>
|
|
310
|
+
{localClient && <InitForm onDone={loadStatus} />}
|
|
311
|
+
</section>
|
|
312
|
+
)}
|
|
313
|
+
|
|
314
|
+
{status && (
|
|
315
|
+
<>
|
|
316
|
+
{status.warnings.length > 0 && (
|
|
317
|
+
<ul className="list-disc space-y-1 pl-5 text-amber-800 text-sm dark:text-amber-200">
|
|
318
|
+
{status.warnings.map((warning) => (
|
|
319
|
+
<li className="break-words" key={warning}>
|
|
320
|
+
{warning}
|
|
321
|
+
</li>
|
|
322
|
+
))}
|
|
323
|
+
</ul>
|
|
324
|
+
)}
|
|
325
|
+
<SourcesPanel
|
|
326
|
+
archiveCollections={archiveCollections}
|
|
327
|
+
busy={busy}
|
|
328
|
+
localClient={localClient}
|
|
329
|
+
onChanged={loadStatus}
|
|
330
|
+
onImport={(sourceId, dryRun) => void runImport(sourceId, dryRun)}
|
|
331
|
+
onRemove={(sourceId) => void removeSource(sourceId)}
|
|
332
|
+
receipts={receipts}
|
|
333
|
+
sources={status.sources}
|
|
334
|
+
/>
|
|
335
|
+
<AutomationPanel
|
|
336
|
+
automation={status.automation}
|
|
337
|
+
localClient={localClient}
|
|
338
|
+
onChanged={loadStatus}
|
|
339
|
+
sources={status.sources}
|
|
340
|
+
/>
|
|
341
|
+
<SessionSearch
|
|
342
|
+
collections={archiveCollections}
|
|
343
|
+
navigate={(to) => navigate(to)}
|
|
344
|
+
/>
|
|
345
|
+
</>
|
|
346
|
+
)}
|
|
347
|
+
</main>
|
|
348
|
+
</div>
|
|
349
|
+
);
|
|
350
|
+
}
|
|
@@ -9,6 +9,7 @@ import type { SyncResult } from "../ingestion";
|
|
|
9
9
|
import type { ModelManager } from "../llm/nodeLlamaCpp/lifecycle";
|
|
10
10
|
import type { ToolContext } from "../mcp/context";
|
|
11
11
|
import type { HttpMcpTransportStatus } from "../mcp/http-transport";
|
|
12
|
+
import type { SessionAutomationRunResult } from "../sessions/types";
|
|
12
13
|
import type { DocumentEventBus } from "./doc-events";
|
|
13
14
|
import type { EmbedResult, EmbedScheduler } from "./embed-scheduler";
|
|
14
15
|
import type { ContextHolder } from "./routes/api";
|
|
@@ -51,6 +52,7 @@ import { defaultSyncService, withContentTypeRules } from "../ingestion";
|
|
|
51
52
|
import { withOwnedInferenceScope } from "../llm/inference-scope";
|
|
52
53
|
import { getActivePreset } from "../llm/registry";
|
|
53
54
|
import { createToolContext, Mutex } from "../mcp/context";
|
|
55
|
+
import { watchedCollections } from "../sessions/config";
|
|
54
56
|
import { SqliteAdapter } from "../store/sqlite/adapter";
|
|
55
57
|
import {
|
|
56
58
|
createServerContext,
|
|
@@ -67,6 +69,10 @@ import {
|
|
|
67
69
|
createStandaloneResidentStatus,
|
|
68
70
|
buildResidentStatusSnapshot,
|
|
69
71
|
} from "./resident-status";
|
|
72
|
+
import {
|
|
73
|
+
notifyAutomationImport,
|
|
74
|
+
SessionAutomationScheduler,
|
|
75
|
+
} from "./session-automation";
|
|
70
76
|
import { CollectionWatchService as DefaultCollectionWatchService } from "./watch-service";
|
|
71
77
|
|
|
72
78
|
const OWNER_LOCK_TIMEOUT_MS = 0;
|
|
@@ -83,6 +89,9 @@ export interface ResidentRuntimeOptions {
|
|
|
83
89
|
watchCallbacks?: CollectionWatchCallbacks;
|
|
84
90
|
/** Daemon mode only: observes every scheduled findings-pass attempt. */
|
|
85
91
|
onFindingsResult?: (result: FindingsPassResult) => void;
|
|
92
|
+
/** Daemon mode on a session archive: observes every automation run. */
|
|
93
|
+
onSessionAutomationResult?: (result: SessionAutomationRunResult) => void;
|
|
94
|
+
onSessionAutomationError?: (error: unknown) => void;
|
|
86
95
|
readerLimit?: number;
|
|
87
96
|
readerQueueLimit?: number;
|
|
88
97
|
shutdownDeadlineMs?: number;
|
|
@@ -117,6 +126,8 @@ export interface ResidentRuntime {
|
|
|
117
126
|
readonly capsuleReverificationScheduler: SavedCapsuleReverificationScheduler;
|
|
118
127
|
/** Present only when daemon mode runs with `findings.enabled`. */
|
|
119
128
|
readonly findingsScheduler: FindingsScheduler | null;
|
|
129
|
+
/** Present only when daemon mode runs on a session-archive config. */
|
|
130
|
+
readonly sessionAutomation: SessionAutomationScheduler | null;
|
|
120
131
|
readonly modelManager: Pick<
|
|
121
132
|
ModelManager,
|
|
122
133
|
"acquireLease" | "getLifecycleStats" | "disposeAll"
|
|
@@ -322,7 +333,7 @@ export async function startResidentRuntime(
|
|
|
322
333
|
deps.watchServiceFactory ??
|
|
323
334
|
((watchOptions) => new DefaultCollectionWatchService(watchOptions))
|
|
324
335
|
)({
|
|
325
|
-
collections: initialConfig
|
|
336
|
+
collections: watchedCollections(initialConfig),
|
|
326
337
|
store,
|
|
327
338
|
scheduler,
|
|
328
339
|
eventBus: options.eventBus ?? null,
|
|
@@ -393,6 +404,31 @@ export async function startResidentRuntime(
|
|
|
393
404
|
// otherwise keep reporting a schedule that no longer exists.
|
|
394
405
|
await deleteFindingsRunState(findingsStatePath);
|
|
395
406
|
}
|
|
407
|
+
// Session automation drains only in daemon mode on an archive config;
|
|
408
|
+
// `serve` never runs admitted hook work or schedules.
|
|
409
|
+
let sessionAutomation: SessionAutomationScheduler | null = null;
|
|
410
|
+
if (mode === "daemon" && initialConfig.sessions) {
|
|
411
|
+
sessionAutomation = new SessionAutomationScheduler({
|
|
412
|
+
store,
|
|
413
|
+
configPath: actualConfigPath,
|
|
414
|
+
indexName: canonicalizeIndexName(options.index ?? DEFAULT_INDEX_NAME),
|
|
415
|
+
dbPath,
|
|
416
|
+
startBackgroundWork: (operation) => backgroundWork.start(operation),
|
|
417
|
+
onResult: (result) => {
|
|
418
|
+
notifyAutomationImport(result, {
|
|
419
|
+
markMutation: () => {
|
|
420
|
+
generations.content += 1;
|
|
421
|
+
generations.index += 1;
|
|
422
|
+
},
|
|
423
|
+
notifySyncComplete: (collections) =>
|
|
424
|
+
scheduler.notifySyncComplete(collections),
|
|
425
|
+
});
|
|
426
|
+
options.onSessionAutomationResult?.(result);
|
|
427
|
+
},
|
|
428
|
+
onError: options.onSessionAutomationError,
|
|
429
|
+
});
|
|
430
|
+
sessionAutomation.start();
|
|
431
|
+
}
|
|
396
432
|
capsuleReverificationScheduler = new SavedCapsuleReverificationScheduler({
|
|
397
433
|
deps: {
|
|
398
434
|
store,
|
|
@@ -412,7 +448,7 @@ export async function startResidentRuntime(
|
|
|
412
448
|
ctxHolder.config = config;
|
|
413
449
|
ctxHolder.current = { ...ctxHolder.current, config };
|
|
414
450
|
ctxHolder.watchService?.updateCollections(
|
|
415
|
-
config
|
|
451
|
+
watchedCollections(config),
|
|
416
452
|
withContentTypeRules({}, config)
|
|
417
453
|
);
|
|
418
454
|
},
|
|
@@ -475,6 +511,7 @@ export async function startResidentRuntime(
|
|
|
475
511
|
jobManager,
|
|
476
512
|
capsuleReverificationScheduler,
|
|
477
513
|
findingsScheduler,
|
|
514
|
+
sessionAutomation,
|
|
478
515
|
modelManager,
|
|
479
516
|
mcpContext,
|
|
480
517
|
generations,
|
|
@@ -621,7 +658,10 @@ export async function startResidentRuntime(
|
|
|
621
658
|
mcpContext,
|
|
622
659
|
modelManager,
|
|
623
660
|
ownerLock,
|
|
624
|
-
stopFindings: () =>
|
|
661
|
+
stopFindings: () => {
|
|
662
|
+
findingsScheduler?.dispose();
|
|
663
|
+
sessionAutomation?.dispose();
|
|
664
|
+
},
|
|
625
665
|
stopCapsules: () => capsuleReverificationScheduler.dispose(),
|
|
626
666
|
closeEvents: () => options.eventBus?.close(),
|
|
627
667
|
disposeContext: deps.disposeServerContext ?? disposeServerContext,
|