@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.
Files changed (160) hide show
  1. package/README.md +60 -5
  2. package/assets/skill/README.md +5 -1
  3. package/assets/skill/SKILL.md +80 -4
  4. package/assets/skill/cli-reference.md +132 -2
  5. package/assets/skill/examples.md +30 -0
  6. package/assets/skill/mcp-reference.md +54 -1
  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.7.0.zip} +0 -0
  13. package/browser-extension/artifacts/gno-browser-clipper-v2.7.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 +449 -35
  17. package/spec/mcp.md +234 -4
  18. package/spec/output-schemas/ask.schema.json +1 -1
  19. package/spec/output-schemas/capture-receipt.schema.json +4 -1
  20. package/spec/output-schemas/doctor.schema.json +88 -0
  21. package/spec/output-schemas/error.schema.json +11 -2
  22. package/spec/output-schemas/get.schema.json +1 -1
  23. package/spec/output-schemas/mcp-capture-result.schema.json +4 -2
  24. package/spec/output-schemas/memory-remember.schema.json +10 -4
  25. package/spec/output-schemas/multi-get.schema.json +4 -1
  26. package/spec/output-schemas/peek.schema.json +2 -9
  27. package/spec/output-schemas/request-status.schema.json +113 -0
  28. package/spec/output-schemas/resident-status.schema.json +22 -0
  29. package/spec/output-schemas/search-result.schema.json +1 -1
  30. package/spec/output-schemas/search-results.schema.json +1 -1
  31. package/spec/output-schemas/sessions-automation-run.schema.json +46 -0
  32. package/spec/output-schemas/sessions-discovery.schema.json +38 -0
  33. package/spec/output-schemas/sessions-import-receipt.schema.json +156 -0
  34. package/spec/output-schemas/sessions-status.schema.json +432 -0
  35. package/spec/output-schemas/status.schema.json +98 -0
  36. package/src/cli/commands/ask.ts +14 -2
  37. package/src/cli/commands/capture.ts +55 -96
  38. package/src/cli/commands/daemon.ts +41 -0
  39. package/src/cli/commands/doctor.ts +54 -20
  40. package/src/cli/commands/embed.ts +41 -3
  41. package/src/cli/commands/ls.ts +3 -0
  42. package/src/cli/commands/memory.ts +12 -3
  43. package/src/cli/commands/query.ts +5 -0
  44. package/src/cli/commands/request-status.ts +59 -0
  45. package/src/cli/commands/reset.ts +39 -5
  46. package/src/cli/commands/sessions.ts +713 -0
  47. package/src/cli/commands/shared.ts +14 -1
  48. package/src/cli/commands/status.ts +63 -5
  49. package/src/cli/commands/vec.ts +54 -0
  50. package/src/cli/detach.ts +29 -1
  51. package/src/cli/errors.ts +13 -9
  52. package/src/cli/program.ts +441 -2
  53. package/src/cli/session-binding.ts +49 -0
  54. package/src/config/types.ts +8 -0
  55. package/src/core/capture-publish.ts +239 -0
  56. package/src/core/capture-sync.ts +12 -2
  57. package/src/core/host-paths.ts +31 -0
  58. package/src/core/memory-remember.ts +234 -122
  59. package/src/core/memory-types.ts +11 -0
  60. package/src/core/network-boundary-inventory.ts +8 -0
  61. package/src/core/request-receipts.ts +671 -0
  62. package/src/core/shutdown-budget.ts +6 -0
  63. package/src/core/vector-partition-status.ts +52 -0
  64. package/src/embed/backlog.ts +124 -18
  65. package/src/embed/fingerprint.ts +6 -3
  66. package/src/embed/retry.ts +66 -27
  67. package/src/embed/variant-backlog.ts +15 -10
  68. package/src/embed/variant-retry.ts +31 -22
  69. package/src/index.ts +30 -2
  70. package/src/llm/native-worker/dispatcher.ts +2 -0
  71. package/src/llm/native-worker/embedding-identity.ts +42 -0
  72. package/src/llm/native-worker/protocol.ts +1 -0
  73. package/src/llm/types.ts +3 -0
  74. package/src/mcp/context.ts +17 -0
  75. package/src/mcp/http-egress.ts +4 -0
  76. package/src/mcp/http-transport.ts +2 -0
  77. package/src/mcp/resources/index.ts +6 -5
  78. package/src/mcp/tool-descriptions-core.ts +1 -1
  79. package/src/mcp/tools/capture.ts +87 -85
  80. package/src/mcp/tools/index.ts +77 -4
  81. package/src/mcp/tools/memory-remember.ts +8 -1
  82. package/src/mcp/tools/memory-shared.ts +7 -1
  83. package/src/mcp/tools/request-status.ts +73 -0
  84. package/src/mcp/tools/sessions.ts +208 -0
  85. package/src/mcp/tools/status.ts +4 -0
  86. package/src/pipeline/hybrid.ts +37 -7
  87. package/src/pipeline/vsearch.ts +14 -2
  88. package/src/sdk/client.ts +180 -84
  89. package/src/sdk/index.ts +6 -0
  90. package/src/sdk/types.ts +54 -2
  91. package/src/serve/capture-service.ts +98 -32
  92. package/src/serve/config-sync.ts +3 -2
  93. package/src/serve/embed-scheduler.ts +133 -19
  94. package/src/serve/host-path-redaction.ts +79 -0
  95. package/src/serve/public/app.tsx +4 -1
  96. package/src/serve/public/components/CaptureModal.tsx +26 -8
  97. package/src/serve/public/components/sessions/AutomationPanel.tsx +800 -0
  98. package/src/serve/public/components/sessions/ImportReceipt.tsx +238 -0
  99. package/src/serve/public/components/sessions/SessionSearch.tsx +286 -0
  100. package/src/serve/public/components/sessions/SourcesPanel.tsx +541 -0
  101. package/src/serve/public/components/sessions/api.ts +40 -0
  102. package/src/serve/public/globals.built.css +1 -1
  103. package/src/serve/public/hooks/use-api.ts +26 -3
  104. package/src/serve/public/lib/request-intent.ts +77 -0
  105. package/src/serve/public/lib/snippet.tsx +52 -0
  106. package/src/serve/public/lib/workspace-actions.ts +12 -1
  107. package/src/serve/public/lib/workspace-tabs.ts +2 -0
  108. package/src/serve/public/pages/Dashboard.tsx +22 -9
  109. package/src/serve/public/pages/DocView.tsx +25 -6
  110. package/src/serve/public/pages/DocumentEditor.tsx +224 -104
  111. package/src/serve/public/pages/Search.tsx +1 -41
  112. package/src/serve/public/pages/Sessions.tsx +350 -0
  113. package/src/serve/resident-runtime.ts +69 -4
  114. package/src/serve/resident-status.ts +13 -1
  115. package/src/serve/routes/api.ts +476 -147
  116. package/src/serve/routes/sessions.ts +766 -0
  117. package/src/serve/security.ts +9 -0
  118. package/src/serve/server.ts +215 -10
  119. package/src/serve/session-automation.ts +146 -0
  120. package/src/serve/status-model.ts +16 -0
  121. package/src/serve/status.ts +2 -0
  122. package/src/serve/watch-reconciliation-shared.ts +3 -0
  123. package/src/serve/watch-service-events.ts +3 -2
  124. package/src/serve/watch-service-run-flush.ts +35 -2
  125. package/src/serve/watch-service.ts +5 -0
  126. package/src/sessions/archive.ts +348 -0
  127. package/src/sessions/automation-state.ts +444 -0
  128. package/src/sessions/automation-status.ts +239 -0
  129. package/src/sessions/automation.ts +1169 -0
  130. package/src/sessions/binding.ts +105 -0
  131. package/src/sessions/claude-hook.ts +240 -0
  132. package/src/sessions/config.ts +176 -0
  133. package/src/sessions/format.ts +191 -0
  134. package/src/sessions/import-child-env.ts +8 -0
  135. package/src/sessions/import-child.ts +152 -0
  136. package/src/sessions/parsers/claude-code.ts +259 -0
  137. package/src/sessions/parsers/codex.ts +303 -0
  138. package/src/sessions/parsers/hermes.ts +248 -0
  139. package/src/sessions/parsers/openclaw.ts +496 -0
  140. package/src/sessions/parsers/shared.ts +184 -0
  141. package/src/sessions/sanitize.ts +222 -0
  142. package/src/sessions/service.ts +1533 -0
  143. package/src/sessions/setup.ts +477 -0
  144. package/src/sessions/sources.ts +518 -0
  145. package/src/sessions/state.ts +118 -0
  146. package/src/sessions/types.ts +457 -0
  147. package/src/store/migrations/031-runtime-independent-vectors.ts +29 -0
  148. package/src/store/migrations/032-vector-runtime-callers.ts +17 -0
  149. package/src/store/migrations/index.ts +4 -0
  150. package/src/store/sqlite/adapter.ts +76 -16
  151. package/src/store/sqlite/scoped-index.ts +9 -0
  152. package/src/store/types.ts +11 -1
  153. package/src/store/vector/lazy.ts +46 -43
  154. package/src/store/vector/runtime-compat.ts +651 -0
  155. package/src/store/vector/sqlite-vec.ts +20 -2
  156. package/src/store/vector/status.ts +276 -35
  157. package/src/store/vector/types.ts +2 -0
  158. package/src/store/vector/variant-search.ts +71 -23
  159. package/src/store/vector/variants.ts +49 -14
  160. package/browser-extension/artifacts/gno-browser-clipper-v2.5.1.zip.sha256 +0 -1
