@gmickel/gno 2.5.1 → 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.
Files changed (105) hide show
  1. package/README.md +60 -5
  2. package/assets/skill/README.md +5 -1
  3. package/assets/skill/SKILL.md +75 -1
  4. package/assets/skill/cli-reference.md +123 -0
  5. package/assets/skill/examples.md +30 -0
  6. package/assets/skill/mcp-reference.md +52 -0
  7. package/assets/skill/recipes/capture-and-file.md +6 -0
  8. package/assets/skill/recipes/memory-file-decision.md +6 -0
  9. package/assets/skill/recipes/memory-supersede-fact.md +5 -0
  10. package/assets/skill/recipes/session-evidence-lookup.md +98 -0
  11. package/assets/spa-production.json.gz +0 -0
  12. package/browser-extension/artifacts/{gno-browser-clipper-v2.5.1.zip → gno-browser-clipper-v2.6.0.zip} +0 -0
  13. package/browser-extension/artifacts/gno-browser-clipper-v2.6.0.zip.sha256 +1 -0
  14. package/browser-extension/dist/manifest.json +1 -1
  15. package/package.json +2 -1
  16. package/spec/cli.md +347 -24
  17. package/spec/mcp.md +198 -2
  18. package/spec/output-schemas/capture-receipt.schema.json +3 -0
  19. package/spec/output-schemas/mcp-capture-result.schema.json +3 -0
  20. package/spec/output-schemas/memory-remember.schema.json +8 -2
  21. package/spec/output-schemas/request-status.schema.json +113 -0
  22. package/spec/output-schemas/sessions-automation-run.schema.json +46 -0
  23. package/spec/output-schemas/sessions-discovery.schema.json +38 -0
  24. package/spec/output-schemas/sessions-import-receipt.schema.json +156 -0
  25. package/spec/output-schemas/sessions-status.schema.json +432 -0
  26. package/src/cli/commands/ask.ts +14 -2
  27. package/src/cli/commands/capture.ts +55 -96
  28. package/src/cli/commands/daemon.ts +41 -0
  29. package/src/cli/commands/ls.ts +3 -0
  30. package/src/cli/commands/memory.ts +12 -3
  31. package/src/cli/commands/request-status.ts +59 -0
  32. package/src/cli/commands/reset.ts +39 -5
  33. package/src/cli/commands/sessions.ts +713 -0
  34. package/src/cli/commands/shared.ts +14 -1
  35. package/src/cli/program.ts +388 -1
  36. package/src/cli/session-binding.ts +49 -0
  37. package/src/config/types.ts +8 -0
  38. package/src/core/capture-publish.ts +239 -0
  39. package/src/core/capture-sync.ts +3 -0
  40. package/src/core/memory-remember.ts +233 -122
  41. package/src/core/memory-types.ts +11 -0
  42. package/src/core/network-boundary-inventory.ts +8 -0
  43. package/src/core/request-receipts.ts +671 -0
  44. package/src/index.ts +9 -0
  45. package/src/mcp/context.ts +8 -0
  46. package/src/mcp/http-egress.ts +4 -0
  47. package/src/mcp/http-transport.ts +2 -0
  48. package/src/mcp/tools/capture.ts +87 -83
  49. package/src/mcp/tools/index.ts +66 -0
  50. package/src/mcp/tools/memory-remember.ts +7 -0
  51. package/src/mcp/tools/memory-shared.ts +7 -1
  52. package/src/mcp/tools/request-status.ts +73 -0
  53. package/src/mcp/tools/sessions.ts +208 -0
  54. package/src/sdk/client.ts +180 -84
  55. package/src/sdk/index.ts +6 -0
  56. package/src/sdk/types.ts +54 -2
  57. package/src/serve/capture-service.ts +98 -32
  58. package/src/serve/config-sync.ts +3 -2
  59. package/src/serve/public/app.tsx +4 -1
  60. package/src/serve/public/components/CaptureModal.tsx +26 -8
  61. package/src/serve/public/components/sessions/AutomationPanel.tsx +800 -0
  62. package/src/serve/public/components/sessions/ImportReceipt.tsx +238 -0
  63. package/src/serve/public/components/sessions/SessionSearch.tsx +286 -0
  64. package/src/serve/public/components/sessions/SourcesPanel.tsx +541 -0
  65. package/src/serve/public/components/sessions/api.ts +40 -0
  66. package/src/serve/public/components/sessions/snippet.tsx +53 -0
  67. package/src/serve/public/globals.built.css +1 -1
  68. package/src/serve/public/hooks/use-api.ts +10 -2
  69. package/src/serve/public/lib/request-intent.ts +69 -0
  70. package/src/serve/public/lib/workspace-actions.ts +12 -1
  71. package/src/serve/public/lib/workspace-tabs.ts +2 -0
  72. package/src/serve/public/pages/Dashboard.tsx +10 -0
  73. package/src/serve/public/pages/DocView.tsx +15 -1
  74. package/src/serve/public/pages/DocumentEditor.tsx +139 -96
  75. package/src/serve/public/pages/Sessions.tsx +350 -0
  76. package/src/serve/resident-runtime.ts +43 -3
  77. package/src/serve/routes/api.ts +476 -147
  78. package/src/serve/routes/sessions.ts +766 -0
  79. package/src/serve/security.ts +9 -0
  80. package/src/serve/server.ts +205 -1
  81. package/src/serve/session-automation.ts +146 -0
  82. package/src/sessions/archive.ts +348 -0
  83. package/src/sessions/automation-state.ts +444 -0
  84. package/src/sessions/automation-status.ts +239 -0
  85. package/src/sessions/automation.ts +1169 -0
  86. package/src/sessions/binding.ts +105 -0
  87. package/src/sessions/claude-hook.ts +240 -0
  88. package/src/sessions/config.ts +176 -0
  89. package/src/sessions/format.ts +191 -0
  90. package/src/sessions/import-child-env.ts +8 -0
  91. package/src/sessions/import-child.ts +152 -0
  92. package/src/sessions/parsers/claude-code.ts +259 -0
  93. package/src/sessions/parsers/codex.ts +303 -0
  94. package/src/sessions/parsers/hermes.ts +248 -0
  95. package/src/sessions/parsers/openclaw.ts +496 -0
  96. package/src/sessions/parsers/shared.ts +184 -0
  97. package/src/sessions/sanitize.ts +222 -0
  98. package/src/sessions/service.ts +1533 -0
  99. package/src/sessions/setup.ts +477 -0
  100. package/src/sessions/sources.ts +518 -0
  101. package/src/sessions/state.ts +118 -0
  102. package/src/sessions/types.ts +457 -0
  103. package/src/store/sqlite/adapter.ts +54 -15
  104. package/src/store/sqlite/scoped-index.ts +9 -0
  105. package/browser-extension/artifacts/gno-browser-clipper-v2.5.1.zip.sha256 +0 -1
