@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
|
@@ -12,7 +12,13 @@ import { basename, dirname, join as pathJoin } from "node:path";
|
|
|
12
12
|
|
|
13
13
|
import type { Collection, Config } from "../config/types";
|
|
14
14
|
import type { PreparedBrowserClip } from "../core/browser-clip";
|
|
15
|
-
import type {
|
|
15
|
+
import type {
|
|
16
|
+
CaptureInput,
|
|
17
|
+
CapturePlan,
|
|
18
|
+
CaptureReceipt,
|
|
19
|
+
CaptureSource,
|
|
20
|
+
SyncCapturedFileResult,
|
|
21
|
+
} from "../core/capture";
|
|
16
22
|
import type { JobManager } from "../core/job-manager";
|
|
17
23
|
import type { SqliteAdapter } from "../store/sqlite/adapter";
|
|
18
24
|
import type { ClipperIdempotencyPlan } from "../store/sqlite/clipper-store-types";
|
|
@@ -30,12 +36,13 @@ import {
|
|
|
30
36
|
hashCaptureContent,
|
|
31
37
|
listCaptureDiskRelPaths,
|
|
32
38
|
planCapture,
|
|
33
|
-
syncCapturedFile,
|
|
34
39
|
} from "../core/capture";
|
|
40
|
+
import { publishCapture } from "../core/capture-publish";
|
|
35
41
|
import { writeCapturePlanFile } from "../core/capture-write";
|
|
36
42
|
import { MCP_ERRORS } from "../core/errors";
|
|
37
43
|
import { withWriteLock } from "../core/file-lock";
|
|
38
44
|
import { recordContentMutation } from "../core/mutation-generations";
|
|
45
|
+
import { localRequestLedger } from "../core/request-receipts";
|
|
39
46
|
import { DEFAULT_LOCK_WAIT_MS, writeLeasePath } from "../core/write-lease";
|
|
40
47
|
import {
|
|
41
48
|
type CollectionSyncResult,
|
|
@@ -331,6 +338,37 @@ const executeCaptureAsJob = async (
|
|
|
331
338
|
};
|
|
332
339
|
};
|
|
333
340
|
|
|
341
|
+
/** Shared leased publication wired to the resident watch/event hooks. */
|
|
342
|
+
const residentPublication = (
|
|
343
|
+
context: ResidentCaptureContext,
|
|
344
|
+
store: SqliteAdapter,
|
|
345
|
+
collection: Collection,
|
|
346
|
+
dependencies: ResidentCaptureDependencies
|
|
347
|
+
) => ({
|
|
348
|
+
collection,
|
|
349
|
+
store,
|
|
350
|
+
lockPath: resolveCaptureLockPath(context, dependencies),
|
|
351
|
+
lockWaitMs: dependencies.lockWaitMs ?? DEFAULT_LOCK_WAIT_MS,
|
|
352
|
+
config: context.config,
|
|
353
|
+
syncPaths: dependencies.syncPaths,
|
|
354
|
+
beforeWrite: (absPath: string) => context.watchService?.suppress(absPath),
|
|
355
|
+
afterSync: (synced: SyncCapturedFileResult, receipt: CaptureReceipt) => {
|
|
356
|
+
if (synced.result) {
|
|
357
|
+
recordContentMutation(synced.result, context.markContentMutation);
|
|
358
|
+
}
|
|
359
|
+
if (!receipt.openedExisting) {
|
|
360
|
+
emitCaptureCreated(context, collection, receipt.relPath);
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
const residentCaptureStatus = (receipt: CaptureReceipt): number =>
|
|
366
|
+
receipt.collisionPolicyResult === "conflict"
|
|
367
|
+
? HTTP_CONFLICT
|
|
368
|
+
: receipt.openedExisting
|
|
369
|
+
? HTTP_OK
|
|
370
|
+
: HTTP_CREATED;
|
|
371
|
+
|
|
334
372
|
/**
|
|
335
373
|
* Write + lexical sync under the shared write lease; the response is sent
|
|
336
374
|
* only once the capture is retrievable. Throws `CaptureSyncError` when the
|
|
@@ -343,37 +381,65 @@ const executeCaptureAwaitingSync = async (
|
|
|
343
381
|
planned: Extract<ResidentCapturePlanResult, { ok: true }>,
|
|
344
382
|
dependencies: ResidentCaptureDependencies
|
|
345
383
|
): Promise<{ body: unknown; status: number }> => {
|
|
346
|
-
const {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
384
|
+
const { receipt } = await publishCapture({
|
|
385
|
+
...residentPublication(context, store, planned.collection, dependencies),
|
|
386
|
+
plan: async () => planned.plan,
|
|
387
|
+
});
|
|
388
|
+
return { body: receipt, status: HTTP_CREATED };
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
/** A resident plan rejection, carried through the leased planner. */
|
|
392
|
+
export class ResidentCapturePlanError extends Error {
|
|
393
|
+
readonly shape: Extract<ResidentCapturePlanResult, { ok: false }>;
|
|
394
|
+
|
|
395
|
+
constructor(shape: Extract<ResidentCapturePlanResult, { ok: false }>) {
|
|
396
|
+
super(shape.message);
|
|
397
|
+
this.name = "ResidentCapturePlanError";
|
|
398
|
+
this.shape = shape;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* `/api/capture` with a request ID: planning moves under the lease and the
|
|
404
|
+
* request-receipt service replays, finishes, or admits the capture.
|
|
405
|
+
*/
|
|
406
|
+
export const executeRequestedResidentCapture = async (
|
|
407
|
+
context: ResidentCaptureContext,
|
|
408
|
+
store: SqliteAdapter,
|
|
409
|
+
input: CaptureInput,
|
|
410
|
+
requestId: string,
|
|
411
|
+
dependencies: ResidentCaptureDependencies = {}
|
|
412
|
+
): Promise<{ body: unknown; status: number }> => {
|
|
413
|
+
const collection = context.config.collections.find(
|
|
414
|
+
(candidate) =>
|
|
415
|
+
candidate.name.toLowerCase() === input.collection.toLowerCase()
|
|
376
416
|
);
|
|
417
|
+
if (!collection) {
|
|
418
|
+
throw new ResidentCapturePlanError({
|
|
419
|
+
ok: false,
|
|
420
|
+
code: "NOT_FOUND",
|
|
421
|
+
message: `Collection not found: ${input.collection}`,
|
|
422
|
+
status: 404,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
const normalized = { ...input, collection: collection.name };
|
|
426
|
+
const published = await publishCapture({
|
|
427
|
+
...residentPublication(context, store, collection, dependencies),
|
|
428
|
+
plan: async () => {
|
|
429
|
+
const planned = await planResidentCapture(context, store, normalized);
|
|
430
|
+
if (!planned.ok) throw new ResidentCapturePlanError(planned);
|
|
431
|
+
return planned.plan;
|
|
432
|
+
},
|
|
433
|
+
request: {
|
|
434
|
+
...localRequestLedger(store.getDbPath()),
|
|
435
|
+
requestId,
|
|
436
|
+
input: normalized,
|
|
437
|
+
},
|
|
438
|
+
});
|
|
439
|
+
return {
|
|
440
|
+
body: { ...published.receipt, request: published.request },
|
|
441
|
+
status: residentCaptureStatus(published.receipt),
|
|
442
|
+
};
|
|
377
443
|
};
|
|
378
444
|
|
|
379
445
|
/**
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { ServerContext } from "./context";
|
|
2
|
+
|
|
3
|
+
/** Read-only compiled context adapters; caller authority comes from the socket. */
|
|
4
|
+
import {
|
|
5
|
+
previewCompiledContext,
|
|
6
|
+
checkCompiledContext,
|
|
7
|
+
} from "../app/compiled-context";
|
|
8
|
+
import {
|
|
9
|
+
compiledContextPreviewInputSchema,
|
|
10
|
+
compiledContextCheckInputSchema,
|
|
11
|
+
} from "../core/compiled-context";
|
|
12
|
+
import {
|
|
13
|
+
isLocalClientRequest,
|
|
14
|
+
type RequestPeerServer,
|
|
15
|
+
} from "./request-locality";
|
|
16
|
+
|
|
17
|
+
export async function handleCompiledContext(
|
|
18
|
+
context: ServerContext,
|
|
19
|
+
request: Request,
|
|
20
|
+
server: RequestPeerServer | undefined,
|
|
21
|
+
operation: "preview" | "check"
|
|
22
|
+
): Promise<Response> {
|
|
23
|
+
try {
|
|
24
|
+
// Bound bytes before JSON parsing, even when Content-Length is absent.
|
|
25
|
+
const reader = request.body?.getReader();
|
|
26
|
+
if (!reader)
|
|
27
|
+
return Response.json(
|
|
28
|
+
{ error: { code: "invalid_input", message: "JSON body required" } },
|
|
29
|
+
{ status: 400 }
|
|
30
|
+
);
|
|
31
|
+
const chunks: Uint8Array[] = [];
|
|
32
|
+
let size = 0;
|
|
33
|
+
while (true) {
|
|
34
|
+
const part = await reader.read();
|
|
35
|
+
if (part.done) break;
|
|
36
|
+
size += part.value.byteLength;
|
|
37
|
+
if (size > 8 * 1024 * 1024 + 65536) {
|
|
38
|
+
await reader.cancel();
|
|
39
|
+
return Response.json(
|
|
40
|
+
{
|
|
41
|
+
error: {
|
|
42
|
+
code: "invalid_input",
|
|
43
|
+
message: "Compiled context request exceeds byte limit",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{ status: 413 }
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
chunks.push(part.value);
|
|
50
|
+
}
|
|
51
|
+
const raw: unknown = JSON.parse(
|
|
52
|
+
await new Blob(chunks.map((chunk) => new Uint8Array(chunk))).text()
|
|
53
|
+
);
|
|
54
|
+
const input =
|
|
55
|
+
operation === "preview"
|
|
56
|
+
? compiledContextPreviewInputSchema.parse(raw)
|
|
57
|
+
: compiledContextCheckInputSchema.parse(raw);
|
|
58
|
+
const local = isLocalClientRequest(request, server);
|
|
59
|
+
const deps = {
|
|
60
|
+
store: context.store,
|
|
61
|
+
config: context.config,
|
|
62
|
+
indexName: context.indexName,
|
|
63
|
+
destinationZone: local ? ("local_process" as const) : ("remote" as const),
|
|
64
|
+
caller: { authenticated: local, operationAuthorized: true },
|
|
65
|
+
};
|
|
66
|
+
const result =
|
|
67
|
+
operation === "preview"
|
|
68
|
+
? await previewCompiledContext(input, deps)
|
|
69
|
+
: await checkCompiledContext(input, deps);
|
|
70
|
+
return Response.json(result);
|
|
71
|
+
} catch {
|
|
72
|
+
// Supplied Capsules may contain private metadata; errors never echo inputs.
|
|
73
|
+
return Response.json(
|
|
74
|
+
{
|
|
75
|
+
error: {
|
|
76
|
+
code: "compiled_context_unavailable",
|
|
77
|
+
message:
|
|
78
|
+
"Compiled context is invalid, stale, or unavailable for this caller",
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
{ status: 400 }
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
package/src/serve/config-sync.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
type MutationResult,
|
|
17
17
|
} from "../core/config-mutation";
|
|
18
18
|
import { withContentTypeRules } from "../ingestion";
|
|
19
|
+
import { watchedCollections } from "../sessions/config";
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Apply a config mutation atomically with serialization.
|
|
@@ -56,7 +57,7 @@ export async function applyConfigChange(
|
|
|
56
57
|
|
|
57
58
|
if (result.ok) {
|
|
58
59
|
ctxHolder.watchService?.updateCollections(
|
|
59
|
-
ctxHolder.config
|
|
60
|
+
watchedCollections(ctxHolder.config),
|
|
60
61
|
withContentTypeRules({}, ctxHolder.config)
|
|
61
62
|
);
|
|
62
63
|
}
|
|
@@ -86,7 +87,7 @@ export async function applyConfigChangeTyped<T>(
|
|
|
86
87
|
|
|
87
88
|
if (result.ok) {
|
|
88
89
|
ctxHolder.watchService?.updateCollections(
|
|
89
|
-
ctxHolder.config
|
|
90
|
+
watchedCollections(ctxHolder.config),
|
|
90
91
|
withContentTypeRules({}, ctxHolder.config)
|
|
91
92
|
);
|
|
92
93
|
}
|
package/src/serve/public/app.tsx
CHANGED
|
@@ -47,7 +47,9 @@ const Collections = lazy(() => import("./pages/Collections"));
|
|
|
47
47
|
const Connectors = lazy(() => import("./pages/Connectors"));
|
|
48
48
|
const Ask = lazy(() => import("./pages/Ask"));
|
|
49
49
|
const GraphView = lazy(() => import("./pages/GraphView"));
|
|
50
|
+
const CompiledContext = lazy(() => import("./pages/CompiledContext"));
|
|
50
51
|
const TraceHistory = lazy(() => import("./pages/TraceHistory"));
|
|
52
|
+
const Sessions = lazy(() => import("./pages/Sessions"));
|
|
51
53
|
|
|
52
54
|
type Route =
|
|
53
55
|
| "/"
|
|
@@ -59,7 +61,9 @@ type Route =
|
|
|
59
61
|
| "/collections"
|
|
60
62
|
| "/graph"
|
|
61
63
|
| "/connectors"
|
|
62
|
-
| "/
|
|
64
|
+
| "/context/compiled"
|
|
65
|
+
| "/traces"
|
|
66
|
+
| "/sessions";
|
|
63
67
|
type Navigate = (to: string | number) => void;
|
|
64
68
|
|
|
65
69
|
interface RoutePageProps {
|
|
@@ -78,6 +82,8 @@ const routes: Record<Route, React.ComponentType<RoutePageProps>> = {
|
|
|
78
82
|
"/ask": Ask,
|
|
79
83
|
"/graph": GraphView,
|
|
80
84
|
"/traces": TraceHistory,
|
|
85
|
+
"/sessions": Sessions,
|
|
86
|
+
"/context/compiled": CompiledContext,
|
|
81
87
|
};
|
|
82
88
|
|
|
83
89
|
interface AppContentProps {
|
|
@@ -196,6 +202,14 @@ function AppContent({
|
|
|
196
202
|
Trace history
|
|
197
203
|
</button>
|
|
198
204
|
<span className="text-border/30">—</span>
|
|
205
|
+
<button
|
|
206
|
+
className="transition-colors duration-300 hover:text-primary"
|
|
207
|
+
onClick={() => navigate("/context/compiled")}
|
|
208
|
+
type="button"
|
|
209
|
+
>
|
|
210
|
+
Compiled context
|
|
211
|
+
</button>
|
|
212
|
+
<span className="text-border/30">—</span>
|
|
199
213
|
<a
|
|
200
214
|
className="transition-colors duration-300 hover:text-primary"
|
|
201
215
|
href="https://github.com/gmickel/gno"
|
|
@@ -26,6 +26,13 @@ import {
|
|
|
26
26
|
resolveNotePreset,
|
|
27
27
|
} from "../../../core/note-presets";
|
|
28
28
|
import { apiFetch } from "../hooks/use-api";
|
|
29
|
+
import {
|
|
30
|
+
clearRequestIntent,
|
|
31
|
+
type RequestIntent,
|
|
32
|
+
requestIdForIntent,
|
|
33
|
+
} from "../lib/request-intent";
|
|
34
|
+
|
|
35
|
+
const CAPTURE_INTENT_KEY = "gno:capture-intent";
|
|
29
36
|
import { getActiveWikiLinkQuery } from "../lib/wiki-link";
|
|
30
37
|
import { IndexingProgress } from "./IndexingProgress";
|
|
31
38
|
import { TagInput } from "./TagInput";
|
|
@@ -169,6 +176,7 @@ export function CaptureModal({
|
|
|
169
176
|
const [wikiLinkPosition, setWikiLinkPosition] = useState({ x: 24, y: 24 });
|
|
170
177
|
const [wikiLinkActiveIndex, setWikiLinkActiveIndex] = useState(-1);
|
|
171
178
|
const contentRef = useRef<HTMLTextAreaElement | null>(null);
|
|
179
|
+
const captureIntentRef = useRef<RequestIntent | null>(null);
|
|
172
180
|
|
|
173
181
|
// Submission state
|
|
174
182
|
const [state, setState] = useState<ModalState>("form");
|
|
@@ -311,19 +319,28 @@ export function CaptureModal({
|
|
|
311
319
|
? selectedPresetId
|
|
312
320
|
: undefined;
|
|
313
321
|
|
|
322
|
+
const payload = {
|
|
323
|
+
collection,
|
|
324
|
+
title,
|
|
325
|
+
folderPath: defaultFolderPath || undefined,
|
|
326
|
+
content: presetOnly ? undefined : content,
|
|
327
|
+
presetId: submitPresetId,
|
|
328
|
+
collisionPolicy: "create_with_suffix",
|
|
329
|
+
source,
|
|
330
|
+
...(tags.length > 0 && { tags }),
|
|
331
|
+
};
|
|
314
332
|
const { data, error: err } = await apiFetch<CaptureResponse>(
|
|
315
333
|
"/api/capture",
|
|
316
334
|
{
|
|
317
335
|
method: "POST",
|
|
318
336
|
body: JSON.stringify({
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
...(tags.length > 0 && { tags }),
|
|
337
|
+
...payload,
|
|
338
|
+
// A retry of this exact capture replays instead of adding a suffix copy.
|
|
339
|
+
requestId: requestIdForIntent(
|
|
340
|
+
captureIntentRef,
|
|
341
|
+
JSON.stringify(payload),
|
|
342
|
+
CAPTURE_INTENT_KEY
|
|
343
|
+
),
|
|
327
344
|
}),
|
|
328
345
|
}
|
|
329
346
|
);
|
|
@@ -335,6 +352,7 @@ export function CaptureModal({
|
|
|
335
352
|
}
|
|
336
353
|
|
|
337
354
|
if (data) {
|
|
355
|
+
clearRequestIntent(captureIntentRef, CAPTURE_INTENT_KEY);
|
|
338
356
|
// Save last used collection
|
|
339
357
|
localStorage.setItem(STORAGE_KEY, collection);
|
|
340
358
|
|