@@ -41,7 +41,11 @@ import {
41
41
  type GlobalOptions,
42
42
  parseGlobalOptions,
43
43
  } from "./context";
44
- import { DETACHED_CHILD_FLAG } from "./detach";
44
+ import {
45
+ DETACHED_CHILD_FLAG,
46
+ formatBackgroundIssue,
47
+ residentIssues,
48
+ } from "./detach";
45
49
  import { CliError } from "./errors";
46
50
  import {
47
51
  assertFormatSupported,
@@ -344,7 +348,7 @@ export function createProgram(): Command {
344
348
  .option("--no-pager", "disable automatic paging of long output");
345
349
 
346
350
  // Resolve globals ONCE before any command runs (ensures consistency)
347
- program.hook("preAction", (thisCommand) => {
351
+ program.hook("preAction", async (thisCommand) => {
348
352
  const rootOpts = thisCommand.optsWithGlobals();
349
353
  const globals = parseGlobalOptions(rootOpts);
350
354
  if (!isValidIndexName(globals.index)) {
@@ -355,6 +359,8 @@ export function createProgram(): Command {
355
359
  }
356
360
  applyGlobalOptions(globals);
357
361
  globalState.current = globals;
362
+ const { assertCliSessionBinding } = await import("./session-binding");
363
+ await assertCliSessionBinding(globals.config, globals.index);
358
364
  });
359
365
 
360
366
  // Wire command groups
@@ -362,6 +368,7 @@ export function createProgram(): Command {
362
368
  wireOnboardingCommands(program);
363
369
  wireCaptureCommand(program);
364
370
  wireMemoryCommands(program);
371
+ wireSessionsCommands(program);
365
372
  wireManagementCommands(program);
366
373
  wireTraceCommands(program);
367
374
  wirePublishCommand(program);
@@ -1874,6 +1881,10 @@ function wireCaptureCommand(program: Command): void {
1874
1881
  .option("--source-author <author>", "source author")
1875
1882
  .option("--source-date <date>", "source observed date/time")
1876
1883
  .option("--source-id <id>", "source external id")
1884
+ .option(
1885
+ "--request-id <id>",
1886
+ "retry identity: reuse it to retry this same capture after a lost response"
1887
+ )
1877
1888
  .option("--json", "JSON output")
1878
1889
  .action(
1879
1890
  async (contentParts: string[], cmdOpts: Record<string, unknown>) => {
@@ -1902,6 +1913,7 @@ function wireCaptureCommand(program: Command): void {
1902
1913
  sourceAuthor: cmdOpts.sourceAuthor as string | undefined,
1903
1914
  sourceDate: cmdOpts.sourceDate as string | undefined,
1904
1915
  sourceId: cmdOpts.sourceId as string | undefined,
1916
+ requestId: cmdOpts.requestId as string | undefined,
1905
1917
  });
1906
1918
  const output = formatCaptureReceipt(receipt, {
1907
1919
  json: format === "json",
@@ -1910,6 +1922,27 @@ function wireCaptureCommand(program: Command): void {
1910
1922
  await writeOutput(output, format);
1911
1923
  }
1912
1924
  );
1925
+
1926
+ program
1927
+ .command("request-status <request-id>")
1928
+ .description(
1929
+ "Show whether a capture/remember request ID is pending, committed, or unknown"
1930
+ )
1931
+ .option("--json", "JSON output")
1932
+ .action(async (requestId: string, cmdOpts: Record<string, unknown>) => {
1933
+ const format = getFormat(cmdOpts);
1934
+ const globals = getGlobals();
1935
+ const { formatRequestStatusOutput, requestStatus } =
1936
+ await import("./commands/request-status");
1937
+ const result = await requestStatus({
1938
+ requestId,
1939
+ indexName: globals.index,
1940
+ });
1941
+ await writeOutput(
1942
+ formatRequestStatusOutput(result, { json: format === "json" }),
1943
+ format
1944
+ );
1945
+ });
1913
1946
  }
1914
1947
 
1915
1948
  // ─────────────────────────────────────────────────────────────────────────────
@@ -1957,6 +1990,10 @@ function wireMemoryCommands(program: Command): void {
1957
1990
  "--session <id>",
1958
1991
  "session identity (default: $GNO_MEMORY_SESSION or ppid:<pid>)"
1959
1992
  )
1993
+ .option(
1994
+ "--request-id <id>",
1995
+ "retry identity: reuse it to retry this same write after a lost response"
1996
+ )
1960
1997
  .option("--json", "JSON output")
1961
1998
  .action(async (text: string, cmdOpts: Record<string, unknown>) => {
1962
1999
  const format = getFormat(cmdOpts);
@@ -1979,6 +2016,7 @@ function wireMemoryCommands(program: Command): void {
1979
2016
  source: cmdOpts.source as string | undefined,
1980
2017
  caller: cmdOpts.caller as string | undefined,
1981
2018
  session: cmdOpts.session as string | undefined,
2019
+ requestId: cmdOpts.requestId as string | undefined,
1982
2020
  });
1983
2021
  await writeOutput(
1984
2022
  formatRememberResult(result, {
@@ -2038,6 +2076,358 @@ function wireMemoryCommands(program: Command): void {
2038
2076
  });
2039
2077
  }
2040
2078
 
2079
+ // ─────────────────────────────────────────────────────────────────────────────
2080
+ // Session archive commands (manual import only)
2081
+ // ─────────────────────────────────────────────────────────────────────────────
2082
+
2083
+ function wireSessionsCommands(program: Command): void {
2084
+ const sessionsCmd = program
2085
+ .command("sessions")
2086
+ .description(
2087
+ "Discover and manually import local agent sessions into a dedicated archive"
2088
+ );
2089
+ const context = () => {
2090
+ const globals = getGlobals();
2091
+ return { configPath: globals.config, indexName: globals.index };
2092
+ };
2093
+ const asJson = (cmdOpts: Record<string, unknown>) =>
2094
+ getFormat(cmdOpts) === "json";
2095
+
2096
+ sessionsCmd
2097
+ .command("discover", { isDefault: true })
2098
+ .description("Preview supported local session sources (never imports)")
2099
+ .option("--json", "JSON output")
2100
+ .action(async (cmdOpts: Record<string, unknown>) => {
2101
+ const { discoverSessions, formatDiscovery } =
2102
+ await import("./commands/sessions");
2103
+ const result = await discoverSessions(context());
2104
+ await writeOutput(
2105
+ formatDiscovery(result, asJson(cmdOpts)),
2106
+ getFormat(cmdOpts)
2107
+ );
2108
+ });
2109
+
2110
+ sessionsCmd
2111
+ .command("init")
2112
+ .description("Create or extend the dedicated archive config/index pair")
2113
+ .option(
2114
+ "--archive <dir>",
2115
+ "absolute archive directory (outside the curated vault)"
2116
+ )
2117
+ .option("--collection <name>", "archive collection to create")
2118
+ .option("--json", "JSON output")
2119
+ .action(async (cmdOpts: Record<string, unknown>) => {
2120
+ const { initSessions } = await import("./commands/sessions");
2121
+ const result = await initSessions(context(), {
2122
+ archive: cmdOpts.archive as string | undefined,
2123
+ collection: cmdOpts.collection as string | undefined,
2124
+ });
2125
+ await writeOutput(
2126
+ asJson(cmdOpts)
2127
+ ? JSON.stringify(result, null, 2)
2128
+ : `Session archive ${result.created ? "created" : "ready"}: ${result.archiveRoot} (collection ${result.collection}, index ${result.index}, config ${result.configPath})`,
2129
+ getFormat(cmdOpts)
2130
+ );
2131
+ });
2132
+
2133
+ const sourceCmd = sessionsCmd
2134
+ .command("source")
2135
+ .description("Register or unregister owner session sources");
2136
+ sourceCmd
2137
+ .command("add <id>")
2138
+ .description("Register a harness session root for manual import")
2139
+ .option("--harness <harness>", "codex | claude-code | openclaw | hermes")
2140
+ .option("--path <path>", "absolute harness session root, file or database")
2141
+ .option("--collection <name>", "default archive collection")
2142
+ .option(
2143
+ "--project <prefix=collection>",
2144
+ "owner-approved working-directory mapping (repeatable)",
2145
+ collectRepeatableValue,
2146
+ []
2147
+ )
2148
+ .option("--json", "JSON output")
2149
+ .action(async (id: string, cmdOpts: Record<string, unknown>) => {
2150
+ const { addSource } = await import("./commands/sessions");
2151
+ const result = await addSource(context(), {
2152
+ id,
2153
+ harness: cmdOpts.harness as string | undefined,
2154
+ path: cmdOpts.path as string | undefined,
2155
+ collection: cmdOpts.collection as string | undefined,
2156
+ projects: cmdOpts.project as string[],
2157
+ });
2158
+ await writeOutput(
2159
+ asJson(cmdOpts)
2160
+ ? JSON.stringify(result, null, 2)
2161
+ : `Source ${result.id} registered. Nothing was imported.`,
2162
+ getFormat(cmdOpts)
2163
+ );
2164
+ });
2165
+ sourceCmd
2166
+ .command("remove <id>")
2167
+ .description("Unregister a source (its archive is retained)")
2168
+ .option("--json", "JSON output")
2169
+ .action(async (id: string, cmdOpts: Record<string, unknown>) => {
2170
+ const { removeSource } = await import("./commands/sessions");
2171
+ const result = await removeSource(context(), id);
2172
+ await writeOutput(
2173
+ asJson(cmdOpts)
2174
+ ? JSON.stringify(result, null, 2)
2175
+ : `Source ${result.id} removed; archived sessions were retained.`,
2176
+ getFormat(cmdOpts)
2177
+ );
2178
+ });
2179
+
2180
+ sessionsCmd
2181
+ .command("import [paths...]")
2182
+ .description("Import selected sources or paths into the archive (manual)")
2183
+ .option("--source <id>", "registered source to import")
2184
+ .option(
2185
+ "--collection <name>",
2186
+ "destination archive collection (path imports)"
2187
+ )
2188
+ .option("--format <harness>", "codex | claude-code | openclaw | hermes")
2189
+ .option("--dry-run", "parse and report without writing archive or index")
2190
+ .option("--limit <n>", "maximum changed units processed this run")
2191
+ .option("--json", "JSON output")
2192
+ .action(async (paths: string[], cmdOpts: Record<string, unknown>) => {
2193
+ const { formatImportReceipt, importSessions } =
2194
+ await import("./commands/sessions");
2195
+ const receipt = await importSessions(context(), {
2196
+ paths,
2197
+ source: cmdOpts.source as string | undefined,
2198
+ collection: cmdOpts.collection as string | undefined,
2199
+ format: cmdOpts.format as string | undefined,
2200
+ dryRun: Boolean(cmdOpts.dryRun),
2201
+ limit: cmdOpts.limit,
2202
+ });
2203
+ await writeOutput(
2204
+ formatImportReceipt(receipt, asJson(cmdOpts)),
2205
+ getFormat(cmdOpts)
2206
+ );
2207
+ if (receipt.status === "failed") {
2208
+ const unsupportedOnly =
2209
+ receipt.counts.unsupported > 0 &&
2210
+ receipt.counts.failed === 0 &&
2211
+ receipt.counts.incomplete === 0;
2212
+ throw unsupportedOnly
2213
+ ? new CliError(
2214
+ "VALIDATION",
2215
+ "No selected unit is a supported session format; see receipt.",
2216
+ { details: { sessionsCode: "SESSIONS_UNSUPPORTED_FORMAT" } }
2217
+ )
2218
+ : new CliError("RUNTIME", "Session import failed; see receipt.", {
2219
+ details: { sessionsCode: "SESSIONS_IMPORT_FAILED" },
2220
+ });
2221
+ }
2222
+ });
2223
+
2224
+ sessionsCmd
2225
+ .command("status")
2226
+ .description("Show archive, source and checkpoint status")
2227
+ .option("--json", "JSON output")
2228
+ .action(async (cmdOpts: Record<string, unknown>) => {
2229
+ const { formatStatus, sessionsStatus } =
2230
+ await import("./commands/sessions");
2231
+ const result = await sessionsStatus(context());
2232
+ await writeOutput(
2233
+ formatStatus(result, asJson(cmdOpts)),
2234
+ getFormat(cmdOpts)
2235
+ );
2236
+ });
2237
+
2238
+ wireSessionsAutomationCommands(sessionsCmd, context, asJson);
2239
+
2240
+ sessionsCmd
2241
+ .command("prune")
2242
+ .description(
2243
+ "Preview (or --apply) removal of archives whose source is gone"
2244
+ )
2245
+ .option("--source <id>", "source to prune")
2246
+ .option("--apply", "delete the previewed archive files")
2247
+ .option("--json", "JSON output")
2248
+ .action(async (cmdOpts: Record<string, unknown>) => {
2249
+ const { formatPrune, pruneSessions } =
2250
+ await import("./commands/sessions");
2251
+ const result = await pruneSessions(context(), {
2252
+ source: cmdOpts.source as string | undefined,
2253
+ apply: Boolean(cmdOpts.apply),
2254
+ });
2255
+ await writeOutput(
2256
+ formatPrune(result, asJson(cmdOpts)),
2257
+ getFormat(cmdOpts)
2258
+ );
2259
+ });
2260
+ }
2261
+
2262
+ /** Opt-in automation: profiles, explicit trigger switches, run-now, host hook. */
2263
+ function wireSessionsAutomationCommands(
2264
+ sessionsCmd: Command,
2265
+ context: () => { configPath?: string; indexName: string },
2266
+ asJson: (cmdOpts: Record<string, unknown>) => boolean
2267
+ ): void {
2268
+ const automationCmd = sessionsCmd
2269
+ .command("automation")
2270
+ .description(
2271
+ "Opt-in hooks and daemon schedules for registered sources (off until enabled)"
2272
+ );
2273
+
2274
+ automationCmd
2275
+ .command("set <profile>")
2276
+ .description(
2277
+ "Create or reconfigure a profile (sources, cadence, budget); enables nothing"
2278
+ )
2279
+ .option(
2280
+ "--source <id>",
2281
+ "registered source to include (repeatable)",
2282
+ collectRepeatableValue,
2283
+ []
2284
+ )
2285
+ .option("--cadence <n>", "elapsed schedule cadence <n>s|m|h|d (min 1m)")
2286
+ .option("--limit <n>", "changed units per source per run")
2287
+ .option("--retries <n>", "automatic retries after a failed run")
2288
+ .option("--json", "JSON output")
2289
+ .action(async (id: string, cmdOpts: Record<string, unknown>) => {
2290
+ const { formatAutomationPreview, setAutomation } =
2291
+ await import("./commands/sessions");
2292
+ const preview = await setAutomation(context(), id, {
2293
+ sources: cmdOpts.source as string[],
2294
+ cadence: cmdOpts.cadence as string | undefined,
2295
+ limit: cmdOpts.limit,
2296
+ retries: cmdOpts.retries,
2297
+ });
2298
+ await writeOutput(
2299
+ formatAutomationPreview(preview, asJson(cmdOpts)),
2300
+ getFormat(cmdOpts)
2301
+ );
2302
+ });
2303
+
2304
+ automationCmd
2305
+ .command("preview <profile>")
2306
+ .description(
2307
+ "Show sources, destinations, hook command, schedule and daemon prerequisite"
2308
+ )
2309
+ .option("--settings <path>", "Claude Code settings file to preview")
2310
+ .option("--json", "JSON output")
2311
+ .action(async (id: string, cmdOpts: Record<string, unknown>) => {
2312
+ const { formatAutomationPreview, previewAutomation } =
2313
+ await import("./commands/sessions");
2314
+ const preview = await previewAutomation(context(), id, {
2315
+ settings: cmdOpts.settings as string | undefined,
2316
+ });
2317
+ await writeOutput(
2318
+ formatAutomationPreview(preview, asJson(cmdOpts)),
2319
+ getFormat(cmdOpts)
2320
+ );
2321
+ });
2322
+
2323
+ automationCmd
2324
+ .command("enable <profile>")
2325
+ .description(
2326
+ "Explicitly switch on a host hook and/or the daemon schedule for a profile"
2327
+ )
2328
+ .option("--hook <harness>", "install the owned hook (claude-code)")
2329
+ .option(
2330
+ "--settings <path>",
2331
+ "Claude Code settings file (default: $CLAUDE_CONFIG_DIR or ~/.claude settings.json)"
2332
+ )
2333
+ .option("--schedule", "run on the daemon's elapsed cadence")
2334
+ .option("--cadence <n>", "elapsed cadence <n>s|m|h|d (min 1m)")
2335
+ .option("--json", "JSON output")
2336
+ .action(async (id: string, cmdOpts: Record<string, unknown>) => {
2337
+ const { enableAutomationCli, formatAutomationPreview } =
2338
+ await import("./commands/sessions");
2339
+ const preview = await enableAutomationCli(context(), id, {
2340
+ hook: cmdOpts.hook as string | undefined,
2341
+ settings: cmdOpts.settings as string | undefined,
2342
+ schedule: Boolean(cmdOpts.schedule),
2343
+ cadence: cmdOpts.cadence as string | undefined,
2344
+ });
2345
+ await writeOutput(
2346
+ formatAutomationPreview(preview, asJson(cmdOpts)),
2347
+ getFormat(cmdOpts)
2348
+ );
2349
+ });
2350
+
2351
+ automationCmd
2352
+ .command("disable <profile>")
2353
+ .description(
2354
+ "Pause: switch triggers off, remove the owned hook entry, clear pending work"
2355
+ )
2356
+ .option("--hook", "only the hook")
2357
+ .option("--schedule", "only the schedule")
2358
+ .option("--json", "JSON output")
2359
+ .action(async (id: string, cmdOpts: Record<string, unknown>) => {
2360
+ const { disableAutomationCli, formatAutomationChange } =
2361
+ await import("./commands/sessions");
2362
+ const change = await disableAutomationCli(context(), id, {
2363
+ hook: Boolean(cmdOpts.hook),
2364
+ schedule: Boolean(cmdOpts.schedule),
2365
+ });
2366
+ await writeOutput(
2367
+ formatAutomationChange(change, asJson(cmdOpts)),
2368
+ getFormat(cmdOpts)
2369
+ );
2370
+ });
2371
+
2372
+ automationCmd
2373
+ .command("remove <profile>")
2374
+ .description(
2375
+ "Uninstall owned integrations and delete the profile (archive retained)"
2376
+ )
2377
+ .option("--json", "JSON output")
2378
+ .action(async (id: string, cmdOpts: Record<string, unknown>) => {
2379
+ const { formatAutomationChange, removeAutomation } =
2380
+ await import("./commands/sessions");
2381
+ const change = await removeAutomation(context(), id);
2382
+ await writeOutput(
2383
+ formatAutomationChange(change, asJson(cmdOpts)),
2384
+ getFormat(cmdOpts)
2385
+ );
2386
+ });
2387
+
2388
+ automationCmd
2389
+ .command("run <profile>")
2390
+ .description("Run a profile now through the manual importer")
2391
+ .option("--json", "JSON output")
2392
+ .action(async (id: string, cmdOpts: Record<string, unknown>) => {
2393
+ const { formatAutomationRun, runAutomation } =
2394
+ await import("./commands/sessions");
2395
+ const result = await runAutomation(context(), id);
2396
+ await writeOutput(
2397
+ formatAutomationRun(result, asJson(cmdOpts)),
2398
+ getFormat(cmdOpts)
2399
+ );
2400
+ if (result.outcome === "failed" && result.reason === "busy") {
2401
+ throw new CliError(
2402
+ "BUSY",
2403
+ "The archive is busy (another import or index writer); the run is recorded; a running daemon retries it, or run it again once the archive is free.",
2404
+ { details: { sessionsCode: "SESSIONS_BUSY" } }
2405
+ );
2406
+ }
2407
+ if (result.outcome === "failed") {
2408
+ throw new CliError(
2409
+ "RUNTIME",
2410
+ `Automation run failed (${result.reason ?? "unknown"}); see gno sessions status.`,
2411
+ { details: { sessionsCode: "SESSIONS_IMPORT_FAILED" } }
2412
+ );
2413
+ }
2414
+ });
2415
+
2416
+ sessionsCmd
2417
+ .command("hook <harness>")
2418
+ .description(
2419
+ "Host hook entrypoint: durably mark a profile pending (installed by automation enable)"
2420
+ )
2421
+ .option("--profile <id>", "automation profile")
2422
+ .action(async (harness: string, cmdOpts: Record<string, unknown>) => {
2423
+ const { runSessionsHook } = await import("./commands/sessions");
2424
+ const line = await runSessionsHook(context(), harness, {
2425
+ profile: cmdOpts.profile as string | undefined,
2426
+ });
2427
+ process.stdout.write(`${line}\n`);
2428
+ });
2429
+ }
2430
+
2041
2431
  // ─────────────────────────────────────────────────────────────────────────────
2042
2432
  // Retrieval Commands (get, multi-get, ls)
2043
2433
  // ─────────────────────────────────────────────────────────────────────────────
@@ -2169,6 +2559,7 @@ function wireRetrievalCommands(program: Command): void {
2169
2559
  const { ls, formatLs } = await import("./commands/ls");
2170
2560
  const result = await ls(scope, {
2171
2561
  configPath: globals.config,
2562
+ indexName: globals.index,
2172
2563
  limit: cmdOpts.limit as number | undefined,
2173
2564
  offset: cmdOpts.offset as number | undefined,
2174
2565
  json: format === "json",
@@ -3157,6 +3548,10 @@ function wireManagementCommands(program: Command): void {
3157
3548
  .option("--model <uri>", "embedding model URI")
3158
3549
  .option("--batch-size <num>", "batch size", "32")
3159
3550
  .option("--force", "regenerate all embeddings")
3551
+ .option(
3552
+ "--new-partition",
3553
+ "confirm building a separate vector partition for an incompatible runtime"
3554
+ )
3160
3555
  .option("--dry-run", "show what would be done")
3161
3556
  .option("--json", "JSON output")
3162
3557
  ).action(
@@ -3180,6 +3575,7 @@ function wireManagementCommands(program: Command): void {
3180
3575
  force: Boolean(cmdOpts.force),
3181
3576
  dryRun: Boolean(cmdOpts.dryRun),
3182
3577
  yes: globals.yes,
3578
+ newPartition: Boolean(cmdOpts.newPartition),
3183
3579
  json: format === "json",
3184
3580
  verbose: globals.verbose,
3185
3581
  offline: globals.offline,
@@ -3288,6 +3684,43 @@ function wireVecCommands(program: Command): void {
3288
3684
  );
3289
3685
  });
3290
3686
 
3687
+ // vec drop
3688
+ addWriteLeaseFlags(
3689
+ vecCmd
3690
+ .command("drop <partition>")
3691
+ .description(
3692
+ "Drop an abandoned shadow vector partition (id prefix from gno status)"
3693
+ )
3694
+ .option("--json", "JSON output")
3695
+ ).action(async (partition: string, cmdOpts: Record<string, unknown>) => {
3696
+ const format = getFormat(cmdOpts);
3697
+ const globals = getGlobals();
3698
+
3699
+ const { vecDrop, formatVecDrop } = await import("./commands/vec");
3700
+ const lease = parseWriteLeaseFlags(cmdOpts);
3701
+ const result = await withCliWriteLease(
3702
+ {
3703
+ indexName: globals.index,
3704
+ lockWaitMs: lease.lockWaitMs,
3705
+ noWait: lease.noWait,
3706
+ },
3707
+ () =>
3708
+ vecDrop(partition, {
3709
+ configPath: globals.config,
3710
+ indexName: globals.index,
3711
+ })
3712
+ );
3713
+ throwIfWriteLeaseBusy(result, format === "json");
3714
+
3715
+ if (!result.success) {
3716
+ throw new CliError("VALIDATION", result.error);
3717
+ }
3718
+
3719
+ process.stdout.write(
3720
+ `${formatVecDrop(result, { json: format === "json" })}\n`
3721
+ );
3722
+ });
3723
+
3291
3724
  // vec rebuild
3292
3725
  addWriteLeaseFlags(
3293
3726
  vecCmd
@@ -4630,6 +5063,9 @@ async function runDaemonStatus(deps: DaemonStatusDeps): Promise<void> {
4630
5063
  } else {
4631
5064
  process.stdout.write(` (${status.log_size_bytes} bytes)\n`);
4632
5065
  }
5066
+ for (const issue of residentIssues(status)) {
5067
+ process.stdout.write(` issue ${formatBackgroundIssue(issue)}\n`);
5068
+ }
4633
5069
  if (findings) {
4634
5070
  process.stdout.write(
4635
5071
  ` findings ${formatFindingsRunStatusLine(findings)}\n`
@@ -4985,6 +5421,9 @@ async function runServeStatus(deps: ServeStatusDeps): Promise<void> {
4985
5421
  } else {
4986
5422
  process.stdout.write(` (${status.log_size_bytes} bytes)\n`);
4987
5423
  }
5424
+ for (const issue of residentIssues(status)) {
5425
+ process.stdout.write(` issue ${formatBackgroundIssue(issue)}\n`);
5426
+ }
4988
5427
 
4989
5428
  if (foreign) {
4990
5429
  // Terminal mode: emit the operator-facing warning on stderr. JSON
@@ -0,0 +1,49 @@
1
+ /**
2
+ * CLI-wide enforcement of the session-archive config/index binding.
3
+ *
4
+ * Runs before every command and again whenever a command opens an index
5
+ * (`initStore`, which also covers indexes named by a `?index=` URI): a config
6
+ * bound to a session archive index cannot be used with another index, and an
7
+ * archive index cannot be opened with a different config.
8
+ *
9
+ * @module src/cli/session-binding
10
+ */
11
+
12
+ import type { Config } from "../config/types";
13
+
14
+ import { getIndexDbPath } from "../app/constants";
15
+ import { getConfigPaths, loadConfig } from "../config";
16
+ import { assertSessionBinding } from "../sessions/binding";
17
+ import { SessionsError } from "../sessions/types";
18
+ import { CliError } from "./errors";
19
+
20
+ export async function assertCliSessionBinding(
21
+ configPath: string | undefined,
22
+ indexName: string,
23
+ loadedConfig?: Config
24
+ ): Promise<void> {
25
+ const path = configPath ?? getConfigPaths().configFile;
26
+ let config = loadedConfig;
27
+ if (!config) {
28
+ if (!(await Bun.file(path).exists())) return;
29
+ const loaded = await loadConfig(path);
30
+ // Unreadable configs are reported by the command itself.
31
+ if (!loaded.ok) return;
32
+ config = loaded.value;
33
+ }
34
+ try {
35
+ await assertSessionBinding({
36
+ config,
37
+ configPath: path,
38
+ indexName,
39
+ dbPath: getIndexDbPath(indexName),
40
+ });
41
+ } catch (error) {
42
+ if (error instanceof SessionsError) {
43
+ throw new CliError("VALIDATION", error.message, {
44
+ details: { sessionsCode: error.code },
45
+ });
46
+ }
47
+ throw error;
48
+ }
49
+ }
@@ -12,6 +12,7 @@ import { z } from "zod";
12
12
  import { URI_PREFIX } from "../app/constants";
13
13
  import { JsonlFieldMappingSchema } from "../converters/adapters/jsonl/config";
14
14
  import { MCP_TOOL_PROFILES } from "../mcp/tool-profile";
15
+ import { SessionsConfigSchema } from "../sessions/config";
15
16
  import { ChunkingConfigSchema, type ChunkingParams } from "./chunking";
16
17
  import { RetrievalTraceConfigSchema } from "./retrieval-traces";
17
18
 
@@ -614,6 +615,13 @@ export const ConfigSchema = z.object({
614
615
  /** Bounded project-aware retrieval affinity. */
615
616
  projectAffinity: ProjectAffinityConfigSchema.optional(),
616
617
 
618
+ /**
619
+ * Session-archive binding. Present only in a dedicated archive config; it
620
+ * binds the file to one index name and archive root and lists
621
+ * owner-registered session sources.
622
+ */
623
+ sessions: SessionsConfigSchema.optional(),
624
+
617
625
  /** Machine-local, timestamp-free project profile provenance. */
618
626
  projectProfileBindings: z
619
627
  .array(ProjectProfileBindingSchema)