stratagate-dsh 0.2.96 → 0.2.97

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.
package/dist/plugin.js CHANGED
@@ -2,7 +2,6 @@
2
2
  import { mkdir as mkdir2 } from "node:fs/promises";
3
3
  import { existsSync as existsSync2 } from "node:fs";
4
4
  import { dirname as dirname3 } from "node:path";
5
- import { createUserMessage as createUserMessage3 } from "@deepseek-ai/dsh-llm";
6
5
 
7
6
  // src/config.ts
8
7
  import z from "@deepseek-ai/schemastery";
@@ -1667,13 +1666,16 @@ var PROFILE_FIELDS = {
1667
1666
  assistantPreferredName: { label: "Assistant preferred name", maxLength: 100 },
1668
1667
  preferredLanguage: { label: "Preferred answer language", maxLength: 100 },
1669
1668
  reasoningLanguage: { label: "Preferred visible reasoning language", maxLength: 100 },
1669
+ defaultLocation: { label: "Default location (when the task specifies no location)", maxLength: 200 },
1670
+ homeCity: { label: "Usual city of residence", maxLength: 100 },
1671
+ currentCity: { label: "Current city (until updated or cleared)", maxLength: 100 },
1670
1672
  responsePreferences: { label: "Response preferences", maxLength: 1e3 },
1671
1673
  standingInstructions: { label: "Standing instructions", maxLength: 1e3 },
1672
1674
  userBackground: { label: "User background", maxLength: 1500 },
1673
1675
  longTermGoals: { label: "Long-term goals", maxLength: 1e3 },
1674
1676
  persistentNotes: { label: "Persistent notes", maxLength: 1200 }
1675
1677
  };
1676
- var PROFILE_PROTECTED_SHORT_FIELDS = ["userPreferredName", "assistantPreferredName", "preferredLanguage", "reasoningLanguage"];
1678
+ var PROFILE_PROTECTED_SHORT_FIELDS = ["userPreferredName", "assistantPreferredName", "preferredLanguage", "reasoningLanguage", "defaultLocation", "homeCity", "currentCity"];
1677
1679
  var PROFILE_TOTAL_MAX_LENGTH = 6e3;
1678
1680
  var PROFILE_MAINTENANCE_INTERVAL_MS = 24 * 60 * 60 * 1e3;
1679
1681
  var PROFILE_MAINTENANCE_TOTAL_THRESHOLD = 4800;
