@anuma/sdk 6.14.0 → 6.14.2

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 (28) hide show
  1. package/dist/expo/{chunk-732ZLOLA.mjs → chunk-JA7CDJCA.mjs} +2 -2
  2. package/dist/expo/{chunk-OUFITMDL.mjs → chunk-M4JSLFTQ.mjs} +1 -1
  3. package/dist/{react/chunk-CJ32HO6Q.mjs → expo/chunk-O25LUKBK.mjs} +11 -2
  4. package/dist/{react/consolidate-5TP3J6BY.mjs → expo/consolidate-S3KFA2WT.mjs} +2 -2
  5. package/dist/expo/index.cjs +13 -4
  6. package/dist/expo/index.d.mts +50 -12
  7. package/dist/expo/index.d.ts +50 -12
  8. package/dist/expo/index.mjs +5 -5
  9. package/dist/expo/{recall-PIATJ336.mjs → recall-JYVHWU6N.mjs} +2 -2
  10. package/dist/react/{chunk-DY5VGC6K.mjs → chunk-2XLHV2XW.mjs} +1 -1
  11. package/dist/react/{chunk-6IBFFWCE.mjs → chunk-DNSPMVBY.mjs} +2 -2
  12. package/dist/{expo/chunk-CJ32HO6Q.mjs → react/chunk-O25LUKBK.mjs} +11 -2
  13. package/dist/{expo/consolidate-4BMNVOL4.mjs → react/consolidate-LSDBMQDW.mjs} +2 -2
  14. package/dist/react/index.cjs +13 -4
  15. package/dist/react/index.d.mts +50 -12
  16. package/dist/react/index.d.ts +50 -12
  17. package/dist/react/index.mjs +5 -5
  18. package/dist/react/{recall-QUWN7DJ7.mjs → recall-2BWBJWGG.mjs} +2 -2
  19. package/dist/server/{chunk-KF5OU4IN.mjs → chunk-7D3RI25K.mjs} +11 -2
  20. package/dist/server/{chunk-MEQ2PAEX.mjs → chunk-GP6YY6H5.mjs} +1 -1
  21. package/dist/server/{chunk-7YTEE6JT.mjs → chunk-O7TB36P3.mjs} +2 -2
  22. package/dist/server/{consolidate-QDWEYFTZ.mjs → consolidate-Z2JJP7LW.mjs} +2 -2
  23. package/dist/server/index.cjs +13 -4
  24. package/dist/server/index.d.mts +50 -12
  25. package/dist/server/index.d.ts +50 -12
  26. package/dist/server/index.mjs +5 -5
  27. package/dist/server/{recall-HOVWMT4Q.mjs → recall-VHU372MK.mjs} +2 -2
  28. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  callPortalJsonCompletion,
3
3
  getLogger
4
- } from "./chunk-CJ32HO6Q.mjs";
4
+ } from "./chunk-O25LUKBK.mjs";
5
5
  import {
6
6
  BASE_URL,
7
7
  createClientConfig
@@ -6885,7 +6885,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
6885
6885
  subQueries = decomp.subQueries;
6886
6886
  }
6887
6887
  }
6888
- const { recall: recall2 } = await import("./recall-PIATJ336.mjs");
6888
+ const { recall: recall2 } = await import("./recall-JYVHWU6N.mjs");
6889
6889
  let recallDegraded = [];
6890
6890
  const result = await recall2(
6891
6891
  query,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getLogger
3
- } from "./chunk-CJ32HO6Q.mjs";
3
+ } from "./chunk-O25LUKBK.mjs";
4
4
 
5
5
  // src/lib/pii/patterns.ts
