stratagate-dsh 0.3.3 → 0.3.5
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 +13 -0
- package/dist/client.js +4 -3
- package/dist/plugin.js +375 -73
- package/dist/plugin.js.map +1 -1
- package/docs/DSH.md +6 -0
- package/docs/DSH.zh-CN.md +6 -0
- package/docs/MEMORY_TOPICS.zh-CN.md +2 -2
- package/package.json +1 -1
package/dist/plugin.js
CHANGED
|
@@ -3727,6 +3727,123 @@ var SqliteStorage = class {
|
|
|
3727
3727
|
|
|
3728
3728
|
// packages/core/src/topics.ts
|
|
3729
3729
|
import { createHash, randomUUID } from "node:crypto";
|
|
3730
|
+
|
|
3731
|
+
// packages/core/src/topic-diagnostics.ts
|
|
3732
|
+
var categories = [
|
|
3733
|
+
"max-tokens",
|
|
3734
|
+
"schema-invalid",
|
|
3735
|
+
"validation-failed",
|
|
3736
|
+
"timeout",
|
|
3737
|
+
"provider-failed",
|
|
3738
|
+
"worker-failed",
|
|
3739
|
+
"source-changed"
|
|
3740
|
+
];
|
|
3741
|
+
var TOPIC_SAFE_REASONS = /* @__PURE__ */ new Set([
|
|
3742
|
+
"A topic may be updated only once per projection.",
|
|
3743
|
+
"Invalid duplicate chapter label conflicts with an existing topic.",
|
|
3744
|
+
"Invalid duplicate chapter label in one projection.",
|
|
3745
|
+
"Invalid duplicate chapter label matches an unexposed existing topic.",
|
|
3746
|
+
"Invalid or duplicate topic section title.",
|
|
3747
|
+
"Invalid section backfill: explicit sections must cover the existing chapter.",
|
|
3748
|
+
"Invalid topic membership source events.",
|
|
3749
|
+
"Invalid topic overview section.",
|
|
3750
|
+
"Invalid topic overview source events.",
|
|
3751
|
+
"Invalid topic overview title.",
|
|
3752
|
+
"Invalid topic section source events.",
|
|
3753
|
+
"Invalid topic sections.",
|
|
3754
|
+
"Invalid topic title or description.",
|
|
3755
|
+
"New topic overview must match a final topic section.",
|
|
3756
|
+
"Old overview evidence must be preserved verbatim; new sections need batch events.",
|
|
3757
|
+
"Old section membership must be preserved; reassignment needs batch events.",
|
|
3758
|
+
"Overview references must belong to returned topic membership.",
|
|
3759
|
+
"Section references must belong to returned topic membership.",
|
|
3760
|
+
"Topic projection lease expired; stale result rejected.",
|
|
3761
|
+
"Topic projection must contain at most 8 overview paragraphs per chapter per batch.",
|
|
3762
|
+
"Topic projection must return 1-12 topics.",
|
|
3763
|
+
"Topic projection omitted batch events.",
|
|
3764
|
+
"Topic projection source version changed; stale result rejected.",
|
|
3765
|
+
"Topic sections omitted batch events.",
|
|
3766
|
+
"Truncated topic sources permit navigation scope only, not factual overview.",
|
|
3767
|
+
"Unknown or unexposed topic id.",
|
|
3768
|
+
"duplicate chapter label; combine its batch assignments",
|
|
3769
|
+
"duplicate section title",
|
|
3770
|
+
"every returned topic must cover a supplied batch Event",
|
|
3771
|
+
"every supplied batch Event must be assigned to a topic",
|
|
3772
|
+
"every topic batch Event must be assigned to a section",
|
|
3773
|
+
"expected only topics, with at most 12 entries",
|
|
3774
|
+
"new overview must match a declared or inherited section",
|
|
3775
|
+
"old overview evidence may only be preserved verbatim; rewritten entries must cite batch Events only",
|
|
3776
|
+
"overview must contain 0-8 source-grounded entries",
|
|
3777
|
+
"overview.sourceEventIds must contain unique, supplied Event ids (at most 12)",
|
|
3778
|
+
"overview.text must be nonempty and at most 600 characters",
|
|
3779
|
+
"overview.title must be nonempty and at most 80 characters",
|
|
3780
|
+
"section backfill must update only its existing chapter",
|
|
3781
|
+
"section.sourceEventIds must contain unique, supplied Event ids",
|
|
3782
|
+
"section.title must be nonempty and at most 80 characters",
|
|
3783
|
+
"sections must assign batch Events independently of overview",
|
|
3784
|
+
"topic.description must be nonempty and at most 400 characters",
|
|
3785
|
+
"topic.sourceEventIds must contain unique, supplied Event ids",
|
|
3786
|
+
"topic.title must be nonempty and at most 120 characters",
|
|
3787
|
+
"topicId must be nonempty and at most 200 characters",
|
|
3788
|
+
"truncated Event evidence may only support scope entries",
|
|
3789
|
+
"unexpected overview field",
|
|
3790
|
+
"unexpected section field",
|
|
3791
|
+
"unexpected topic field",
|
|
3792
|
+
"unknown or repeated topicId",
|
|
3793
|
+
"unknown overview kind",
|
|
3794
|
+
"topic input exceeds model context capacity",
|
|
3795
|
+
"output token limit reached",
|
|
3796
|
+
"structured topic schema mismatch",
|
|
3797
|
+
"tool arguments are not valid JSON",
|
|
3798
|
+
"expected exactly one structured topic call",
|
|
3799
|
+
"topic semantic validation failed",
|
|
3800
|
+
"structured model task timed out",
|
|
3801
|
+
"provider or route error",
|
|
3802
|
+
"topic worker failed",
|
|
3803
|
+
"source version changed; stale result rejected",
|
|
3804
|
+
"topic projection lease expired"
|
|
3805
|
+
]);
|
|
3806
|
+
function safeTopicDiagnostics(value) {
|
|
3807
|
+
if (!value || typeof value !== "object") return void 0;
|
|
3808
|
+
const input = value;
|
|
3809
|
+
if (!categories.includes(input.category)) return void 0;
|
|
3810
|
+
const result = {
|
|
3811
|
+
category: input.category,
|
|
3812
|
+
reason: typeof input.reason === "string" && TOPIC_SAFE_REASONS.has(input.reason) ? input.reason : "topic worker failed"
|
|
3813
|
+
};
|
|
3814
|
+
for (const key of [
|
|
3815
|
+
"eventCount",
|
|
3816
|
+
"candidateTopicCount",
|
|
3817
|
+
"estimatedInputTokens",
|
|
3818
|
+
"requestedOutputTokens",
|
|
3819
|
+
"maxOutputTokens",
|
|
3820
|
+
"modelCalls",
|
|
3821
|
+
"attempt",
|
|
3822
|
+
"splitDepth"
|
|
3823
|
+
]) {
|
|
3824
|
+
const number = input[key];
|
|
3825
|
+
if (typeof number === "number" && Number.isSafeInteger(number) && number >= 0) result[key] = number;
|
|
3826
|
+
}
|
|
3827
|
+
if (["stop", "max-tokens", "error", "aborted", "tool-calls", "unknown"].includes(String(input.finishReason))) {
|
|
3828
|
+
result.finishReason = input.finishReason;
|
|
3829
|
+
}
|
|
3830
|
+
if (["requested-unverified", "reasoning-observed", "fallback", "unavailable"].includes(String(input.reasoningOff))) {
|
|
3831
|
+
result.reasoningOff = input.reasoningOff;
|
|
3832
|
+
}
|
|
3833
|
+
if (typeof input.reasoningObserved === "boolean") result.reasoningObserved = input.reasoningObserved;
|
|
3834
|
+
return result;
|
|
3835
|
+
}
|
|
3836
|
+
var TopicProjectionError = class extends Error {
|
|
3837
|
+
diagnostics;
|
|
3838
|
+
constructor(category, reason, metadata = {}) {
|
|
3839
|
+
const diagnostics = safeTopicDiagnostics({ ...metadata, category, reason });
|
|
3840
|
+
super(`${diagnostics.category}: ${diagnostics.reason}`);
|
|
3841
|
+
this.name = "TopicProjectionError";
|
|
3842
|
+
this.diagnostics = diagnostics;
|
|
3843
|
+
}
|
|
3844
|
+
};
|
|
3845
|
+
|
|
3846
|
+
// packages/core/src/topics.ts
|
|
3730
3847
|
var MEMORY_TOPIC_PROJECTOR_VERSION = 3;
|
|
3731
3848
|
var TOPIC_BATCH_LIMIT = 12;
|
|
3732
3849
|
var TOPIC_CANDIDATE_LIMIT = 12;
|
|
@@ -3937,6 +4054,13 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
3937
4054
|
job.superseded = true;
|
|
3938
4055
|
job.lastError = "source-changed";
|
|
3939
4056
|
job.updatedAt = now;
|
|
4057
|
+
job.diagnostics = safeTopicDiagnostics({
|
|
4058
|
+
...job.diagnostics,
|
|
4059
|
+
category: "source-changed",
|
|
4060
|
+
reason: "source version changed; stale result rejected",
|
|
4061
|
+
eventCount: job.sourceEventIds.length,
|
|
4062
|
+
attempt: job.attempts
|
|
4063
|
+
});
|
|
3940
4064
|
}
|
|
3941
4065
|
const finished = this.state.jobs.filter((job) => job.status === "completed" || job.status === "failed" && job.nextRetryAt === null);
|
|
3942
4066
|
const retain = new Set([...finished.slice(-64), ...finished.filter((job) => job.status === "failed" && !job.superseded && job.attempts >= TOPIC_MAX_ATTEMPTS)].map((job) => job.id));
|
|
@@ -3962,6 +4086,14 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
3962
4086
|
}
|
|
3963
4087
|
recover(now) {
|
|
3964
4088
|
for (const job of this.state.jobs) if (job.status === "running" && Date.parse(job.leaseUntil ?? job.updatedAt) <= Date.parse(now)) {
|
|
4089
|
+
job.diagnostics = safeTopicDiagnostics({
|
|
4090
|
+
category: "timeout",
|
|
4091
|
+
reason: "topic projection lease expired",
|
|
4092
|
+
eventCount: job.sourceEventIds.length,
|
|
4093
|
+
candidateTopicCount: job.context?.existingTopics.length,
|
|
4094
|
+
attempt: job.attempts,
|
|
4095
|
+
splitDepth: job.splitDepth ?? 0
|
|
4096
|
+
});
|
|
3965
4097
|
job.status = "failed";
|
|
3966
4098
|
job.lastError = "timeout";
|
|
3967
4099
|
job.context = null;
|
|
@@ -3999,7 +4131,7 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
3999
4131
|
const versions = sourceVersions(sources2);
|
|
4000
4132
|
const running = this.state.jobs.find((job) => job.status === "running");
|
|
4001
4133
|
if (running) return Date.parse(running.leaseUntil ?? running.updatedAt) <= now || !jobInputsCurrent(running, versions);
|
|
4002
|
-
return this.pendingWork(events).some(({ event, sectionBackfillTopicId }) => this.matchesMode([event.id], versions, mode, sectionBackfillTopicId)) || this.state.jobs.some((job) => !job.superseded && job.projectorVersion === MEMORY_TOPIC_PROJECTOR_VERSION && job.attempts < TOPIC_MAX_ATTEMPTS && (job.status === "pending" || job.status === "failed" && job.nextRetryAt !== null && Date.parse(job.nextRetryAt) <= now) && jobInputsCurrent(job, versions) && this.matchesMode(job.sourceEventIds, versions, mode, job.sectionBackfillTopicId));
|
|
4134
|
+
return this.pendingWork(events).some(({ event, sectionBackfillTopicId }) => this.matchesMode([event.id], versions, mode, sectionBackfillTopicId)) || this.state.jobs.some((job) => !job.superseded && job.projectorVersion === MEMORY_TOPIC_PROJECTOR_VERSION && job.attempts < TOPIC_MAX_ATTEMPTS && (job.status === "pending" && (job.nextRetryAt === null || Date.parse(job.nextRetryAt) <= now) || job.status === "failed" && job.nextRetryAt !== null && Date.parse(job.nextRetryAt) <= now) && jobInputsCurrent(job, versions) && this.matchesMode(job.sourceEventIds, versions, mode, job.sectionBackfillTopicId));
|
|
4003
4135
|
}
|
|
4004
4136
|
claim(events, now, mode = "all") {
|
|
4005
4137
|
if (this.state.bootstrap?.projectorVersion !== MEMORY_TOPIC_PROJECTOR_VERSION) {
|
|
@@ -4010,7 +4142,7 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4010
4142
|
if (this.state.jobs.some((job2) => job2.status === "running")) return null;
|
|
4011
4143
|
const sources2 = new Map(events.filter(visible).map((event) => [event.id, event]));
|
|
4012
4144
|
const versions = sourceVersions(sources2);
|
|
4013
|
-
const eligible = this.state.jobs.filter((job2) => !job2.superseded && job2.attempts < TOPIC_MAX_ATTEMPTS && (job2.status === "pending" || job2.status === "failed" && job2.nextRetryAt !== null && Date.parse(job2.nextRetryAt) <= Date.parse(now)) && jobInputsCurrent(job2, versions) && this.matchesMode(job2.sourceEventIds, versions, mode, job2.sectionBackfillTopicId)).sort((a, b) => Number(this.isBootstrap(a.sourceEventIds[0], versions.get(a.sourceEventIds[0]), a.sectionBackfillTopicId)) - Number(this.isBootstrap(b.sourceEventIds[0], versions.get(b.sourceEventIds[0]), b.sectionBackfillTopicId)))[0];
|
|
4145
|
+
const eligible = this.state.jobs.filter((job2) => !job2.superseded && job2.attempts < TOPIC_MAX_ATTEMPTS && (job2.status === "pending" && (job2.nextRetryAt === null || Date.parse(job2.nextRetryAt) <= Date.parse(now)) || job2.status === "failed" && job2.nextRetryAt !== null && Date.parse(job2.nextRetryAt) <= Date.parse(now)) && jobInputsCurrent(job2, versions) && this.matchesMode(job2.sourceEventIds, versions, mode, job2.sectionBackfillTopicId)).sort((a, b) => Number(this.isBootstrap(a.sourceEventIds[0], versions.get(a.sourceEventIds[0]), a.sectionBackfillTopicId)) - Number(this.isBootstrap(b.sourceEventIds[0], versions.get(b.sourceEventIds[0]), b.sectionBackfillTopicId)))[0];
|
|
4014
4146
|
let job = eligible;
|
|
4015
4147
|
if (job && this.isBootstrap(job.sourceEventIds[0], versions.get(job.sourceEventIds[0]), job.sectionBackfillTopicId) && mode === "all" && this.pendingWork(events).some(({ event, sectionBackfillTopicId }) => !this.isBootstrap(event.id, versions.get(event.id), sectionBackfillTopicId))) job = void 0;
|
|
4016
4148
|
if (job?.status === "failed") {
|
|
@@ -4134,6 +4266,7 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4134
4266
|
...!storedById.get(topic.id).invalidated ? Object.keys(storedById.get(topic.id).dependencyVersions ?? {}).filter((id) => sources2.has(id)) : []
|
|
4135
4267
|
])]);
|
|
4136
4268
|
job.dependencyVersions = Object.fromEntries(dependencyIds.map((id) => [id, versions.get(id)]));
|
|
4269
|
+
job.diagnostics = void 0;
|
|
4137
4270
|
job.status = "running";
|
|
4138
4271
|
job.attempts += 1;
|
|
4139
4272
|
job.lastError = null;
|
|
@@ -4149,15 +4282,15 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4149
4282
|
const job = this.requireJob(id);
|
|
4150
4283
|
if (job.status === "completed") return { topicIds: [...job.topicIds] };
|
|
4151
4284
|
if (job.status !== "running" || !job.context) throw new Error(`Topic projection ${id} is not running`);
|
|
4152
|
-
if (Date.parse(job.leaseUntil ?? job.updatedAt) <= Date.parse(now)) throw new
|
|
4285
|
+
if (Date.parse(job.leaseUntil ?? job.updatedAt) <= Date.parse(now)) throw new TopicProjectionError("source-changed", "Topic projection lease expired; stale result rejected.");
|
|
4153
4286
|
const sources2 = new Map(events.filter(visible).map((event) => [event.id, event]));
|
|
4154
4287
|
const versions = sourceVersions(sources2);
|
|
4155
4288
|
if (!jobInputsCurrent(job, versions) || Object.entries(job.candidateVersions).some(([topicId, version]) => digest(this.state.topics.find((topic) => topic.id === topicId)) !== version)) {
|
|
4156
|
-
throw new
|
|
4289
|
+
throw new TopicProjectionError("source-changed", "Topic projection source version changed; stale result rejected.");
|
|
4157
4290
|
}
|
|
4158
|
-
if (!Array.isArray(result?.topics) || result.topics.length < 1 || result.topics.length > 12) throw new
|
|
4291
|
+
if (!Array.isArray(result?.topics) || result.topics.length < 1 || result.topics.length > 12) throw new TopicProjectionError("validation-failed", "Topic projection must return 1-12 topics.");
|
|
4159
4292
|
if (job.sectionBackfillTopicId && (result.topics.length !== 1 || result.topics[0].topicId !== job.sectionBackfillTopicId || result.topics[0].sections === void 0)) {
|
|
4160
|
-
throw new
|
|
4293
|
+
throw new TopicProjectionError("validation-failed", "Invalid section backfill: explicit sections must cover the existing chapter.");
|
|
4161
4294
|
}
|
|
4162
4295
|
const candidates = new Map(job.context.existingTopics.map((topic) => [topic.id, topic]));
|
|
4163
4296
|
const allowed = /* @__PURE__ */ new Set([...job.sourceEventIds, ...job.context.existingTopics.flatMap((topic) => topic.sourceEventIds)]);
|
|
@@ -4167,7 +4300,7 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4167
4300
|
const labels = /* @__PURE__ */ new Set();
|
|
4168
4301
|
const proposals = [];
|
|
4169
4302
|
const checkIds = (ids, label) => {
|
|
4170
|
-
if (!Array.isArray(ids) || ids.length === 0 || unique2(ids).length !== ids.length || ids.some((eventId) => typeof eventId !== "string" || !allowed.has(eventId))) throw new
|
|
4303
|
+
if (!Array.isArray(ids) || ids.length === 0 || unique2(ids).length !== ids.length || ids.some((eventId) => typeof eventId !== "string" || !allowed.has(eventId))) throw new TopicProjectionError("validation-failed", `Invalid topic ${label} source events.`);
|
|
4171
4304
|
return ids;
|
|
4172
4305
|
};
|
|
4173
4306
|
for (const proposal of result.topics) {
|
|
@@ -4177,45 +4310,45 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4177
4310
|
const label = normalizedLabel;
|
|
4178
4311
|
const sameLabel = typeof proposal.title === "string" ? this.state.topics.filter((topic) => !topic.invalidated && label(topic.title) === label(proposal.title)) : [];
|
|
4179
4312
|
if (sameLabel.some((topic) => !candidates.has(topic.id))) {
|
|
4180
|
-
throw new
|
|
4313
|
+
throw new TopicProjectionError("validation-failed", "Invalid duplicate chapter label matches an unexposed existing topic.");
|
|
4181
4314
|
}
|
|
4182
4315
|
const existing = proposal.topicId ? this.state.topics.find((topic) => topic.id === proposal.topicId) : inferred ?? (sameLabel.length === 1 ? sameLabel[0] : void 0);
|
|
4183
4316
|
if (sameLabel.some((topic) => topic.id !== existing?.id)) {
|
|
4184
|
-
throw new
|
|
4317
|
+
throw new TopicProjectionError("validation-failed", "Invalid duplicate chapter label conflicts with an existing topic.");
|
|
4185
4318
|
}
|
|
4186
4319
|
if (typeof proposal.title === "string") {
|
|
4187
4320
|
const key = label(proposal.title);
|
|
4188
|
-
if (labels.has(key)) throw new
|
|
4321
|
+
if (labels.has(key)) throw new TopicProjectionError("validation-failed", "Invalid duplicate chapter label in one projection.");
|
|
4189
4322
|
labels.add(key);
|
|
4190
4323
|
}
|
|
4191
|
-
if (proposal.topicId && (!existing || !candidates.has(proposal.topicId))) throw new
|
|
4324
|
+
if (proposal.topicId && (!existing || !candidates.has(proposal.topicId))) throw new TopicProjectionError("validation-failed", "Unknown or unexposed topic id.");
|
|
4192
4325
|
const topicId = existing?.id ?? `topic_${randomUUID()}`;
|
|
4193
|
-
if (touched.has(topicId)) throw new
|
|
4326
|
+
if (touched.has(topicId)) throw new TopicProjectionError("validation-failed", "A topic may be updated only once per projection.");
|
|
4194
4327
|
touched.add(topicId);
|
|
4195
|
-
if (typeof proposal.title !== "string" || !proposal.title.trim() || [...proposal.title].length > 120 || typeof proposal.description !== "string" || !proposal.description.trim() || [...proposal.description].length > 400) throw new
|
|
4328
|
+
if (typeof proposal.title !== "string" || !proposal.title.trim() || [...proposal.title].length > 120 || typeof proposal.description !== "string" || !proposal.description.trim() || [...proposal.description].length > 400) throw new TopicProjectionError("validation-failed", "Invalid topic title or description.");
|
|
4196
4329
|
for (const eventId of ids) if (batchIds.has(eventId)) covered.add(eventId);
|
|
4197
|
-
if (!Array.isArray(proposal.overview) || proposal.overview.length > 8) throw new
|
|
4330
|
+
if (!Array.isArray(proposal.overview) || proposal.overview.length > 8) throw new TopicProjectionError("validation-failed", "Topic projection must contain at most 8 overview paragraphs per chapter per batch.");
|
|
4198
4331
|
for (const part of proposal.overview) {
|
|
4199
|
-
if (part.title !== void 0 && (typeof part.title !== "string" || !part.title.trim() || [...part.title].length > 80)) throw new
|
|
4200
|
-
if (!["history", "decision", "change", "open-question", "scope"].includes(part.kind) || typeof part.text !== "string" || !part.text.trim() || [...part.text].length > 600) throw new
|
|
4332
|
+
if (part.title !== void 0 && (typeof part.title !== "string" || !part.title.trim() || [...part.title].length > 80)) throw new TopicProjectionError("validation-failed", "Invalid topic overview title.");
|
|
4333
|
+
if (!["history", "decision", "change", "open-question", "scope"].includes(part.kind) || typeof part.text !== "string" || !part.text.trim() || [...part.text].length > 600) throw new TopicProjectionError("validation-failed", "Invalid topic overview section.");
|
|
4201
4334
|
checkIds(part.sourceEventIds, "overview");
|
|
4202
4335
|
if (part.kind !== "scope" && part.sourceEventIds.some((eventId) => job.context.truncatedEventIds?.includes(eventId))) {
|
|
4203
|
-
throw new
|
|
4336
|
+
throw new TopicProjectionError("validation-failed", "Truncated topic sources permit navigation scope only, not factual overview.");
|
|
4204
4337
|
}
|
|
4205
|
-
if (part.sourceEventIds.length > 12 || part.sourceEventIds.some((eventId) => !ids.includes(eventId))) throw new
|
|
4206
|
-
if (part.sourceEventIds.some((eventId) => !batchIds.has(eventId)) && !candidates.get(topicId)?.overview.some((old) => digest(old) === digest(part))) throw new
|
|
4338
|
+
if (part.sourceEventIds.length > 12 || part.sourceEventIds.some((eventId) => !ids.includes(eventId))) throw new TopicProjectionError("validation-failed", "Overview references must belong to returned topic membership.");
|
|
4339
|
+
if (part.sourceEventIds.some((eventId) => !batchIds.has(eventId)) && !candidates.get(topicId)?.overview.some((old) => digest(old) === digest(part))) throw new TopicProjectionError("validation-failed", "Old overview evidence must be preserved verbatim; new sections need batch events.");
|
|
4207
4340
|
}
|
|
4208
|
-
if (proposal.sections !== void 0 && !Array.isArray(proposal.sections)) throw new
|
|
4341
|
+
if (proposal.sections !== void 0 && !Array.isArray(proposal.sections)) throw new TopicProjectionError("validation-failed", "Invalid topic sections.");
|
|
4209
4342
|
const assignments = memoryTopicMembershipSections(proposal);
|
|
4210
4343
|
const sectionLabels = /* @__PURE__ */ new Set();
|
|
4211
4344
|
for (const section of assignments) {
|
|
4212
|
-
if (!section || typeof section.title !== "string" || !section.title.trim() || [...section.title].length > 80 || sectionLabels.has(label(section.title))) throw new
|
|
4345
|
+
if (!section || typeof section.title !== "string" || !section.title.trim() || [...section.title].length > 80 || sectionLabels.has(label(section.title))) throw new TopicProjectionError("validation-failed", "Invalid or duplicate topic section title.");
|
|
4213
4346
|
sectionLabels.add(label(section.title));
|
|
4214
4347
|
checkIds(section.sourceEventIds, "section");
|
|
4215
|
-
if (section.sourceEventIds.some((eventId) => !ids.includes(eventId))) throw new
|
|
4216
|
-
if (section.sourceEventIds.some((eventId) => !batchIds.has(eventId)) && !memoryTopicMembershipSections(existing ?? { overview: [] }).some((old) => label(old.title) === label(section.title) && section.sourceEventIds.filter((eventId) => !batchIds.has(eventId)).every((eventId) => old.sourceEventIds.includes(eventId)))) throw new
|
|
4348
|
+
if (section.sourceEventIds.some((eventId) => !ids.includes(eventId))) throw new TopicProjectionError("validation-failed", "Section references must belong to returned topic membership.");
|
|
4349
|
+
if (section.sourceEventIds.some((eventId) => !batchIds.has(eventId)) && !memoryTopicMembershipSections(existing ?? { overview: [] }).some((old) => label(old.title) === label(section.title) && section.sourceEventIds.filter((eventId) => !batchIds.has(eventId)).every((eventId) => old.sourceEventIds.includes(eventId)))) throw new TopicProjectionError("validation-failed", "Old section membership must be preserved; reassignment needs batch events.");
|
|
4217
4350
|
}
|
|
4218
|
-
if (proposal.sections !== void 0 && ids.some((eventId) => batchIds.has(eventId) && !assignments.some((section) => section.sourceEventIds.includes(eventId)))) throw new
|
|
4351
|
+
if (proposal.sections !== void 0 && ids.some((eventId) => batchIds.has(eventId) && !assignments.some((section) => section.sourceEventIds.includes(eventId)))) throw new TopicProjectionError("validation-failed", "Topic sections omitted batch events.");
|
|
4219
4352
|
const membership = unique2([...(existing?.sourceEventIds ?? []).filter((eventId) => sources2.has(eventId)), ...ids]);
|
|
4220
4353
|
const assignedIds = new Set(proposal.sections === void 0 ? [] : assignments.flatMap((section) => section.sourceEventIds).filter((eventId) => batchIds.has(eventId)));
|
|
4221
4354
|
const sections = existing && !existing.invalidated ? memoryTopicMembershipSections(existing) : [];
|
|
@@ -4231,7 +4364,7 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4231
4364
|
const inherited = existing && !existing.invalidated ? existing.overview : [];
|
|
4232
4365
|
if (proposal.sections !== void 0) for (const part of proposal.overview) {
|
|
4233
4366
|
if (!inherited.some((old) => digest(old) === digest(part)) && !memoryTopicOverviewMatchesSection(part, titles)) {
|
|
4234
|
-
throw new
|
|
4367
|
+
throw new TopicProjectionError("validation-failed", "New topic overview must match a final topic section.");
|
|
4235
4368
|
}
|
|
4236
4369
|
}
|
|
4237
4370
|
const overview2 = unique2(proposal.overview.filter((part) => memoryTopicOverviewMatchesSection(part, titles)).map((part) => JSON.stringify(part))).map((part) => JSON.parse(part));
|
|
@@ -4251,7 +4384,7 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4251
4384
|
updatedAt: now
|
|
4252
4385
|
});
|
|
4253
4386
|
}
|
|
4254
|
-
if (job.sourceEventIds.some((eventId) => !covered.has(eventId))) throw new
|
|
4387
|
+
if (job.sourceEventIds.some((eventId) => !covered.has(eventId))) throw new TopicProjectionError("validation-failed", "every supplied batch Event must be assigned to a topic");
|
|
4255
4388
|
for (const topic of proposals) {
|
|
4256
4389
|
const index = this.state.topics.findIndex((current) => current.id === topic.id);
|
|
4257
4390
|
if (index < 0) this.state.topics.push(topic);
|
|
@@ -4281,13 +4414,49 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4281
4414
|
fail(id, error, now) {
|
|
4282
4415
|
const job = this.state.jobs.find((candidate) => candidate.id === id);
|
|
4283
4416
|
if (!job || job.status !== "running") return;
|
|
4417
|
+
const diagnostic = error instanceof TopicProjectionError ? safeTopicDiagnostics(error.diagnostics) : void 0;
|
|
4418
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4419
|
+
const category = diagnostic?.category ?? (/timeout|timed out/i.test(message) ? "timeout" : /source.*changed|stale|not running/i.test(message) ? "source-changed" : "worker-failed");
|
|
4420
|
+
job.diagnostics = safeTopicDiagnostics({
|
|
4421
|
+
...diagnostic,
|
|
4422
|
+
category,
|
|
4423
|
+
reason: diagnostic?.reason ?? (category === "timeout" ? "structured model task timed out" : category === "source-changed" ? "source version changed; stale result rejected" : "topic worker failed"),
|
|
4424
|
+
eventCount: job.sourceEventIds.length,
|
|
4425
|
+
candidateTopicCount: diagnostic?.candidateTopicCount ?? job.context?.existingTopics.length,
|
|
4426
|
+
attempt: job.attempts,
|
|
4427
|
+
splitDepth: job.splitDepth ?? 0
|
|
4428
|
+
});
|
|
4284
4429
|
job.status = "failed";
|
|
4285
4430
|
job.context = null;
|
|
4286
4431
|
job.leaseUntil = null;
|
|
4287
|
-
|
|
4288
|
-
job.lastError = /timeout|timed out/i.test(message) ? "timeout" : /source.*changed|stale|not running/i.test(message) ? "source-changed" : /invalid|omitted|unknown|overview|projection must|truncat/i.test(message) ? "invalid-output" : "worker-failed";
|
|
4432
|
+
job.lastError = category === "validation-failed" ? `${category}: ${job.diagnostics.reason}` : category;
|
|
4289
4433
|
job.nextRetryAt = job.attempts < TOPIC_MAX_ATTEMPTS ? new Date(Date.parse(now) + 3e4 * 2 ** Math.max(0, job.attempts - 1)).toISOString() : null;
|
|
4290
4434
|
job.updatedAt = now;
|
|
4435
|
+
if (category === "max-tokens" && diagnostic?.finishReason === "max-tokens" && job.sourceEventIds.length > 1 && (job.splitDepth ?? 0) < 4) {
|
|
4436
|
+
const middle = Math.ceil(job.sourceEventIds.length / 2);
|
|
4437
|
+
for (const ids of [job.sourceEventIds.slice(0, middle), job.sourceEventIds.slice(middle)]) {
|
|
4438
|
+
this.state.jobs.push({
|
|
4439
|
+
...structuredClone(job),
|
|
4440
|
+
id: `tproj_${randomUUID()}`,
|
|
4441
|
+
sourceEventIds: ids,
|
|
4442
|
+
sourceVersions: Object.fromEntries(ids.map((id2) => [id2, job.sourceVersions[id2]])),
|
|
4443
|
+
dependencyVersions: {},
|
|
4444
|
+
candidateVersions: {},
|
|
4445
|
+
status: "pending",
|
|
4446
|
+
attempts: 0,
|
|
4447
|
+
diagnostics: void 0,
|
|
4448
|
+
splitDepth: (job.splitDepth ?? 0) + 1,
|
|
4449
|
+
topicIds: [],
|
|
4450
|
+
superseded: false,
|
|
4451
|
+
lastError: null,
|
|
4452
|
+
nextRetryAt: job.nextRetryAt,
|
|
4453
|
+
createdAt: now,
|
|
4454
|
+
updatedAt: now
|
|
4455
|
+
});
|
|
4456
|
+
}
|
|
4457
|
+
job.superseded = true;
|
|
4458
|
+
job.nextRetryAt = null;
|
|
4459
|
+
}
|
|
4291
4460
|
}
|
|
4292
4461
|
requireJob(id) {
|
|
4293
4462
|
const job = this.state.jobs.find((candidate) => candidate.id === id);
|
|
@@ -4311,6 +4480,8 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4311
4480
|
dependencyVersions: {},
|
|
4312
4481
|
candidateVersions: {},
|
|
4313
4482
|
topicIds: [],
|
|
4483
|
+
diagnostics: void 0,
|
|
4484
|
+
splitDepth: 0,
|
|
4314
4485
|
lastError: null,
|
|
4315
4486
|
nextRetryAt: null,
|
|
4316
4487
|
leaseUntil: null,
|
|
@@ -7754,7 +7925,7 @@ ${JSON_RETRY_INSTRUCTION}`, tool: toolSchema("topicProjector"), payload }));
|
|
|
7754
7925
|
}
|
|
7755
7926
|
function parseTopicProjection(value, context) {
|
|
7756
7927
|
const fail = (message) => {
|
|
7757
|
-
throw new
|
|
7928
|
+
throw new TopicProjectionError("validation-failed", message);
|
|
7758
7929
|
};
|
|
7759
7930
|
const raw = object(value);
|
|
7760
7931
|
if (Object.keys(raw).some((key) => key !== "topics") || !Array.isArray(raw.topics) || raw.topics.length > 12) {
|
|
@@ -7841,6 +8012,7 @@ function parseTopicProjection(value, context) {
|
|
|
7841
8012
|
if ([...eventIds].some((id) => !covered.has(id))) fail("every supplied batch Event must be assigned to a topic");
|
|
7842
8013
|
return { topics };
|
|
7843
8014
|
}
|
|
8015
|
+
var TOPIC_OUTPUT_TOKEN_BUDGET = 32768;
|
|
7844
8016
|
var DshModelBridge = class {
|
|
7845
8017
|
constructor(ctx, config, liveStructuredReasoningEffort) {
|
|
7846
8018
|
this.ctx = ctx;
|
|
@@ -7849,12 +8021,14 @@ var DshModelBridge = class {
|
|
|
7849
8021
|
this.structuredReasoningEffort = config.structuredReasoningEffort ?? "auto";
|
|
7850
8022
|
this.ctx.on?.("llm/adapters-updated", () => {
|
|
7851
8023
|
this.offCapabilities.clear();
|
|
8024
|
+
this.contextWindows.clear();
|
|
7852
8025
|
for (const listener of this.adaptersUpdatedListeners) listener();
|
|
7853
8026
|
});
|
|
7854
8027
|
}
|
|
7855
8028
|
ctx;
|
|
7856
8029
|
config;
|
|
7857
8030
|
liveStructuredReasoningEffort;
|
|
8031
|
+
contextWindows = /* @__PURE__ */ new Map();
|
|
7858
8032
|
sessions = new AsyncLocalStorage();
|
|
7859
8033
|
successfulResponses = [];
|
|
7860
8034
|
offCapabilities = /* @__PURE__ */ new Map();
|
|
@@ -8084,7 +8258,7 @@ Use project scope for repository decisions, user scope for stable preferences/id
|
|
|
8084
8258
|
return { reason: text2(raw.reason, "Projected Event evidence into the Knowledge Graph."), nodes, edges };
|
|
8085
8259
|
};
|
|
8086
8260
|
topicProjector = async (context) => {
|
|
8087
|
-
const { payload, shownContext } = topicProjectionPayload(context,
|
|
8261
|
+
const { payload, shownContext } = topicProjectionPayload(context, TOPIC_OUTPUT_TOKEN_BUDGET);
|
|
8088
8262
|
try {
|
|
8089
8263
|
const raw = await this.callStructured(
|
|
8090
8264
|
"topicProjector",
|
|
@@ -8092,13 +8266,16 @@ Use project scope for repository decisions, user scope for stable preferences/id
|
|
|
8092
8266
|
payload,
|
|
8093
8267
|
(value) => {
|
|
8094
8268
|
parseTopicProjection(value, shownContext);
|
|
8095
|
-
}
|
|
8269
|
+
},
|
|
8270
|
+
{ eventCount: shownContext.events.length, candidateTopicCount: shownContext.existingTopics.length }
|
|
8096
8271
|
);
|
|
8097
8272
|
return parseTopicProjection(raw, shownContext);
|
|
8098
8273
|
} catch (error) {
|
|
8099
|
-
if (error instanceof
|
|
8100
|
-
if (error instanceof Error && /^StrataGate structured model task timed out after \d+ms$/.test(error.message))
|
|
8101
|
-
|
|
8274
|
+
if (error instanceof TopicProjectionError) throw error;
|
|
8275
|
+
if (error instanceof Error && /^StrataGate structured model task timed out after \d+ms$/.test(error.message)) {
|
|
8276
|
+
throw new TopicProjectionError("timeout", "structured model task timed out");
|
|
8277
|
+
}
|
|
8278
|
+
throw new TopicProjectionError("provider-failed", "provider or route error");
|
|
8102
8279
|
}
|
|
8103
8280
|
};
|
|
8104
8281
|
externalMemoryDecider = async (context) => {
|
|
@@ -8150,17 +8327,25 @@ Use project scope for repository decisions, user scope for stable preferences/id
|
|
|
8150
8327
|
}
|
|
8151
8328
|
return proposed;
|
|
8152
8329
|
}
|
|
8153
|
-
async callStructured(kind, system, payload, validate) {
|
|
8330
|
+
async callStructured(kind, system, payload, validate, topicMetadata) {
|
|
8154
8331
|
const execution = this.sessions.getStore();
|
|
8155
8332
|
if (!execution) throw new Error("StrataGate model callback ran without an execution context");
|
|
8156
8333
|
const baseRoute = this.resolveRoute(execution.session);
|
|
8157
8334
|
const routeKey = `${baseRoute.provider}\0${baseRoute.model}`;
|
|
8158
8335
|
let useOff = await this.shouldUseOff(baseRoute);
|
|
8336
|
+
const isTopic = kind === "topicProjector";
|
|
8337
|
+
const topicDiagnostics = {
|
|
8338
|
+
...topicMetadata,
|
|
8339
|
+
requestedOutputTokens: TOPIC_OUTPUT_TOKEN_BUDGET,
|
|
8340
|
+
modelCalls: 0
|
|
8341
|
+
};
|
|
8342
|
+
const topicError = (category, reason) => new TopicProjectionError(category, reason, topicDiagnostics);
|
|
8159
8343
|
let lastError;
|
|
8160
8344
|
let lastResponse = "";
|
|
8161
8345
|
let attemptsUsed = 0;
|
|
8162
8346
|
let noAdapterRetried = false;
|
|
8163
8347
|
let graphRetryFeedback = "";
|
|
8348
|
+
let offFallback = false;
|
|
8164
8349
|
for (let attempt = 1; attempt <= JSON_RESPONSE_ATTEMPTS; attempt += 1) {
|
|
8165
8350
|
attemptsUsed = attempt;
|
|
8166
8351
|
const message = createUserMessage({
|
|
@@ -8184,10 +8369,33 @@ ${JSON_RETRY_INSTRUCTION}${graphRetryFeedback}`,
|
|
|
8184
8369
|
type: "function",
|
|
8185
8370
|
function: { name: STRUCTURED_TOOLS[kind].name }
|
|
8186
8371
|
},
|
|
8187
|
-
maxTokens: kind === "profileMaintenance" ? Math.max(this.config.maxOutputTokens, Math.min(12e3, 512 + 2 * Array.from(JSON.stringify(payload)).length)) : this.config.maxOutputTokens,
|
|
8372
|
+
maxTokens: kind === "profileMaintenance" ? Math.max(this.config.maxOutputTokens, Math.min(12e3, 512 + 2 * Array.from(JSON.stringify(payload)).length)) : isTopic ? TOPIC_OUTPUT_TOKEN_BUDGET : this.config.maxOutputTokens,
|
|
8188
8373
|
sessionId: execution.sessionId,
|
|
8189
8374
|
purpose: "compaction"
|
|
8190
8375
|
};
|
|
8376
|
+
if (isTopic) {
|
|
8377
|
+
const estimateInput = () => estimateTokens(JSON.stringify({ system: request.system, tools: request.tools, messages: request.messages }));
|
|
8378
|
+
let inputTokens = estimateInput();
|
|
8379
|
+
const window = this.contextWindows.get(routeKey);
|
|
8380
|
+
if (window !== void 0 && window - inputTokens - 1024 < 256) {
|
|
8381
|
+
Object.assign(topicDiagnostics, { estimatedInputTokens: inputTokens, maxOutputTokens: 0 });
|
|
8382
|
+
throw topicError("validation-failed", "topic input exceeds model context capacity");
|
|
8383
|
+
}
|
|
8384
|
+
if (window !== void 0) request.maxTokens = Math.min(TOPIC_OUTPUT_TOKEN_BUDGET, Math.floor(window - inputTokens - 1024));
|
|
8385
|
+
request.messages = [{ ...message, content: [{ type: "text", text: JSON.stringify({
|
|
8386
|
+
...payload,
|
|
8387
|
+
outputTokenBudget: request.maxTokens
|
|
8388
|
+
}) }] }];
|
|
8389
|
+
inputTokens = estimateInput();
|
|
8390
|
+
delete topicDiagnostics.finishReason;
|
|
8391
|
+
delete topicDiagnostics.reasoningObserved;
|
|
8392
|
+
Object.assign(topicDiagnostics, {
|
|
8393
|
+
estimatedInputTokens: inputTokens,
|
|
8394
|
+
maxOutputTokens: request.maxTokens,
|
|
8395
|
+
modelCalls: (topicDiagnostics.modelCalls ?? 0) + 1,
|
|
8396
|
+
reasoningOff: useOff ? "requested-unverified" : offFallback ? "fallback" : "unavailable"
|
|
8397
|
+
});
|
|
8398
|
+
}
|
|
8191
8399
|
try {
|
|
8192
8400
|
await this.consumeStructuredStream(request, assembler);
|
|
8193
8401
|
} catch (error) {
|
|
@@ -8199,13 +8407,19 @@ ${JSON_RETRY_INSTRUCTION}${graphRetryFeedback}`,
|
|
|
8199
8407
|
if (useOff && isOffRejection(error)) {
|
|
8200
8408
|
this.offCapabilities.set(routeKey, "unsupported");
|
|
8201
8409
|
useOff = false;
|
|
8410
|
+
offFallback = true;
|
|
8202
8411
|
this.warnOffFallbackOnce(routeKey, `${baseRoute.provider}/${baseRoute.model} rejected reasoningEffort=off; retrying once without it`);
|
|
8203
8412
|
attempt -= 1;
|
|
8204
8413
|
continue;
|
|
8205
8414
|
}
|
|
8415
|
+
if (isTopic) {
|
|
8416
|
+
const timedOut = error instanceof Error && /^StrataGate structured model task timed out after \d+ms$/.test(error.message);
|
|
8417
|
+
throw topicError(timedOut ? "timeout" : "provider-failed", timedOut ? "structured model task timed out" : "provider or route error");
|
|
8418
|
+
}
|
|
8206
8419
|
throw error;
|
|
8207
8420
|
}
|
|
8208
8421
|
const finish = assembler.finish;
|
|
8422
|
+
if (isTopic) topicDiagnostics.finishReason = finish.kind;
|
|
8209
8423
|
if (finish.kind === "error" || finish.kind === "aborted") {
|
|
8210
8424
|
const failure = new Error(`StrataGate model call failed [${finish.failure.code}]: ${finish.failure.message}`);
|
|
8211
8425
|
if (!noAdapterRetried && isNoAdapter(finish.failure)) {
|
|
@@ -8216,26 +8430,34 @@ ${JSON_RETRY_INSTRUCTION}${graphRetryFeedback}`,
|
|
|
8216
8430
|
if (useOff && isOffRejection(finish.failure)) {
|
|
8217
8431
|
this.offCapabilities.set(routeKey, "unsupported");
|
|
8218
8432
|
useOff = false;
|
|
8433
|
+
offFallback = true;
|
|
8219
8434
|
this.warnOffFallbackOnce(routeKey, `${baseRoute.provider}/${baseRoute.model} rejected reasoningEffort=off; retrying once without it`);
|
|
8220
8435
|
attempt -= 1;
|
|
8221
8436
|
continue;
|
|
8222
8437
|
}
|
|
8438
|
+
if (isTopic) throw topicError("provider-failed", "provider or route error");
|
|
8223
8439
|
throw failure;
|
|
8224
8440
|
}
|
|
8225
8441
|
if (useOff) this.offCapabilities.set(routeKey, "supported");
|
|
8226
8442
|
const blocks = assembler.blocks();
|
|
8227
8443
|
const calls = blocks.filter((block) => block.type === "tool-call");
|
|
8228
|
-
|
|
8444
|
+
if (isTopic) {
|
|
8445
|
+
topicDiagnostics.reasoningObserved = blocks.some((block) => block.type === "reasoning" && block.text.trim().length > 0);
|
|
8446
|
+
if (topicDiagnostics.reasoningObserved && useOff) topicDiagnostics.reasoningOff = "reasoning-observed";
|
|
8447
|
+
if (finish.kind === "max-tokens") throw topicError("max-tokens", "output token limit reached");
|
|
8448
|
+
}
|
|
8449
|
+
const responseForError = isTopic ? "" : `${renderBlocksForDiagnostics(blocks, finish.kind)}
|
|
8229
8450
|
[finish=${finish.kind}; toolCalls=${calls.length}]`;
|
|
8230
8451
|
lastResponse = responseForError;
|
|
8231
8452
|
try {
|
|
8232
8453
|
const expectedTool = STRUCTURED_TOOLS[kind].name;
|
|
8233
8454
|
let parsed;
|
|
8234
8455
|
if (calls.length !== 1 || calls[0]?.name !== expectedTool) {
|
|
8235
|
-
const textFallback = blocks.filter((block) => block.type === "text" || block.type === "reasoning").map((block) => block.text).join("\n");
|
|
8456
|
+
const textFallback = blocks.filter((block) => block.type === "text" || !isTopic && block.type === "reasoning").map((block) => block.text).join("\n");
|
|
8236
8457
|
try {
|
|
8237
8458
|
parsed = parseJsonResponse(textFallback, STRUCTURED_FIELDS[kind]);
|
|
8238
8459
|
} catch {
|
|
8460
|
+
if (isTopic) throw topicError("schema-invalid", "expected exactly one structured topic call");
|
|
8239
8461
|
throw new ModelJsonResponseError(
|
|
8240
8462
|
`StrataGate model response did not call ${expectedTool} exactly once`,
|
|
8241
8463
|
{ response: responseForError }
|
|
@@ -8245,6 +8467,7 @@ ${JSON_RETRY_INSTRUCTION}${graphRetryFeedback}`,
|
|
|
8245
8467
|
try {
|
|
8246
8468
|
parsed = JSON.parse(calls[0].arguments);
|
|
8247
8469
|
} catch {
|
|
8470
|
+
if (isTopic) throw topicError("schema-invalid", "tool arguments are not valid JSON");
|
|
8248
8471
|
throw new ModelJsonResponseError(
|
|
8249
8472
|
`StrataGate ${expectedTool} arguments were not valid JSON`,
|
|
8250
8473
|
{ response: responseForError }
|
|
@@ -8254,13 +8477,14 @@ ${JSON_RETRY_INSTRUCTION}${graphRetryFeedback}`,
|
|
|
8254
8477
|
if (kind === "graphProjector") parsed = normalizeGraphNameProvenance(parsed);
|
|
8255
8478
|
const violations = validateArgs(STRUCTURED_TOOLS[kind].parameters, parsed);
|
|
8256
8479
|
if (violations.length > 0) {
|
|
8480
|
+
if (isTopic) throw topicError("schema-invalid", "structured topic schema mismatch");
|
|
8257
8481
|
if (kind === "graphProjector") {
|
|
8258
8482
|
const paths = violations.flatMap((violation) => violation.match(/nodes\[\d+\]\.metadataProvenance\.name(?:\[\d+\])?/g) ?? []);
|
|
8259
8483
|
graphRetryFeedback = paths.length > 0 ? `
|
|
8260
8484
|
Correct these canonical-name provenance fields: ${[...new Set(paths)].slice(0, 24).join(", ")}. metadataProvenance.name must be an array of supplied Event ID strings, e.g. ["evt_x"], supporting the actual node.name. Only aliases/tags use { value, sourceEventIds } objects. Do not substitute provenance for a different name.` : "";
|
|
8261
8485
|
}
|
|
8262
8486
|
throw new ModelJsonResponseError(
|
|
8263
|
-
`StrataGate ${expectedTool} arguments were invalid: ${
|
|
8487
|
+
`StrataGate ${expectedTool} arguments were invalid: ${violations.join("; ")}`,
|
|
8264
8488
|
{ response: responseForError }
|
|
8265
8489
|
);
|
|
8266
8490
|
}
|
|
@@ -8276,6 +8500,7 @@ Correct these canonical-name provenance fields: ${[...new Set(paths)].slice(0, 2
|
|
|
8276
8500
|
try {
|
|
8277
8501
|
validate?.(parsed);
|
|
8278
8502
|
} catch (error) {
|
|
8503
|
+
if (isTopic) throw topicError("validation-failed", error instanceof TopicProjectionError ? error.diagnostics.reason : "topic semantic validation failed");
|
|
8279
8504
|
throw new ModelJsonResponseError(
|
|
8280
8505
|
`StrataGate ${expectedTool} arguments were invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
8281
8506
|
{ cause: error, response: responseForError }
|
|
@@ -8292,7 +8517,7 @@ Correct these canonical-name provenance fields: ${[...new Set(paths)].slice(0, 2
|
|
|
8292
8517
|
}
|
|
8293
8518
|
return parsed;
|
|
8294
8519
|
} catch (error) {
|
|
8295
|
-
if (!(error instanceof ModelJsonResponseError)) throw error;
|
|
8520
|
+
if (!(error instanceof ModelJsonResponseError) && !(isTopic && error instanceof TopicProjectionError)) throw error;
|
|
8296
8521
|
lastError = finish.kind === "max-tokens" ? new ModelJsonResponseError(
|
|
8297
8522
|
`StrataGate ${STRUCTURED_TOOLS[kind].name} call was truncated before valid arguments`,
|
|
8298
8523
|
{ cause: error, response: responseForError }
|
|
@@ -8303,6 +8528,7 @@ Correct these canonical-name provenance fields: ${[...new Set(paths)].slice(0, 2
|
|
|
8303
8528
|
}
|
|
8304
8529
|
}
|
|
8305
8530
|
}
|
|
8531
|
+
if (isTopic && lastError instanceof TopicProjectionError) throw lastError;
|
|
8306
8532
|
const validationDetail = lastError?.message ? `: ${lastError.message}` : "";
|
|
8307
8533
|
throw new ModelJsonResponseError(
|
|
8308
8534
|
`StrataGate model did not produce a valid ${STRUCTURED_TOOLS[kind].name} call after ${attemptsUsed} attempt${attemptsUsed === 1 ? "" : "s"}${validationDetail}`,
|
|
@@ -8339,6 +8565,7 @@ Correct these canonical-name provenance fields: ${[...new Set(paths)].slice(0, 2
|
|
|
8339
8565
|
timer.unref?.();
|
|
8340
8566
|
})
|
|
8341
8567
|
]);
|
|
8568
|
+
if (Number.isFinite(info.context?.contextWindow) && info.context.contextWindow > 0) this.contextWindows.set(key, info.context.contextWindow);
|
|
8342
8569
|
if (!info.reasoning) return this.currentStructuredReasoningEffort() === "force-off";
|
|
8343
8570
|
const supported = info.reasoning.efforts.some(({ id }) => String(id) === "off");
|
|
8344
8571
|
this.offCapabilities.set(key, supported ? "supported" : "unsupported");
|
|
@@ -8790,11 +9017,11 @@ function topicNavigation(topics, events) {
|
|
|
8790
9017
|
coverage: topic.coverage,
|
|
8791
9018
|
isFallback: topic.isFallback === true
|
|
8792
9019
|
}));
|
|
8793
|
-
const
|
|
9020
|
+
const categories2 = CATEGORIES.map((category) => ({
|
|
8794
9021
|
...category,
|
|
8795
9022
|
count: entries.filter((entry) => entry.category === category.id).length
|
|
8796
9023
|
})).filter(({ count }) => count > 0);
|
|
8797
|
-
return { entries, categories };
|
|
9024
|
+
return { entries, categories: categories2 };
|
|
8798
9025
|
}
|
|
8799
9026
|
function topicPage(topics, events, options = {}) {
|
|
8800
9027
|
const navigation = topicNavigation(topics, events);
|
|
@@ -8818,27 +9045,58 @@ function lineText(value, maximum) {
|
|
|
8818
9045
|
return value.replace(/\s+/g, " ").trim().slice(0, maximum);
|
|
8819
9046
|
}
|
|
8820
9047
|
function renderMemoryDirectory(topics, events) {
|
|
8821
|
-
const { entries, categories } = topicNavigation(topics, events);
|
|
9048
|
+
const { entries, categories: categories2 } = topicNavigation(topics, events);
|
|
8822
9049
|
if (entries.length === 0) return "";
|
|
8823
|
-
const header = "[StrataGate \u8BB0\u5FC6\u76EE\u5F55]\n\u4EC5\u4F9B\u5BFC\u822A\uFF0C\
|
|
8824
|
-
const
|
|
8825
|
-
const
|
|
8826
|
-
|
|
9050
|
+
const header = "[StrataGate \u8BB0\u5FC6\u76EE\u5F55]\n\u4EC5\u4F9B\u5BFC\u822A\uFF0C\u4E0D\u662F\u4E8B\u5B9E\u8BC1\u636E\u3002\u9700\u8981\u4E3B\u9898\u8109\u7EDC\u65F6\u7528 memory_expand_topic\uFF1B\u9700\u8981\u4E8B\u5B9E\u8BC1\u636E\u53EF\u76F4\u63A5 memory_search_events(topic_id)\uFF0C\u4E5F\u53EF\u76F4\u63A5\u641C\u7D22\u4E8B\u4EF6/\u56FE\u8C31\u3002";
|
|
9051
|
+
const byId = new Map(topics.map((topic) => [topic.id, topic]));
|
|
9052
|
+
const chapters = entries.map((entry) => ({
|
|
9053
|
+
...entry,
|
|
9054
|
+
line: `- ${entry.id}\uFF1A${lineText(entry.title, 64)}${entry.description ? `\uFF1B${lineText(entry.description, 48)}` : ""}`,
|
|
9055
|
+
// Reuse the book's stable section order, including summary-free and legacy sections.
|
|
9056
|
+
sections: memoryTopicSections(byId.get(entry.id)).map(({ title }) => ` - ${lineText(title, 64)}`)
|
|
9057
|
+
}));
|
|
9058
|
+
const render = (prefix, selected2) => `${prefix}
|
|
9059
|
+
${selected2.flatMap(({ line, sections }) => [line, ...sections]).join("\n")}`.trim();
|
|
9060
|
+
const complete = render(header, chapters);
|
|
8827
9061
|
if (estimateTokens(complete) <= MEMORY_DIRECTORY_TOKEN_BUDGET) return complete;
|
|
8828
|
-
const branches =
|
|
9062
|
+
const branches = categories2.map(({ id, label, count }) => `${id}\uFF08${label} ${count}\uFF09`).join("\uFF1B");
|
|
8829
9063
|
const navigation = `${header}
|
|
8830
|
-
\u5171 ${entries.length} \u9879\uFF1B\u5206\u7C7B\uFF1A${branches}\u3002\u5B8C\u6574\u76EE\u5F55\u7528 memory_list_topics(category, offset) \u5206\u9875\uFF1Bquery \u53EF\u67E5\u4E3B\u9898\
|
|
8831
|
-
|
|
8832
|
-
|
|
8833
|
-
|
|
8834
|
-
const
|
|
8835
|
-
|
|
8836
|
-
|
|
9064
|
+
\u5171 ${entries.length} \u9879\uFF1B\u5206\u7C7B\uFF1A${branches}\u3002\u76EE\u5F55\u5DF2\u88C1\u526A\uFF1B\u5B8C\u6574\u76EE\u5F55\u7528 memory_list_topics(category, offset) \u5206\u9875\uFF1Bquery \u53EF\u67E5\u4E3B\u9898\uFF0C\u5C0F\u8282\u7528 memory_expand_topic(id)\u3002`;
|
|
9065
|
+
let selected = chapters.map((chapter) => ({ ...chapter, sections: [] }));
|
|
9066
|
+
if (estimateTokens(render(navigation, selected)) > MEMORY_DIRECTORY_TOKEN_BUDGET) {
|
|
9067
|
+
selected = [];
|
|
9068
|
+
const admit = (chapter) => {
|
|
9069
|
+
for (const line of [chapter.line, `- ${chapter.id}\uFF1A${lineText(chapter.title, 64)}`]) {
|
|
9070
|
+
const candidate = { ...chapter, line, sections: [] };
|
|
9071
|
+
if (estimateTokens(render(navigation, [...selected, candidate])) <= MEMORY_DIRECTORY_TOKEN_BUDGET) {
|
|
9072
|
+
selected.push(candidate);
|
|
9073
|
+
return;
|
|
9074
|
+
}
|
|
9075
|
+
}
|
|
9076
|
+
};
|
|
9077
|
+
for (const category of categories2) admit(chapters.find((chapter) => chapter.category === category.id));
|
|
9078
|
+
for (const chapter of chapters) if (!selected.some(({ id }) => id === chapter.id)) admit(chapter);
|
|
8837
9079
|
}
|
|
8838
9080
|
const order = new Map(entries.map(({ id }, index) => [id, index]));
|
|
8839
9081
|
selected.sort((a, b) => order.get(a.id) - order.get(b.id));
|
|
8840
|
-
|
|
8841
|
-
|
|
9082
|
+
const sectionLines = new Map(chapters.map(({ id, sections }) => [id, sections]));
|
|
9083
|
+
const exhausted = /* @__PURE__ */ new Set();
|
|
9084
|
+
for (let index = 0; exhausted.size < selected.length; index++) {
|
|
9085
|
+
for (const chapter of selected) {
|
|
9086
|
+
if (exhausted.has(chapter.id)) continue;
|
|
9087
|
+
const line = sectionLines.get(chapter.id)[index];
|
|
9088
|
+
if (line === void 0) {
|
|
9089
|
+
exhausted.add(chapter.id);
|
|
9090
|
+
continue;
|
|
9091
|
+
}
|
|
9092
|
+
chapter.sections.push(line);
|
|
9093
|
+
if (estimateTokens(render(navigation, selected)) > MEMORY_DIRECTORY_TOKEN_BUDGET) {
|
|
9094
|
+
chapter.sections.pop();
|
|
9095
|
+
exhausted.add(chapter.id);
|
|
9096
|
+
}
|
|
9097
|
+
}
|
|
9098
|
+
}
|
|
9099
|
+
return render(navigation, selected);
|
|
8842
9100
|
}
|
|
8843
9101
|
function boundedTopic(topic, envelope = {}) {
|
|
8844
9102
|
const { sections: _sections, ...summaryTopic } = topic;
|
|
@@ -9384,9 +9642,21 @@ var StrataGateRuntime = class {
|
|
|
9384
9642
|
}
|
|
9385
9643
|
async buildMemoryDirectory(session) {
|
|
9386
9644
|
await this.flush();
|
|
9387
|
-
const
|
|
9388
|
-
|
|
9389
|
-
|
|
9645
|
+
const namespace = this.namespaceFor(session);
|
|
9646
|
+
const active = this.spaces.get(namespace);
|
|
9647
|
+
if (active) {
|
|
9648
|
+
const memory = await active;
|
|
9649
|
+
await this.refreshExternalImportMemory(namespace, memory);
|
|
9650
|
+
return renderMemoryDirectory(this.visibleTopics(memory), memory.listAllEvents());
|
|
9651
|
+
}
|
|
9652
|
+
if (this.config.database === ":memory:" || !existsSync(this.config.database)) return "";
|
|
9653
|
+
const storage = new SqliteStorage({ filename: this.config.database, readonly: true });
|
|
9654
|
+
try {
|
|
9655
|
+
const memory = await StrataGate.openWithStorage({ storage, namespace });
|
|
9656
|
+
return renderMemoryDirectory(this.visibleTopics(memory), memory.listAllEvents());
|
|
9657
|
+
} finally {
|
|
9658
|
+
await storage.close();
|
|
9659
|
+
}
|
|
9390
9660
|
}
|
|
9391
9661
|
visibleTopics(memory) {
|
|
9392
9662
|
if (this.agentMemoryRetrievalWeight > 0) return memory.listMemoryTopics();
|
|
@@ -9663,6 +9933,7 @@ var StrataGateRuntime = class {
|
|
|
9663
9933
|
async buildAutoContext(session) {
|
|
9664
9934
|
await this.flush();
|
|
9665
9935
|
const memory = await this.space(session);
|
|
9936
|
+
await this.refreshExternalImportMemory(this.namespaceFor(session), memory);
|
|
9666
9937
|
const threadId = String(session.id);
|
|
9667
9938
|
const blockContexts = memory.getBlockContext(threadId);
|
|
9668
9939
|
if (this.syncDecayedBlockSurface(session, memory, blockContexts)) {
|
|
@@ -10467,6 +10738,7 @@ var StrataGateRuntime = class {
|
|
|
10467
10738
|
const jobId = id.trim();
|
|
10468
10739
|
if (!key) throw new TypeError("StrataGate admin namespace must not be empty");
|
|
10469
10740
|
if (!jobId) throw new TypeError("StrataGate job id must not be empty");
|
|
10741
|
+
if (kind === "topic-projection") return this.adminRetryTopicProjection(key, jobId);
|
|
10470
10742
|
const retryKey = `${key}\0${kind}\0${jobId}`;
|
|
10471
10743
|
const existing = this.adminJobRetryRuns.get(retryKey);
|
|
10472
10744
|
if (existing) return existing;
|
|
@@ -11976,6 +12248,7 @@ function topicDirectoryProjection(snapshot, agentMemoryWeight) {
|
|
|
11976
12248
|
const failedWork = /* @__PURE__ */ new Set();
|
|
11977
12249
|
const failures = state.jobs.flatMap((job) => {
|
|
11978
12250
|
if (job.superseded || job.projectorVersion !== MEMORY_TOPIC_PROJECTOR_VERSION || job.status !== "failed" || job.attempts < TOPIC_MAX_ATTEMPTS || !Object.entries({ ...job.sourceVersions, ...job.dependencyVersions }).every(([id, version]) => versions.get(id) === version)) return [];
|
|
12251
|
+
const diagnostics = safeTopicDiagnostics(job.diagnostics);
|
|
11979
12252
|
const ids = job.sourceEventIds.filter((id) => {
|
|
11980
12253
|
const key = relationKey(job.sectionBackfillTopicId, id, job.sourceVersions[id]);
|
|
11981
12254
|
if (!outstandingWork.has(key)) return false;
|
|
@@ -11987,7 +12260,8 @@ function topicDirectoryProjection(snapshot, agentMemoryWeight) {
|
|
|
11987
12260
|
eventIds: ids,
|
|
11988
12261
|
attempts: job.attempts,
|
|
11989
12262
|
// Topic failures store reason codes, never raw model output.
|
|
11990
|
-
|
|
12263
|
+
diagnostics,
|
|
12264
|
+
lastError: diagnostics ? `${diagnostics.category}: ${diagnostics.reason}` : ["timeout", "source-changed", "invalid-output", "worker-failed"].includes(job.lastError ?? "") ? job.lastError : "worker-failed",
|
|
11991
12265
|
updatedAt: job.updatedAt
|
|
11992
12266
|
}] : [];
|
|
11993
12267
|
});
|
|
@@ -12203,17 +12477,41 @@ async function overview(runtime, cachedEntries) {
|
|
|
12203
12477
|
sourceEventIds: job.sourceEventIds
|
|
12204
12478
|
};
|
|
12205
12479
|
};
|
|
12480
|
+
const topicJobs = (snapshot.memoryTopicState?.jobs ?? []).filter((job) => !job.superseded);
|
|
12481
|
+
const describeTopicJob = (job) => {
|
|
12482
|
+
const diagnostics = safeTopicDiagnostics(job.diagnostics);
|
|
12483
|
+
const lastError = diagnostics ? `${diagnostics.category}: ${diagnostics.reason}` : ["timeout", "source-changed", "invalid-output", "worker-failed"].includes(job.lastError ?? "") ? job.lastError : "worker-failed";
|
|
12484
|
+
return {
|
|
12485
|
+
id: job.id,
|
|
12486
|
+
kind: "topic-projection",
|
|
12487
|
+
state: derivationJobState(job, job.status === "completed"),
|
|
12488
|
+
status: job.status,
|
|
12489
|
+
attempts: job.attempts,
|
|
12490
|
+
nextRetryAt: job.nextRetryAt,
|
|
12491
|
+
updatedAt: job.updatedAt,
|
|
12492
|
+
lastError,
|
|
12493
|
+
lastErrorFull: lastError,
|
|
12494
|
+
diagnostics,
|
|
12495
|
+
eventCount: job.sourceEventIds.length,
|
|
12496
|
+
blockIds: [],
|
|
12497
|
+
threadIds: [],
|
|
12498
|
+
blockDetails: []
|
|
12499
|
+
};
|
|
12500
|
+
};
|
|
12501
|
+
const topicStatus = summarizeDerivationJobs(topicJobs, (job) => job.status === "completed");
|
|
12206
12502
|
const summaryStatus = summarizeDerivationJobs(snapshot.summaryJobs, (job) => job.status === "succeeded");
|
|
12207
12503
|
const extractionStatus = summarizeDerivationJobs(snapshot.extractionJobs, (job) => job.status === "succeeded" || job.status === "skipped");
|
|
12208
12504
|
const graphStatus = summarizeDerivationJobs(snapshot.graphProjectionJobs, (job) => job.status === "completed");
|
|
12209
|
-
const failedJobs = summaryStatus.terminalFailed + extractionStatus.terminalFailed + graphStatus.terminalFailed;
|
|
12210
|
-
const processingJobs = summaryStatus.processing + extractionStatus.processing + graphStatus.processing;
|
|
12505
|
+
const failedJobs = summaryStatus.terminalFailed + extractionStatus.terminalFailed + graphStatus.terminalFailed + topicStatus.terminalFailed;
|
|
12506
|
+
const processingJobs = summaryStatus.processing + extractionStatus.processing + graphStatus.processing + topicStatus.processing;
|
|
12211
12507
|
const failedJobDetails = [
|
|
12508
|
+
...topicJobs.filter((job) => job.status === "failed").map(describeTopicJob),
|
|
12212
12509
|
...snapshot.summaryJobs.filter(({ status }) => status === "failed").map((job) => describeBlockJob("block-summary", job)),
|
|
12213
12510
|
...snapshot.extractionJobs.filter(({ status }) => status === "failed").map((job) => describeBlockJob("event-extraction", job)),
|
|
12214
12511
|
...snapshot.graphProjectionJobs.filter(({ status }) => status === "failed").map(describeGraphJob)
|
|
12215
12512
|
];
|
|
12216
12513
|
const processingJobDetails = [
|
|
12514
|
+
...topicJobs.filter((job) => ["processing", "retryable"].includes(derivationJobState(job, job.status === "completed"))).map(describeTopicJob),
|
|
12217
12515
|
...snapshot.summaryJobs.filter((job) => derivationJobState(job, job.status === "succeeded") === "processing" || derivationJobState(job, job.status === "succeeded") === "retryable").map((job) => describeBlockJob("block-summary", job)),
|
|
12218
12516
|
...snapshot.extractionJobs.filter((job) => derivationJobState(job, job.status === "succeeded" || job.status === "skipped") === "processing" || derivationJobState(job, job.status === "succeeded" || job.status === "skipped") === "retryable").map((job) => describeBlockJob("event-extraction", job)),
|
|
12219
12517
|
...snapshot.graphProjectionJobs.filter(graphProjectionIsProcessing).map(describeGraphJob)
|
|
@@ -12243,7 +12541,8 @@ async function overview(runtime, cachedEntries) {
|
|
|
12243
12541
|
taskStatus: {
|
|
12244
12542
|
blockSummary: summaryStatus,
|
|
12245
12543
|
eventExtraction: extractionStatus,
|
|
12246
|
-
graphProjection: graphStatus
|
|
12544
|
+
graphProjection: graphStatus,
|
|
12545
|
+
topicProjection: topicStatus
|
|
12247
12546
|
},
|
|
12248
12547
|
graphMigration: (() => {
|
|
12249
12548
|
const projected = new Set(snapshot.graphProjectionJobs.filter(({ status, projectorVersion }) => status === "completed" && projectorVersion === KNOWLEDGE_GRAPH_PROJECTOR_VERSION).flatMap(({ sourceEventIds }) => sourceEventIds)).size;
|
|
@@ -12884,13 +13183,13 @@ async function retryJob(runtime, url) {
|
|
|
12884
13183
|
const kind = url.searchParams.get("kind")?.trim() ?? "";
|
|
12885
13184
|
const jobId = url.searchParams.get("jobId")?.trim() ?? "";
|
|
12886
13185
|
if (!namespace) throw new AdminHttpError(400, "namespace is required");
|
|
12887
|
-
if (!["block-summary", "event-extraction", "graph-projection"].includes(kind)) {
|
|
12888
|
-
throw new AdminHttpError(400, "kind must be block-summary, event-extraction, or
|
|
13186
|
+
if (!["block-summary", "event-extraction", "graph-projection", "topic-projection"].includes(kind)) {
|
|
13187
|
+
throw new AdminHttpError(400, "kind must be block-summary, event-extraction, graph-projection, or topic-projection");
|
|
12889
13188
|
}
|
|
12890
13189
|
if (!jobId) throw new AdminHttpError(400, "jobId is required");
|
|
12891
13190
|
if (jobId.startsWith("virtual:")) throw new AdminHttpError(409, "Recovered legacy fragments cannot be retried");
|
|
12892
13191
|
const snapshot = await requiredSnapshot(runtime, namespace);
|
|
12893
|
-
const job = kind === "block-summary" ? snapshot.summaryJobs.find(({ blockId }) => blockId === jobId) : kind === "event-extraction" ? snapshot.extractionJobs.find(({ blockId }) => blockId === jobId) : snapshot.graphProjectionJobs.find(({ id }) => id === jobId);
|
|
13192
|
+
const job = kind === "block-summary" ? snapshot.summaryJobs.find(({ blockId }) => blockId === jobId) : kind === "event-extraction" ? snapshot.extractionJobs.find(({ blockId }) => blockId === jobId) : kind === "topic-projection" ? snapshot.memoryTopicState?.jobs.find(({ id }) => id === jobId) : snapshot.graphProjectionJobs.find(({ id }) => id === jobId);
|
|
12894
13193
|
if (!job) throw new AdminHttpError(404, `Unknown ${kind} job: ${jobId}`);
|
|
12895
13194
|
if (job.status !== "failed") throw new AdminHttpError(409, `${kind} job is ${job.status}, not failed`);
|
|
12896
13195
|
try {
|
|
@@ -12900,6 +13199,7 @@ async function retryJob(runtime, url) {
|
|
|
12900
13199
|
jobId
|
|
12901
13200
|
);
|
|
12902
13201
|
} catch (error) {
|
|
13202
|
+
if (kind === "topic-projection" && error instanceof Error && /Topic retry conflict|Unknown topic projection/.test(error.message)) throw new AdminHttpError(409, "\u6765\u6E90\u6216\u4EFB\u52A1\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u91CD\u65B0\u8BFB\u53D6\u76EE\u5F55");
|
|
12903
13203
|
throw new AdminHttpError(422, error instanceof Error ? error.message : String(error));
|
|
12904
13204
|
}
|
|
12905
13205
|
}
|
|
@@ -13607,20 +13907,22 @@ async function apply(ctx, config) {
|
|
|
13607
13907
|
}
|
|
13608
13908
|
const session = context.agent?.session;
|
|
13609
13909
|
if (!session) return { ...assembled, contexts };
|
|
13910
|
+
let autoMemory;
|
|
13610
13911
|
try {
|
|
13611
|
-
const
|
|
13612
|
-
|
|
13912
|
+
const text3 = await runtime.buildAutoContext(session);
|
|
13913
|
+
autoMemory = { name: "stratagate:auto-memory", text: text3 };
|
|
13613
13914
|
} catch (error) {
|
|
13614
|
-
ctx.logger.warn(`stratagate-memory
|
|
13915
|
+
ctx.logger.warn(`stratagate-memory auto-context failed: ${renderError(error)}`);
|
|
13615
13916
|
runtime.notePluginError(session, error);
|
|
13616
13917
|
}
|
|
13617
13918
|
try {
|
|
13618
|
-
const
|
|
13619
|
-
contexts.push({ name: "stratagate:
|
|
13919
|
+
const directory = await runtime.buildMemoryDirectory(session);
|
|
13920
|
+
if (directory) contexts.push({ name: "stratagate:memory-directory", text: directory });
|
|
13620
13921
|
} catch (error) {
|
|
13621
|
-
ctx.logger.warn(`stratagate-memory
|
|
13922
|
+
ctx.logger.warn(`stratagate-memory directory failed: ${renderError(error)}`);
|
|
13622
13923
|
runtime.notePluginError(session, error);
|
|
13623
13924
|
}
|
|
13925
|
+
if (autoMemory) contexts.push(autoMemory);
|
|
13624
13926
|
const feedbackSuggestion = runtime.takeFeedbackSuggestion(session);
|
|
13625
13927
|
if (feedbackSuggestion) contexts.push({ name: "stratagate:feedback-suggestion", text: feedbackSuggestion });
|
|
13626
13928
|
return { ...assembled, contexts };
|