@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
@@ -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 { CaptureInput, CapturePlan, CaptureSource } from "../core/capture";
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 { collection, fullPath, plan } = planned;
347
- return withWriteLock(
348
- resolveCaptureLockPath(context, dependencies),
349
- async () => {
350
- await mkdir(dirname(fullPath), { recursive: true });
351
- context.watchService?.suppress(fullPath);
352
- await writeCapturePlanFile(plan, fullPath);
353
- const synced = await syncCapturedFile({
354
- collection,
355
- store,
356
- relPath: plan.relPath,
357
- absPath: fullPath,
358
- config: context.config,
359
- syncPaths: dependencies.syncPaths,
360
- });
361
- if (synced.result) {
362
- recordContentMutation(synced.result, context.markContentMutation);
363
- }
364
- emitCaptureCreated(context, collection, plan.relPath);
365
- return {
366
- body: buildCaptureReceipt({
367
- plan,
368
- absPath: fullPath,
369
- docid: synced.docid,
370
- sync: synced.sync,
371
- }),
372
- status: HTTP_CREATED,
373
- };
374
- },
375
- dependencies.lockWaitMs ?? DEFAULT_LOCK_WAIT_MS
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
  /**
@@ -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.collections,
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.collections,
90
+ watchedCollections(ctxHolder.config),
90
91
  withContentTypeRules({}, ctxHolder.config)
91
92
  );
92
93
  }
@@ -49,6 +49,7 @@ const Ask = lazy(() => import("./pages/Ask"));
49
49
  const GraphView = lazy(() => import("./pages/GraphView"));
50
50
  const CompiledContext = lazy(() => import("./pages/CompiledContext"));
51
51
  const TraceHistory = lazy(() => import("./pages/TraceHistory"));
52
+ const Sessions = lazy(() => import("./pages/Sessions"));
52
53
 
53
54
  type Route =
54
55
  | "/"
@@ -61,7 +62,8 @@ type Route =
61
62
  | "/graph"
62
63
  | "/connectors"
63
64
  | "/context/compiled"
64
- | "/traces";
65
+ | "/traces"
66
+ | "/sessions";
65
67
  type Navigate = (to: string | number) => void;
66
68
 
67
69
  interface RoutePageProps {
@@ -80,6 +82,7 @@ const routes: Record<Route, React.ComponentType<RoutePageProps>> = {
80
82
  "/ask": Ask,
81
83
  "/graph": GraphView,
82
84
  "/traces": TraceHistory,
85
+ "/sessions": Sessions,
83
86
  "/context/compiled": CompiledContext,
84
87
  };
85
88
 
@@ -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
- collection,
320
- title,
321
- folderPath: defaultFolderPath || undefined,
322
- content: presetOnly ? undefined : content,
323
- presetId: submitPresetId,
324
- collisionPolicy: "create_with_suffix",
325
- source,
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