stratagate-dsh 0.2.82 → 0.2.83
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 +6 -0
- package/dist/client.js +7 -2
- package/dist/plugin.js +58 -14
- package/dist/plugin.js.map +1 -1
- package/docs/DSH.md +1 -1
- package/docs/DSH.zh-CN.md +1 -1
- package/docs/assets/stratagate-profile-0.2.83-conflict.png +0 -0
- package/docs/assets/stratagate-profile-0.2.83-lower.png +0 -0
- package/docs/assets/stratagate-profile-0.2.83-reasoning-conflict.png +0 -0
- package/docs/assets/stratagate-profile-0.2.83.png +0 -0
- package/package.json +3 -1
package/dist/plugin.js
CHANGED
|
@@ -1614,13 +1614,15 @@ function normalizeRetrievalAssessment(input, batchEvidenceRefs) {
|
|
|
1614
1614
|
var PROFILE_FIELDS = {
|
|
1615
1615
|
userPreferredName: { label: "User preferred name", maxLength: 100 },
|
|
1616
1616
|
assistantPreferredName: { label: "Assistant preferred name", maxLength: 100 },
|
|
1617
|
-
preferredLanguage: { label: "Preferred language", maxLength: 100 },
|
|
1617
|
+
preferredLanguage: { label: "Preferred answer language", maxLength: 100 },
|
|
1618
|
+
reasoningLanguage: { label: "Preferred visible reasoning language", maxLength: 100 },
|
|
1618
1619
|
responsePreferences: { label: "Response preferences", maxLength: 1e3 },
|
|
1619
1620
|
standingInstructions: { label: "Standing instructions", maxLength: 1e3 },
|
|
1620
1621
|
userBackground: { label: "User background", maxLength: 1500 },
|
|
1621
1622
|
longTermGoals: { label: "Long-term goals", maxLength: 1e3 },
|
|
1622
1623
|
persistentNotes: { label: "Persistent notes", maxLength: 1200 }
|
|
1623
1624
|
};
|
|
1625
|
+
var PROFILE_PROTECTED_SHORT_FIELDS = ["userPreferredName", "assistantPreferredName", "preferredLanguage", "reasoningLanguage"];
|
|
1624
1626
|
var PROFILE_TOTAL_MAX_LENGTH = 6e3;
|
|
1625
1627
|
var PROFILE_MAINTENANCE_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
1626
1628
|
var PROFILE_MAINTENANCE_TOTAL_THRESHOLD = 4800;
|
|
@@ -1652,12 +1654,19 @@ function profileMaintenanceDue(profile, timeBasisAt, now = Date.now()) {
|
|
|
1652
1654
|
function renderPersistentProfile(profile) {
|
|
1653
1655
|
const lines = Object.keys(PROFILE_FIELDS).filter((field) => profile[field].length > 0).map((field) => `${PROFILE_FIELDS[field].label}: ${profile[field]}`);
|
|
1654
1656
|
if (lines.length === 0) return null;
|
|
1657
|
+
const languageGuidance = [
|
|
1658
|
+
profile.preferredLanguage ? "\u201CPreferred answer language\u201D applies to the assistant's final/user-facing answer." : null,
|
|
1659
|
+
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,
|
|
1660
|
+
profile.preferredLanguage || profile.reasoningLanguage ? "These are independent preferences. Do not infer one from the other." : null
|
|
1661
|
+
].filter(Boolean);
|
|
1655
1662
|
return `[StrataGate Persistent Profile]
|
|
1656
1663
|
This is user-authorized persistent profile data provided by StrataGate.
|
|
1657
1664
|
Treat it as stable cross-session context.
|
|
1658
1665
|
Do not invent additional facts from it.
|
|
1659
1666
|
It does not override higher-priority system instructions.
|
|
1660
|
-
|
|
1667
|
+
${languageGuidance.length ? `
|
|
1668
|
+
${languageGuidance.join("\n")}
|
|
1669
|
+
` : ""}
|
|
1661
1670
|
${lines.join("\n")}`;
|
|
1662
1671
|
}
|
|
1663
1672
|
|
|
@@ -2647,6 +2656,17 @@ var SqliteStorage = class {
|
|
|
2647
2656
|
for (const row of rows) if (isProfileField(row.field)) profile[row.field] = row.value;
|
|
2648
2657
|
return profile;
|
|
2649
2658
|
}
|
|
2659
|
+
getProfileSnapshot() {
|
|
2660
|
+
this.assertOpen();
|
|
2661
|
+
const profile = emptyProfile();
|
|
2662
|
+
const revisions = Object.fromEntries(Object.keys(PROFILE_FIELDS).map((field) => [field, 0]));
|
|
2663
|
+
const rows = this.database.prepare("SELECT p.field, p.value, COALESCE(r.revision, 0) AS revision FROM persistent_profile p LEFT JOIN (SELECT field, MAX(id) AS revision FROM persistent_profile_changes GROUP BY field) r ON r.field = p.field").all();
|
|
2664
|
+
for (const row of rows) if (isProfileField(row.field)) {
|
|
2665
|
+
profile[row.field] = row.value;
|
|
2666
|
+
revisions[row.field] = row.revision;
|
|
2667
|
+
}
|
|
2668
|
+
return { profile, revisions };
|
|
2669
|
+
}
|
|
2650
2670
|
getProfileChanges() {
|
|
2651
2671
|
this.assertOpen();
|
|
2652
2672
|
const rows = this.database.prepare("SELECT field, old_value, new_value, source, updated_at, source_message_id FROM persistent_profile_changes ORDER BY id").all();
|
|
@@ -2678,7 +2698,7 @@ var SqliteStorage = class {
|
|
|
2678
2698
|
const nextRetryAt = new Date(now + (count >= 3 ? 24 * 60 : count * 5) * 60 * 1e3).toISOString();
|
|
2679
2699
|
this.database.prepare("INSERT INTO persistent_profile_maintenance_failures (id, input_json, failure_count, next_retry_at) VALUES (1, ?, ?, ?) ON CONFLICT (id) DO UPDATE SET input_json = excluded.input_json, failure_count = excluded.failure_count, next_retry_at = excluded.next_retry_at").run(inputJson, count, nextRetryAt);
|
|
2680
2700
|
}
|
|
2681
|
-
updateProfileField(field, value, source, sourceMessageId) {
|
|
2701
|
+
updateProfileField(field, value, source, sourceMessageId, expectedValue, expectedRevision) {
|
|
2682
2702
|
this.assertOpen();
|
|
2683
2703
|
if (!isProfileField(field)) throw new TypeError(`Unknown Persistent Profile field: ${field}`);
|
|
2684
2704
|
if (typeof value !== "string") throw new TypeError("Persistent Profile value must be a string");
|
|
@@ -2687,6 +2707,11 @@ var SqliteStorage = class {
|
|
|
2687
2707
|
const profile = this.getPersistentProfile();
|
|
2688
2708
|
const wasNonempty = Object.values(profile).some((entry) => entry.length > 0);
|
|
2689
2709
|
const oldValue = profile[field];
|
|
2710
|
+
if (expectedRevision !== void 0) {
|
|
2711
|
+
const row = this.database.prepare("SELECT COALESCE(MAX(id), 0) AS revision FROM persistent_profile_changes WHERE field = ?").get(field);
|
|
2712
|
+
if (row.revision !== expectedRevision) return { field, value: oldValue, modified: false, conflict: true };
|
|
2713
|
+
}
|
|
2714
|
+
if (expectedValue !== void 0 && oldValue !== expectedValue) return { field, value: oldValue, modified: false, conflict: true };
|
|
2690
2715
|
if (oldValue === value) return { field, value, modified: false };
|
|
2691
2716
|
profile[field] = value;
|
|
2692
2717
|
validateProfile(profile);
|
|
@@ -2708,6 +2733,9 @@ var SqliteStorage = class {
|
|
|
2708
2733
|
this.assertOpen();
|
|
2709
2734
|
validateProfile(proposed);
|
|
2710
2735
|
if (Object.keys(proposed).length !== Object.keys(PROFILE_FIELDS).length || Object.keys(proposed).some((field) => !isProfileField(field))) throw new TypeError("Maintenance returned unknown or missing Profile fields");
|
|
2736
|
+
for (const field of PROFILE_PROTECTED_SHORT_FIELDS) {
|
|
2737
|
+
if (proposed[field].trim() !== expected[field].trim()) throw new Error(`Profile maintenance changed protected short field ${field}`);
|
|
2738
|
+
}
|
|
2711
2739
|
return this.immediateTransaction(() => {
|
|
2712
2740
|
const current = this.getPersistentProfile();
|
|
2713
2741
|
if (JSON.stringify(current) !== JSON.stringify(expected)) return false;
|
|
@@ -5892,7 +5920,7 @@ var STRUCTURED_TOOLS = {
|
|
|
5892
5920
|
},
|
|
5893
5921
|
profileMaintenance: {
|
|
5894
5922
|
name: "stratagate_maintain_profile",
|
|
5895
|
-
description: "Return the same
|
|
5923
|
+
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.",
|
|
5896
5924
|
parameters: PROFILE_MAINTENANCE_PARAMETERS
|
|
5897
5925
|
}
|
|
5898
5926
|
};
|
|
@@ -6189,7 +6217,7 @@ var DshModelBridge = class {
|
|
|
6189
6217
|
async maintainProfile(profile) {
|
|
6190
6218
|
const raw = object(await this.callStructured(
|
|
6191
6219
|
"profileMaintenance",
|
|
6192
|
-
`You maintain only the supplied StrataGate Persistent Profile. Call ${STRUCTURED_TOOLS.profileMaintenance.name} exactly once with all
|
|
6220
|
+
`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.`,
|
|
6193
6221
|
{ profile, fieldDefinitions: PROFILE_FIELDS }
|
|
6194
6222
|
));
|
|
6195
6223
|
if (Object.keys(raw).length !== Object.keys(PROFILE_FIELDS).length || Object.keys(raw).some((field) => !(field in PROFILE_FIELDS))) {
|
|
@@ -6197,7 +6225,7 @@ var DshModelBridge = class {
|
|
|
6197
6225
|
}
|
|
6198
6226
|
const proposed = raw;
|
|
6199
6227
|
validateProfile(proposed);
|
|
6200
|
-
for (const field of
|
|
6228
|
+
for (const field of PROFILE_PROTECTED_SHORT_FIELDS) {
|
|
6201
6229
|
if (proposed[field].trim() !== profile[field].trim()) throw new Error(`Profile maintenance changed protected short field ${field}`);
|
|
6202
6230
|
}
|
|
6203
6231
|
return proposed;
|
|
@@ -6985,14 +7013,17 @@ var StrataGateRuntime = class {
|
|
|
6985
7013
|
getPersistentProfile() {
|
|
6986
7014
|
return this.profileStore().getPersistentProfile();
|
|
6987
7015
|
}
|
|
7016
|
+
getProfileSnapshot() {
|
|
7017
|
+
return this.profileStore().getProfileSnapshot();
|
|
7018
|
+
}
|
|
6988
7019
|
renderProfileContext() {
|
|
6989
7020
|
return renderPersistentProfile(this.getPersistentProfile());
|
|
6990
7021
|
}
|
|
6991
7022
|
getProfileChanges() {
|
|
6992
7023
|
return this.profileStore().getProfileChanges();
|
|
6993
7024
|
}
|
|
6994
|
-
updatePersistentProfile(field, value, source, sourceMessageId) {
|
|
6995
|
-
const result = this.profileStore().updateProfileField(field, value, source, sourceMessageId);
|
|
7025
|
+
updatePersistentProfile(field, value, source, sourceMessageId, expectedValue, expectedRevision) {
|
|
7026
|
+
const result = this.profileStore().updateProfileField(field, value, source, sourceMessageId, expectedValue, expectedRevision);
|
|
6996
7027
|
if (result.modified) this.wakeBackgroundWorker();
|
|
6997
7028
|
return result;
|
|
6998
7029
|
}
|
|
@@ -8811,7 +8842,14 @@ function sessionOf(exec) {
|
|
|
8811
8842
|
function registerMemoryTools(ctx, runtime) {
|
|
8812
8843
|
ctx.tools.register(defineTool({
|
|
8813
8844
|
name: "memory_profile_update",
|
|
8814
|
-
description: `This tool is provided by the StrataGate plugin. Update exactly one field of the user's global Persistent Profile. Persistent Profile data is injected into every future conversation without retrieval, so use this tool only for information that should remain continuously available or continuously affect future behavior, such as how to address the user, what the user wants the assistant to be called,
|
|
8845
|
+
description: `This tool is provided by the StrataGate plugin. Update exactly one field of the user's global Persistent Profile. Persistent Profile data is injected into every future conversation without retrieval, so use this tool only for information that should remain continuously available or continuously affect future behavior, such as how to address the user, what the user wants the assistant to be called, language preferences, stable response preferences, standing instructions, stable user background, long-term goals, or other genuinely persistent notes.
|
|
8846
|
+
|
|
8847
|
+
preferredLanguage sets only the default language of the final/user-facing answer. reasoningLanguage sets only the desired language of reasoning/thinking text visible to the user in the DSH or host UI, when supported; it cannot control hidden internal chain-of-thought. An empty reasoningLanguage adds no visible-reasoning language requirement. These are independent settings: never infer one from the other, and never change one merely because the other changed.
|
|
8848
|
+
|
|
8849
|
+
Examples:
|
|
8850
|
+
User: \u201C\u4EE5\u540E\u90FD\u7528\u4E2D\u6587\u56DE\u7B54\u6211\u201D \u2192 call memory_profile_update with preferredLanguage = \u4E2D\u6587; do not change reasoningLanguage.
|
|
8851
|
+
User: \u201C\u4EE5\u540E\u601D\u8003\u8FC7\u7A0B\u7528\u4E2D\u6587\u201D or \u201C\u601D\u8003\u94FE\u7528\u4E2D\u6587\u201D \u2192 call memory_profile_update with reasoningLanguage = \u4E2D\u6587; do not change preferredLanguage.
|
|
8852
|
+
User: \u201C\u4EE5\u540E\u56DE\u7B54\u548C\u601D\u8003\u8FC7\u7A0B\u90FD\u7528\u4E2D\u6587\u201D \u2192 make two separate memory_profile_update calls: first preferredLanguage = \u4E2D\u6587, then reasoningLanguage = \u4E2D\u6587. Each call still changes exactly one field.
|
|
8815
8853
|
|
|
8816
8854
|
Do not use this tool merely because the user says "remember". If the information describes something that happened, a decision, an activity, a project change, a dated fact, or something that only needs to be recalled when relevant, it belongs in Event memory instead. A separate Event-memory tool is reserved for that purpose and is not part of this implementation.
|
|
8817
8855
|
|
|
@@ -8821,7 +8859,7 @@ If the assistant only infers that something might be a useful persistent prefere
|
|
|
8821
8859
|
|
|
8822
8860
|
Each call changes exactly one predefined Profile field. Never create, delete, or rename Profile fields, and never rewrite the complete Profile when only one field is being changed.`,
|
|
8823
8861
|
parameters: {
|
|
8824
|
-
field: { type: "string", required: true, enum: ["userPreferredName", "assistantPreferredName", "preferredLanguage", "responsePreferences", "standingInstructions", "userBackground", "longTermGoals", "persistentNotes"] },
|
|
8862
|
+
field: { type: "string", required: true, enum: ["userPreferredName", "assistantPreferredName", "preferredLanguage", "reasoningLanguage", "responsePreferences", "standingInstructions", "userBackground", "longTermGoals", "persistentNotes"] },
|
|
8825
8863
|
value: { type: "string", required: true }
|
|
8826
8864
|
},
|
|
8827
8865
|
output: jsonOutput,
|
|
@@ -9579,7 +9617,10 @@ async function updateSettings(runtime, url) {
|
|
|
9579
9617
|
return result;
|
|
9580
9618
|
}
|
|
9581
9619
|
async function persistentProfile(runtime, req) {
|
|
9582
|
-
if (req.method === "GET")
|
|
9620
|
+
if (req.method === "GET") {
|
|
9621
|
+
const snapshot = runtime.getProfileSnapshot();
|
|
9622
|
+
return { ...snapshot.profile, _revisions: snapshot.revisions };
|
|
9623
|
+
}
|
|
9583
9624
|
if (req.method !== "PATCH") throw new AdminHttpError(405, "Persistent Profile requires GET or PATCH");
|
|
9584
9625
|
let suppliedBody = req.body;
|
|
9585
9626
|
if (suppliedBody === void 0 && typeof req[Symbol.asyncIterator] === "function") {
|
|
@@ -9603,11 +9644,14 @@ async function persistentProfile(runtime, req) {
|
|
|
9603
9644
|
}
|
|
9604
9645
|
if (!body || typeof body !== "object" || Array.isArray(body)) throw new AdminHttpError(400, "Profile update must be an object");
|
|
9605
9646
|
const input = body;
|
|
9606
|
-
if (Object.keys(input).length !==
|
|
9607
|
-
throw new AdminHttpError(400, "Profile update requires
|
|
9647
|
+
if (Object.keys(input).length !== 4 || !Object.hasOwn(input, "field") || !Object.hasOwn(input, "value") || !Object.hasOwn(input, "expectedValue") || !Object.hasOwn(input, "expectedRevision") || typeof input.field !== "string" || typeof input.value !== "string" || typeof input.expectedValue !== "string" || !Number.isSafeInteger(input.expectedRevision) || input.expectedRevision < 0) {
|
|
9648
|
+
throw new AdminHttpError(400, "Profile update requires field, value, expectedValue strings and expectedRevision number");
|
|
9608
9649
|
}
|
|
9609
9650
|
try {
|
|
9610
|
-
|
|
9651
|
+
const result = runtime.updatePersistentProfile(input.field, input.value, "settings", null, input.expectedValue, input.expectedRevision);
|
|
9652
|
+
if (result.conflict) throw new AdminHttpError(409, "\u8BE5\u9879\u521A\u521A\u5728\u5176\u4ED6\u4F4D\u7F6E\u66F4\u65B0");
|
|
9653
|
+
const snapshot = runtime.getProfileSnapshot();
|
|
9654
|
+
return { ...result, snapshot: { ...snapshot.profile, _revisions: snapshot.revisions } };
|
|
9611
9655
|
} catch (error) {
|
|
9612
9656
|
if (error instanceof TypeError || error instanceof RangeError) throw new AdminHttpError(400, error.message);
|
|
9613
9657
|
throw error;
|