@gmickel/gno 2.5.1 → 2.7.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 +60 -5
- package/assets/skill/README.md +5 -1
- package/assets/skill/SKILL.md +80 -4
- package/assets/skill/cli-reference.md +132 -2
- package/assets/skill/examples.md +30 -0
- package/assets/skill/mcp-reference.md +54 -1
- 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.5.1.zip → gno-browser-clipper-v2.7.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.7.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +2 -1
- package/spec/cli.md +449 -35
- package/spec/mcp.md +234 -4
- package/spec/output-schemas/ask.schema.json +1 -1
- package/spec/output-schemas/capture-receipt.schema.json +4 -1
- package/spec/output-schemas/doctor.schema.json +88 -0
- package/spec/output-schemas/error.schema.json +11 -2
- package/spec/output-schemas/get.schema.json +1 -1
- package/spec/output-schemas/mcp-capture-result.schema.json +4 -2
- package/spec/output-schemas/memory-remember.schema.json +10 -4
- package/spec/output-schemas/multi-get.schema.json +4 -1
- package/spec/output-schemas/peek.schema.json +2 -9
- package/spec/output-schemas/request-status.schema.json +113 -0
- package/spec/output-schemas/resident-status.schema.json +22 -0
- package/spec/output-schemas/search-result.schema.json +1 -1
- package/spec/output-schemas/search-results.schema.json +1 -1
- 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/spec/output-schemas/status.schema.json +98 -0
- package/src/cli/commands/ask.ts +14 -2
- package/src/cli/commands/capture.ts +55 -96
- package/src/cli/commands/daemon.ts +41 -0
- package/src/cli/commands/doctor.ts +54 -20
- package/src/cli/commands/embed.ts +41 -3
- package/src/cli/commands/ls.ts +3 -0
- package/src/cli/commands/memory.ts +12 -3
- package/src/cli/commands/query.ts +5 -0
- 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/commands/status.ts +63 -5
- package/src/cli/commands/vec.ts +54 -0
- package/src/cli/detach.ts +29 -1
- package/src/cli/errors.ts +13 -9
- package/src/cli/program.ts +441 -2
- 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 +12 -2
- package/src/core/host-paths.ts +31 -0
- package/src/core/memory-remember.ts +234 -122
- package/src/core/memory-types.ts +11 -0
- package/src/core/network-boundary-inventory.ts +8 -0
- package/src/core/request-receipts.ts +671 -0
- package/src/core/shutdown-budget.ts +6 -0
- package/src/core/vector-partition-status.ts +52 -0
- package/src/embed/backlog.ts +124 -18
- package/src/embed/fingerprint.ts +6 -3
- package/src/embed/retry.ts +66 -27
- package/src/embed/variant-backlog.ts +15 -10
- package/src/embed/variant-retry.ts +31 -22
- package/src/index.ts +30 -2
- package/src/llm/native-worker/dispatcher.ts +2 -0
- package/src/llm/native-worker/embedding-identity.ts +42 -0
- package/src/llm/native-worker/protocol.ts +1 -0
- package/src/llm/types.ts +3 -0
- package/src/mcp/context.ts +17 -0
- package/src/mcp/http-egress.ts +4 -0
- package/src/mcp/http-transport.ts +2 -0
- package/src/mcp/resources/index.ts +6 -5
- package/src/mcp/tool-descriptions-core.ts +1 -1
- package/src/mcp/tools/capture.ts +87 -85
- package/src/mcp/tools/index.ts +77 -4
- package/src/mcp/tools/memory-remember.ts +8 -1
- package/src/mcp/tools/memory-shared.ts +7 -1
- package/src/mcp/tools/request-status.ts +73 -0
- package/src/mcp/tools/sessions.ts +208 -0
- package/src/mcp/tools/status.ts +4 -0
- package/src/pipeline/hybrid.ts +37 -7
- package/src/pipeline/vsearch.ts +14 -2
- package/src/sdk/client.ts +180 -84
- package/src/sdk/index.ts +6 -0
- package/src/sdk/types.ts +54 -2
- package/src/serve/capture-service.ts +98 -32
- package/src/serve/config-sync.ts +3 -2
- package/src/serve/embed-scheduler.ts +133 -19
- package/src/serve/host-path-redaction.ts +79 -0
- package/src/serve/public/app.tsx +4 -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/globals.built.css +1 -1
- package/src/serve/public/hooks/use-api.ts +26 -3
- package/src/serve/public/lib/request-intent.ts +77 -0
- package/src/serve/public/lib/snippet.tsx +52 -0
- package/src/serve/public/lib/workspace-actions.ts +12 -1
- package/src/serve/public/lib/workspace-tabs.ts +2 -0
- package/src/serve/public/pages/Dashboard.tsx +22 -9
- package/src/serve/public/pages/DocView.tsx +25 -6
- package/src/serve/public/pages/DocumentEditor.tsx +224 -104
- package/src/serve/public/pages/Search.tsx +1 -41
- package/src/serve/public/pages/Sessions.tsx +350 -0
- package/src/serve/resident-runtime.ts +69 -4
- package/src/serve/resident-status.ts +13 -1
- 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 +215 -10
- package/src/serve/session-automation.ts +146 -0
- package/src/serve/status-model.ts +16 -0
- package/src/serve/status.ts +2 -0
- package/src/serve/watch-reconciliation-shared.ts +3 -0
- package/src/serve/watch-service-events.ts +3 -2
- package/src/serve/watch-service-run-flush.ts +35 -2
- package/src/serve/watch-service.ts +5 -0
- 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/migrations/031-runtime-independent-vectors.ts +29 -0
- package/src/store/migrations/032-vector-runtime-callers.ts +17 -0
- package/src/store/migrations/index.ts +4 -0
- package/src/store/sqlite/adapter.ts +76 -16
- package/src/store/sqlite/scoped-index.ts +9 -0
- package/src/store/types.ts +11 -1
- package/src/store/vector/lazy.ts +46 -43
- package/src/store/vector/runtime-compat.ts +651 -0
- package/src/store/vector/sqlite-vec.ts +20 -2
- package/src/store/vector/status.ts +276 -35
- package/src/store/vector/types.ts +2 -0
- package/src/store/vector/variant-search.ts +71 -23
- package/src/store/vector/variants.ts +49 -14
- package/browser-extension/artifacts/gno-browser-clipper-v2.5.1.zip.sha256 +0 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { useCallback, useState } from "react";
|
|
2
2
|
|
|
3
|
+
import { writeOutcomeUnknown } from "../lib/request-intent";
|
|
4
|
+
|
|
3
5
|
interface ApiState<T> {
|
|
4
6
|
data: T | null;
|
|
5
7
|
loading: boolean;
|
|
@@ -81,7 +83,17 @@ export function useApi<T>() {
|
|
|
81
83
|
export async function apiFetch<T>(
|
|
82
84
|
endpoint: string,
|
|
83
85
|
options?: RequestInit
|
|
84
|
-
): Promise<{
|
|
86
|
+
): Promise<{
|
|
87
|
+
data: T | null;
|
|
88
|
+
error: string | null;
|
|
89
|
+
/** `error.details` of the JSON error envelope, when the server sent one. */
|
|
90
|
+
details?: Record<string, unknown>;
|
|
91
|
+
/**
|
|
92
|
+
* A write may or may not have committed: no readable response, a server
|
|
93
|
+
* error, or the request is still pending under its request ID.
|
|
94
|
+
*/
|
|
95
|
+
outcomeUnknown?: true;
|
|
96
|
+
}> {
|
|
85
97
|
try {
|
|
86
98
|
const res = await fetch(endpoint, {
|
|
87
99
|
headers: { "Content-Type": "application/json" },
|
|
@@ -91,14 +103,24 @@ export async function apiFetch<T>(
|
|
|
91
103
|
const { json, parseError } = await parseJsonSafe(res);
|
|
92
104
|
|
|
93
105
|
if (parseError) {
|
|
94
|
-
return { data: null, error: parseError };
|
|
106
|
+
return { data: null, error: parseError, outcomeUnknown: true };
|
|
95
107
|
}
|
|
96
108
|
|
|
97
109
|
if (!res.ok) {
|
|
98
|
-
const apiError = json as {
|
|
110
|
+
const apiError = json as {
|
|
111
|
+
error?: {
|
|
112
|
+
code?: string;
|
|
113
|
+
message?: string;
|
|
114
|
+
details?: Record<string, unknown>;
|
|
115
|
+
};
|
|
116
|
+
};
|
|
99
117
|
return {
|
|
100
118
|
data: null,
|
|
101
119
|
error: apiError.error?.message || `Request failed: ${res.status}`,
|
|
120
|
+
details: apiError.error?.details,
|
|
121
|
+
...(writeOutcomeUnknown(res.status, apiError.error?.code)
|
|
122
|
+
? { outcomeUnknown: true as const }
|
|
123
|
+
: {}),
|
|
102
124
|
};
|
|
103
125
|
}
|
|
104
126
|
|
|
@@ -107,6 +129,7 @@ export async function apiFetch<T>(
|
|
|
107
129
|
return {
|
|
108
130
|
data: null,
|
|
109
131
|
error: err instanceof Error ? err.message : "Network error",
|
|
132
|
+
outcomeUnknown: true,
|
|
110
133
|
};
|
|
111
134
|
}
|
|
112
135
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One request ID per submitted write intent.
|
|
3
|
+
*
|
|
4
|
+
* Retrying the same payload (after a lost response, an error, or a page
|
|
5
|
+
* refresh) reuses the ID, so the server replays the committed outcome instead
|
|
6
|
+
* of writing twice. A changed payload is a new intent and gets a new ID. The
|
|
7
|
+
* unresolved intent is kept in sessionStorage so it survives a refresh of
|
|
8
|
+
* this tab; it is cleared once the write is confirmed.
|
|
9
|
+
*/
|
|
10
|
+
export interface RequestIntent {
|
|
11
|
+
key: string;
|
|
12
|
+
requestId: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Compact payload fingerprint (cyrb53); a collision only yields a conflict error. */
|
|
16
|
+
function fingerprint(text: string): string {
|
|
17
|
+
let h1 = 0xde_ad_be_ef;
|
|
18
|
+
let h2 = 0x41_c6_ce_57;
|
|
19
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
20
|
+
const code = text.charCodeAt(index);
|
|
21
|
+
h1 = Math.imul(h1 ^ code, 2_654_435_761);
|
|
22
|
+
h2 = Math.imul(h2 ^ code, 1_597_334_677);
|
|
23
|
+
}
|
|
24
|
+
h1 = Math.imul(h1 ^ (h1 >>> 16), 2_246_822_507);
|
|
25
|
+
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3_266_489_909);
|
|
26
|
+
h2 = Math.imul(h2 ^ (h2 >>> 16), 2_246_822_507);
|
|
27
|
+
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3_266_489_909);
|
|
28
|
+
return (4_294_967_296 * (2_097_151 & h2) + (h1 >>> 0)).toString(36);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readStored(storageKey: string): RequestIntent | null {
|
|
32
|
+
try {
|
|
33
|
+
const raw = sessionStorage.getItem(storageKey);
|
|
34
|
+
return raw ? (JSON.parse(raw) as RequestIntent) : null;
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function requestIdForIntent(
|
|
41
|
+
slot: { current: RequestIntent | null },
|
|
42
|
+
payload: string,
|
|
43
|
+
storageKey: string
|
|
44
|
+
): string {
|
|
45
|
+
const key = fingerprint(payload);
|
|
46
|
+
slot.current ??= readStored(storageKey);
|
|
47
|
+
if (slot.current?.key !== key) {
|
|
48
|
+
slot.current = { key, requestId: crypto.randomUUID() };
|
|
49
|
+
try {
|
|
50
|
+
sessionStorage.setItem(storageKey, JSON.stringify(slot.current));
|
|
51
|
+
} catch {
|
|
52
|
+
// Storage unavailable: the ID still covers retries in this page.
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return slot.current.requestId;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The intent's write is confirmed: the next submit is a new intent. */
|
|
59
|
+
export function clearRequestIntent(
|
|
60
|
+
slot: { current: RequestIntent | null },
|
|
61
|
+
storageKey: string
|
|
62
|
+
): void {
|
|
63
|
+
slot.current = null;
|
|
64
|
+
try {
|
|
65
|
+
sessionStorage.removeItem(storageKey);
|
|
66
|
+
} catch {
|
|
67
|
+
// Nothing persisted to clear.
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Whether a failed write's HTTP answer leaves its commit unknown: a server
|
|
73
|
+
* error, or the request ID's earlier attempt is still in progress.
|
|
74
|
+
*/
|
|
75
|
+
export function writeOutcomeUnknown(status: number, code?: string): boolean {
|
|
76
|
+
return status >= 500 || code === "REQUEST_PENDING";
|
|
77
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
const MARK_OPEN = "<mark>";
|
|
4
|
+
const MARK_CLOSE = "</mark>";
|
|
5
|
+
|
|
6
|
+
/** CommonMark backslash escape: `\` before any ASCII punctuation character. */
|
|
7
|
+
const MARKDOWN_ESCAPE = /\\([!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~])/g;
|
|
8
|
+
|
|
9
|
+
/** Drop Markdown backslash escapes (`state.db\#` -> `state.db#`). */
|
|
10
|
+
export function unescapeMarkdown(text: string): string {
|
|
11
|
+
return text.replace(MARKDOWN_ESCAPE, "$1");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Render a search snippet: FTS `<mark>` highlights become real <mark>
|
|
16
|
+
* elements, everything else stays React text (never parsed as HTML).
|
|
17
|
+
* Markdown backslash escapes are dropped because indexed text keeps them.
|
|
18
|
+
*/
|
|
19
|
+
export function renderSnippet(snippet: string): ReactNode[] {
|
|
20
|
+
const parts: ReactNode[] = [];
|
|
21
|
+
let remaining = snippet;
|
|
22
|
+
let key = 0;
|
|
23
|
+
|
|
24
|
+
while (remaining.length > 0) {
|
|
25
|
+
const markStart = remaining.indexOf(MARK_OPEN);
|
|
26
|
+
if (markStart === -1) {
|
|
27
|
+
parts.push(unescapeMarkdown(remaining));
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
if (markStart > 0) {
|
|
31
|
+
parts.push(unescapeMarkdown(remaining.slice(0, markStart)));
|
|
32
|
+
}
|
|
33
|
+
const markEnd = remaining.indexOf(MARK_CLOSE, markStart);
|
|
34
|
+
if (markEnd === -1) {
|
|
35
|
+
parts.push(unescapeMarkdown(remaining.slice(markStart)));
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
parts.push(
|
|
39
|
+
<mark
|
|
40
|
+
className="rounded bg-primary/20 px-0.5 font-medium text-primary"
|
|
41
|
+
key={key++}
|
|
42
|
+
>
|
|
43
|
+
{unescapeMarkdown(
|
|
44
|
+
remaining.slice(markStart + MARK_OPEN.length, markEnd)
|
|
45
|
+
)}
|
|
46
|
+
</mark>
|
|
47
|
+
);
|
|
48
|
+
remaining = remaining.slice(markEnd + MARK_CLOSE.length);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return parts;
|
|
52
|
+
}
|
|
@@ -14,7 +14,8 @@ export type WorkspaceActionId =
|
|
|
14
14
|
| "go-ask"
|
|
15
15
|
| "go-graph"
|
|
16
16
|
| "go-collections"
|
|
17
|
-
| "go-connectors"
|
|
17
|
+
| "go-connectors"
|
|
18
|
+
| "go-sessions";
|
|
18
19
|
|
|
19
20
|
export interface WorkspaceAction {
|
|
20
21
|
id: WorkspaceActionId;
|
|
@@ -155,6 +156,13 @@ export function getWorkspaceActions(
|
|
|
155
156
|
keywords: ["connectors", "mcp", "skills", "agents"],
|
|
156
157
|
available: true,
|
|
157
158
|
},
|
|
159
|
+
{
|
|
160
|
+
id: "go-sessions",
|
|
161
|
+
group: "Go To",
|
|
162
|
+
label: "Agent sessions",
|
|
163
|
+
keywords: ["sessions", "archive", "transcripts", "import", "agents"],
|
|
164
|
+
available: true,
|
|
165
|
+
},
|
|
158
166
|
];
|
|
159
167
|
}
|
|
160
168
|
|
|
@@ -220,6 +228,9 @@ export function runWorkspaceAction(
|
|
|
220
228
|
case "go-connectors":
|
|
221
229
|
handlers.navigate("/connectors");
|
|
222
230
|
break;
|
|
231
|
+
case "go-sessions":
|
|
232
|
+
handlers.navigate("/sessions");
|
|
233
|
+
break;
|
|
223
234
|
}
|
|
224
235
|
|
|
225
236
|
handlers.closePalette();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ArchiveIcon,
|
|
2
3
|
BookOpen,
|
|
3
4
|
CheckCircle2Icon,
|
|
4
5
|
CpuIcon,
|
|
@@ -540,6 +541,15 @@ export default function Dashboard({ navigate }: PageProps) {
|
|
|
540
541
|
<HistoryIcon className="size-4" />
|
|
541
542
|
Trace history
|
|
542
543
|
</Button>
|
|
544
|
+
<Button
|
|
545
|
+
className="gap-2"
|
|
546
|
+
onClick={() => navigate("/sessions")}
|
|
547
|
+
size="lg"
|
|
548
|
+
variant="outline"
|
|
549
|
+
>
|
|
550
|
+
<ArchiveIcon className="size-4" />
|
|
551
|
+
Agent sessions
|
|
552
|
+
</Button>
|
|
543
553
|
<Button
|
|
544
554
|
onClick={() => navigate("/context/compiled")}
|
|
545
555
|
size="lg"
|
|
@@ -778,25 +788,28 @@ export default function Dashboard({ navigate }: PageProps) {
|
|
|
778
788
|
}
|
|
779
789
|
style={{ animationDelay: `${0.4 + index * 0.1}s` }}
|
|
780
790
|
>
|
|
781
|
-
<CardContent className="flex items-center justify-between py-4">
|
|
782
|
-
<div className="flex items-center gap-3">
|
|
791
|
+
<CardContent className="flex flex-wrap items-center justify-between gap-3 py-4">
|
|
792
|
+
<div className="flex min-w-0 flex-1 basis-48 items-center gap-3">
|
|
783
793
|
{syncing ? (
|
|
784
|
-
<Loader2Icon className="size-4 animate-spin text-amber-500" />
|
|
794
|
+
<Loader2Icon className="size-4 shrink-0 animate-spin text-amber-500" />
|
|
785
795
|
) : collection.embeddedCount >= collection.chunkCount ? (
|
|
786
|
-
<CheckCircle2Icon className="size-4 text-green-500" />
|
|
796
|
+
<CheckCircle2Icon className="size-4 shrink-0 text-green-500" />
|
|
787
797
|
) : (
|
|
788
|
-
<div className="size-4 rounded-full border-2 border-amber-500" />
|
|
798
|
+
<div className="size-4 shrink-0 rounded-full border-2 border-amber-500" />
|
|
789
799
|
)}
|
|
790
|
-
<div>
|
|
791
|
-
<div className="font-medium text-lg transition-colors group-hover:text-primary">
|
|
800
|
+
<div className="min-w-0">
|
|
801
|
+
<div className="truncate font-medium text-lg transition-colors group-hover:text-primary">
|
|
792
802
|
{collection.name}
|
|
793
803
|
</div>
|
|
794
|
-
<div
|
|
804
|
+
<div
|
|
805
|
+
className="truncate font-mono text-muted-foreground text-sm"
|
|
806
|
+
title={collection.path}
|
|
807
|
+
>
|
|
795
808
|
{collection.path}
|
|
796
809
|
</div>
|
|
797
810
|
</div>
|
|
798
811
|
</div>
|
|
799
|
-
<div className="flex items-center gap-3 text-right">
|
|
812
|
+
<div className="flex shrink-0 items-center gap-3 text-right">
|
|
800
813
|
<Button
|
|
801
814
|
onClick={(event) => {
|
|
802
815
|
event.stopPropagation();
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
type FileRefactorPreviewPlan,
|
|
38
38
|
} from "../../../core/file-refactor-contract";
|
|
39
39
|
import { extractSections } from "../../../core/sections";
|
|
40
|
+
import { updateFrontmatterTags } from "../../../ingestion/frontmatter";
|
|
40
41
|
import {
|
|
41
42
|
CodeBlock,
|
|
42
43
|
CodeBlockCopyButton,
|
|
@@ -83,6 +84,13 @@ import {
|
|
|
83
84
|
isPdfDocument,
|
|
84
85
|
} from "../lib/doc-asset-url";
|
|
85
86
|
import { waitForDocumentAvailability } from "../lib/document-availability";
|
|
87
|
+
import {
|
|
88
|
+
clearRequestIntent,
|
|
89
|
+
type RequestIntent,
|
|
90
|
+
requestIdForIntent,
|
|
91
|
+
} from "../lib/request-intent";
|
|
92
|
+
|
|
93
|
+
const TAG_INTENT_KEY = "gno:tag-intent";
|
|
86
94
|
import {
|
|
87
95
|
buildReadableSectionUrl,
|
|
88
96
|
createCitationSectionUrl,
|
|
@@ -576,11 +584,10 @@ export default function DocView({ navigate }: PageProps) {
|
|
|
576
584
|
return buildDocAssetUrl(doc.uri, doc.relPath);
|
|
577
585
|
}, [doc, isPdf]);
|
|
578
586
|
|
|
579
|
-
// Remote "Open original" target
|
|
580
|
-
//
|
|
581
|
-
// previous file:// scope (every read-only source) for remote clients.
|
|
587
|
+
// Remote "Open original" target: /api/doc-asset serves any collection file
|
|
588
|
+
// inline by URI, and remote clients never receive the host absPath.
|
|
582
589
|
const sourceAssetUrl = useMemo(() => {
|
|
583
|
-
if (!doc
|
|
590
|
+
if (!doc) {
|
|
584
591
|
return null;
|
|
585
592
|
}
|
|
586
593
|
return buildDocAssetUrl(doc.uri, doc.relPath);
|
|
@@ -1201,7 +1208,8 @@ export default function DocView({ navigate }: PageProps) {
|
|
|
1201
1208
|
setTagSaveError(null);
|
|
1202
1209
|
}, []);
|
|
1203
1210
|
|
|
1204
|
-
// Save tags
|
|
1211
|
+
// Save tags (one request ID per tag-set intent, reused on retry)
|
|
1212
|
+
const tagIntentRef = useRef<RequestIntent | null>(null);
|
|
1205
1213
|
const handleSaveTags = useCallback(async () => {
|
|
1206
1214
|
if (!doc) return;
|
|
1207
1215
|
|
|
@@ -1218,6 +1226,11 @@ export default function DocView({ navigate }: PageProps) {
|
|
|
1218
1226
|
expectedSourceHash: doc.source.sourceHash,
|
|
1219
1227
|
expectedModifiedAt: doc.source.modifiedAt,
|
|
1220
1228
|
uri: doc.uri,
|
|
1229
|
+
requestId: requestIdForIntent(
|
|
1230
|
+
tagIntentRef,
|
|
1231
|
+
`${doc.uri}\u0000${doc.source.sourceHash}\u0000${editedTags.join(",")}`,
|
|
1232
|
+
TAG_INTENT_KEY
|
|
1233
|
+
),
|
|
1221
1234
|
}),
|
|
1222
1235
|
}
|
|
1223
1236
|
);
|
|
@@ -1229,10 +1242,16 @@ export default function DocView({ navigate }: PageProps) {
|
|
|
1229
1242
|
return;
|
|
1230
1243
|
}
|
|
1231
1244
|
|
|
1232
|
-
|
|
1245
|
+
clearRequestIntent(tagIntentRef, TAG_INTENT_KEY);
|
|
1246
|
+
// Mirror the committed write-back so the frontmatter tag list is current
|
|
1247
|
+
// without waiting for the index to resync.
|
|
1233
1248
|
setDoc({
|
|
1234
1249
|
...doc,
|
|
1235
1250
|
tags: editedTags,
|
|
1251
|
+
content:
|
|
1252
|
+
data?.writeBack === "applied" && doc.content !== null
|
|
1253
|
+
? updateFrontmatterTags(doc.content, editedTags)
|
|
1254
|
+
: doc.content,
|
|
1236
1255
|
source: {
|
|
1237
1256
|
...doc.source,
|
|
1238
1257
|
sourceHash: data?.version.sourceHash ?? doc.source.sourceHash,
|