@frontera-sdk/cli 1.52.39 → 1.52.41

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 (235) hide show
  1. package/README.md +9 -0
  2. package/package.json +6 -4
  3. package/src/adopt.ts +6 -2
  4. package/src/api/apps-api.ts +86 -4
  5. package/src/api/blueprint-authoring-api.ts +35 -11
  6. package/src/api/dataset-api.ts +29 -0
  7. package/src/api/governed-action-api.ts +90 -2
  8. package/src/api/platform-api.ts +87 -2
  9. package/src/api/workflow-api.ts +33 -2
  10. package/src/app-contract.ts +156 -0
  11. package/src/app-lock.ts +125 -0
  12. package/src/args.ts +14 -3
  13. package/src/auth-verify.ts +10 -0
  14. package/src/automation-template.ts +12 -1
  15. package/src/blueprint/action-rules.ts +516 -0
  16. package/src/blueprint/action-semantic-fit.ts +205 -0
  17. package/src/blueprint/compile.ts +51 -2
  18. package/src/blueprint/diff.ts +34 -2
  19. package/src/blueprint/metric-query.ts +215 -0
  20. package/src/blueprint/metric-rules.ts +122 -0
  21. package/src/blueprint/object-sets.ts +116 -0
  22. package/src/blueprint/ontology-edit-plan.ts +49 -5
  23. package/src/blueprint/projection.ts +24 -1
  24. package/src/blueprint/scaffold.ts +84 -42
  25. package/src/commands/action/available.ts +1 -1
  26. package/src/commands/action/criteria-refusal.ts +84 -0
  27. package/src/commands/action/decide.ts +5 -2
  28. package/src/commands/action/deploy.ts +13 -4
  29. package/src/commands/action/list.ts +134 -19
  30. package/src/commands/action/requests.ts +34 -6
  31. package/src/commands/action/submit.ts +28 -13
  32. package/src/commands/agent/draft-changes.ts +1 -1
  33. package/src/commands/agent/index-commands.ts +69 -7
  34. package/src/commands/agent/resolve.ts +2 -0
  35. package/src/commands/app/checks.ts +44 -0
  36. package/src/commands/app/deploy.ts +243 -60
  37. package/src/commands/app/deployments.ts +97 -0
  38. package/src/commands/app/dev.ts +103 -11
  39. package/src/commands/app/init.ts +36 -3
  40. package/src/commands/app/promote.ts +45 -19
  41. package/src/commands/app/pull.ts +3 -1
  42. package/src/commands/app/rollback.ts +46 -0
  43. package/src/commands/app/sdk.ts +54 -8
  44. package/src/commands/app/unpin.ts +27 -0
  45. package/src/commands/app/versions.ts +1 -1
  46. package/src/commands/automation/bindings.ts +4 -2
  47. package/src/commands/automation/build-entry.ts +4 -1
  48. package/src/commands/automation/index-commands.ts +3 -1
  49. package/src/commands/automation/pull.ts +6 -1
  50. package/src/commands/automation/run.ts +34 -9
  51. package/src/commands/blueprint/bind.ts +2 -1
  52. package/src/commands/blueprint/declarative.ts +332 -32
  53. package/src/commands/blueprint/editable.ts +3 -3
  54. package/src/commands/blueprint/get.ts +6 -2
  55. package/src/commands/blueprint/grants.ts +64 -10
  56. package/src/commands/blueprint/metric.ts +108 -0
  57. package/src/commands/blueprint/object-sets.ts +71 -0
  58. package/src/commands/blueprint/query.ts +155 -31
  59. package/src/commands/dataset/index-commands.ts +156 -7
  60. package/src/commands/knowledge/index-commands.ts +122 -20
  61. package/src/commands/local/check.ts +657 -44
  62. package/src/commands/local/data.ts +33 -12
  63. package/src/commands/local/dev.ts +876 -89
  64. package/src/commands/local/docs-mcp.ts +10 -18
  65. package/src/commands/local/index-commands.ts +300 -63
  66. package/src/commands/local/init.ts +18 -5
  67. package/src/commands/local/key.ts +0 -3
  68. package/src/commands/local/providers.ts +0 -7
  69. package/src/commands/local/remove.ts +116 -0
  70. package/src/commands/local/schema.ts +3 -4
  71. package/src/commands/local/sdk-refresh.ts +59 -0
  72. package/src/commands/local/snapshot.ts +100 -0
  73. package/src/commands/local/sync-request.ts +321 -14
  74. package/src/commands/local/sync.ts +103 -6
  75. package/src/commands/mcp/serve.ts +34 -0
  76. package/src/commands/registry.ts +54 -10
  77. package/src/commands/types.ts +29 -0
  78. package/src/commands/workflow/archive.ts +46 -0
  79. package/src/commands/workflow/get.ts +2 -1
  80. package/src/commands/workflow/index-commands.ts +5 -0
  81. package/src/commands/workflow/push.ts +49 -2
  82. package/src/commands/workflow/run.ts +65 -7
  83. package/src/commands/workflow/runs.ts +9 -6
  84. package/src/commands/workflow/webhook-test.ts +166 -0
  85. package/src/config.ts +43 -6
  86. package/src/dev-broker.ts +52 -0
  87. package/src/exit.ts +103 -4
  88. package/src/flag-help.ts +77 -17
  89. package/src/forge/claude-commands.ts +11 -6
  90. package/src/forge/claude-skills.ts +4 -4
  91. package/src/forge/touches.ts +15 -12
  92. package/src/function-outside-imports.ts +89 -0
  93. package/src/harness.ts +42 -24
  94. package/src/heal.ts +113 -2
  95. package/src/kit.ts +1 -1
  96. package/src/local/fix-words.ts +12 -0
  97. package/src/local/function-bundle-recovery.ts +775 -0
  98. package/src/local/local-actions.ts +103 -10
  99. package/src/local/local-agent-action-grants.ts +210 -0
  100. package/src/local/local-agent-checks.ts +232 -0
  101. package/src/local/local-agent-grants.ts +29 -7
  102. package/src/local/local-agent-lean-plugins.ts +68 -0
  103. package/src/local/local-agent-missing-tools.ts +68 -0
  104. package/src/local/local-agent-name.ts +27 -0
  105. package/src/local/local-app-code.ts +191 -0
  106. package/src/local/local-app-dev-files.ts +186 -0
  107. package/src/local/local-app-dev-session.ts +88 -0
  108. package/src/local/local-app-lock-install.ts +114 -0
  109. package/src/local/local-app-pulled.ts +167 -0
  110. package/src/local/local-app-restore.ts +111 -0
  111. package/src/local/local-app-runtime.ts +68 -0
  112. package/src/local/local-app-sdk-paths.ts +149 -0
  113. package/src/local/local-app-sdk.ts +509 -0
  114. package/src/local/local-applied.ts +212 -22
  115. package/src/local/local-apply-history.ts +84 -0
  116. package/src/local/local-apply-summary.ts +86 -0
  117. package/src/local/local-apply-watchdog.ts +95 -0
  118. package/src/local/local-apps.ts +181 -8
  119. package/src/local/local-blueprint-errors.ts +198 -0
  120. package/src/local/local-blueprint-outline.ts +31 -12
  121. package/src/local/local-blueprint-schemas.ts +582 -0
  122. package/src/local/local-build-inputs.ts +48 -0
  123. package/src/local/local-build-summary.ts +495 -28
  124. package/src/local/local-case-evidence.ts +120 -0
  125. package/src/local/local-case-not-run.ts +153 -0
  126. package/src/local/local-chat-apps.ts +93 -16
  127. package/src/local/local-check-inherited.ts +83 -0
  128. package/src/local/local-cities.ts +89 -0
  129. package/src/local/local-component-health.ts +119 -0
  130. package/src/local/local-control.ts +34 -7
  131. package/src/local/local-db-snapshot.ts +348 -0
  132. package/src/local/local-dev-references.ts +347 -36
  133. package/src/local/local-dev-sync.ts +141 -12
  134. package/src/local/local-docker.ts +35 -1
  135. package/src/local/local-docs-search.ts +1 -1
  136. package/src/local/local-docs.ts +281 -2
  137. package/src/local/local-empty-workflows.ts +92 -0
  138. package/src/local/local-env.ts +16 -1
  139. package/src/local/local-file-checks.ts +104 -0
  140. package/src/local/local-function-ids.ts +141 -0
  141. package/src/local/local-function-recovery.ts +141 -0
  142. package/src/local/local-function-state.ts +87 -0
  143. package/src/local/local-keys.ts +7 -5
  144. package/src/local/local-knowledge-documents.ts +1 -1
  145. package/src/local/local-knowledge-ingestion.ts +225 -0
  146. package/src/local/local-knowledge-uploads.ts +217 -0
  147. package/src/local/local-lessons.ts +1 -1
  148. package/src/local/local-library.ts +1 -1
  149. package/src/local/local-media-placeholders.ts +182 -0
  150. package/src/local/local-media-sets.ts +90 -10
  151. package/src/local/local-model-parameters.ts +89 -0
  152. package/src/local/local-object-sets.ts +18 -6
  153. package/src/local/local-operating.ts +35 -1
  154. package/src/local/local-org-settings.ts +206 -8
  155. package/src/local/local-orphans.ts +321 -0
  156. package/src/local/local-personas.ts +296 -0
  157. package/src/local/local-portability.ts +143 -18
  158. package/src/local/local-ports.ts +99 -7
  159. package/src/local/local-project-notices.ts +234 -0
  160. package/src/local/local-project-secrets.ts +133 -0
  161. package/src/local/local-project.ts +12 -0
  162. package/src/local/local-provider-refusal.ts +66 -0
  163. package/src/local/local-request-evidence.ts +112 -0
  164. package/src/local/local-runtime-health.ts +123 -0
  165. package/src/local/local-runtime-history.ts +50 -0
  166. package/src/local/local-runtime-target.ts +42 -0
  167. package/src/local/local-sample-data.ts +141 -0
  168. package/src/local/local-sample-remake.ts +138 -0
  169. package/src/local/local-scaffold.ts +336 -53
  170. package/src/local/local-schema-check.ts +148 -0
  171. package/src/local/local-schemas.ts +742 -25
  172. package/src/local/local-secret-carry.ts +107 -0
  173. package/src/local/local-secrets.ts +41 -11
  174. package/src/local/local-seed.ts +102 -26
  175. package/src/local/local-stand-ins.ts +3 -3
  176. package/src/local/local-state.ts +43 -2
  177. package/src/local/local-studio-server.ts +170 -7
  178. package/src/local/local-supervisor.ts +44 -6
  179. package/src/local/local-sync-merge.ts +1 -1
  180. package/src/local/local-sync-names.ts +2 -0
  181. package/src/local/local-sync-plan.ts +19 -8
  182. package/src/local/local-sync-push.ts +15 -12
  183. package/src/local/local-sync-refs.ts +49 -3
  184. package/src/local/local-sync-remote.ts +349 -38
  185. package/src/local/local-sync-snapshot.ts +18 -13
  186. package/src/local/local-synthetic-data.ts +2116 -99
  187. package/src/local/local-test-baseline.ts +113 -0
  188. package/src/local/local-test-cases.ts +76 -1
  189. package/src/local/local-test-runner.ts +582 -91
  190. package/src/local/local-test-runs.ts +13 -5
  191. package/src/local/local-vendored-sdk.ts +183 -0
  192. package/src/local/local-workflow-state.ts +16 -3
  193. package/src/local/local-workflow-tests.ts +482 -0
  194. package/src/local/local-workspace-grants.ts +81 -0
  195. package/src/main.ts +50 -9
  196. package/src/mcp/a11y-scan.ts +55 -0
  197. package/src/mcp/browser/ax-snapshot.ts +181 -0
  198. package/src/mcp/browser/credentials.ts +78 -0
  199. package/src/mcp/browser/frames.ts +336 -0
  200. package/src/mcp/browser/headless-session.ts +413 -0
  201. package/src/mcp/browser/headless-tab.ts +575 -0
  202. package/src/mcp/browser/keys.ts +70 -0
  203. package/src/mcp/browser/page-scripts.ts +129 -0
  204. package/src/mcp/browser/wait.ts +73 -0
  205. package/src/mcp/forge-link.ts +126 -0
  206. package/src/mcp/headless.ts +199 -0
  207. package/src/mcp/mcp-apps.ts +435 -0
  208. package/src/mcp/mcp-chat.ts +226 -0
  209. package/src/mcp/mcp-cli.ts +53 -0
  210. package/src/mcp/mcp-protocol.ts +122 -0
  211. package/src/mcp/mcp-runtime.ts +113 -0
  212. package/src/mcp/mcp-server.ts +108 -0
  213. package/src/mcp/mcp-traces.ts +409 -0
  214. package/src/mcp/page-observation.ts +193 -0
  215. package/src/mcp/tools/agent-tools.ts +700 -0
  216. package/src/mcp/tools/app-tools.ts +241 -0
  217. package/src/mcp/tools/browser-tools.ts +310 -0
  218. package/src/mcp/tools/docs-tools.ts +32 -0
  219. package/src/mcp/tools/project-tools.ts +205 -0
  220. package/src/mcp/tools/test-tools.ts +339 -0
  221. package/src/mcp/tools/workflow-tools.ts +611 -0
  222. package/src/output.ts +1 -1
  223. package/src/pack.ts +8 -3
  224. package/src/packaging.ts +1 -1
  225. package/src/project-bootstrap.ts +107 -4
  226. package/src/project-context.ts +18 -1
  227. package/src/project.ts +27 -2
  228. package/src/provenance.ts +25 -0
  229. package/src/render-evidence.ts +65 -7
  230. package/src/scopes.ts +1 -1
  231. package/src/template.ts +14 -8
  232. package/src/templates/next-app-files.ts +35 -6
  233. package/src/templates/next-skills.ts +61 -27
  234. package/src/vendor/kit-assets.json +6 -6
  235. package/src/vendor/sdk-sources.json +17 -14