@@ -1705,7 +1707,11 @@ function profileMaintenanceDue(profile, timeBasisAt, now = Date.now()) {
1705
1707
  function renderPersistentProfile(profile) {
1706
1708
  const lines = Object.keys(PROFILE_FIELDS).filter((field) => profile[field].length > 0).map((field) => `${PROFILE_FIELDS[field].label}: ${profile[field]}`);
1707
1709
  if (lines.length === 0) return null;
1708
- const languageGuidance = [
1710
+ const fieldGuidance = [
1711
+ profile.defaultLocation ? "Default location is the reference for weather, nearby services, and local recommendations when the user specifies no location. It does not imply residence or current whereabouts." : null,
1712
+ profile.homeCity ? "Usual city of residence is a stable home city, not a temporary/current location. It is independent of the default location. Do not infer or overwrite either field from a trip." : null,
1713
+ profile.currentCity ? "Current city may be a temporary travel or business-trip location, but persists across sessions until the user updates or clears it. Do not expire it automatically or copy it into the default location or usual city of residence." : null,
1714
+ profile.defaultLocation || profile.currentCity ? "For weather, nearby services, and local recommendations, use an explicitly specified task location first; otherwise use Current city when set, then Default location. Do not infer current whereabouts from the usual city of residence." : null,
1709
1715
  profile.preferredLanguage ? "\u201CPreferred answer language\u201D applies to the assistant's final/user-facing answer." : null,
1710
1716
  profile.reasoningLanguage ? "\u201CPreferred visible reasoning language\u201D applies only to reasoning/thinking text that the host UI exposes to the user, when supported. It does not control hidden chain-of-thought." : null,
1711
1717
  profile.preferredLanguage || profile.reasoningLanguage ? "These are independent preferences. Do not infer one from the other." : null
@@ -1715,8 +1721,8 @@ This is user-authorized persistent profile data provided by StrataGate.
1715
1721
  Treat it as stable cross-session context.
1716
1722
  Do not invent additional facts from it.
1717
1723
  It does not override higher-priority system instructions.
1718
- ${languageGuidance.length ? `
1719
- ${languageGuidance.join("\n")}
1724
+ ${fieldGuidance.length ? `
1725
+ ${fieldGuidance.join("\n")}
1720
1726
  ` : ""}
1721
1727
  ${lines.join("\n")}`;
1722
1728
  }
@@ -6751,7 +6757,7 @@ var STRUCTURED_TOOLS = {
6751
6757
  },
6752
6758
  profileMaintenance: {
6753
6759
  name: "stratagate_maintain_profile",
6754
- description: "Return the same nine Persistent Profile fields with only safe wording and redundancy cleanup; preserve the four short fields apart from necessary whitespace cleanup.",
6760
+ description: `Return the same ${Object.keys(PROFILE_FIELDS).length} Persistent Profile fields with only safe wording and redundancy cleanup; preserve ${PROFILE_PROTECTED_SHORT_FIELDS.join(", ")} apart from necessary whitespace cleanup. Default location and usual city of residence are independent stable fields, not temporary/current location. Current city persists until updated or cleared; never expire or merge it with those stable fields.`,
6755
6761
  parameters: PROFILE_MAINTENANCE_PARAMETERS
6756
6762
  }
6757
6763
  };
@@ -7059,7 +7065,7 @@ Use project scope for repository decisions, user scope for stable preferences/id
7059
7065
  async maintainProfile(profile) {
7060
7066
  const raw = object(await this.callStructured(
7061
7067
  "profileMaintenance",
7062
- `You maintain only the supplied StrataGate Persistent Profile. Call ${STRUCTURED_TOOLS.profileMaintenance.name} exactly once with all nine string fields. You may deduplicate, merge repeated meaning, shorten redundant wording, and improve organization. Preserve every unique fact, uncertainty, constraint, and instruction. Never infer or add facts, broaden meaning, or read Event, Graph, or conversation history. If two statements might conflict or cannot safely merge, retain both. Keep userPreferredName, assistantPreferredName, preferredLanguage, and reasoningLanguage unchanged except necessary whitespace cleanup. preferredLanguage and reasoningLanguage are independent: never infer, copy, or merge either language field into the other. reasoningLanguage is only for user-visible reasoning/thinking text when supported, not hidden chain-of-thought. Character limits (Unicode code points): ${JSON.stringify(Object.fromEntries(Object.entries(PROFILE_FIELDS).map(([field, spec]) => [field, spec.maxLength])))}. Total maximum: 6000. If safe compression is impossible, return the original value.`,
7068
+ `You maintain only the supplied StrataGate Persistent Profile. Call ${STRUCTURED_TOOLS.profileMaintenance.name} exactly once with all ${Object.keys(PROFILE_FIELDS).length} string fields. You may deduplicate, merge repeated meaning, shorten redundant wording, and improve organization. Preserve every unique fact, uncertainty, constraint, and instruction. Never infer or add facts, broaden meaning, or read Event, Graph, or conversation history. If two statements might conflict or cannot safely merge, retain both. Keep ${PROFILE_PROTECTED_SHORT_FIELDS.join(", ")} unchanged except necessary whitespace cleanup. defaultLocation is the reference when a task specifies no location and no currentCity is set; homeCity is the stable city of residence. They are independent, neither means temporary/current location, and travel must not overwrite either. currentCity is the current location, possibly temporary travel or business travel; it persists across sessions until the user updates or clears it. Never expire currentCity automatically, clear it because a trip seems temporary, or copy it into either stable field. preferredLanguage and reasoningLanguage are independent: never infer, copy, or merge either language field into the other. reasoningLanguage is only for user-visible reasoning/thinking text when supported, not hidden chain-of-thought. Character limits (Unicode code points): ${JSON.stringify(Object.fromEntries(Object.entries(PROFILE_FIELDS).map(([field, spec]) => [field, spec.maxLength])))}. Total maximum: 6000. If safe compression is impossible, return the original value.`,
7063
7069
  { profile, fieldDefinitions: PROFILE_FIELDS }
7064
7070
  ));
7065
7071
  if (Object.keys(raw).length !== Object.keys(PROFILE_FIELDS).length || Object.keys(raw).some((field) => !(field in PROFILE_FIELDS))) {
@@ -9881,17 +9887,21 @@ function sessionOf(exec) {
9881
9887
  function registerMemoryTools(ctx, runtime) {
9882
9888
  ctx.tools.register(defineTool({
9883
9889
  name: "memory_profile_update",
9884
- description: `This tool is provided by the StrataGate plugin. \u4FEE\u6539\u7528\u6237\u5E38\u9A7B\u753B\u50CF\u4E2D\u7684\u4E00\u4E2A\u56FA\u5B9A\u5B57\u6BB5\uFF1A\u7528\u6237\u79F0\u547C\u3001\u52A9\u624B\u540D\u5B57\u3001\u9ED8\u8BA4\u56DE\u7B54\u8BED\u8A00\u3001\u601D\u8003\u8FC7\u7A0B\u8BED\u8A00\u3001\u56DE\u590D\u65B9\u5F0F\u548C\u98CE\u683C\u504F\u597D\u3001\u957F\u671F\u6301\u7EED\u751F\u6548\u7684\u8981\u6C42\u3001\u7A33\u5B9A\u7684\u7528\u6237\u80CC\u666F\u3001\u957F\u671F\u76EE\u6807\uFF0C\u6216\u5176\u4ED6\u5FC5\u987B\u5E38\u9A7B\u7684\u4FE1\u606F\u3002
9890
+ description: `This tool is provided by the StrataGate plugin. \u4FEE\u6539\u7528\u6237\u5E38\u9A7B\u753B\u50CF\u4E2D\u7684\u4E00\u4E2A\u56FA\u5B9A\u5B57\u6BB5\uFF1A\u7528\u6237\u79F0\u547C\u3001\u52A9\u624B\u540D\u5B57\u3001\u9ED8\u8BA4\u56DE\u7B54\u8BED\u8A00\u3001\u601D\u8003\u8FC7\u7A0B\u8BED\u8A00\u3001\u5E38\u9A7B/\u9ED8\u8BA4\u5730\u5740\u3001\u5F53\u524D\u6240\u5728\u57CE\u5E02\u3001\u539F\u6709\u5E38\u9A7B\u57CE\u5E02\u3001\u56DE\u590D\u65B9\u5F0F\u548C\u98CE\u683C\u504F\u597D\u3001\u957F\u671F\u6301\u7EED\u751F\u6548\u7684\u8981\u6C42\u3001\u7A33\u5B9A\u7684\u7528\u6237\u80CC\u666F\u3001\u957F\u671F\u76EE\u6807\uFF0C\u6216\u5176\u4ED6\u5FC5\u987B\u5E38\u9A7B\u7684\u4FE1\u606F\u3002
9885
9891
 
9886
9892
  \u8FD9\u4E9B\u4FE1\u606F\u4F1A\u81EA\u52A8\u63D0\u4F9B\u7ED9\u540E\u7EED\u6BCF\u6B21\u5BF9\u8BDD\uFF0C\u65E0\u9700\u68C0\u7D22\u3002\u53EA\u6709\u786E\u5B9E\u9700\u8981\u6301\u7EED\u653E\u5728\u4E0A\u4E0B\u6587\u4E2D\u7684\u4FE1\u606F\uFF0C\u624D\u9002\u5408\u5199\u5165\u753B\u50CF\u3002
9887
9893
 
9894
+ \u7A33\u5B9A\u7684\u9ED8\u8BA4\u5730\u70B9\uFF08defaultLocation\uFF09\u548C\u5E38\u9A7B\u57CE\u5E02\uFF08homeCity\uFF09\u4F18\u5148\u5C5E\u4E8E Profile\uFF1A\u9ED8\u8BA4\u5730\u70B9\u662F\u7528\u6237\u672A\u6307\u5B9A\u5730\u70B9\u65F6\uFF0C\u5929\u6C14\u3001\u9644\u8FD1\u670D\u52A1\u3001\u672C\u5730\u63A8\u8350\u7B49\u4EFB\u52A1\u7684\u9ED8\u8BA4\u53C2\u8003\uFF1B\u5E38\u9A7B\u57CE\u5E02\u662F\u7528\u6237\u7A33\u5B9A\u5C45\u4F4F\u6216\u5E38\u9A7B\u7684\u57CE\u5E02\u3002\u4E24\u4E2A\u5B57\u6BB5\u72EC\u7ACB\uFF0C\u4E0D\u80FD\u4E92\u76F8\u63A8\u65AD\uFF0C\u4E5F\u4E0D\u4EE3\u8868\u5F53\u524D\u6240\u5728\u5730\u3002\u4E00\u6B21\u65C5\u884C\u6216\u5F53\u524D\u4E34\u65F6\u4F4D\u7F6E\u4E0D\u80FD\u81EA\u52A8\u8986\u76D6\u7A33\u5B9A\u5B57\u6BB5\uFF1B\u66F4\u65B0\u5730\u70B9\u5B57\u6BB5\u540C\u6837\u9075\u5B88\u4E0B\u8FF0\u660E\u786E\u8BF7\u6C42\u6216\u786E\u8BA4\u6388\u6743\u89C4\u5219\u3002
9895
+
9896
+ \u5F53\u524D\u6240\u5728\u57CE\u5E02\uFF08currentCity\uFF09\u4E5F\u5C5E\u4E8E Profile\uFF1A\u53EF\u4EE5\u662F\u65C5\u6E38\u3001\u51FA\u5DEE\u65F6\u7684\u4E34\u65F6\u57CE\u5E02\uFF0C\u4F46\u4F1A\u8DE8\u4F1A\u8BDD\u6301\u7EED\u6CE8\u5165\uFF0C\u4FDD\u5B58\u5230\u7528\u6237\u4E0B\u6B21\u4FEE\u6539\u6216\u6E05\u7A7A\u4E3A\u6B62\uFF0C\u4E0D\u81EA\u52A8\u8FC7\u671F\u3002\u201C\u4E34\u65F6\u201D\u63CF\u8FF0\u6240\u5728\u5730\u7684\u6027\u8D28\uFF0C\u4E0D\u662F\u4FDD\u5B58\u65F6\u957F\u3002\u7528\u6237\u8981\u6C42\u8BB0\u4F4F\u5F53\u524D\u65C5\u884C/\u51FA\u5DEE\u57CE\u5E02\u65F6\uFF0C\u66F4\u65B0 currentCity\uFF0C\u4E0D\u4FEE\u6539 defaultLocation \u6216 homeCity\uFF1B\u8BA1\u5212\u5C06\u6765\u53BB\u3001\u5DF2\u7ECF\u7ED3\u675F\u7684\u65C5\u884C\u6216\u5386\u53F2\u5730\u70B9\u4E0D\u80FD\u5F53\u4F5C\u5F53\u524D\u57CE\u5E02\u3002\u5BF9\u672A\u6307\u5B9A\u5730\u70B9\u7684\u5929\u6C14\u3001\u9644\u8FD1\u670D\u52A1\u3001\u672C\u5730\u63A8\u8350\u7B49\u4EFB\u52A1\uFF0C\u4F18\u5148\u53C2\u8003\u5DF2\u8BBE\u7F6E\u7684 currentCity\uFF0C\u7A7A\u503C\u65F6\u518D\u53C2\u8003 defaultLocation\uFF1B\u4EFB\u52A1\u660E\u786E\u6307\u5B9A\u7684\u5730\u70B9\u4F18\u5148\u3002\u66F4\u65B0\u6216\u6E05\u7A7A currentCity \u4ECD\u9075\u5B88\u540C\u4E00 Profile \u6388\u6743\u89C4\u5219\u3002
9897
+
9888
9898
  \u9ED8\u8BA4\u56DE\u7B54\u8BED\u8A00\u53EA\u63A7\u5236\u6700\u7EC8\u9762\u5411\u7528\u6237\u7684\u56DE\u7B54\uFF1B\u601D\u8003\u8FC7\u7A0B\u8BED\u8A00\u53EA\u63A7\u5236\u5BBF\u4E3B\u754C\u9762\u652F\u6301\u65F6\u7528\u6237\u53EF\u89C1\u7684\u601D\u8003\u6587\u672C\uFF0C\u4E0D\u63A7\u5236\u9690\u85CF\u63A8\u7406\u3002\u4E24\u8005\u662F\u72EC\u7ACB\u5B57\u6BB5\uFF0C\u53EA\u4FEE\u6539\u7528\u6237\u6307\u5B9A\u7684\u5B57\u6BB5\u3002\u4F8B\u5982\u201C\u4EE5\u540E\u90FD\u7528\u4E2D\u6587\u56DE\u7B54\u6211\u201D\u53EA\u4FEE\u6539 preferredLanguage\uFF0C\u201C\u4EE5\u540E\u601D\u8003\u8FC7\u7A0B\u7528\u4E2D\u6587\u201D\u53EA\u4FEE\u6539 reasoningLanguage\uFF1B\u4E24\u8005\u90FD\u8981\u6C42\u65F6\u5206\u522B\u8C03\u7528\u4E24\u6B21\u3002
9889
9899
 
9890
9900
  \u6BCF\u6B21\u8C03\u7528\u53EA\u4FEE\u6539\u4E00\u4E2A\u5DF2\u6709\u5B57\u6BB5\uFF0C\u4E0D\u65B0\u589E\u3001\u5220\u9664\u6216\u6539\u540D\u5B57\u6BB5\uFF0C\u4E5F\u4E0D\u4E3A\u4FEE\u6539\u4E00\u4E2A\u5B57\u6BB5\u91CD\u5199\u6574\u4EFD\u753B\u50CF\u3002\u7528\u6237\u660E\u786E\u8981\u6C42\u4FEE\u6539\u65F6\u53EF\u76F4\u63A5\u6267\u884C\uFF1B\u5982\u679C\u662F\u4F60\u4E3B\u52A8\u5EFA\u8BAE\u4FEE\u6539\uFF0C\u987B\u5148\u8BF4\u660E\u5177\u4F53\u5B57\u6BB5\u548C\u65B0\u503C\u3002\u53EA\u6709\u7528\u6237\u7D27\u63A5\u7740\u660E\u786E\u56DE\u590D\u201C\u540C\u610F\u201D\uFF0C\u624D\u6388\u6743\u8FD9\u4E00\u6B21\u4FEE\u6539\uFF1B\u6C89\u9ED8\u3001\u62D2\u7EDD\u3001\u6362\u8BDD\u9898\u6216\u63D0\u51FA\u4E0D\u540C\u4FEE\u6539\u90FD\u4E0D\u7B97\u6388\u6743\u3002
9891
9901
 
9892
9902
  \u53EA\u9700\u5728\u76F8\u5173\u60C5\u5883\u4E2D\u60F3\u8D77\u7684\u9879\u76EE\u4E8B\u5B9E\u3001\u7ECF\u5386\u3001\u51B3\u5B9A\u6216\u504F\u597D\uFF0C\u8BF7\u4F7F\u7528 memory_remember\uFF1B\u4EC5\u672C\u6B21\u6709\u6548\u7684\u8981\u6C42\u4E0D\u5199\u5165\u8BB0\u5FC6\u3002\u4E0D\u80FD\u56E0\u4E3A\u7528\u6237\u8BF4\u4E86\u201C\u8BB0\u4F4F\u201D\u5C31\u81EA\u52A8\u9009\u62E9\u672C\u5DE5\u5177\uFF0C\u4E5F\u4E0D\u80FD\u5728 memory_remember \u4E0D\u53EF\u7528\u65F6\u628A Event \u4FE1\u606F\u6539\u5199\u6210\u5E38\u9A7B\u753B\u50CF\u3002`,
9893
9903
  parameters: {
9894
- field: { type: "string", required: true, enum: ["userPreferredName", "assistantPreferredName", "preferredLanguage", "reasoningLanguage", "responsePreferences", "standingInstructions", "userBackground", "longTermGoals", "persistentNotes"] },
9904
+ field: { type: "string", required: true, enum: ["userPreferredName", "assistantPreferredName", "preferredLanguage", "reasoningLanguage", "defaultLocation", "homeCity", "currentCity", "responsePreferences", "standingInstructions", "userBackground", "longTermGoals", "persistentNotes"] },
9895
9905
  value: { type: "string", required: true }
9896
9906
  },
9897
9907
  output: jsonOutput,
@@ -10024,7 +10034,7 @@ function registerMemoryTools(ctx, runtime) {
10024
10034
  }));
10025
10035
  ctx.tools.register(defineTool({
10026
10036
  name: "memory_assess",
10027
- description: "This tool is provided by the StrataGate plugin. Apply StrataGate Evidence Gate to a retrieval batch. Pass batch_id from the retrieval result; omitting it remains compatible with sequential flows and selects the latest batch. The response reports every input ref that was not adopted and why.",
10037
+ description: "This tool is provided by the StrataGate plugin. Apply StrataGate Evidence Gate to a retrieval batch BEFORE the final user answer. Assess evidence, close all batches with memory_record_use, then output the user answer. Pass batch_id from the retrieval result; omitting it remains compatible with sequential flows and selects the latest batch. The response reports every input ref that was not adopted and why.",
10028
10038
  parameters: {
10029
10039
  batch_id: { type: "string", description: "The batchId returned by the retrieval to assess. Omit only in a strictly sequential flow." },
10030
10040
  verdict: { type: "string", enum: ["sufficient", "partial", "wrong"], required: true },
@@ -10042,7 +10052,7 @@ function registerMemoryTools(ctx, runtime) {
10042
10052
  }));
10043
10053
  ctx.tools.register(defineTool({
10044
10054
  name: "memory_record_use",
10045
- description: "This tool is provided by the StrataGate plugin. Close one StrataGate retrieval batch. Pass its batch_id and exactly the evidenceRefs from that batch actually used in the answer, or [] when none were used. Non-empty refs require that batch's sufficient assessment. The host renders successful selections as answer-tail citations, so do not write a manual citation list. Omitting batch_id selects the latest batch for sequential compatibility.",
10055
+ description: "This tool is provided by the StrataGate plugin. Close one StrataGate retrieval batch BEFORE outputting the final user answer. Complete memory_assess / memory_record_use as pre-answer evidence processing for ALL batches, then generate the final user answer. Pass its batch_id and exactly the evidenceRefs from that batch actually adopted for the answer, or [] when none are used. Non-empty refs require that batch's sufficient assessment. Never write the answer first and record usage afterwards. Do not turn this tool's internal receipt or batch status into a user-facing answer. The host renders successful selections as answer-tail citations, so do not write a manual citation list. Omitting batch_id selects the latest batch for sequential compatibility.",
10046
10056
  parameters: {
10047
10057
  batch_id: { type: "string", description: "The batchId to close. Omit only in a strictly sequential flow." },
10048
10058
  evidence_refs: { type: "array", items: { type: "string" }, required: true }
@@ -11425,6 +11435,78 @@ function registerAdminRoutes(ctx, runtime) {
11425
11435
  });
11426
11436
  }
11427
11437
 
11438
+ // src/retrieval-finalization.ts
11439
+ import { createUserMessage as createUserMessage3 } from "@deepseek-ai/dsh-llm";
11440
+ var MAX_ANSWER_ATTEMPTS = 3;
11441
+ function isCompleteAnswer(data) {
11442
+ if (data.interrupted || data.message.content.some((block) => block.type === "tool-call")) return false;
11443
+ if (!data.message.content.some((block) => block.type === "text" && block.text.trim().length > 0)) return false;
11444
+ const stream = data.stream ?? [];
11445
+ for (let index = stream.length - 1; index >= 0; index--) {
11446
+ const record2 = stream[index];
11447
+ if (record2.type === "chunk" && record2.chunk.type === "finish") return record2.chunk.reason.kind === "stop";
11448
+ }
11449
+ return true;
11450
+ }
11451
+ function answerFailure() {
11452
+ return new Error(`StrataGate final answer recovery failed after ${MAX_ANSWER_ATTEMPTS} attempts: no complete non-empty user-visible answer was confirmed. Retry the turn.`);
11453
+ }
11454
+ var FINAL_ANSWER_INSTRUCTION = "Resume the original real user request in this turn, not the plugin instructions. All omitted memory receipts have now been handled. Now produce the complete final user-facing answer as the last assistant text. If you already gave the answer before the receipts, restate it in full, preserving its supported facts, conclusions, and requested deliverables; do not merely refer to the earlier answer. Use only evidence actually adopted for that answer. Do not perform new retrieval or other tool work. Do not report batch closure, evidence-use bookkeeping, unresolvedBatchIds, or other StrataGate internal status; these are not the user's answer.";
11455
+ function installRetrievalFinalization(ctx, runtime) {
11456
+ const recoveries = /* @__PURE__ */ new WeakMap();
11457
+ const steer = (agent, text3) => agent.steer(createUserMessage3({
11458
+ content: [{ type: "text", text: text3 }],
11459
+ source: dshMessageSource("instructions")
11460
+ }));
11461
+ ctx.on("agent/turn-stopping", ({ agent, turn, signal }) => {
11462
+ if (signal?.aborted) return;
11463
+ const pending = runtime.pendingBatchIds(agent.session);
11464
+ const recovery = recoveries.get(agent.session);
11465
+ if (pending.length === 0) {
11466
+ if (!recovery) return;
11467
+ if (recovery.phase === "answer-complete") return;
11468
+ if (recovery.answerAttempts >= MAX_ANSWER_ATTEMPTS) throw answerFailure();
11469
+ steer(agent, FINAL_ANSWER_INSTRUCTION);
11470
+ return;
11471
+ }
11472
+ if (recovery && recovery.phase !== "closing") {
11473
+ throw new Error("StrataGate final answer recovery created a new retrieval batch; final answer cannot be confirmed.");
11474
+ }
11475
+ if (recovery && (recovery.remainingStops === 0 || ![...recovery.pending].some((id) => !pending.includes(id)))) {
11476
+ throw new Error("StrataGate retrieval cleanup made no progress; unresolved batches remain. Retry the turn to complete memory_record_use.");
11477
+ }
11478
+ recoveries.set(agent.session, { turn, phase: "closing", pending: new Set(pending), remainingStops: recovery ? recovery.remainingStops - 1 : pending.length, answerAttempts: 0 });
11479
+ steer(agent, `StrataGate retrieval batches are still unresolved: ${pending.join(", ")}. Before answering, close every one with memory_record_use using its batch_id and exactly the evidence_refs actually adopted for the user answer, or [] if none were used. Non-empty refs still require that batch's sufficient memory_assess; do not reinforce evidence merely because it was retrieved. This is only the evidence-processing phase. Do not write a final answer or a batch-status explanation yet. After all receipts succeed, you will receive an instruction to produce the complete user answer again.`);
11480
+ });
11481
+ ctx.on("system-prompt/assemble", async (_assembly, context, next) => {
11482
+ const assembled = await next();
11483
+ const recovery = context.agent && recoveries.get(context.agent.session);
11484
+ if (!recovery || !context.agent || context.signal?.aborted) return assembled;
11485
+ if (runtime.needsRecordUse(context.agent.session)) {
11486
+ if (recovery.phase !== "closing") throw new Error("StrataGate final answer recovery created a new retrieval batch; final answer cannot be confirmed.");
11487
+ return assembled;
11488
+ }
11489
+ if (recovery.phase !== "answer-complete" && recovery.answerAttempts >= MAX_ANSWER_ATTEMPTS) throw answerFailure();
11490
+ return { ...assembled, tools: [], contexts: [...assembled.contexts, { name: "stratagate:final-answer-recovery", text: FINAL_ANSWER_INSTRUCTION }] };
11491
+ });
11492
+ ctx.on("session/event", (session, event) => {
11493
+ const recovery = recoveries.get(session);
11494
+ if (!recovery) return;
11495
+ if (event.type === "turn/end" && event.data.turn === recovery.turn) {
11496
+ recoveries.delete(session);
11497
+ } else if (event.type === "step/start" && event.data.turn === recovery.turn) {
11498
+ if (recovery.phase === "closing" && runtime.needsRecordUse(session)) return;
11499
+ recovery.phase = "awaiting-answer";
11500
+ recovery.answerStep = event.data.step;
11501
+ recovery.answerAttempts++;
11502
+ } else if (event.type === "assistant/message" && event.data.turn === recovery.turn && event.data.step === recovery.answerStep) {
11503
+ recovery.phase = isCompleteAnswer(event.data) ? "answer-complete" : "awaiting-answer";
11504
+ } else if (event.type === "assistant/attempt" && event.data.turn === recovery.turn && event.data.step === recovery.answerStep) {
11505
+ recovery.phase = "awaiting-answer";
11506
+ }
11507
+ });
11508
+ }
11509
+
11428
11510
  // src/legacy-session.ts
11429
11511
  import * as zlib from "node:zlib";
11430
11512
  import { constants as fsConstants } from "node:fs";
@@ -11774,11 +11856,14 @@ Memory use:
11774
11856
  - For explicit retrieval, treat relevance and sufficiency separately. Mark evidence sufficient only when it directly supports all material parts needed for the answer; partial when relevant evidence exists but important facts, time, relationships, or source details are missing; wrong when the retrieved evidence does not support the requested claim or refers to a different subject.
11775
11857
  - If evidence is partial or wrong, follow nextStrategy with a targeted next step: refine the Event or Graph search, expand the relevant Event, Graph node, or Block, or inspect raw memory. Do not repeat the same failed search unchanged, and do not present uncertain memory as fact.
11776
11858
  - Only evidence actually used in the final answer or action may be reinforced.
11859
+ - Every explicit retrieval batch must be closed BEFORE you begin outputting the final user-facing answer. The required order is retrieval \u2192 Evidence Gate (memory_assess) \u2192 memory_record_use for every batch \u2192 final user answer. Assessment and usage recording are pre-answer evidence processing. Decide which evidence the answer will actually adopt, assess it, and record only those refs; close unused batches with evidence_refs = []. Never write the user answer first and append receipts afterwards. After memory_record_use, generate the user answer; never turn its internal batch/receipt status into the final answer.
11777
11860
 
11778
11861
  Memory writing:
11779
11862
 
11780
11863
  - Choose by scope rather than the word "remember": memory_profile_update is for always-on global Profile fields supplied to future conversations without retrieval; memory_remember is for durable information that should surface when relevant; information that matters only to the current turn needs neither. Store the same information in one place by default.
11781
11864
  - Use memory_profile_update only for information that belongs in a Profile field. Explicit user requests may be applied directly; inferred changes must follow the tool's consent rule. Final-answer language and visible-reasoning language are independent fields. Do not use memory_remember to bypass Profile consent.
11865
+ - Stable default location (defaultLocation: reference for weather, nearby services, or local recommendations when no location is specified) and usual city of residence (homeCity) belong preferentially in the always-on Profile. They are independent: do not infer either from the other or from a current/temporary location or a trip. Temporary travel must not automatically overwrite these stable fields. Follow the same Profile consent rule.
11866
+ - Current city (currentCity) also belongs in the always-on Profile. Travel or business-trip location can be temporary while its saved value persists across sessions until the user updates or clears it; do not expire it automatically. A user request to remember their current trip city belongs here, not in retrieval-dependent memory_remember. Future trip plans, completed trips, and historical locations do not establish current whereabouts. Never copy currentCity into defaultLocation or homeCity. For local tasks use an explicit task location first, otherwise currentCity when set, then defaultLocation. Follow the same Profile consent rule for updates and clearing.
11782
11867
  - Use memory_remember for durable project facts, past decisions, corrections, experiences, and context-specific preferences. Record one self-contained, grounded fact per call, with necessary project, time, and scope. Never record speculation, secrets, credentials, or transient task state.`;
11783
11868
  var FEEDBACK_PROTOCOL = `[StrataGate feedback policy]
11784
11869
  The feedback_prepare tool creates a local draft for the user to review; it never submits the draft.
@@ -11872,16 +11957,7 @@ async function apply(ctx, config) {
11872
11957
  if (feedbackSuggestion) contexts.push({ name: "stratagate:feedback-suggestion", text: feedbackSuggestion });
11873
11958
  return { ...assembled, contexts };
11874
11959
  });
11875
- ctx.on("agent/turn-stopping", ({ agent }) => {
11876
- if (!runtime.needsRecordUse(agent.session)) return;
11877
- agent.steer(createUserMessage3({
11878
- content: [{
11879
- type: "text",
11880
- text: `StrataGate retrieval batches are still unresolved: ${runtime.pendingBatchIds(agent.session).join(", ")}. Before ending this turn, close each one with memory_record_use using its batch_id and evidence_refs set to exactly the refs from that batch used in the answer, or [] if none were used.`
11881
- }],
11882
- source: dshMessageSource("instructions")
11883
- }));
11884
- });
11960
+ installRetrievalFinalization(ctx, runtime);
11885
11961
  registerMemoryTools(ctx, runtime);
11886
11962
  ctx.on("tools/result", (exec, result) => {
11887
11963
  if (!result.isError || !exec.agent) return;