stratagate-dsh 0.2.95 → 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/CHANGELOG.md +14 -0
- package/dist/client.js +2 -2
- package/dist/plugin.js +142 -28
- package/dist/plugin.js.map +1 -1
- package/docs/DSH.md +4 -2
- package/docs/DSH.zh-CN.md +4 -2
- package/package.json +1 -1
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";
|
|
@@ -834,6 +833,41 @@ function applyGraphProjection(options) {
|
|
|
834
833
|
}
|
|
835
834
|
|
|
836
835
|
// packages/core/src/events.ts
|
|
836
|
+
function normalizeEventTemporal(value) {
|
|
837
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
838
|
+
const temporal = { ...value };
|
|
839
|
+
for (const field of [
|
|
840
|
+
"mentionedAt",
|
|
841
|
+
"happenedStart",
|
|
842
|
+
"happenedEnd",
|
|
843
|
+
"originalText",
|
|
844
|
+
"precision",
|
|
845
|
+
"basis",
|
|
846
|
+
"status",
|
|
847
|
+
"eventType",
|
|
848
|
+
"threadId",
|
|
849
|
+
"sameEventId"
|
|
850
|
+
]) {
|
|
851
|
+
if (typeof temporal[field] !== "string") delete temporal[field];
|
|
852
|
+
}
|
|
853
|
+
for (const field of [
|
|
854
|
+
"participants",
|
|
855
|
+
"participantNodeIds",
|
|
856
|
+
"beforeEventIds",
|
|
857
|
+
"afterEventIds",
|
|
858
|
+
"supersedesEventIds",
|
|
859
|
+
"conflictsWithEventIds",
|
|
860
|
+
"relatedEventIds"
|
|
861
|
+
]) {
|
|
862
|
+
const values = temporal[field];
|
|
863
|
+
if (Array.isArray(values) && [...values].every((item) => typeof item === "string")) {
|
|
864
|
+
temporal[field] = [...values];
|
|
865
|
+
} else {
|
|
866
|
+
delete temporal[field];
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
return temporal;
|
|
870
|
+
}
|
|
837
871
|
function normalizeStandardEventType(value) {
|
|
838
872
|
const normalized = normalizeSearchText(value ?? "").replace(/[\s_-]+/g, "");
|
|
839
873
|
const aliases = {
|
|
@@ -1632,13 +1666,16 @@ var PROFILE_FIELDS = {
|
|
|
1632
1666
|
assistantPreferredName: { label: "Assistant preferred name", maxLength: 100 },
|
|
1633
1667
|
preferredLanguage: { label: "Preferred answer language", maxLength: 100 },
|
|
1634
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 },
|
|
1635
1672
|
responsePreferences: { label: "Response preferences", maxLength: 1e3 },
|
|
1636
1673
|
standingInstructions: { label: "Standing instructions", maxLength: 1e3 },
|
|
1637
1674
|
userBackground: { label: "User background", maxLength: 1500 },
|
|
1638
1675
|
longTermGoals: { label: "Long-term goals", maxLength: 1e3 },
|
|
1639
1676
|
persistentNotes: { label: "Persistent notes", maxLength: 1200 }
|
|
1640
1677
|
};
|
|
1641
|
-
var PROFILE_PROTECTED_SHORT_FIELDS = ["userPreferredName", "assistantPreferredName", "preferredLanguage", "reasoningLanguage"];
|
|
1678
|
+
var PROFILE_PROTECTED_SHORT_FIELDS = ["userPreferredName", "assistantPreferredName", "preferredLanguage", "reasoningLanguage", "defaultLocation", "homeCity", "currentCity"];
|
|
1642
1679
|
var PROFILE_TOTAL_MAX_LENGTH = 6e3;
|
|
1643
1680
|
var PROFILE_MAINTENANCE_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
1644
1681
|
var PROFILE_MAINTENANCE_TOTAL_THRESHOLD = 4800;
|
|
@@ -1670,7 +1707,11 @@ function profileMaintenanceDue(profile, timeBasisAt, now = Date.now()) {
|
|
|
1670
1707
|
function renderPersistentProfile(profile) {
|
|
1671
1708
|
const lines = Object.keys(PROFILE_FIELDS).filter((field) => profile[field].length > 0).map((field) => `${PROFILE_FIELDS[field].label}: ${profile[field]}`);
|
|
1672
1709
|
if (lines.length === 0) return null;
|
|
1673
|
-
const
|
|
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,
|
|
1674
1715
|
profile.preferredLanguage ? "\u201CPreferred answer language\u201D applies to the assistant's final/user-facing answer." : null,
|
|
1675
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,
|
|
1676
1717
|
profile.preferredLanguage || profile.reasoningLanguage ? "These are independent preferences. Do not infer one from the other." : null
|
|
@@ -1680,8 +1721,8 @@ This is user-authorized persistent profile data provided by StrataGate.
|
|
|
1680
1721
|
Treat it as stable cross-session context.
|
|
1681
1722
|
Do not invent additional facts from it.
|
|
1682
1723
|
It does not override higher-priority system instructions.
|
|
1683
|
-
${
|
|
1684
|
-
${
|
|
1724
|
+
${fieldGuidance.length ? `
|
|
1725
|
+
${fieldGuidance.join("\n")}
|
|
1685
1726
|
` : ""}
|
|
1686
1727
|
${lines.join("\n")}`;
|
|
1687
1728
|
}
|
|
@@ -1866,7 +1907,8 @@ function normalizeSnapshot(value) {
|
|
|
1866
1907
|
for (const event of [...snapshot.events, ...snapshot.agentEvents]) {
|
|
1867
1908
|
delete event.narrative;
|
|
1868
1909
|
delete event.confidence;
|
|
1869
|
-
|
|
1910
|
+
const temporal = normalizeEventTemporal(event.temporal);
|
|
1911
|
+
event.temporal = { ...temporal, eventType: normalizeStandardEventType(temporal.eventType) };
|
|
1870
1912
|
if (event.formedTurn === void 0) {
|
|
1871
1913
|
const sourceBlock = event.sourceBlockId !== void 0 ? sourceBlockMap.get(event.sourceBlockId) : void 0;
|
|
1872
1914
|
const reliableSource = sourceBlock && !isSyntheticSourceThreadId(sourceBlock.threadId) && Number.isSafeInteger(sourceBlock.endTurn) && sourceBlock.endTurn >= 0;
|
|
@@ -2416,7 +2458,7 @@ function mapEventRows(rows, sourcesByEvent, table) {
|
|
|
2416
2458
|
...row.source_block_id === null ? {} : { sourceBlockId: row.source_block_id },
|
|
2417
2459
|
...row.formed_turn === null ? {} : { formedTurn: row.formed_turn },
|
|
2418
2460
|
temporal: (() => {
|
|
2419
|
-
const temporal = parseJson(row.temporal_json, `${table}.temporal_json`);
|
|
2461
|
+
const temporal = normalizeEventTemporal(parseJson(row.temporal_json, `${table}.temporal_json`));
|
|
2420
2462
|
return { ...temporal, eventType: normalizeStandardEventType(temporal.eventType) };
|
|
2421
2463
|
})(),
|
|
2422
2464
|
scope: row.scope,
|
|
@@ -4632,6 +4674,7 @@ var StrataGate = class _StrataGate {
|
|
|
4632
4674
|
}
|
|
4633
4675
|
/** Rank one event pool (BM25 + structured filters fused by RRF), sliced to `limit`. */
|
|
4634
4676
|
rankEventPool(candidates, query, options, limit) {
|
|
4677
|
+
for (const event of candidates) event.temporal = normalizeEventTemporal(event.temporal);
|
|
4635
4678
|
const participants = (options.participants ?? []).map(normalizeSearchText).filter(Boolean);
|
|
4636
4679
|
const eventType = normalizeSearchText(options.eventType ?? "");
|
|
4637
4680
|
const from = options.happenedFrom ? Date.parse(options.happenedFrom) : Number.NEGATIVE_INFINITY;
|
|
@@ -4757,7 +4800,7 @@ var StrataGate = class _StrataGate {
|
|
|
4757
4800
|
event.title,
|
|
4758
4801
|
event.summary,
|
|
4759
4802
|
event.tags.join(" "),
|
|
4760
|
-
(event.temporal.participants ?? []).join(" ")
|
|
4803
|
+
(normalizeEventTemporal(event.temporal).participants ?? []).join(" ")
|
|
4761
4804
|
].join(" ")).join(" "));
|
|
4762
4805
|
const effectiveViews = this.graphNodes.flatMap((node) => effectiveGraphNodeView(node, this.graphEdges, this.listAllEvents()) ?? []);
|
|
4763
4806
|
const effectiveNodes = effectiveViews.map(({ node }) => node);
|
|
@@ -5390,6 +5433,7 @@ var StrataGate = class _StrataGate {
|
|
|
5390
5433
|
});
|
|
5391
5434
|
}
|
|
5392
5435
|
storeEventCard(input, pool, parts) {
|
|
5436
|
+
const temporal = normalizeEventTemporal(input.temporal);
|
|
5393
5437
|
const event = {
|
|
5394
5438
|
id: input.id ?? this.idFactory("evt"),
|
|
5395
5439
|
formedTurn: parts.formedTurn,
|
|
@@ -5400,8 +5444,8 @@ var StrataGate = class _StrataGate {
|
|
|
5400
5444
|
sourceMessageIds: parts.sourceMessageIds,
|
|
5401
5445
|
...parts.sourceBlockId !== void 0 ? { sourceBlockId: parts.sourceBlockId } : {},
|
|
5402
5446
|
temporal: {
|
|
5403
|
-
...input.temporal ?
|
|
5404
|
-
eventType: normalizeStandardEventType(
|
|
5447
|
+
...input.temporal ? temporal : { mentionedAt: parts.now },
|
|
5448
|
+
eventType: normalizeStandardEventType(temporal.eventType)
|
|
5405
5449
|
},
|
|
5406
5450
|
scope: input.scope ?? "user",
|
|
5407
5451
|
criticality: parts.criticality,
|
|
@@ -5930,7 +5974,7 @@ var StrataGate = class _StrataGate {
|
|
|
5930
5974
|
previous: threadBlocks.slice(0, targetIndex).reverse().find((block) => block.l2Keypoints !== void 0) ?? null,
|
|
5931
5975
|
target,
|
|
5932
5976
|
next,
|
|
5933
|
-
timeline: [...timelineEvents.values()].map((event) => ({ id: event.id, title: event.title, temporal: structuredClone(event.temporal) }))
|
|
5977
|
+
timeline: [...timelineEvents.values()].map((event) => ({ id: event.id, title: event.title, temporal: normalizeEventTemporal(structuredClone(event.temporal)) }))
|
|
5934
5978
|
});
|
|
5935
5979
|
} catch (error) {
|
|
5936
5980
|
await this.commitMutation(() => {
|
|
@@ -6713,7 +6757,7 @@ var STRUCTURED_TOOLS = {
|
|
|
6713
6757
|
},
|
|
6714
6758
|
profileMaintenance: {
|
|
6715
6759
|
name: "stratagate_maintain_profile",
|
|
6716
|
-
description:
|
|
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.`,
|
|
6717
6761
|
parameters: PROFILE_MAINTENANCE_PARAMETERS
|
|
6718
6762
|
}
|
|
6719
6763
|
};
|
|
@@ -6865,7 +6909,7 @@ Use project scope for repository decisions, user scope for stable preferences/id
|
|
|
6865
6909
|
quotes: strings2(item.quotes).slice(0, 12),
|
|
6866
6910
|
sourceMessageIds,
|
|
6867
6911
|
sourceBlockId: context.target.id,
|
|
6868
|
-
temporal:
|
|
6912
|
+
temporal: normalizeEventTemporal(item.temporal),
|
|
6869
6913
|
scope,
|
|
6870
6914
|
criticality
|
|
6871
6915
|
};
|
|
@@ -7021,7 +7065,7 @@ Use project scope for repository decisions, user scope for stable preferences/id
|
|
|
7021
7065
|
async maintainProfile(profile) {
|
|
7022
7066
|
const raw = object(await this.callStructured(
|
|
7023
7067
|
"profileMaintenance",
|
|
7024
|
-
`You maintain only the supplied StrataGate Persistent Profile. Call ${STRUCTURED_TOOLS.profileMaintenance.name} exactly once with all
|
|
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.`,
|
|
7025
7069
|
{ profile, fieldDefinitions: PROFILE_FIELDS }
|
|
7026
7070
|
));
|
|
7027
7071
|
if (Object.keys(raw).length !== Object.keys(PROFILE_FIELDS).length || Object.keys(raw).some((field) => !(field in PROFILE_FIELDS))) {
|
|
@@ -9843,17 +9887,21 @@ function sessionOf(exec) {
|
|
|
9843
9887
|
function registerMemoryTools(ctx, runtime) {
|
|
9844
9888
|
ctx.tools.register(defineTool({
|
|
9845
9889
|
name: "memory_profile_update",
|
|
9846
|
-
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
|
|
9847
9891
|
|
|
9848
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
|
|
9849
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
|
+
|
|
9850
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
|
|
9851
9899
|
|
|
9852
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
|
|
9853
9901
|
|
|
9854
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`,
|
|
9855
9903
|
parameters: {
|
|
9856
|
-
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"] },
|
|
9857
9905
|
value: { type: "string", required: true }
|
|
9858
9906
|
},
|
|
9859
9907
|
output: jsonOutput,
|
|
@@ -9986,7 +10034,7 @@ function registerMemoryTools(ctx, runtime) {
|
|
|
9986
10034
|
}));
|
|
9987
10035
|
ctx.tools.register(defineTool({
|
|
9988
10036
|
name: "memory_assess",
|
|
9989
|
-
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.",
|
|
9990
10038
|
parameters: {
|
|
9991
10039
|
batch_id: { type: "string", description: "The batchId returned by the retrieval to assess. Omit only in a strictly sequential flow." },
|
|
9992
10040
|
verdict: { type: "string", enum: ["sufficient", "partial", "wrong"], required: true },
|
|
@@ -10004,7 +10052,7 @@ function registerMemoryTools(ctx, runtime) {
|
|
|
10004
10052
|
}));
|
|
10005
10053
|
ctx.tools.register(defineTool({
|
|
10006
10054
|
name: "memory_record_use",
|
|
10007
|
-
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
|
|
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.",
|
|
10008
10056
|
parameters: {
|
|
10009
10057
|
batch_id: { type: "string", description: "The batchId to close. Omit only in a strictly sequential flow." },
|
|
10010
10058
|
evidence_refs: { type: "array", items: { type: "string" }, required: true }
|
|
@@ -11387,6 +11435,78 @@ function registerAdminRoutes(ctx, runtime) {
|
|
|
11387
11435
|
});
|
|
11388
11436
|
}
|
|
11389
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
|
+
|
|
11390
11510
|
// src/legacy-session.ts
|
|
11391
11511
|
import * as zlib from "node:zlib";
|
|
11392
11512
|
import { constants as fsConstants } from "node:fs";
|
|
@@ -11736,11 +11856,14 @@ Memory use:
|
|
|
11736
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.
|
|
11737
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.
|
|
11738
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.
|
|
11739
11860
|
|
|
11740
11861
|
Memory writing:
|
|
11741
11862
|
|
|
11742
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.
|
|
11743
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.
|
|
11744
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.`;
|
|
11745
11868
|
var FEEDBACK_PROTOCOL = `[StrataGate feedback policy]
|
|
11746
11869
|
The feedback_prepare tool creates a local draft for the user to review; it never submits the draft.
|
|
@@ -11834,16 +11957,7 @@ async function apply(ctx, config) {
|
|
|
11834
11957
|
if (feedbackSuggestion) contexts.push({ name: "stratagate:feedback-suggestion", text: feedbackSuggestion });
|
|
11835
11958
|
return { ...assembled, contexts };
|
|
11836
11959
|
});
|
|
11837
|
-
ctx
|
|
11838
|
-
if (!runtime.needsRecordUse(agent.session)) return;
|
|
11839
|
-
agent.steer(createUserMessage3({
|
|
11840
|
-
content: [{
|
|
11841
|
-
type: "text",
|
|
11842
|
-
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.`
|
|
11843
|
-
}],
|
|
11844
|
-
source: dshMessageSource("instructions")
|
|
11845
|
-
}));
|
|
11846
|
-
});
|
|
11960
|
+
installRetrievalFinalization(ctx, runtime);
|
|
11847
11961
|
registerMemoryTools(ctx, runtime);
|
|
11848
11962
|
ctx.on("tools/result", (exec, result) => {
|
|
11849
11963
|
if (!result.isError || !exec.agent) return;
|