@@ -1,7 +1,10 @@
1
1
  import { basename } from 'node:path'
2
2
 
3
- import { PlatformApi } from '../../api/platform-api'
3
+ import { PlatformApi, type KnowledgeRetrieval } from '../../api/platform-api'
4
4
  import { CliError, UsageError } from '../../errors'
5
+ import { DEFAULT_KNOWLEDGE_THRESHOLD, KEYLESS_KNOWLEDGE_THRESHOLD, KEYLESS_RETRIEVAL_NOTE, PLATFORM_DEFAULT_EMBEDDING, sameEmbedding, WIDENED_KNOWLEDGE_THRESHOLD } from '../../local/local-org-settings'
6
+ import { localRuntimeAt } from '../../local/local-runtime-target'
7
+ import { isLocalEmbeddings } from '../../local/local-seed'
5
8
  import { table } from '../../table'
6
9
  import { resolveAgentRef } from '../agent/resolve'
7
10
  import { flagString, type Command } from '../types'
@@ -124,9 +127,11 @@ const get: Command = {
124
127
 
125
128
  const client = new PlatformApi(ctx.apiUrl, ctx.token, ctx.workspaceId)
126
129
  const base = await client.knowledgeBase(await resolveKnowledgeRef(client, ref))
130
+ // On the keyless local embeddings the threshold shown is the one set for them, and retrieval is approximate: said.
131
+ const local = (await keylessRetrieval(client, ctx.cwd, ctx.apiUrl)) ? { localRetrieval: keylessRetrievalInfo() } : {}
127
132
 
128
133
  return {
129
- data: base,
134
+ data: { ...base, ...local },
130
135
  // Whatever the route returns, flattened. Naming the fields here would
131
136
  // print "?" for every one the service renames, and the embedding model
132
137
  // and chunker are exactly the fields a caller comes to this verb to read.
@@ -137,7 +142,7 @@ const get: Command = {
137
142
  v === null || v === undefined ? '' : typeof v === 'object' ? JSON.stringify(v) : String(v),
138
143
  ]),
139
144
  [undefined, 70],
140
- ),
145
+ ) + ('localRetrieval' in local ? `\n\nNote: ${KEYLESS_RETRIEVAL_NOTE}.` : ''),
141
146
  }