@@ -0,0 +1,208 @@
1
+ /**
2
+ * MCP session-archive tools: bounded status (including opt-in automation),
3
+ * import of owner-registered sources, and a run request for a configured
4
+ * automation profile. Thin adapters over the core sessions services. Hooks,
5
+ * schedules and source access are managed only by the local owner.
6
+ *
7
+ * Remote callers never discover host directories and never name paths:
8
+ * import accepts a registered source ID only. Import is registered only with
9
+ * `--enable-write`, and neither tool is part of the core profile.
10
+ *
11
+ * @module src/mcp/tools/sessions
12
+ */
13
+
14
+ import { z } from "zod";
15
+
16
+ import type { ToolContext } from "../server";
17
+
18
+ import { runAutomationProfile } from "../../sessions/automation";
19
+ import {
20
+ formatAutomationRunText,
21
+ formatImportReceiptText,
22
+ formatStatusText,
23
+ } from "../../sessions/format";
24
+ import { importInChildProcess } from "../../sessions/import-child";
25
+ import { SessionsService } from "../../sessions/service";
26
+ import {
27
+ MAX_IMPORT_LIMIT,
28
+ type SessionAutomationRunResult,
29
+ type SessionImportReceipt,
30
+ remoteSafeSessionsError,
31
+ } from "../../sessions/types";
32
+ import { runTool, type ToolResult } from "./index";
33
+
34
+ export const sessionsStatusInputSchema = z.object({});
35
+
36
+ export const sessionsImportInputSchema = z
37
+ .object({
38
+ sourceId: z
39
+ .string()
40
+ .trim()
41
+ .min(1)
42
+ .max(64)
43
+ .describe(
44
+ "ID of an owner-registered session source (see gno_sessions_status)"
45
+ ),
46
+ dryRun: z
47
+ .boolean()
48
+ .optional()
49
+ .describe("Parse and report without writing archive or index state"),
50
+ limit: z
51
+ .number()
52
+ .int()
53
+ .min(1)
54
+ .max(MAX_IMPORT_LIMIT)
55
+ .optional()
56
+ .describe(
57
+ "Maximum changed units processed this call; the rest are deferred"
58
+ ),
59
+ })
60
+ .strict();
61
+
62
+ export type SessionsImportToolInput = z.infer<typeof sessionsImportInputSchema>;
63
+
64
+ export const sessionsAutomationRunInputSchema = z
65
+ .object({
66
+ profileId: z
67
+ .string()
68
+ .trim()
69
+ .min(1)
70
+ .max(64)
71
+ .describe(
72
+ "ID of an owner-configured automation profile (see gno_sessions_status)"
73
+ ),
74
+ })
75
+ .strict();
76
+
77
+ export type SessionsAutomationRunToolInput = z.infer<
78
+ typeof sessionsAutomationRunInputSchema
79
+ >;
80
+
81
+ export const SESSIONS_AUTOMATION_RUN_MCP_ANNOTATIONS = {
82
+ readOnlyHint: false,
83
+ destructiveHint: false,
84
+ idempotentHint: true,
85
+ openWorldHint: false,
86
+ } as const;
87
+
88
+ export const SESSIONS_STATUS_MCP_ANNOTATIONS = {
89
+ readOnlyHint: true,
90
+ destructiveHint: false,
91
+ idempotentHint: true,
92
+ openWorldHint: false,
93
+ } as const;
94
+
95
+ export const SESSIONS_IMPORT_MCP_ANNOTATIONS = {
96
+ readOnlyHint: false,
97
+ destructiveHint: false,
98
+ idempotentHint: true,
99
+ openWorldHint: false,
100
+ } as const;
101
+
102
+ function service(ctx: ToolContext): SessionsService {
103
+ return new SessionsService({
104
+ config: ctx.config,
105
+ configPath: ctx.actualConfigPath,
106
+ indexName: ctx.indexName,
107
+ store: ctx.store,
108
+ });
109
+ }
110
+
111
+ /** Re-throw as `CODE: message` (the shape runTool parses), never with host paths. */
112
+ function rethrowSessionsError(error: unknown): never {
113
+ const typed = remoteSafeSessionsError(error);
114
+ throw new Error(`${typed.code}: ${typed.message}`);
115
+ }
116
+
117
+ export function handleSessionsStatus(ctx: ToolContext): Promise<ToolResult> {
118
+ return runTool(
119
+ ctx,
120
+ "gno_sessions_status",
121
+ async () => {
122
+ try {
123
+ return await service(ctx).status();
124
+ } catch (error) {
125
+ return rethrowSessionsError(error);
126
+ }
127
+ },
128
+ formatStatusText
129
+ );
130
+ }
131
+
132
+ export function handleSessionsImport(
133
+ args: SessionsImportToolInput,
134
+ ctx: ToolContext
135
+ ): Promise<ToolResult> {
136
+ return runTool(
137
+ ctx,
138
+ "gno_sessions_import",
139
+ async () => {
140
+ if (!ctx.enableWrite) {
141
+ throw new Error(
142
+ "WRITE_DISABLED: gno_sessions_import requires --enable-write or GNO_MCP_ENABLE_WRITE=1"
143
+ );
144
+ }
145
+ let receipt: SessionImportReceipt;
146
+ try {
147
+ // A child process keeps this server answering during a long import.
148
+ receipt = await importInChildProcess({
149
+ config: ctx.config,
150
+ configPath: ctx.actualConfigPath,
151
+ indexName: ctx.indexName,
152
+ sourceId: args.sourceId,
153
+ dryRun: args.dryRun === true,
154
+ limit: args.limit,
155
+ });
156
+ } catch (error) {
157
+ return rethrowSessionsError(error);
158
+ }
159
+ if (!receipt.dryRun && receipt.lexical.collections.length > 0) {
160
+ ctx.markContentMutation?.();
161
+ ctx.markIndexMutation?.();
162
+ }
163
+ return receipt;
164
+ },
165
+ formatImportReceiptText
166
+ );
167
+ }
168
+
169
+ export function handleSessionsAutomationRun(
170
+ args: SessionsAutomationRunToolInput,
171
+ ctx: ToolContext
172
+ ): Promise<ToolResult> {
173
+ return runTool(
174
+ ctx,
175
+ "gno_sessions_automation_run",
176
+ async () => {
177
+ if (!ctx.enableWrite) {
178
+ throw new Error(
179
+ "WRITE_DISABLED: gno_sessions_automation_run requires --enable-write or GNO_MCP_ENABLE_WRITE=1"
180
+ );
181
+ }
182
+ let result: SessionAutomationRunResult;
183
+ try {
184
+ result = await runAutomationProfile(
185
+ {
186
+ configPath: ctx.actualConfigPath,
187
+ indexName: ctx.indexName,
188
+ store: ctx.store,
189
+ // A child process keeps this server answering during the run.
190
+ inChildProcess: true,
191
+ },
192
+ args.profileId,
193
+ { trigger: "manual" }
194
+ );
195
+ } catch (error) {
196
+ return rethrowSessionsError(error);
197
+ }
198
+ if (
199
+ result.receipts.some((receipt) => receipt.lexical.collections.length)
200
+ ) {
201
+ ctx.markContentMutation?.();
202
+ ctx.markIndexMutation?.();
203
+ }
204
+ return result;
205
+ },
206
+ formatAutomationRunText
207
+ );
208
+ }
package/src/sdk/client.ts CHANGED
@@ -21,6 +21,7 @@ import type {
21
21
  GnoCaptureOptions,
22
22
  GnoCaptureResult,
23
23
  GnoClient,
24
+ GnoRequestStatusResult,
24
25
  GnoCreateFolderOptions,
25
26
  GnoCreateFolderResult,
26
27
  GnoCreateNoteOptions,
@@ -49,6 +50,11 @@ import type {
49
50
  GnoRenameNoteApplyOptions,
50
51
  GnoRenameNoteOptions,
51
52
  GnoSearchOptions,
53
+ GnoSessionsAutomationRunResult,
54
+ GnoSessionsDiscovery,
55
+ GnoSessionsImportInput,
56
+ GnoSessionsImportReceipt,
57
+ GnoSessionsStatus,
52
58
  GnoUpdateOptions,
53
59
  GnoVectorSearchOptions,
54
60
  KnowledgeChangesResult,
@@ -90,17 +96,17 @@ import { buildVerifiedAsk } from "../app/verified-ask";
90
96
  import {
91
97
  buildContentTypeBoostStatus,
92
98
  ConfigSchema,
99
+ getConfigPaths,
93
100
  loadConfig,
94
101
  normalizeConfigContentTypes,
95
102
  normalizeContentTypes,
96
103
  } from "../config";
97
104
  import {
98
- buildCaptureReceipt,
99
- type CapturePlan,
105
+ CaptureSyncError,
100
106
  listCaptureDiskRelPaths,
101
107
  planCapture,
102
108
  } from "../core/capture";
103
- import { writeCapturePlanFile } from "../core/capture-write";
109
+ import { publishCapture } from "../core/capture-publish";
104
110
  import { projectCollectionEgressPolicy } from "../core/collection-egress-policy-projection";
105
111
  import { CollectionEgressPolicyService } from "../core/collection-egress-policy-service";
106
112
  import { applyConfigChange } from "../core/config-mutation";
@@ -140,6 +146,12 @@ import {
140
146
  ProjectAffinityInputError,
141
147
  resolveRemoteProjectAffinity,
142
148
  } from "../core/project-affinity-surface";
149
+ import {
150
+ localRequestLedger,
151
+ readRequestStatus,
152
+ RequestReceiptError,
153
+ type RequestReceiptErrorCode,
154
+ } from "../core/request-receipts";
143
155
  import { RetrievalTraceManagementService } from "../core/retrieval-trace-management";
144
156
  import {
145
157
  finishRetrievalTraceAfterError,
@@ -169,7 +181,6 @@ import {
169
181
  normalizeMetadataPredicate,
170
182
  type MetadataPredicate,
171
183
  } from "../core/typed-metadata";
172
- import { writeLeasePath } from "../core/write-lease";
173
184
  import {
174
185
  defaultSyncService,
175
186
  type SyncResult,
@@ -193,6 +204,10 @@ import { searchHybrid } from "../pipeline/hybrid";
193
204
  import { RequestHydration } from "../pipeline/hydration";
194
205
  import { searchBm25 } from "../pipeline/search";
195
206
  import { searchVectorWithEmbedding } from "../pipeline/vsearch";
207
+ import { runAutomationProfile } from "../sessions/automation";
208
+ import { assertSessionBinding } from "../sessions/binding";
209
+ import { SessionsService } from "../sessions/service";
210
+ import { SESSIONS_VALIDATION_CODES, SessionsError } from "../sessions/types";
196
211
  import { SqliteAdapter } from "../store/sqlite/adapter";
197
212
  import { openScopedIndexStore } from "../store/sqlite/scoped-index";
198
213
  import { createVectorIndexPort } from "../store/vector";
@@ -314,6 +329,19 @@ async function resolveClientState(
314
329
  options.indexName ?? DEFAULT_INDEX_NAME
315
330
  );
316
331
  const dbPath = options.dbPath ?? getIndexDbPath(indexName);
332
+ try {
333
+ await assertSessionBinding({
334
+ config,
335
+ configPath:
336
+ configSource === "inline"
337
+ ? "<inline-config>"
338
+ : (options.configPath ?? getConfigPaths().configFile),
339
+ indexName,
340
+ dbPath,
341
+ });
342
+ } catch (cause) {
343
+ throw toSessionsSdkError(cause);
344
+ }
317
345
  await mkdir(dirname(dbPath), { recursive: true });
318
346
 
319
347
  const store = new SqliteAdapter();
@@ -337,6 +365,16 @@ async function resolveClientState(
337
365
  };
338
366
  }
339
367
 
368
+ /** Map a sessions error onto the SDK family; the sessions code survives in `details.code`. */
369
+ function toSessionsSdkError(cause: unknown): unknown {
370
+ if (!(cause instanceof SessionsError)) return cause;
371
+ return sdkError(
372
+ SESSIONS_VALIDATION_CODES.has(cause.code) ? "VALIDATION" : "RUNTIME",
373
+ cause.message,
374
+ { cause, details: { code: cause.code } }
375
+ );
376
+ }
377
+
340
378
  /** SDK error family per memory code; exhaustive so a new code fails to compile. */
341
379
  const MEMORY_ERROR_TO_SDK: Readonly<Record<MemoryErrorCode, GnoSdkErrorCode>> =
342
380
  {
@@ -363,9 +401,28 @@ const MEMORY_ERROR_TO_SDK: Readonly<Record<MemoryErrorCode, GnoSdkErrorCode>> =
363
401
  MEMORY_QUERY_FAILED: "RUNTIME",
364
402
  };
365
403
 
404
+ const REQUEST_ERROR_TO_SDK: Record<RequestReceiptErrorCode, GnoSdkErrorCode> = {
405
+ REQUEST_ID_INVALID: "VALIDATION",
406
+ REQUEST_ID_CONFLICT: "VALIDATION",
407
+ REQUEST_EXPIRED: "VALIDATION",
408
+ REQUEST_PENDING: "RUNTIME",
409
+ REQUEST_RECOVERY_CONFLICT: "RUNTIME",
410
+ REQUEST_CAPACITY_EXHAUSTED: "RUNTIME",
411
+ REQUEST_LEDGER_UNAVAILABLE: "RUNTIME",
412
+ };
413
+
414
+ /** Request receipt errors carry their stable code in `details.code`. */
415
+ function toRequestSdkError(cause: unknown): unknown {
416
+ if (!(cause instanceof RequestReceiptError)) return cause;
417
+ return sdkError(REQUEST_ERROR_TO_SDK[cause.code], cause.message, {
418
+ cause,
419
+ details: { code: cause.code },
420
+ });
421
+ }
422
+
366
423
  /** Map a core MemoryError onto the SDK error family; the memory code survives in `details.code`. */
367
424
  function toMemorySdkError(cause: unknown): unknown {
368
- if (!(cause instanceof MemoryError)) return cause;
425
+ if (!(cause instanceof MemoryError)) return toRequestSdkError(cause);
369
426
  return sdkError(MEMORY_ERROR_TO_SDK[cause.code], cause.message, {
370
427
  cause,
371
428
  details: { code: cause.code },
@@ -1345,6 +1402,8 @@ class GnoClientImpl implements GnoClient {
1345
1402
  requestedIndexName: resolution.value.indexName,
1346
1403
  config: this.config,
1347
1404
  configPath: this.configPath,
1405
+ }).catch((cause: unknown) => {
1406
+ throw toSessionsSdkError(cause);
1348
1407
  });
1349
1408
  try {
1350
1409
  const result = await getDocumentByRef(
@@ -1384,6 +1443,8 @@ class GnoClientImpl implements GnoClient {
1384
1443
  requestedIndexName: resolution.value.indexName,
1385
1444
  config: this.config,
1386
1445
  configPath: this.configPath,
1446
+ }).catch((cause: unknown) => {
1447
+ throw toSessionsSdkError(cause);
1387
1448
  });
1388
1449
  try {
1389
1450
  const result = await multiGetDocuments(
@@ -1790,7 +1851,8 @@ class GnoClientImpl implements GnoClient {
1790
1851
  store: this.store,
1791
1852
  config: this.config,
1792
1853
  collections: this.config.collections,
1793
- lockPath: writeLeasePath(this.dbPath),
1854
+ lockPath: this.requestLedger().lockPath,
1855
+ requests: this.requestLedger(),
1794
1856
  embedPort: ports.embedPort,
1795
1857
  vectorIndex: ports.vectorIndex,
1796
1858
  })
@@ -1802,6 +1864,62 @@ class GnoClientImpl implements GnoClient {
1802
1864
  }
1803
1865
  }
1804
1866
 
1867
+ private sessionsService(): SessionsService {
1868
+ this.assertOpen();
1869
+ return new SessionsService({
1870
+ config: this.config,
1871
+ configPath: this.configPath ?? getConfigPaths().configFile,
1872
+ indexName: this.indexName,
1873
+ store: this.store,
1874
+ });
1875
+ }
1876
+
1877
+ async sessionsStatus(): Promise<GnoSessionsStatus> {
1878
+ try {
1879
+ return await this.sessionsService().status();
1880
+ } catch (cause) {
1881
+ throw toSessionsSdkError(cause);
1882
+ }
1883
+ }
1884
+
1885
+ async discoverSessions(): Promise<GnoSessionsDiscovery> {
1886
+ try {
1887
+ return await this.sessionsService().discover();
1888
+ } catch (cause) {
1889
+ throw toSessionsSdkError(cause);
1890
+ }
1891
+ }
1892
+
1893
+ async importSessions(
1894
+ input: GnoSessionsImportInput
1895
+ ): Promise<GnoSessionsImportReceipt> {
1896
+ try {
1897
+ // The SDK runs in the owner's process, so explicit paths are allowed.
1898
+ return await this.sessionsService().import(input, { allowPaths: true });
1899
+ } catch (cause) {
1900
+ throw toSessionsSdkError(cause);
1901
+ }
1902
+ }
1903
+
1904
+ async runSessionsAutomation(input: {
1905
+ profileId: string;
1906
+ }): Promise<GnoSessionsAutomationRunResult> {
1907
+ this.assertOpen();
1908
+ try {
1909
+ return await runAutomationProfile(
1910
+ {
1911
+ configPath: this.configPath ?? getConfigPaths().configFile,
1912
+ indexName: this.indexName,
1913
+ store: this.store,
1914
+ },
1915
+ input.profileId,
1916
+ { trigger: "manual" }
1917
+ );
1918
+ } catch (cause) {
1919
+ throw toSessionsSdkError(cause);
1920
+ }
1921
+ }
1922
+
1805
1923
  async remember(input: GnoRememberInput): Promise<GnoRememberResult> {
1806
1924
  return this.withMemoryService(input?.collection, (service) =>
1807
1925
  service.remember(input)
@@ -1823,98 +1941,76 @@ class GnoClientImpl implements GnoClient {
1823
1941
  `Collection not found: ${options.collection}`
1824
1942
  );
1825
1943
  }
1826
-
1827
- const existingList = await this.store.listDocuments(collection.name);
1828
- if (!existingList.ok) {
1829
- throw sdkError("STORE", existingList.error.message, {
1830
- cause: existingList.error.cause,
1831
- });
1832
- }
1833
- const { overwrite: _unsupportedOverwrite, ...captureOptions } =
1834
- options as GnoCaptureOptions & { overwrite?: unknown };
1944
+ const {
1945
+ overwrite: _unsupportedOverwrite,
1946
+ requestId,
1947
+ ...captureOptions
1948
+ } = options as GnoCaptureOptions & { overwrite?: unknown };
1835
1949
  if (_unsupportedOverwrite !== undefined) {
1836
1950
  throw sdkError(
1837
1951
  "VALIDATION",
1838
1952
  "overwrite is not supported by client.capture(); use collisionPolicy instead"
1839
1953
  );
1840
1954
  }
1955
+ const input = { ...captureOptions, collection: collection.name };
1841
1956
 
1842
- let plan: CapturePlan;
1843
1957
  try {
1844
- plan = planCapture({
1845
- input: {
1846
- ...captureOptions,
1847
- collection: collection.name,
1958
+ const published = await publishCapture({
1959
+ collection,
1960
+ store: this.store,
1961
+ lockPath: this.requestLedger().lockPath,
1962
+ reportSyncFailure: true,
1963
+ config: this.config,
1964
+ plan: async () => {
1965
+ const existingList = await this.store.listDocuments(collection.name);
1966
+ if (!existingList.ok) {
1967
+ throw sdkError("STORE", existingList.error.message, {
1968
+ cause: existingList.error.cause,
1969
+ });
1970
+ }
1971
+ try {
1972
+ return planCapture({
1973
+ input,
1974
+ existingRelPaths: existingList.value.map((doc) => doc.relPath),
1975
+ diskRelPaths: await listCaptureDiskRelPaths(collection.path),
1976
+ });
1977
+ } catch (error) {
1978
+ throw sdkError(
1979
+ "VALIDATION",
1980
+ error instanceof Error ? error.message : String(error)
1981
+ );
1982
+ }
1848
1983
  },
1849
- existingRelPaths: existingList.value.map((doc) => doc.relPath),
1850
- diskRelPaths: await listCaptureDiskRelPaths(collection.path),
1984
+ request:
1985
+ requestId === undefined
1986
+ ? undefined
1987
+ : { ...this.requestLedger(), requestId, input },
1851
1988
  });
1852
- } catch (error) {
1853
- throw sdkError(
1854
- "VALIDATION",
1855
- error instanceof Error ? error.message : String(error)
1856
- );
1857
- }
1858
-
1859
- const fullPath = `${collection.path}/${plan.relPath}`;
1860
- if (plan.openedExisting) {
1861
- const existingDoc = await this.store.getDocument(
1862
- collection.name,
1863
- plan.relPath
1864
- );
1865
- if (!existingDoc.ok) {
1866
- throw sdkError("STORE", existingDoc.error.message, {
1867
- cause: existingDoc.error.cause,
1989
+ return published.request
1990
+ ? { ...published.receipt, request: published.request }
1991
+ : published.receipt;
1992
+ } catch (cause) {
1993
+ if (cause instanceof CaptureSyncError) {
1994
+ throw sdkError("RUNTIME", cause.message, {
1995
+ cause,
1996
+ details: { code: cause.code, absPath: cause.absPath },
1868
1997
  });
1869
1998
  }
1870
- return buildCaptureReceipt({
1871
- plan,
1872
- absPath: fullPath,
1873
- docid: existingDoc.value?.docid,
1874
- sync: existingDoc.value
1875
- ? { status: "completed" }
1876
- : {
1877
- status: "skipped",
1878
- reason: "Existing file is not indexed yet.",
1879
- },
1880
- });
1999
+ throw toRequestSdkError(cause);
1881
2000
  }
2001
+ }
1882
2002
 
1883
- await mkdir(dirname(fullPath), { recursive: true });
1884
- await writeCapturePlanFile(plan, fullPath);
1885
- const syncResults = await defaultSyncService.syncFiles(
1886
- collection,
1887
- this.store,
1888
- [plan.relPath],
1889
- withContentTypeRules(
1890
- {
1891
- runUpdateCmd: false,
1892
- gitPull: false,
1893
- },
1894
- this.config
1895
- )
1896
- );
1897
- const syncResult = syncResults[0];
1898
- const docResult = await this.store.getDocument(
1899
- collection.name,
1900
- plan.relPath
1901
- );
1902
- const docid = docResult.ok ? docResult.value?.docid : undefined;
1903
- return buildCaptureReceipt({
1904
- plan,
1905
- absPath: fullPath,
1906
- docid: syncResult?.docid ?? docid,
1907
- sync:
1908
- syncResult?.status === "error"
1909
- ? {
1910
- status: "failed",
1911
- error:
1912
- syncResult.errorMessage ??
1913
- syncResult.errorCode ??
1914
- "Unknown sync error",
1915
- }
1916
- : { status: "completed" },
1917
- });
2003
+ private requestLedger() {
2004
+ return localRequestLedger(this.dbPath);
2005
+ }
2006
+
2007
+ async requestStatus(requestId: string): Promise<GnoRequestStatusResult> {
2008
+ this.assertOpen();
2009
+ try {
2010
+ return await readRequestStatus({ ...this.requestLedger(), requestId });
2011
+ } catch (cause) {
2012
+ throw toRequestSdkError(cause);
2013
+ }
1918
2014
  }
1919
2015
 
1920
2016
  async createFolder(
package/src/sdk/index.ts CHANGED
@@ -62,10 +62,16 @@ export type {
62
62
  GnoRecallResult,
63
63
  GnoRememberInput,
64
64
  GnoRememberResult,
65
+ GnoRequestStatusResult,
65
66
  GnoRenameNoteApplyOptions,
66
67
  GnoRenameNoteOptions,
67
68
  GnoProjectHintOptions,
68
69
  GnoSearchOptions,
70
+ GnoSessionsDiscovery,
71
+ GnoSessionsImportInput,
72
+ GnoSessionsImportReceipt,
73
+ GnoSessionsAutomationRunResult,
74
+ GnoSessionsStatus,
69
75
  GnoSkippedDocument,
70
76
  GnoUpdateOptions,
71
77
  GnoVectorSearchOptions,
package/src/sdk/types.ts CHANGED
@@ -67,6 +67,10 @@ import type {
67
67
  } from "../core/memory";
68
68
  import type { NoteCollisionPolicy } from "../core/note-creation";
69
69
  import type { NotePresetId } from "../core/note-presets";
70
+ import type {
71
+ RequestReceiptInfo,
72
+ RequestStatusResult,
73
+ } from "../core/request-receipts";
70
74
  import type {
71
75
  RetrievalTraceDeleteResult,
72
76
  RetrievalTraceDetail,
@@ -94,6 +98,13 @@ import type {
94
98
  SearchOptions,
95
99
  SearchResults,
96
100
  } from "../pipeline/types";
101
+ import type { SessionImportInput } from "../sessions/service";
102
+ import type {
103
+ SessionAutomationRunResult,
104
+ SessionImportReceipt,
105
+ SessionsDiscovery,
106
+ SessionsStatus,
107
+ } from "../sessions/types";
97
108
  import type { IndexStatus } from "../store/types";
98
109
 
99
110
  export type {
@@ -263,11 +274,25 @@ export interface GnoCreateNoteResult {
263
274
  createdWithSuffix?: boolean;
264
275
  }
265
276
 
266
- export interface GnoCaptureOptions extends Omit<CaptureInput, "overwrite"> {}
277
+ export interface GnoCaptureOptions extends Omit<CaptureInput, "overwrite"> {
278
+ /** Opt-in retry identity; reuse it only to retry this same capture. */
279
+ requestId?: string;
280
+ }
267
281
 
268
- export type GnoCaptureResult = CaptureReceipt;
282
+ export type GnoCaptureResult = CaptureReceipt & {
283
+ /** Present when the call carried a requestId. */
284
+ request?: RequestReceiptInfo;
285
+ };
286
+
287
+ /** Lookup of a capture/remember request ID in the local-owner namespace. */
288
+ export type GnoRequestStatusResult = RequestStatusResult;
269
289
 
270
290
  /** Shared memory contract (identical on CLI, MCP, REST, and SDK). */
291
+ export type GnoSessionsStatus = SessionsStatus;
292
+ export type GnoSessionsDiscovery = SessionsDiscovery;
293
+ export type GnoSessionsImportReceipt = SessionImportReceipt;
294
+ export type GnoSessionsImportInput = SessionImportInput;
295
+ export type GnoSessionsAutomationRunResult = SessionAutomationRunResult;
271
296
  export type GnoRememberInput = RememberInput;
272
297
  export type GnoRememberResult = RememberResult;
273
298
  export type GnoRecallInput = RecallInput;
@@ -414,6 +439,11 @@ export interface GnoClient {
414
439
  embed(options?: GnoEmbedOptions): Promise<GnoEmbedResult>;
415
440
  index(options?: GnoIndexOptions): Promise<GnoIndexResult>;
416
441
  capture(options: GnoCaptureOptions): Promise<GnoCaptureResult>;
442
+ /**
443
+ * Look up a capture/remember `requestId` before retrying it: pending,
444
+ * committed (with a content-free result pointer), expired, or not_found.
445
+ */
446
+ requestStatus(requestId: string): Promise<GnoRequestStatusResult>;
417
447
  /**
418
448
  * Store one fact in a memory-managed collection, or propose candidates when
419
449
  * `decision` is omitted. Requires caller + session identity and explicit
@@ -425,6 +455,28 @@ export interface GnoClient {
425
455
  * The result carries a content-free fencing receipt.
426
456
  */
427
457
  recall(input: GnoRecallInput): Promise<GnoRecallResult>;
458
+ /**
459
+ * Session-archive status for a client opened on the dedicated archive
460
+ * config/index pair. Throws VALIDATION (`details.code`
461
+ * SESSIONS_NOT_CONFIGURED) on any other config.
462
+ */
463
+ sessionsStatus(): Promise<GnoSessionsStatus>;
464
+ /** Preview supported local session sources on this host. Never imports. */
465
+ discoverSessions(): Promise<GnoSessionsDiscovery>;
466
+ /**
467
+ * Manually import a registered source (`sourceId`) or explicit local paths
468
+ * (`paths` + `collection`) into the archive. Returns the shared receipt.
469
+ */
470
+ importSessions(
471
+ input: GnoSessionsImportInput
472
+ ): Promise<GnoSessionsImportReceipt>;
473
+ /**
474
+ * Run one configured automation profile now through the manual importer.
475
+ * Hooks and schedules are enabled only from the CLI or a same-host browser.
476
+ */
477
+ runSessionsAutomation(input: {
478
+ profileId: string;
479
+ }): Promise<GnoSessionsAutomationRunResult>;
428
480
  createNote(options: GnoCreateNoteOptions): Promise<GnoCreateNoteResult>;
429
481
  createFolder(options: GnoCreateFolderOptions): Promise<GnoCreateFolderResult>;
430
482
  previewRenameNote(