6
6
  function luhnCheck(digits) {
@@ -65,6 +65,7 @@ function defaultBaseUrl() {
65
65
  }
66
66
  var RESPONSE_FORMAT_OK = /* @__PURE__ */ new Set(["openai", "inclusionai", "deepseek"]);
67
67
  var RESPONSE_SCHEMA_OK = /* @__PURE__ */ new Set(["openai"]);
68
+ var JSON_CONTRACT_REMINDER = "Output ONLY the strict JSON object requested. No prose, no explanation, no markdown.";
68
69
  function supportsResponseFormat(model, variant = "json_object") {
69
70
  const allow = variant === "json_schema" ? RESPONSE_SCHEMA_OK : RESPONSE_FORMAT_OK;
70
71
  return model.split("/").some((seg) => allow.has(seg));
@@ -122,6 +123,7 @@ async function callPortalJsonCompletion(req) {
122
123
  const startedAt = Date.now();
123
124
  const overBudget = () => req.totalTimeoutMs !== void 0 && Date.now() - startedAt >= req.totalTimeoutMs;
124
125
  let lastFailure;
126
+ let reinforce = false;
125
127
  const giveUp = () => {
126
128
  req.onFailure?.(lastFailure ?? { reason: "network", attempts: maxAttempts });
127
129
  return null;
@@ -137,8 +139,9 @@ async function callPortalJsonCompletion(req) {
137
139
  timeoutMs: Math.max(
138
140
  1,
139
141
  Math.min(req.timeoutMs ?? 6e4, req.totalTimeoutMs - (Date.now() - startedAt))
140
- )
141
- } : req;
142
+ ),
143
+ ...reinforce && { reinforceJsonContract: true }
144
+ } : reinforce ? { ...req, reinforceJsonContract: true } : req;
142
145
  const outcome = await attemptPortalJson(attemptReq, endpoint);
143
146
  if (outcome.kind === "ok") return outcome.value;
144
147
  lastFailure = {
@@ -146,6 +149,7 @@ async function callPortalJsonCompletion(req) {
146
149
  ...outcome.httpStatus !== void 0 && { httpStatus: outcome.httpStatus },
147
150
  attempts: attempt
148
151
  };
152
+ if (outcome.code === "invalid-json" || outcome.code === "null-completion") reinforce = true;
149
153
  if (outcome.kind === "terminal") {
150
154
  log.warn(`[${req.tag}] ${outcome.reason}`);
151
155
  return giveUp();
@@ -184,6 +188,11 @@ async function attemptPortalJson(req, endpoint) {
184
188
  // keeps the portal's detector reading them as genuine rather than markerless.
185
189
  // See ../internalFlowMarker.
186
190
  { role: "system", content: withInternalFlowMarker(req.systemPrompt) },
191
+ // Retry-only. Kept as a SEPARATE message rather than appended to the system
192
+ // prompt so the marked prompt above stays byte-identical across attempts —
193
+ // the portal's internal-flow detector reads that one, and rewriting it on a
194
+ // retry would change what the detector sees for reasons unrelated to auth.
195
+ ...req.reinforceJsonContract ? [{ role: "system", content: JSON_CONTRACT_REMINDER }] : [],
187
196
  { role: "user", content: req.userMessage }
188
197
  ];
189
198
  if (prefill) messages.push({ role: "assistant", content: prefill });
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  notifyConsolidationFallback,
3
3
  resolvePiiRedactor
4
- } from "./chunk-DY5VGC6K.mjs";
4
+ } from "./chunk-M4JSLFTQ.mjs";
5
5
  import {
6
6
  callPortalJsonCompletion
7
- } from "./chunk-CJ32HO6Q.mjs";
7
+ } from "./chunk-O25LUKBK.mjs";
8
8
  import "./chunk-GYKJOWEO.mjs";
9
9
 
10
10
  // src/lib/memory/consolidate.ts
@@ -6197,6 +6197,7 @@ async function callPortalJsonCompletion(req) {
6197
6197
  const startedAt = Date.now();
6198
6198
  const overBudget = () => req.totalTimeoutMs !== void 0 && Date.now() - startedAt >= req.totalTimeoutMs;
6199
6199
  let lastFailure;
6200
+ let reinforce = false;
6200
6201
  const giveUp = () => {
6201
6202
  req.onFailure?.(lastFailure ?? { reason: "network", attempts: maxAttempts });
6202
6203
  return null;
@@ -6212,8 +6213,9 @@ async function callPortalJsonCompletion(req) {
6212
6213
  timeoutMs: Math.max(
6213
6214
  1,
6214
6215
  Math.min(req.timeoutMs ?? 6e4, req.totalTimeoutMs - (Date.now() - startedAt))
6215
- )
6216
- } : req;
6216
+ ),
6217
+ ...reinforce && { reinforceJsonContract: true }
6218
+ } : reinforce ? { ...req, reinforceJsonContract: true } : req;
6217
6219
  const outcome = await attemptPortalJson(attemptReq, endpoint);
6218
6220
  if (outcome.kind === "ok") return outcome.value;
6219
6221
  lastFailure = {
@@ -6221,6 +6223,7 @@ async function callPortalJsonCompletion(req) {
6221
6223
  ...outcome.httpStatus !== void 0 && { httpStatus: outcome.httpStatus },
6222
6224
  attempts: attempt
6223
6225
  };
6226
+ if (outcome.code === "invalid-json" || outcome.code === "null-completion") reinforce = true;
6224
6227
  if (outcome.kind === "terminal") {
6225
6228
  log.warn(`[${req.tag}] ${outcome.reason}`);
6226
6229
  return giveUp();
@@ -6259,6 +6262,11 @@ async function attemptPortalJson(req, endpoint) {
6259
6262
  // keeps the portal's detector reading them as genuine rather than markerless.
6260
6263
  // See ../internalFlowMarker.
6261
6264
  { role: "system", content: withInternalFlowMarker(req.systemPrompt) },
6265
+ // Retry-only. Kept as a SEPARATE message rather than appended to the system
6266
+ // prompt so the marked prompt above stays byte-identical across attempts —
6267
+ // the portal's internal-flow detector reads that one, and rewriting it on a
6268
+ // retry would change what the detector sees for reasons unrelated to auth.
6269
+ ...req.reinforceJsonContract ? [{ role: "system", content: JSON_CONTRACT_REMINDER }] : [],
6262
6270
  { role: "user", content: req.userMessage }
6263
6271
  ];
6264
6272
  if (prefill) messages.push({ role: "assistant", content: prefill });
@@ -6421,7 +6429,7 @@ function extractCompletionContent(body) {
6421
6429
  const content = first?.message?.content;
6422
6430
  return typeof content === "string" ? content : null;
6423
6431
  }
6424
- var RESPONSE_FORMAT_OK, RESPONSE_SCHEMA_OK, RETRYABLE_HTTP;
6432
+ var RESPONSE_FORMAT_OK, RESPONSE_SCHEMA_OK, JSON_CONTRACT_REMINDER, RETRYABLE_HTTP;
6425
6433
  var init_portalLlm = __esm({
6426
6434
  "src/lib/memory/portalLlm.ts"() {
6427
6435
  "use strict";
@@ -6432,6 +6440,7 @@ var init_portalLlm = __esm({
6432
6440
  init_taskType();
6433
6441
  RESPONSE_FORMAT_OK = /* @__PURE__ */ new Set(["openai", "inclusionai", "deepseek"]);
6434
6442
  RESPONSE_SCHEMA_OK = /* @__PURE__ */ new Set(["openai"]);
6443
+ JSON_CONTRACT_REMINDER = "Output ONLY the strict JSON object requested. No prose, no explanation, no markdown.";
6435
6444
  RETRYABLE_HTTP = /* @__PURE__ */ new Set([408, 409, 425, 429]);
6436
6445
  }
6437
6446
  });
@@ -15429,7 +15438,7 @@ async function ingestPublishedPhotoMemoriesOp(ctx, rows) {
15429
15438
  ({ row, content }) => ctx.vaultMemoryCollection.prepareCreate((record) => {
15430
15439
  record._raw.id = row.memoryId;
15431
15440
  record._setRaw("content", content);
15432
- record._setRaw("scope", "private");
15441
+ record._setRaw("scope", "shared");
15433
15442
  record._setRaw("folder_id", null);
15434
15443
  record._setRaw("user_id", ctx.userId ?? null);
15435
15444
  record._setRaw("is_deleted", false);
@@ -6335,15 +6335,26 @@ declare function extractAndRetain(messages: AutoExtractMessage[], retainCtx: Ret
6335
6335
  }>;
6336
6336
 
6337
6337
  /**
6338
- * People Nearby cross-user visibility. ORTHOGONAL to `scope` (model access):
6339
- * - `private`: local-only (default; null column grandfathered as private)
6340
- * - `public`: embedding + plaintext may be published (compatibility matching,
6341
- * profile display, discovery answers, digital-twin prompts)
6338
+ * LEGACY. This column no longer decides anything.
6339
+ *
6340
+ * `scope === 'shared'` is now the single publication axis: it means the memory
6341
+ * is on the People Nearby profile, its text goes to the nearby server, and it
6342
+ * may inform what strangers see. `scope: 'private'` means it is not. There is
6343
+ * no second control, and scope no longer gates model access either — every
6344
+ * model tier reads every memory. Do NOT reason about publication from this
6345
+ * type; read `scope`.
6346
+ *
6347
+ * The column survives in the schema and is still stamped on writes so that
6348
+ * stored rows stay readable by builds that predate the change. Nothing on a
6349
+ * current build reads it to make a decision.
6350
+ *
6351
+ * Values, kept for tolerance of already-stored data:
6352
+ * - `private`: local-only (a null column reads as private)
6353
+ * - `public`: the row was published
6342
6354
  *
6343
6355
  * TWO TIERS ONLY (decided 2026-07-27). An earlier design had a middle
6344
6356
  * `matchable` tier that published a memory's embedding while keeping its text
6345
- * on-device; it was dropped, so a memory either never leaves the device or is
6346
- * published with its content. Any unrecognised stored value — including a
6357
+ * on-device; it was dropped. Any unrecognised stored value — including a
6347
6358
  * `matchable` row written by a pre-release build — reads as `private`, which is
6348
6359
  * the fail-safe direction: it un-publishes rather than exposes.
6349
6360
  */
@@ -6997,12 +7008,16 @@ declare function updateVaultMemoryEmbeddingOp(ctx: VaultMemoryOperationsContext,
6997
7008
  * candidate to be reconciled against the vault — it is an already-decided
6998
7009
  * fact the server has published.
6999
7010
  *
7000
- * The rows are written `visibility: "public"` because they genuinely ARE
7001
- * published — the reconciler reads local visibility as the user's intent, and
7002
- * anything else would make it immediately revoke a memory the user never turned
7003
- * off. They carry `source: SOURCE_PHOTO`, which is what exempts them from
7004
- * decay's auto-archive; see the note there for why archiving one would silently
7005
- * unpublish it.
7011
+ * The rows are written `scope: "shared"` because they genuinely ARE published.
7012
+ * `scope === 'shared'` is the publication axis — the single one. The reconciler
7013
+ * reads local scope as the user's intent, and it builds its `local` set from a
7014
+ * vault read narrowed to `{scopes: ['shared']}`. A row stamped anything else
7015
+ * never enters that read, so it is never adopted into the publish ledger, and
7016
+ * `toRevoke` is computed only over ids the ledger holds. The user's off-toggle
7017
+ * would then do nothing server-side while nearby kept serving the memory. The
7018
+ * `visibility` column is legacy: nothing reads it any more. They carry
7019
+ * `source: SOURCE_PHOTO`, which is what exempts them from decay's auto-archive;
7020
+ * see the note there for why archiving one would silently unpublish it.
7006
7021
  *
7007
7022
  * No embedding is computed here. `searchTool`'s un-embedded lane embeds any row
7008
7023
  * lacking a vector at search time, uses it in that same search and persists it
@@ -7050,6 +7065,29 @@ interface PhotoIngestResult {
7050
7065
  * class of bug as publishing a version the user has since changed. Re-ingesting
7051
7066
  * changed text is a follow-up that needs a merge decision, not a clobber.
7052
7067
  *
7068
+ * That skip is also why this op does NOT repair a row an older build stamped
7069
+ * `scope: "private"`. Such a row is present, so ingest leaves it alone, and it
7070
+ * stays outside the published-set read. The user's switch cannot revoke it.
7071
+ * Repair belongs in a one-time pass at upgrade. Do not add it here, for two
7072
+ * reasons:
7073
+ *
7074
+ * - **The two states are indistinguishable from this op.** After this build a
7075
+ * `photo:` row with `scope: "private"` is also exactly what the user's own
7076
+ * off-toggle produces. The toggle calls `updateVaultMemoryOp`, which writes
7077
+ * `scope` and never touches `visibility`, so the mis-stamped row and the
7078
+ * turned-off row carry identical columns. No test here can tell them apart.
7079
+ *
7080
+ * - **A re-stamp here would revert the off-toggle.** The pass runs ingest
7081
+ * BEFORE it reads the vault, so a row re-stamped `shared` re-enters the
7082
+ * desired set on that same pass and never reaches `toRevoke`. The switch
7083
+ * would spring back to "on" and nearby would keep serving the memory. That
7084
+ * is worse than the bug it repairs.
7085
+ *
7086
+ * A one-time pass does not have the ambiguity. Before this build the switch
7087
+ * already read "off" for these rows, because it reads `scope`, so no user can
7088
+ * have turned one off. Every `private` photo row at that instant came from the
7089
+ * old stamp.
7090
+ *
7053
7091
  * Non-`photo:` ids are ignored: everything else in the published set is a
7054
7092
  * client-published memory that by definition already lives in this vault (or in
7055
7093
  * another device's, which is not ours to recreate).
@@ -6335,15 +6335,26 @@ declare function extractAndRetain(messages: AutoExtractMessage[], retainCtx: Ret
6335
6335
  }>;
6336
6336
 
6337
6337
  /**
6338
- * People Nearby cross-user visibility. ORTHOGONAL to `scope` (model access):
6339
- * - `private`: local-only (default; null column grandfathered as private)
6340
- * - `public`: embedding + plaintext may be published (compatibility matching,
6341
- * profile display, discovery answers, digital-twin prompts)
6338
+ * LEGACY. This column no longer decides anything.
6339
+ *
6340
+ * `scope === 'shared'` is now the single publication axis: it means the memory
6341
+ * is on the People Nearby profile, its text goes to the nearby server, and it
6342
+ * may inform what strangers see. `scope: 'private'` means it is not. There is
6343
+ * no second control, and scope no longer gates model access either — every
6344
+ * model tier reads every memory. Do NOT reason about publication from this
6345
+ * type; read `scope`.
6346
+ *
6347
+ * The column survives in the schema and is still stamped on writes so that
6348
+ * stored rows stay readable by builds that predate the change. Nothing on a
6349
+ * current build reads it to make a decision.
6350
+ *
6351
+ * Values, kept for tolerance of already-stored data:
6352
+ * - `private`: local-only (a null column reads as private)
6353
+ * - `public`: the row was published
6342
6354
  *
6343
6355
  * TWO TIERS ONLY (decided 2026-07-27). An earlier design had a middle
6344
6356
  * `matchable` tier that published a memory's embedding while keeping its text
6345
- * on-device; it was dropped, so a memory either never leaves the device or is
6346
- * published with its content. Any unrecognised stored value — including a
6357
+ * on-device; it was dropped. Any unrecognised stored value — including a
6347
6358
  * `matchable` row written by a pre-release build — reads as `private`, which is
6348
6359
  * the fail-safe direction: it un-publishes rather than exposes.
6349
6360
  */
@@ -6997,12 +7008,16 @@ declare function updateVaultMemoryEmbeddingOp(ctx: VaultMemoryOperationsContext,
6997
7008
  * candidate to be reconciled against the vault — it is an already-decided
6998
7009
  * fact the server has published.
6999
7010
  *
7000
- * The rows are written `visibility: "public"` because they genuinely ARE
7001
- * published — the reconciler reads local visibility as the user's intent, and
7002
- * anything else would make it immediately revoke a memory the user never turned
7003
- * off. They carry `source: SOURCE_PHOTO`, which is what exempts them from
7004
- * decay's auto-archive; see the note there for why archiving one would silently
7005
- * unpublish it.
7011
+ * The rows are written `scope: "shared"` because they genuinely ARE published.
7012
+ * `scope === 'shared'` is the publication axis — the single one. The reconciler
7013
+ * reads local scope as the user's intent, and it builds its `local` set from a
7014
+ * vault read narrowed to `{scopes: ['shared']}`. A row stamped anything else
7015
+ * never enters that read, so it is never adopted into the publish ledger, and
7016
+ * `toRevoke` is computed only over ids the ledger holds. The user's off-toggle
7017
+ * would then do nothing server-side while nearby kept serving the memory. The
7018
+ * `visibility` column is legacy: nothing reads it any more. They carry
7019
+ * `source: SOURCE_PHOTO`, which is what exempts them from decay's auto-archive;
7020
+ * see the note there for why archiving one would silently unpublish it.
7006
7021
  *
7007
7022
  * No embedding is computed here. `searchTool`'s un-embedded lane embeds any row
7008
7023
  * lacking a vector at search time, uses it in that same search and persists it
@@ -7050,6 +7065,29 @@ interface PhotoIngestResult {
7050
7065
  * class of bug as publishing a version the user has since changed. Re-ingesting
7051
7066
  * changed text is a follow-up that needs a merge decision, not a clobber.
7052
7067
  *
7068
+ * That skip is also why this op does NOT repair a row an older build stamped
7069
+ * `scope: "private"`. Such a row is present, so ingest leaves it alone, and it
7070
+ * stays outside the published-set read. The user's switch cannot revoke it.
7071
+ * Repair belongs in a one-time pass at upgrade. Do not add it here, for two
7072
+ * reasons:
7073
+ *
7074
+ * - **The two states are indistinguishable from this op.** After this build a
7075
+ * `photo:` row with `scope: "private"` is also exactly what the user's own
7076
+ * off-toggle produces. The toggle calls `updateVaultMemoryOp`, which writes
7077
+ * `scope` and never touches `visibility`, so the mis-stamped row and the
7078
+ * turned-off row carry identical columns. No test here can tell them apart.
7079
+ *
7080
+ * - **A re-stamp here would revert the off-toggle.** The pass runs ingest
7081
+ * BEFORE it reads the vault, so a row re-stamped `shared` re-enters the
7082
+ * desired set on that same pass and never reaches `toRevoke`. The switch
7083
+ * would spring back to "on" and nearby would keep serving the memory. That
7084
+ * is worse than the bug it repairs.
7085
+ *
7086
+ * A one-time pass does not have the ambiguity. Before this build the switch
7087
+ * already read "off" for these rows, because it reads `scope`, so no user can
7088
+ * have turned one off. Every `private` photo row at that instant came from the
7089
+ * old stamp.
7090
+ *
7053
7091
  * Non-`photo:` ids are ignored: everything else in the published set is a
7054
7092
  * client-published memory that by definition already lives in this vault (or in
7055
7093
  * another device's, which is not ours to recreate).
@@ -132,7 +132,7 @@ import {
132
132
  upsertMessageOp,
133
133
  useEncryption,
134
134
  vaultMemoryToStored
135
- } from "./chunk-732ZLOLA.mjs";
135
+ } from "./chunk-JA7CDJCA.mjs";
136
136
  import {
137
137
  PII_PATTERNS,
138
138
  PiiRedactor,
@@ -140,7 +140,7 @@ import {
140
140
  isPiiRedactor,
141
141
  notifyConsolidationFallback,
142
142
  resolvePiiRedactor
143
- } from "./chunk-OUFITMDL.mjs";
143
+ } from "./chunk-M4JSLFTQ.mjs";
144
144
  import {
145
145
  INTERNAL_FLOW_MARKER,
146
146
  callPortalJsonCompletion,
@@ -154,7 +154,7 @@ import {
154
154
  taskTypeHeader,
155
155
  validateEndpointOverride,
156
156
  withInternalFlowMarker
157
- } from "./chunk-CJ32HO6Q.mjs";
157
+ } from "./chunk-O25LUKBK.mjs";
158
158
  import {
159
159
  BASE_URL,
160
160
  MCP_R2_DOMAIN,
@@ -5819,7 +5819,7 @@ async function ingestPublishedPhotoMemoriesOp(ctx, rows) {
5819
5819
  ({ row, content }) => ctx.vaultMemoryCollection.prepareCreate((record) => {
5820
5820
  record._raw.id = row.memoryId;
5821
5821
  record._setRaw("content", content);
5822
- record._setRaw("scope", "private");
5822
+ record._setRaw("scope", "shared");
5823
5823
  record._setRaw("folder_id", null);
5824
5824
  record._setRaw("user_id", ctx.userId ?? null);
5825
5825
  record._setRaw("is_deleted", false);
@@ -6868,7 +6868,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
6868
6868
  content: m.content,
6869
6869
  similarity: m.similarity
6870
6870
  }));
6871
- const { consolidateMemory: doConsolidate } = await import("./consolidate-4BMNVOL4.mjs");
6871
+ const { consolidateMemory: doConsolidate } = await import("./consolidate-S3KFA2WT.mjs");
6872
6872
  const decision = await doConsolidate(trimmed, candidates, consolidateOptions);
6873
6873
  if (decision.action === "create") return null;
6874
6874
  if (decision.action === "supersede" && decision.content) {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  recall
3
- } from "./chunk-732ZLOLA.mjs";
4
- import "./chunk-CJ32HO6Q.mjs";
3
+ } from "./chunk-JA7CDJCA.mjs";
4
+ import "./chunk-O25LUKBK.mjs";
5
5
  import "./chunk-GYKJOWEO.mjs";
6
6
  export {
7
7
  recall
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getLogger
3
- } from "./chunk-CJ32HO6Q.mjs";
3
+ } from "./chunk-O25LUKBK.mjs";
4
4
 
5
5
  // src/lib/pii/patterns.ts
6
6
  function luhnCheck(digits) {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  callPortalJsonCompletion,
3
3
  getLogger
4
- } from "./chunk-CJ32HO6Q.mjs";
4
+ } from "./chunk-O25LUKBK.mjs";
5
5
  import {
6
6
  BASE_URL,
7
7
  createClientConfig
@@ -7169,7 +7169,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
7169
7169
  subQueries = decomp.subQueries;
7170
7170
  }
7171
7171
  }
7172
- const { recall: recall2 } = await import("./recall-QUWN7DJ7.mjs");
7172
+ const { recall: recall2 } = await import("./recall-2BWBJWGG.mjs");
7173
7173
  let recallDegraded = [];
7174
7174
  const result = await recall2(
7175
7175
  query,
@@ -65,6 +65,7 @@ function defaultBaseUrl() {
65
65
  }
66
66
  var RESPONSE_FORMAT_OK = /* @__PURE__ */ new Set(["openai", "inclusionai", "deepseek"]);
67
67
  var RESPONSE_SCHEMA_OK = /* @__PURE__ */ new Set(["openai"]);
68
+ var JSON_CONTRACT_REMINDER = "Output ONLY the strict JSON object requested. No prose, no explanation, no markdown.";
68
69
  function supportsResponseFormat(model, variant = "json_object") {
69
70
  const allow = variant === "json_schema" ? RESPONSE_SCHEMA_OK : RESPONSE_FORMAT_OK;
70
71
  return model.split("/").some((seg) => allow.has(seg));
@@ -122,6 +123,7 @@ async function callPortalJsonCompletion(req) {
122
123
  const startedAt = Date.now();
123
124
  const overBudget = () => req.totalTimeoutMs !== void 0 && Date.now() - startedAt >= req.totalTimeoutMs;
124
125
  let lastFailure;
126
+ let reinforce = false;
125
127
  const giveUp = () => {
126
128
  req.onFailure?.(lastFailure ?? { reason: "network", attempts: maxAttempts });
127
129
  return null;
@@ -137,8 +139,9 @@ async function callPortalJsonCompletion(req) {
137
139
  timeoutMs: Math.max(
138
140
  1,
139
141
  Math.min(req.timeoutMs ?? 6e4, req.totalTimeoutMs - (Date.now() - startedAt))
140
- )
141
- } : req;
142
+ ),
143
+ ...reinforce && { reinforceJsonContract: true }
144
+ } : reinforce ? { ...req, reinforceJsonContract: true } : req;
142
145
  const outcome = await attemptPortalJson(attemptReq, endpoint);
143
146
  if (outcome.kind === "ok") return outcome.value;
144
147
  lastFailure = {
@@ -146,6 +149,7 @@ async function callPortalJsonCompletion(req) {
146
149
  ...outcome.httpStatus !== void 0 && { httpStatus: outcome.httpStatus },
147
150
  attempts: attempt
148
151
  };
152
+ if (outcome.code === "invalid-json" || outcome.code === "null-completion") reinforce = true;
149
153
  if (outcome.kind === "terminal") {
150
154
  log.warn(`[${req.tag}] ${outcome.reason}`);
151
155
  return giveUp();
@@ -184,6 +188,11 @@ async function attemptPortalJson(req, endpoint) {
184
188
  // keeps the portal's detector reading them as genuine rather than markerless.
185
189
  // See ../internalFlowMarker.
186
190
  { role: "system", content: withInternalFlowMarker(req.systemPrompt) },
191
+ // Retry-only. Kept as a SEPARATE message rather than appended to the system
192
+ // prompt so the marked prompt above stays byte-identical across attempts —
193
+ // the portal's internal-flow detector reads that one, and rewriting it on a
194
+ // retry would change what the detector sees for reasons unrelated to auth.
195
+ ...req.reinforceJsonContract ? [{ role: "system", content: JSON_CONTRACT_REMINDER }] : [],
187
196
  { role: "user", content: req.userMessage }
188
197
  ];
189
198
  if (prefill) messages.push({ role: "assistant", content: prefill });
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  notifyConsolidationFallback,
3
3
  resolvePiiRedactor
4
- } from "./chunk-OUFITMDL.mjs";
4
+ } from "./chunk-2XLHV2XW.mjs";
5
5
  import {
6
6
  callPortalJsonCompletion
7
- } from "./chunk-CJ32HO6Q.mjs";
7
+ } from "./chunk-O25LUKBK.mjs";
8
8
  import "./chunk-GYKJOWEO.mjs";
9
9
 
10
10
  // src/lib/memory/consolidate.ts
@@ -6481,6 +6481,7 @@ async function callPortalJsonCompletion(req) {
6481
6481
  const startedAt = Date.now();
6482
6482
  const overBudget = () => req.totalTimeoutMs !== void 0 && Date.now() - startedAt >= req.totalTimeoutMs;
6483
6483
  let lastFailure;
6484
+ let reinforce = false;
6484
6485
  const giveUp = () => {
6485
6486
  req.onFailure?.(lastFailure ?? { reason: "network", attempts: maxAttempts });
6486
6487
  return null;
@@ -6496,8 +6497,9 @@ async function callPortalJsonCompletion(req) {
6496
6497
  timeoutMs: Math.max(
6497
6498
  1,
6498
6499
  Math.min(req.timeoutMs ?? 6e4, req.totalTimeoutMs - (Date.now() - startedAt))
6499
- )
6500
- } : req;
6500
+ ),
6501
+ ...reinforce && { reinforceJsonContract: true }
6502
+ } : reinforce ? { ...req, reinforceJsonContract: true } : req;
6501
6503
  const outcome = await attemptPortalJson(attemptReq, endpoint);
6502
6504
  if (outcome.kind === "ok") return outcome.value;
6503
6505
  lastFailure = {
@@ -6505,6 +6507,7 @@ async function callPortalJsonCompletion(req) {
6505
6507
  ...outcome.httpStatus !== void 0 && { httpStatus: outcome.httpStatus },
6506
6508
  attempts: attempt
6507
6509
  };
6510
+ if (outcome.code === "invalid-json" || outcome.code === "null-completion") reinforce = true;
6508
6511
  if (outcome.kind === "terminal") {
6509
6512
  log.warn(`[${req.tag}] ${outcome.reason}`);
6510
6513
  return giveUp();
@@ -6543,6 +6546,11 @@ async function attemptPortalJson(req, endpoint) {
6543
6546
  // keeps the portal's detector reading them as genuine rather than markerless.
6544
6547
  // See ../internalFlowMarker.
6545
6548
  { role: "system", content: withInternalFlowMarker(req.systemPrompt) },
6549
+ // Retry-only. Kept as a SEPARATE message rather than appended to the system
6550
+ // prompt so the marked prompt above stays byte-identical across attempts —
6551
+ // the portal's internal-flow detector reads that one, and rewriting it on a
6552
+ // retry would change what the detector sees for reasons unrelated to auth.
6553
+ ...req.reinforceJsonContract ? [{ role: "system", content: JSON_CONTRACT_REMINDER }] : [],
6546
6554
  { role: "user", content: req.userMessage }
6547
6555
  ];
6548
6556
  if (prefill) messages.push({ role: "assistant", content: prefill });
@@ -6705,7 +6713,7 @@ function extractCompletionContent(body) {
6705
6713
  const content = first?.message?.content;
6706
6714
  return typeof content === "string" ? content : null;
6707
6715
  }
6708
- var RESPONSE_FORMAT_OK, RESPONSE_SCHEMA_OK, RETRYABLE_HTTP;
6716
+ var RESPONSE_FORMAT_OK, RESPONSE_SCHEMA_OK, JSON_CONTRACT_REMINDER, RETRYABLE_HTTP;
6709
6717
  var init_portalLlm = __esm({
6710
6718
  "src/lib/memory/portalLlm.ts"() {
6711
6719
  "use strict";
@@ -6716,6 +6724,7 @@ var init_portalLlm = __esm({
6716
6724
  init_taskType();
6717
6725
  RESPONSE_FORMAT_OK = /* @__PURE__ */ new Set(["openai", "inclusionai", "deepseek"]);
6718
6726
  RESPONSE_SCHEMA_OK = /* @__PURE__ */ new Set(["openai"]);
6727
+ JSON_CONTRACT_REMINDER = "Output ONLY the strict JSON object requested. No prose, no explanation, no markdown.";
6719
6728
  RETRYABLE_HTTP = /* @__PURE__ */ new Set([408, 409, 425, 429]);
6720
6729
  }
6721
6730
  });
@@ -17067,7 +17076,7 @@ async function ingestPublishedPhotoMemoriesOp(ctx, rows) {
17067
17076
  ({ row, content }) => ctx.vaultMemoryCollection.prepareCreate((record) => {
17068
17077
  record._raw.id = row.memoryId;
17069
17078
  record._setRaw("content", content);
17070
- record._setRaw("scope", "private");
17079
+ record._setRaw("scope", "shared");
17071
17080
  record._setRaw("folder_id", null);
17072
17081
  record._setRaw("user_id", ctx.userId ?? null);
17073
17082
  record._setRaw("is_deleted", false);
@@ -7604,15 +7604,26 @@ declare function extractAndRetain(messages: AutoExtractMessage[], retainCtx: Ret
7604
7604
  }>;
7605
7605
 
7606
7606
  /**
7607
- * People Nearby cross-user visibility. ORTHOGONAL to `scope` (model access):
7608
- * - `private`: local-only (default; null column grandfathered as private)
7609
- * - `public`: embedding + plaintext may be published (compatibility matching,
7610
- * profile display, discovery answers, digital-twin prompts)
7607
+ * LEGACY. This column no longer decides anything.
7608
+ *
7609
+ * `scope === 'shared'` is now the single publication axis: it means the memory
7610
+ * is on the People Nearby profile, its text goes to the nearby server, and it
7611
+ * may inform what strangers see. `scope: 'private'` means it is not. There is
7612
+ * no second control, and scope no longer gates model access either — every
7613
+ * model tier reads every memory. Do NOT reason about publication from this
7614
+ * type; read `scope`.
7615
+ *
7616
+ * The column survives in the schema and is still stamped on writes so that
7617
+ * stored rows stay readable by builds that predate the change. Nothing on a
7618
+ * current build reads it to make a decision.
7619
+ *
7620
+ * Values, kept for tolerance of already-stored data:
7621
+ * - `private`: local-only (a null column reads as private)
7622
+ * - `public`: the row was published
7611
7623
  *
7612
7624
  * TWO TIERS ONLY (decided 2026-07-27). An earlier design had a middle
7613
7625
  * `matchable` tier that published a memory's embedding while keeping its text
7614
- * on-device; it was dropped, so a memory either never leaves the device or is
7615
- * published with its content. Any unrecognised stored value — including a
7626
+ * on-device; it was dropped. Any unrecognised stored value — including a
7616
7627
  * `matchable` row written by a pre-release build — reads as `private`, which is
7617
7628
  * the fail-safe direction: it un-publishes rather than exposes.
7618
7629
  */
@@ -8352,12 +8363,16 @@ declare function updateVaultMemoryEmbeddingOp(ctx: VaultMemoryOperationsContext,
8352
8363
  * candidate to be reconciled against the vault — it is an already-decided
8353
8364
  * fact the server has published.
8354
8365
  *
8355
- * The rows are written `visibility: "public"` because they genuinely ARE
8356
- * published — the reconciler reads local visibility as the user's intent, and
8357
- * anything else would make it immediately revoke a memory the user never turned
8358
- * off. They carry `source: SOURCE_PHOTO`, which is what exempts them from
8359
- * decay's auto-archive; see the note there for why archiving one would silently
8360
- * unpublish it.
8366
+ * The rows are written `scope: "shared"` because they genuinely ARE published.
8367
+ * `scope === 'shared'` is the publication axis — the single one. The reconciler
8368
+ * reads local scope as the user's intent, and it builds its `local` set from a
8369
+ * vault read narrowed to `{scopes: ['shared']}`. A row stamped anything else
8370
+ * never enters that read, so it is never adopted into the publish ledger, and
8371
+ * `toRevoke` is computed only over ids the ledger holds. The user's off-toggle
8372
+ * would then do nothing server-side while nearby kept serving the memory. The
8373
+ * `visibility` column is legacy: nothing reads it any more. They carry
8374
+ * `source: SOURCE_PHOTO`, which is what exempts them from decay's auto-archive;
8375
+ * see the note there for why archiving one would silently unpublish it.
8361
8376
  *
8362
8377
  * No embedding is computed here. `searchTool`'s un-embedded lane embeds any row
8363
8378
  * lacking a vector at search time, uses it in that same search and persists it
@@ -8405,6 +8420,29 @@ interface PhotoIngestResult {
8405
8420
  * class of bug as publishing a version the user has since changed. Re-ingesting
8406
8421
  * changed text is a follow-up that needs a merge decision, not a clobber.
8407
8422
  *
8423
+ * That skip is also why this op does NOT repair a row an older build stamped
8424
+ * `scope: "private"`. Such a row is present, so ingest leaves it alone, and it
8425
+ * stays outside the published-set read. The user's switch cannot revoke it.
8426
+ * Repair belongs in a one-time pass at upgrade. Do not add it here, for two
8427
+ * reasons:
8428
+ *
8429
+ * - **The two states are indistinguishable from this op.** After this build a
8430
+ * `photo:` row with `scope: "private"` is also exactly what the user's own
8431
+ * off-toggle produces. The toggle calls `updateVaultMemoryOp`, which writes
8432
+ * `scope` and never touches `visibility`, so the mis-stamped row and the
8433
+ * turned-off row carry identical columns. No test here can tell them apart.
8434
+ *
8435
+ * - **A re-stamp here would revert the off-toggle.** The pass runs ingest
8436
+ * BEFORE it reads the vault, so a row re-stamped `shared` re-enters the
8437
+ * desired set on that same pass and never reaches `toRevoke`. The switch
8438
+ * would spring back to "on" and nearby would keep serving the memory. That
8439
+ * is worse than the bug it repairs.
8440
+ *
8441
+ * A one-time pass does not have the ambiguity. Before this build the switch
8442
+ * already read "off" for these rows, because it reads `scope`, so no user can
8443
+ * have turned one off. Every `private` photo row at that instant came from the
8444
+ * old stamp.
8445
+ *
8408
8446
  * Non-`photo:` ids are ignored: everything else in the published set is a
8409
8447
  * client-published memory that by definition already lives in this vault (or in
8410
8448
  * another device's, which is not ours to recreate).
@@ -7604,15 +7604,26 @@ declare function extractAndRetain(messages: AutoExtractMessage[], retainCtx: Ret
7604
7604
  }>;
7605
7605
 
7606
7606
  /**
7607
- * People Nearby cross-user visibility. ORTHOGONAL to `scope` (model access):
7608
- * - `private`: local-only (default; null column grandfathered as private)
7609
- * - `public`: embedding + plaintext may be published (compatibility matching,
7610
- * profile display, discovery answers, digital-twin prompts)
7607
+ * LEGACY. This column no longer decides anything.
7608
+ *
7609
+ * `scope === 'shared'` is now the single publication axis: it means the memory
7610
+ * is on the People Nearby profile, its text goes to the nearby server, and it
7611
+ * may inform what strangers see. `scope: 'private'` means it is not. There is
7612
+ * no second control, and scope no longer gates model access either — every
7613
+ * model tier reads every memory. Do NOT reason about publication from this
7614
+ * type; read `scope`.
7615
+ *
7616
+ * The column survives in the schema and is still stamped on writes so that
7617
+ * stored rows stay readable by builds that predate the change. Nothing on a
7618
+ * current build reads it to make a decision.
7619
+ *
7620
+ * Values, kept for tolerance of already-stored data:
7621
+ * - `private`: local-only (a null column reads as private)
7622
+ * - `public`: the row was published
7611
7623
  *
7612
7624
  * TWO TIERS ONLY (decided 2026-07-27). An earlier design had a middle
7613
7625
  * `matchable` tier that published a memory's embedding while keeping its text
7614
- * on-device; it was dropped, so a memory either never leaves the device or is
7615
- * published with its content. Any unrecognised stored value — including a
7626
+ * on-device; it was dropped. Any unrecognised stored value — including a
7616
7627
  * `matchable` row written by a pre-release build — reads as `private`, which is
7617
7628
  * the fail-safe direction: it un-publishes rather than exposes.
7618
7629
  */
@@ -8352,12 +8363,16 @@ declare function updateVaultMemoryEmbeddingOp(ctx: VaultMemoryOperationsContext,
8352
8363
  * candidate to be reconciled against the vault — it is an already-decided
8353
8364
  * fact the server has published.
8354
8365
  *
8355
- * The rows are written `visibility: "public"` because they genuinely ARE
8356
- * published — the reconciler reads local visibility as the user's intent, and
8357
- * anything else would make it immediately revoke a memory the user never turned
8358
- * off. They carry `source: SOURCE_PHOTO`, which is what exempts them from
8359
- * decay's auto-archive; see the note there for why archiving one would silently
8360
- * unpublish it.
8366
+ * The rows are written `scope: "shared"` because they genuinely ARE published.
8367
+ * `scope === 'shared'` is the publication axis — the single one. The reconciler
8368
+ * reads local scope as the user's intent, and it builds its `local` set from a
8369
+ * vault read narrowed to `{scopes: ['shared']}`. A row stamped anything else
8370
+ * never enters that read, so it is never adopted into the publish ledger, and
8371
+ * `toRevoke` is computed only over ids the ledger holds. The user's off-toggle
8372
+ * would then do nothing server-side while nearby kept serving the memory. The
8373
+ * `visibility` column is legacy: nothing reads it any more. They carry
8374
+ * `source: SOURCE_PHOTO`, which is what exempts them from decay's auto-archive;
8375
+ * see the note there for why archiving one would silently unpublish it.
8361
8376
  *
8362
8377
  * No embedding is computed here. `searchTool`'s un-embedded lane embeds any row
8363
8378
  * lacking a vector at search time, uses it in that same search and persists it
@@ -8405,6 +8420,29 @@ interface PhotoIngestResult {
8405
8420
  * class of bug as publishing a version the user has since changed. Re-ingesting
8406
8421
  * changed text is a follow-up that needs a merge decision, not a clobber.
8407
8422
  *
8423
+ * That skip is also why this op does NOT repair a row an older build stamped
8424
+ * `scope: "private"`. Such a row is present, so ingest leaves it alone, and it
8425
+ * stays outside the published-set read. The user's switch cannot revoke it.
8426
+ * Repair belongs in a one-time pass at upgrade. Do not add it here, for two
8427
+ * reasons:
8428
+ *
8429
+ * - **The two states are indistinguishable from this op.** After this build a
8430
+ * `photo:` row with `scope: "private"` is also exactly what the user's own
8431
+ * off-toggle produces. The toggle calls `updateVaultMemoryOp`, which writes
8432
+ * `scope` and never touches `visibility`, so the mis-stamped row and the
8433
+ * turned-off row carry identical columns. No test here can tell them apart.
8434
+ *
8435
+ * - **A re-stamp here would revert the off-toggle.** The pass runs ingest
8436
+ * BEFORE it reads the vault, so a row re-stamped `shared` re-enters the
8437
+ * desired set on that same pass and never reaches `toRevoke`. The switch
8438
+ * would spring back to "on" and nearby would keep serving the memory. That
8439
+ * is worse than the bug it repairs.
8440
+ *
8441
+ * A one-time pass does not have the ambiguity. Before this build the switch
8442
+ * already read "off" for these rows, because it reads `scope`, so no user can
8443
+ * have turned one off. Every `private` photo row at that instant came from the
8444
+ * old stamp.
8445
+ *
8408
8446
  * Non-`photo:` ids are ignored: everything else in the published set is a
8409
8447
  * client-published memory that by definition already lives in this vault (or in
8410
8448
  * another device's, which is not ours to recreate).
@@ -178,14 +178,14 @@ import {
178
178
  updateVaultMemoryOp,
179
179
  useEncryption,
180
180
  vaultMemoryToStored
181
- } from "./chunk-6IBFFWCE.mjs";
181
+ } from "./chunk-DNSPMVBY.mjs";
182
182
  import {
183
183
  PiiRedactor,
184
184
  createStreamingDeAnonymizer,
185
185
  isPiiRedactor,
186
186
  notifyConsolidationFallback,
187
187
  resolvePiiRedactor
188
- } from "./chunk-DY5VGC6K.mjs";
188
+ } from "./chunk-2XLHV2XW.mjs";
189
189
  import {
190
190
  INTERNAL_FLOW_MARKER,
191
191
  callPortalJsonCompletion,
@@ -199,7 +199,7 @@ import {
199
199
  taskTypeHeader,
200
200
  validateEndpointOverride,
201
201
  withInternalFlowMarker
202
- } from "./chunk-CJ32HO6Q.mjs";
202
+ } from "./chunk-O25LUKBK.mjs";
203
203
  import {
204
204
  BASE_URL,
205
205
  MCP_R2_DOMAIN,
@@ -6976,7 +6976,7 @@ async function ingestPublishedPhotoMemoriesOp(ctx, rows) {
6976
6976
  ({ row, content }) => ctx.vaultMemoryCollection.prepareCreate((record) => {
6977
6977
  record._raw.id = row.memoryId;
6978
6978
  record._setRaw("content", content);
6979
- record._setRaw("scope", "private");
6979
+ record._setRaw("scope", "shared");
6980
6980
  record._setRaw("folder_id", null);
6981
6981
  record._setRaw("user_id", ctx.userId ?? null);
6982
6982
  record._setRaw("is_deleted", false);
@@ -7686,7 +7686,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
7686
7686
  content: m.content,
7687
7687
  similarity: m.similarity
7688
7688
  }));
7689
- const { consolidateMemory: doConsolidate } = await import("./consolidate-5TP3J6BY.mjs");
7689
+ const { consolidateMemory: doConsolidate } = await import("./consolidate-LSDBMQDW.mjs");
7690
7690
  const decision = await doConsolidate(trimmed, candidates, consolidateOptions);
7691
7691
  if (decision.action === "create") return null;
7692
7692
  if (decision.action === "supersede" && decision.content) {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  recall
3
- } from "./chunk-6IBFFWCE.mjs";
4
- import "./chunk-CJ32HO6Q.mjs";
3
+ } from "./chunk-DNSPMVBY.mjs";
4
+ import "./chunk-O25LUKBK.mjs";
5
5
  import "./chunk-GYKJOWEO.mjs";
6
6
  export {
7
7
  recall
@@ -52,6 +52,7 @@ function defaultBaseUrl() {
52
52
  }
53
53
  var RESPONSE_FORMAT_OK = /* @__PURE__ */ new Set(["openai", "inclusionai", "deepseek"]);
54
54
  var RESPONSE_SCHEMA_OK = /* @__PURE__ */ new Set(["openai"]);
55
+ var JSON_CONTRACT_REMINDER = "Output ONLY the strict JSON object requested. No prose, no explanation, no markdown.";
55
56
  function supportsResponseFormat(model, variant = "json_object") {
56
57
  const allow = variant === "json_schema" ? RESPONSE_SCHEMA_OK : RESPONSE_FORMAT_OK;
57
58
  return model.split("/").some((seg) => allow.has(seg));
@@ -109,6 +110,7 @@ async function callPortalJsonCompletion(req) {
109
110
  const startedAt = Date.now();
110
111
  const overBudget = () => req.totalTimeoutMs !== void 0 && Date.now() - startedAt >= req.totalTimeoutMs;
111
112
  let lastFailure;
113
+ let reinforce = false;
112
114
  const giveUp = () => {
113
115
  req.onFailure?.(lastFailure ?? { reason: "network", attempts: maxAttempts });
114
116
  return null;
@@ -124,8 +126,9 @@ async function callPortalJsonCompletion(req) {
124
126
  timeoutMs: Math.max(
125
127
  1,
126
128
  Math.min(req.timeoutMs ?? 6e4, req.totalTimeoutMs - (Date.now() - startedAt))
127
- )
128
- } : req;
129
+ ),
130
+ ...reinforce && { reinforceJsonContract: true }
131
+ } : reinforce ? { ...req, reinforceJsonContract: true } : req;
129
132
  const outcome = await attemptPortalJson(attemptReq, endpoint);
130
133
  if (outcome.kind === "ok") return outcome.value;
131
134
  lastFailure = {
@@ -133,6 +136,7 @@ async function callPortalJsonCompletion(req) {
133
136
  ...outcome.httpStatus !== void 0 && { httpStatus: outcome.httpStatus },
134
137
  attempts: attempt
135
138
  };
139
+ if (outcome.code === "invalid-json" || outcome.code === "null-completion") reinforce = true;
136
140
  if (outcome.kind === "terminal") {
137
141
  log.warn(`[${req.tag}] ${outcome.reason}`);
138
142
  return giveUp();
@@ -171,6 +175,11 @@ async function attemptPortalJson(req, endpoint) {
171
175
  // keeps the portal's detector reading them as genuine rather than markerless.
172
176
  // See ../internalFlowMarker.
173
177
  { role: "system", content: withInternalFlowMarker(req.systemPrompt) },
178
+ // Retry-only. Kept as a SEPARATE message rather than appended to the system
179
+ // prompt so the marked prompt above stays byte-identical across attempts —
180
+ // the portal's internal-flow detector reads that one, and rewriting it on a
181
+ // retry would change what the detector sees for reasons unrelated to auth.
182
+ ...req.reinforceJsonContract ? [{ role: "system", content: JSON_CONTRACT_REMINDER }] : [],
174
183
  { role: "user", content: req.userMessage }
175
184
  ];
176
185
  if (prefill) messages.push({ role: "assistant", content: prefill });
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getLogger
3
- } from "./chunk-KF5OU4IN.mjs";
3
+ } from "./chunk-7D3RI25K.mjs";
4
4
 
5
5
  // src/lib/pii/patterns.ts
6
6
  function luhnCheck(digits) {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  callPortalJsonCompletion,
3
3
  getLogger
4
- } from "./chunk-KF5OU4IN.mjs";
4
+ } from "./chunk-7D3RI25K.mjs";
5
5
  import {
6
6
  BASE_URL,
7
7
  __commonJS,
@@ -20923,7 +20923,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
20923
20923
  subQueries = decomp.subQueries;
20924
20924
  }
20925
20925
  }
20926
- const { recall: recall2 } = await import("./recall-HOVWMT4Q.mjs");
20926
+ const { recall: recall2 } = await import("./recall-VHU372MK.mjs");
20927
20927
  let recallDegraded = [];
20928
20928
  const result = await recall2(
20929
20929
  query,
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  notifyConsolidationFallback,
3
3
  resolvePiiRedactor
4
- } from "./chunk-MEQ2PAEX.mjs";
4
+ } from "./chunk-GP6YY6H5.mjs";
5
5
  import {
6
6
  callPortalJsonCompletion
7
- } from "./chunk-KF5OU4IN.mjs";
7
+ } from "./chunk-7D3RI25K.mjs";
8
8
  import "./chunk-3RPGXXPZ.mjs";
9
9
 
10
10
  // src/lib/memory/consolidate.ts
@@ -20663,6 +20663,7 @@ async function callPortalJsonCompletion(req) {
20663
20663
  const startedAt = Date.now();
20664
20664
  const overBudget = () => req.totalTimeoutMs !== void 0 && Date.now() - startedAt >= req.totalTimeoutMs;
20665
20665
  let lastFailure;
20666
+ let reinforce = false;
20666
20667
  const giveUp = () => {
20667
20668
  req.onFailure?.(lastFailure ?? { reason: "network", attempts: maxAttempts });
20668
20669
  return null;
@@ -20678,8 +20679,9 @@ async function callPortalJsonCompletion(req) {
20678
20679
  timeoutMs: Math.max(
20679
20680
  1,
20680
20681
  Math.min(req.timeoutMs ?? 6e4, req.totalTimeoutMs - (Date.now() - startedAt))
20681
- )
20682
- } : req;
20682
+ ),
20683
+ ...reinforce && { reinforceJsonContract: true }
20684
+ } : reinforce ? { ...req, reinforceJsonContract: true } : req;
20683
20685
  const outcome = await attemptPortalJson(attemptReq, endpoint);
20684
20686
  if (outcome.kind === "ok") return outcome.value;
20685
20687
  lastFailure = {
@@ -20687,6 +20689,7 @@ async function callPortalJsonCompletion(req) {
20687
20689
  ...outcome.httpStatus !== void 0 && { httpStatus: outcome.httpStatus },
20688
20690
  attempts: attempt
20689
20691
  };
20692
+ if (outcome.code === "invalid-json" || outcome.code === "null-completion") reinforce = true;
20690
20693
  if (outcome.kind === "terminal") {
20691
20694
  log.warn(`[${req.tag}] ${outcome.reason}`);
20692
20695
  return giveUp();
@@ -20725,6 +20728,11 @@ async function attemptPortalJson(req, endpoint) {
20725
20728
  // keeps the portal's detector reading them as genuine rather than markerless.
20726
20729
  // See ../internalFlowMarker.
20727
20730
  { role: "system", content: withInternalFlowMarker(req.systemPrompt) },
20731
+ // Retry-only. Kept as a SEPARATE message rather than appended to the system
20732
+ // prompt so the marked prompt above stays byte-identical across attempts —
20733
+ // the portal's internal-flow detector reads that one, and rewriting it on a
20734
+ // retry would change what the detector sees for reasons unrelated to auth.
20735
+ ...req.reinforceJsonContract ? [{ role: "system", content: JSON_CONTRACT_REMINDER }] : [],
20728
20736
  { role: "user", content: req.userMessage }
20729
20737
  ];
20730
20738
  if (prefill) messages.push({ role: "assistant", content: prefill });
@@ -20887,7 +20895,7 @@ function extractCompletionContent(body) {
20887
20895
  const content = first?.message?.content;
20888
20896
  return typeof content === "string" ? content : null;
20889
20897
  }
20890
- var RESPONSE_FORMAT_OK, RESPONSE_SCHEMA_OK, RETRYABLE_HTTP;
20898
+ var RESPONSE_FORMAT_OK, RESPONSE_SCHEMA_OK, JSON_CONTRACT_REMINDER, RETRYABLE_HTTP;
20891
20899
  var init_portalLlm = __esm({
20892
20900
  "src/lib/memory/portalLlm.ts"() {
20893
20901
  "use strict";
@@ -20898,6 +20906,7 @@ var init_portalLlm = __esm({
20898
20906
  init_taskType();
20899
20907
  RESPONSE_FORMAT_OK = /* @__PURE__ */ new Set(["openai", "inclusionai", "deepseek"]);
20900
20908
  RESPONSE_SCHEMA_OK = /* @__PURE__ */ new Set(["openai"]);
20909
+ JSON_CONTRACT_REMINDER = "Output ONLY the strict JSON object requested. No prose, no explanation, no markdown.";
20901
20910
  RETRYABLE_HTTP = /* @__PURE__ */ new Set([408, 409, 425, 429]);
20902
20911
  }
20903
20912
  });
@@ -27417,7 +27426,7 @@ async function ingestPublishedPhotoMemoriesOp(ctx, rows) {
27417
27426
  ({ row, content }) => ctx.vaultMemoryCollection.prepareCreate((record) => {
27418
27427
  record._raw.id = row.memoryId;
27419
27428
  record._setRaw("content", content);
27420
- record._setRaw("scope", "private");
27429
+ record._setRaw("scope", "shared");
27421
27430
  record._setRaw("folder_id", null);
27422
27431
  record._setRaw("user_id", ctx.userId ?? null);
27423
27432
  record._setRaw("is_deleted", false);
@@ -6253,15 +6253,26 @@ declare function extractAndRetain(messages: AutoExtractMessage[], retainCtx: Ret
6253
6253
  }>;
6254
6254
 
6255
6255
  /**
6256
- * People Nearby cross-user visibility. ORTHOGONAL to `scope` (model access):
6257
- * - `private`: local-only (default; null column grandfathered as private)
6258
- * - `public`: embedding + plaintext may be published (compatibility matching,
6259
- * profile display, discovery answers, digital-twin prompts)
6256
+ * LEGACY. This column no longer decides anything.
6257
+ *
6258
+ * `scope === 'shared'` is now the single publication axis: it means the memory
6259
+ * is on the People Nearby profile, its text goes to the nearby server, and it
6260
+ * may inform what strangers see. `scope: 'private'` means it is not. There is
6261
+ * no second control, and scope no longer gates model access either — every
6262
+ * model tier reads every memory. Do NOT reason about publication from this
6263
+ * type; read `scope`.
6264
+ *
6265
+ * The column survives in the schema and is still stamped on writes so that
6266
+ * stored rows stay readable by builds that predate the change. Nothing on a
6267
+ * current build reads it to make a decision.
6268
+ *
6269
+ * Values, kept for tolerance of already-stored data:
6270
+ * - `private`: local-only (a null column reads as private)
6271
+ * - `public`: the row was published
6260
6272
  *
6261
6273
  * TWO TIERS ONLY (decided 2026-07-27). An earlier design had a middle
6262
6274
  * `matchable` tier that published a memory's embedding while keeping its text
6263
- * on-device; it was dropped, so a memory either never leaves the device or is
6264
- * published with its content. Any unrecognised stored value — including a
6275
+ * on-device; it was dropped. Any unrecognised stored value — including a
6265
6276
  * `matchable` row written by a pre-release build — reads as `private`, which is
6266
6277
  * the fail-safe direction: it un-publishes rather than exposes.
6267
6278
  */
@@ -6853,12 +6864,16 @@ declare function updateVaultMemoryEmbeddingOp(ctx: VaultMemoryOperationsContext,
6853
6864
  * candidate to be reconciled against the vault — it is an already-decided
6854
6865
  * fact the server has published.
6855
6866
  *
6856
- * The rows are written `visibility: "public"` because they genuinely ARE
6857
- * published — the reconciler reads local visibility as the user's intent, and
6858
- * anything else would make it immediately revoke a memory the user never turned
6859
- * off. They carry `source: SOURCE_PHOTO`, which is what exempts them from
6860
- * decay's auto-archive; see the note there for why archiving one would silently
6861
- * unpublish it.
6867
+ * The rows are written `scope: "shared"` because they genuinely ARE published.
6868
+ * `scope === 'shared'` is the publication axis — the single one. The reconciler
6869
+ * reads local scope as the user's intent, and it builds its `local` set from a
6870
+ * vault read narrowed to `{scopes: ['shared']}`. A row stamped anything else
6871
+ * never enters that read, so it is never adopted into the publish ledger, and
6872
+ * `toRevoke` is computed only over ids the ledger holds. The user's off-toggle
6873
+ * would then do nothing server-side while nearby kept serving the memory. The
6874
+ * `visibility` column is legacy: nothing reads it any more. They carry
6875
+ * `source: SOURCE_PHOTO`, which is what exempts them from decay's auto-archive;
6876
+ * see the note there for why archiving one would silently unpublish it.
6862
6877
  *
6863
6878
  * No embedding is computed here. `searchTool`'s un-embedded lane embeds any row
6864
6879
  * lacking a vector at search time, uses it in that same search and persists it
@@ -6906,6 +6921,29 @@ interface PhotoIngestResult {
6906
6921
  * class of bug as publishing a version the user has since changed. Re-ingesting
6907
6922
  * changed text is a follow-up that needs a merge decision, not a clobber.
6908
6923
  *
6924
+ * That skip is also why this op does NOT repair a row an older build stamped
6925
+ * `scope: "private"`. Such a row is present, so ingest leaves it alone, and it
6926
+ * stays outside the published-set read. The user's switch cannot revoke it.
6927
+ * Repair belongs in a one-time pass at upgrade. Do not add it here, for two
6928
+ * reasons:
6929
+ *
6930
+ * - **The two states are indistinguishable from this op.** After this build a
6931
+ * `photo:` row with `scope: "private"` is also exactly what the user's own
6932
+ * off-toggle produces. The toggle calls `updateVaultMemoryOp`, which writes
6933
+ * `scope` and never touches `visibility`, so the mis-stamped row and the
6934
+ * turned-off row carry identical columns. No test here can tell them apart.
6935
+ *
6936
+ * - **A re-stamp here would revert the off-toggle.** The pass runs ingest
6937
+ * BEFORE it reads the vault, so a row re-stamped `shared` re-enters the
6938
+ * desired set on that same pass and never reaches `toRevoke`. The switch
6939
+ * would spring back to "on" and nearby would keep serving the memory. That
6940
+ * is worse than the bug it repairs.
6941
+ *
6942
+ * A one-time pass does not have the ambiguity. Before this build the switch
6943
+ * already read "off" for these rows, because it reads `scope`, so no user can
6944
+ * have turned one off. Every `private` photo row at that instant came from the
6945
+ * old stamp.
6946
+ *
6909
6947
  * Non-`photo:` ids are ignored: everything else in the published set is a
6910
6948
  * client-published memory that by definition already lives in this vault (or in
6911
6949
  * another device's, which is not ours to recreate).
@@ -6253,15 +6253,26 @@ declare function extractAndRetain(messages: AutoExtractMessage[], retainCtx: Ret
6253
6253
  }>;
6254
6254
 
6255
6255
  /**
6256
- * People Nearby cross-user visibility. ORTHOGONAL to `scope` (model access):
6257
- * - `private`: local-only (default; null column grandfathered as private)
6258
- * - `public`: embedding + plaintext may be published (compatibility matching,
6259
- * profile display, discovery answers, digital-twin prompts)
6256
+ * LEGACY. This column no longer decides anything.
6257
+ *
6258
+ * `scope === 'shared'` is now the single publication axis: it means the memory
6259
+ * is on the People Nearby profile, its text goes to the nearby server, and it
6260
+ * may inform what strangers see. `scope: 'private'` means it is not. There is
6261
+ * no second control, and scope no longer gates model access either — every
6262
+ * model tier reads every memory. Do NOT reason about publication from this
6263
+ * type; read `scope`.
6264
+ *
6265
+ * The column survives in the schema and is still stamped on writes so that
6266
+ * stored rows stay readable by builds that predate the change. Nothing on a
6267
+ * current build reads it to make a decision.
6268
+ *
6269
+ * Values, kept for tolerance of already-stored data:
6270
+ * - `private`: local-only (a null column reads as private)
6271
+ * - `public`: the row was published
6260
6272
  *
6261
6273
  * TWO TIERS ONLY (decided 2026-07-27). An earlier design had a middle
6262
6274
  * `matchable` tier that published a memory's embedding while keeping its text
6263
- * on-device; it was dropped, so a memory either never leaves the device or is
6264
- * published with its content. Any unrecognised stored value — including a
6275
+ * on-device; it was dropped. Any unrecognised stored value — including a
6265
6276
  * `matchable` row written by a pre-release build — reads as `private`, which is
6266
6277
  * the fail-safe direction: it un-publishes rather than exposes.
6267
6278
  */
@@ -6853,12 +6864,16 @@ declare function updateVaultMemoryEmbeddingOp(ctx: VaultMemoryOperationsContext,
6853
6864
  * candidate to be reconciled against the vault — it is an already-decided
6854
6865
  * fact the server has published.
6855
6866
  *
6856
- * The rows are written `visibility: "public"` because they genuinely ARE
6857
- * published — the reconciler reads local visibility as the user's intent, and
6858
- * anything else would make it immediately revoke a memory the user never turned
6859
- * off. They carry `source: SOURCE_PHOTO`, which is what exempts them from
6860
- * decay's auto-archive; see the note there for why archiving one would silently
6861
- * unpublish it.
6867
+ * The rows are written `scope: "shared"` because they genuinely ARE published.
6868
+ * `scope === 'shared'` is the publication axis — the single one. The reconciler
6869
+ * reads local scope as the user's intent, and it builds its `local` set from a
6870
+ * vault read narrowed to `{scopes: ['shared']}`. A row stamped anything else
6871
+ * never enters that read, so it is never adopted into the publish ledger, and
6872
+ * `toRevoke` is computed only over ids the ledger holds. The user's off-toggle
6873
+ * would then do nothing server-side while nearby kept serving the memory. The
6874
+ * `visibility` column is legacy: nothing reads it any more. They carry
6875
+ * `source: SOURCE_PHOTO`, which is what exempts them from decay's auto-archive;
6876
+ * see the note there for why archiving one would silently unpublish it.
6862
6877
  *
6863
6878
  * No embedding is computed here. `searchTool`'s un-embedded lane embeds any row
6864
6879
  * lacking a vector at search time, uses it in that same search and persists it
@@ -6906,6 +6921,29 @@ interface PhotoIngestResult {
6906
6921
  * class of bug as publishing a version the user has since changed. Re-ingesting
6907
6922
  * changed text is a follow-up that needs a merge decision, not a clobber.
6908
6923
  *
6924
+ * That skip is also why this op does NOT repair a row an older build stamped
6925
+ * `scope: "private"`. Such a row is present, so ingest leaves it alone, and it
6926
+ * stays outside the published-set read. The user's switch cannot revoke it.
6927
+ * Repair belongs in a one-time pass at upgrade. Do not add it here, for two
6928
+ * reasons:
6929
+ *
6930
+ * - **The two states are indistinguishable from this op.** After this build a
6931
+ * `photo:` row with `scope: "private"` is also exactly what the user's own
6932
+ * off-toggle produces. The toggle calls `updateVaultMemoryOp`, which writes
6933
+ * `scope` and never touches `visibility`, so the mis-stamped row and the
6934
+ * turned-off row carry identical columns. No test here can tell them apart.
6935
+ *
6936
+ * - **A re-stamp here would revert the off-toggle.** The pass runs ingest
6937
+ * BEFORE it reads the vault, so a row re-stamped `shared` re-enters the
6938
+ * desired set on that same pass and never reaches `toRevoke`. The switch
6939
+ * would spring back to "on" and nearby would keep serving the memory. That
6940
+ * is worse than the bug it repairs.
6941
+ *
6942
+ * A one-time pass does not have the ambiguity. Before this build the switch
6943
+ * already read "off" for these rows, because it reads `scope`, so no user can
6944
+ * have turned one off. Every `private` photo row at that instant came from the
6945
+ * old stamp.
6946
+ *
6909
6947
  * Non-`photo:` ids are ignored: everything else in the published set is a
6910
6948
  * client-published memory that by definition already lives in this vault (or in
6911
6949
  * another device's, which is not ours to recreate).
@@ -141,12 +141,12 @@ import {
141
141
  updateVaultMemoryOp,
142
142
  upsertMessageOp,
143
143
  vaultMemoryToStored
144
- } from "./chunk-7YTEE6JT.mjs";
144
+ } from "./chunk-O7TB36P3.mjs";
145
145
  import {
146
146
  createStreamingDeAnonymizer,
147
147
  notifyConsolidationFallback,
148
148
  resolvePiiRedactor
149
- } from "./chunk-MEQ2PAEX.mjs";
149
+ } from "./chunk-GP6YY6H5.mjs";
150
150
  import {
151
151
  INTERNAL_FLOW_MARKER,
152
152
  callPortalJsonCompletion,
@@ -157,7 +157,7 @@ import {
157
157
  taskTypeHeader,
158
158
  validateEndpointOverride,
159
159
  withInternalFlowMarker
160
- } from "./chunk-KF5OU4IN.mjs";
160
+ } from "./chunk-7D3RI25K.mjs";
161
161
  import {
162
162
  BASE_URL,
163
163
  __commonJS,
@@ -3736,7 +3736,7 @@ async function ingestPublishedPhotoMemoriesOp(ctx, rows) {
3736
3736
  ({ row, content }) => ctx.vaultMemoryCollection.prepareCreate((record) => {
3737
3737
  record._raw.id = row.memoryId;
3738
3738
  record._setRaw("content", content);
3739
- record._setRaw("scope", "private");
3739
+ record._setRaw("scope", "shared");
3740
3740
  record._setRaw("folder_id", null);
3741
3741
  record._setRaw("user_id", ctx.userId ?? null);
3742
3742
  record._setRaw("is_deleted", false);
@@ -5700,7 +5700,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
5700
5700
  content: m.content,
5701
5701
  similarity: m.similarity
5702
5702
  }));
5703
- const { consolidateMemory: doConsolidate } = await import("./consolidate-QDWEYFTZ.mjs");
5703
+ const { consolidateMemory: doConsolidate } = await import("./consolidate-Z2JJP7LW.mjs");
5704
5704
  const decision = await doConsolidate(trimmed, candidates, consolidateOptions);
5705
5705
  if (decision.action === "create") return null;
5706
5706
  if (decision.action === "supersede" && decision.content) {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  recall
3
- } from "./chunk-7YTEE6JT.mjs";
4
- import "./chunk-KF5OU4IN.mjs";
3
+ } from "./chunk-O7TB36P3.mjs";
4
+ import "./chunk-7D3RI25K.mjs";
5
5
  import "./chunk-3RPGXXPZ.mjs";
6
6
  export {
7
7
  recall
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anuma/sdk",
3
- "version": "6.14.0",
3
+ "version": "6.14.2",
4
4
  "description": "",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",