142
147
  },
143
148
  }
@@ -160,9 +165,13 @@ const search: Command = {
160
165
  ],
161
166
  flags: { 'top-k': 'string', threshold: 'string' },
162
167
  summary: 'Search a knowledge base and show the chunks that come back',
168
+ details: [
169
+ 'Without --threshold the base\'s own match threshold cuts the results, as an Agent\'s retrieval does. The cutoff is a maximum cosine distance from 0 to 2 (lower is stricter), while a score is higher-is-better: with vector scoring a score is 1 − distance / 2, so a 0.5 cutoff keeps scores above 0.75. The output says which cutoff applied.',
170
+ ],
163
171
  examples: [
164
172
  'frontera knowledge search product-docs "how do refunds work"',
165
173
  'frontera knowledge search product-docs "refund policy" --top-k 3 --json',
174
+ 'frontera knowledge search product-docs "refund policy" --threshold 0.8',
166
175
  ],
167
176
  },
168
177
  async run(ctx) {
@@ -182,8 +191,9 @@ const search: Command = {
182
191
  }
183
192
  const thresholdRaw = flagString(ctx, 'threshold')
184
193
  const threshold = thresholdRaw === undefined ? undefined : Number(thresholdRaw)
185
- if (threshold !== undefined && Number.isNaN(threshold)) {
186
- throw new UsageError(`--threshold must be a number, not "${thresholdRaw}"`, 'try --threshold 0.7')
194
+ // A maximum cosine distance: 0 matches only identical text, 2 matches everything.
195
+ if (threshold !== undefined && (Number.isNaN(threshold) || threshold < 0 || threshold > 2)) {
196
+ throw new UsageError(`--threshold must be a cosine distance from 0 to 2, not "${thresholdRaw}"`, 'lower is stricter: try --threshold 0.8 to widen a search the base\'s own cutoff empties')
187
197
  }
188
198
 
189
199
  const client = new PlatformApi(ctx.apiUrl, ctx.token, ctx.workspaceId)
@@ -195,42 +205,104 @@ const search: Command = {
195
205
  })
196
206
 
197
207
  const chunks = result.results
208
+ const cutoff = cutoffLine(result.retrieval, threshold)
209
+ // On the keyless local embeddings: retrieval is approximate, said with what gives the platform's.
210
+ const keyless = await keylessRetrieval(client, ctx.cwd, ctx.apiUrl)
211
+ const keylessLine = keyless ? `\n Note: ${KEYLESS_RETRIEVAL_NOTE}.` : ''
212
+ if (keyless) Object.assign(result, { localRetrieval: keylessRetrievalInfo() })
198
213
  if (chunks.length === 0) {
199
214
  // The overwhelmingly common cause, and the one a caller cannot see from
200
215
  // here: ingestion is asynchronous, so a base uploaded a minute ago is
201
216
  // empty rather than broken.
217
+ // How near the search came, when the service says: the threshold that would show it.
218
+ const nearest = result.retrieval?.nearestDistance
219
+ const max = typeof result.retrieval?.maxDistance === 'number' ? result.retrieval.maxDistance : threshold
220
+ if (typeof nearest === 'number') {
221
+ // Only asked when it could matter: a near miss the widened local threshold would keep.
222
+ const embedding = !keyless && nearMissOfWidening(nearest) ? await client.embeddingSetting().catch(() => null) : null
223
+ const widening = embeddingNote(nearest, embedding, isLoopbackApi(ctx.apiUrl))
224
+ return {
225
+ data: result,
226
+ text: `No chunks matched: the nearest chunk is at distance ${nearest}, past the cutoff${max === undefined ? '' : ` of ${max}`}. \`--threshold\` above ${nearest} (up to 2) shows it; setting the base's \`similarityThreshold\` there changes what an Agent's retrieval keeps.\n ${cutoff}${widening ? `\n ${widening}` : ''}${keylessLine}`,
227
+ }
228
+ }
229
+ if (nearest === null) {
230
+ return {
231
+ data: result,
232
+ text: `No chunks matched: ${ref} has nothing searchable yet. \`frontera knowledge sources ${ref}\` lists its documents: none, or still \`processing\`.`,
233
+ }
234
+ }
202
235
  return {
203
236
  data: result,
204
237
  text:
205
238
  'No chunks matched.\n'
206
239
  + ` If the upload was recent, check \`frontera knowledge sources ${ref}\` — a source\n`
207
240
  + ' still `processing` is not searchable yet.\n'
208
- // Stated the right way round: --threshold is a distance cutoff, so a
209
- // LOW value is the restrictive one and 0 can never match.
210
- + (threshold !== undefined
211
- ? ` --threshold ${threshold} is a maximum cosine distance — raise it to widen the search.\n`
212
- : ' The default cutoff is 0.9; pass a higher --threshold to widen the search.\n')
213
- + ' Otherwise the corpus may simply not cover this.',
241
+ + ` ${cutoff} Raise --threshold (up to 2) to widen the search.\n`
242
+ + ' Otherwise the corpus may simply not cover this.'
243
+ + keylessLine,
214
244
  }
215
245
  }
216
246
 
247
+ // A distance beside each score when the service gives one: it is what the cutoff compares.
248
+ const distances = chunks.some((c) => typeof c.distance === 'number')
217
249
  return {
218
250
  data: result,
219
251
  text: table(
220
- ['score', 'source', 'chunk'],
252
+ ['score', ...(distances ? ['distance'] : []), 'source', 'chunk'],
221
253
  chunks.map((c) => [
222
254
  typeof c.score === 'number' ? c.score.toFixed(3) : String(c.score ?? ''),
255
+ ...(distances ? [typeof c.distance === 'number' ? c.distance.toFixed(3) : ''] : []),
223
256
  String(c.fileName ?? c.sourceId ?? ''),
224
257
  String(c.content ?? c.text ?? '').replace(/\s+/g, ' ').trim(),
225
258
  ]),
226
- [8, 30, 90],
227
- ),
259
+ distances ? [8, 9, 30, 90] : [8, 30, 90],
260
+ ) + `\n\n${cutoff}${keylessLine}`,
228
261
  }
229
262
  },
230
263
  }
231
264
 
265
+ /** Whether the nearest distance sits between the default threshold and the one a local runtime widens to. */
266
+ function nearMissOfWidening(nearest: number): boolean {
267
+ return nearest > DEFAULT_KNOWLEDGE_THRESHOLD && nearest <= WIDENED_KNOWLEDGE_THRESHOLD
268
+ }
269
+
232
270
  /**
233
- * Re-run ingestion for a source that failed.
271
+ * Why a near miss may be the embedding model rather than the corpus: the
272
+ * default threshold is tuned for the platform's default embedding model, and
273
+ * another model ranks on-topic passages a little further away. A local
274
+ * runtime widens such a base's threshold when `frontera local dev` applies
275
+ * it, and this says so. Null when the miss is not in that band, or the model
276
+ * is the default or not known.
277
+ */
278
+ export function embeddingNote(nearest: number, embedding: { provider: string | null; model: string | null } | null, local = true): string | null {
279
+ if (!nearMissOfWidening(nearest)) return null
280
+ if (!embedding?.provider || !embedding.model || sameEmbedding(embedding, PLATFORM_DEFAULT_EMBEDDING)) return null
281
+ // A hosted workspace hears only what it can act on: its own model, and the base's Match threshold.
282
+ if (!local) return `This organization embeds with ${embedding.provider} / ${embedding.model}, not the model the default threshold of ${DEFAULT_KNOWLEDGE_THRESHOLD} is tuned for, so on-topic passages sit further away: if relevant passages are cut off, raise the base's Match threshold.`
283
+ return `This organization embeds with ${embedding.provider} / ${embedding.model}, not the platform's default (${PLATFORM_DEFAULT_EMBEDDING.provider} / ${PLATFORM_DEFAULT_EMBEDDING.model}) the default threshold of ${DEFAULT_KNOWLEDGE_THRESHOLD} is tuned for, so on-topic passages sit further away. A local runtime widens a base's threshold to ${WIDENED_KNOWLEDGE_THRESHOLD} when \`frontera local dev\` applies it, if its file sets none or ${DEFAULT_KNOWLEDGE_THRESHOLD}.`
284
+ }
285
+
286
+ /**
287
+ * Which cutoff the search applied, on the scale `score` is read on: the
288
+ * base's own match threshold unless `--threshold` named one (that is what an
289
+ * Agent's retrieval uses), a maximum cosine distance, so a 0.5 cutoff keeps
290
+ * similarity scores above 0.75 and a 0.70 score is outside it.
291
+ */
292
+ export function cutoffLine(retrieval: KnowledgeRetrieval | undefined, threshold: number | undefined): string {
293
+ const max = typeof retrieval?.maxDistance === 'number' ? retrieval.maxDistance : threshold
294
+ const from = retrieval?.thresholdFrom === 'request' || (!retrieval && threshold !== undefined) ? '--threshold' : 'the base\'s own match threshold, as an Agent\'s retrieval uses it'
295
+ if (max === undefined) return 'Cutoff: the base\'s own match threshold, as an Agent\'s retrieval uses it (a maximum cosine distance from 0 to 2; lower is stricter).'
296
+ if (retrieval?.scoring === 'fusion' || retrieval?.scoring === 'rerank') {
297
+ return `Cutoff: a maximum cosine distance of ${max}, from ${from}. Scores here are the ${retrieval.scoring === 'rerank' ? 'reranker\'s' : 'keyword and vector fusion\'s'}, so a score isn't comparable to the cutoff.`
298
+ }
299
+ const minScore = typeof retrieval?.minScore === 'number' ? retrieval.minScore : Math.max(0, 1 - max / 2)
300
+ return `Cutoff: a maximum cosine distance of ${max}, from ${from}: scores ≥ ${Number(minScore.toFixed(3))} (score = 1 − distance / 2).`
301
+ }
302
+
303
+ /**
304
+ * Re-run ingestion for a source that failed, or that is stuck pending or
305
+ * processing with no ingestion behind it (`local status` names one).
234
306
  *
235
307
  * `upload` exits 0 on a partial batch by design — one unreadable file does not
236
308
  * end the run — so a failed source is normal and recovery has to be normal
@@ -246,7 +318,7 @@ const retry: Command = {
246
318
  { name: 'source', required: true, description: 'source id, from `frontera knowledge sources`' },
247
319
  ],
248
320
  flags: {},
249
- summary: 'Re-run ingestion for one source that failed',
321
+ summary: 'Re-run ingestion for one source that failed or is stuck processing',
250
322
  examples: ['frontera knowledge retry product-docs <sourceId>'],
251
323
  },
252
324
  async run(ctx) {
@@ -511,12 +583,15 @@ const agents: Command = {
511
583
  rows.length === 0
512
584
  ? `No agent can read ${ref} — attach one with \`frontera knowledge attach ${ref} <agent>\`.`
513
585
  : table(
514
- ['agent', 'id'],
586
+ ['agent', 'id', 'attached in'],
515
587
  rows.map((r) => {
516
588
  const id = String(r.agentId ?? '')
517
- // An agent still in `configuring` is absent from `agent list`,
518
- // so print the id rather than a "?" that reads as corruption.
519
- return [slugs.get(id) ?? id, id]
589
+ // The row names the Agent's slug; an older platform's does not, and an agent still in
590
+ // `configuring` is absent from `agent list`, so print the id rather than a "?".
591
+ const slug = typeof r.agentSlug === 'string' && r.agentSlug ? r.agentSlug : slugs.get(id) ?? id
592
+ // Published, staged in its draft, or both; an older platform lists only published ones.
593
+ const where = r.published === false ? 'draft' : r.staged === false ? 'published (its draft drops it)' : r.staged === true ? 'published and draft' : 'published'
594
+ return [slug, id, where]
520
595
  }),
521
596
  ),
522
597
  }
@@ -563,3 +638,30 @@ export const knowledgeCommands: Command[] = [
563
638
  agents,
564
639
  ...reserved,
565
640
  ]
641
+
642
+ /** Whether the command talks to a runtime on this machine (a local runtime is always on a loopback address). */
643
+ /**
644
+ * Whether this runs on the keyless local embeddings: only against the
645
+ * project's own local runtime (`isLocalEmbeddings` reads this computer's
646
+ * keys), read from the organization's embedding setting. False when it
647
+ * can't say, and anywhere else.
648
+ */
649
+ async function keylessRetrieval(client: PlatformApi, cwd: string, apiUrl: string): Promise<boolean> {
650
+ if (!localRuntimeAt(cwd, apiUrl)) return false
651
+ const embedding = await client.embeddingSetting().catch(() => null)
652
+ return !!embedding && isLocalEmbeddings(embedding)
653
+ }
654
+
655
+ /** What `--json` carries beside a base or a search on the keyless local embeddings. */
656
+ export function keylessRetrievalInfo() {
657
+ return { embeddings: 'keyless-local', approximate: true, threshold: KEYLESS_KNOWLEDGE_THRESHOLD, note: KEYLESS_RETRIEVAL_NOTE } as const
658
+ }
659
+
660
+ function isLoopbackApi(apiUrl: string): boolean {
661
+ try {
662
+ const host = new URL(apiUrl).hostname
663
+ return host === 'localhost' || host === '127.0.0.1' || host === '[::1]' || host === '::1' || host.endsWith('.localhost')
664
+ } catch {
665
+ return false
666
+ }
667
+ }