stratagate-dsh 0.3.2 → 0.3.3
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 +2 -2
- package/dist/plugin.js +228 -48
- package/dist/plugin.js.map +1 -1
- package/docs/MEMORY_TOPICS.zh-CN.md +5 -3
- package/package.json +1 -1
package/dist/plugin.js
CHANGED
|
@@ -3762,8 +3762,25 @@ function memoryTopicSectionTitle(part) {
|
|
|
3762
3762
|
return part.title?.trim() || SECTION_TITLES[part.kind] || "\u4E3B\u9898\u6982\u89C8";
|
|
3763
3763
|
}
|
|
3764
3764
|
var normalizedLabel = (value) => value.normalize("NFKC").trim().replace(/\s+/g, " ").toLowerCase();
|
|
3765
|
+
function memoryTopicOverviewMatchesSection(part, titles) {
|
|
3766
|
+
const display = normalizedLabel(memoryTopicSectionTitle(part));
|
|
3767
|
+
return titles.some((title) => normalizedLabel(title) === display);
|
|
3768
|
+
}
|
|
3769
|
+
function memoryTopicMembershipSections(topic) {
|
|
3770
|
+
if (topic.sections !== void 0) return structuredClone(topic.sections);
|
|
3771
|
+
const sections = /* @__PURE__ */ new Map();
|
|
3772
|
+
for (const part of topic.overview) {
|
|
3773
|
+
const title = memoryTopicSectionTitle(part);
|
|
3774
|
+
const key = normalizedLabel(title);
|
|
3775
|
+
const section = sections.get(key) ?? { title, sourceEventIds: [] };
|
|
3776
|
+
section.sourceEventIds = unique2([...section.sourceEventIds, ...part.sourceEventIds]);
|
|
3777
|
+
sections.set(key, section);
|
|
3778
|
+
}
|
|
3779
|
+
return [...sections.values()];
|
|
3780
|
+
}
|
|
3765
3781
|
function sectionTitles(topic) {
|
|
3766
3782
|
const titles = /* @__PURE__ */ new Map();
|
|
3783
|
+
for (const section of memoryTopicMembershipSections(topic)) titles.set(normalizedLabel(section.title), section.title);
|
|
3767
3784
|
for (const part of topic.overview) {
|
|
3768
3785
|
const title = memoryTopicSectionTitle(part);
|
|
3769
3786
|
if (!titles.has(normalizedLabel(title))) titles.set(normalizedLabel(title), title);
|
|
@@ -3788,6 +3805,7 @@ function topicView(topic, events, versions) {
|
|
|
3788
3805
|
description: valid ? topic.description : "\u6982\u89C8\u7B49\u5F85\u91CD\u65B0\u6574\u7406\uFF0C\u8BF7\u67E5\u770B\u5173\u8054\u4E8B\u4EF6\u3002",
|
|
3789
3806
|
sourceEventIds: ids,
|
|
3790
3807
|
overview: overview2,
|
|
3808
|
+
sections: valid ? memoryTopicMembershipSections(topic) : [],
|
|
3791
3809
|
createdAt: topic.createdAt,
|
|
3792
3810
|
updatedAt: topic.updatedAt,
|
|
3793
3811
|
coverage: { totalEvents: ids.length, summarizedEvents: summarized.size, omittedEvents: ids.length - summarized.size },
|
|
@@ -3816,6 +3834,9 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
3816
3834
|
state = { topics: [], jobs: [], projectedVersions: {} };
|
|
3817
3835
|
restore(state) {
|
|
3818
3836
|
this.state = state ? structuredClone(state) : { topics: [], jobs: [], projectedVersions: {} };
|
|
3837
|
+
for (const topic of this.state.topics) if (topic.sections === void 0) {
|
|
3838
|
+
topic.sections = memoryTopicMembershipSections(topic);
|
|
3839
|
+
}
|
|
3819
3840
|
if (state && state.rebuildVersions === void 0 && state.topics.some((topic) => topic.invalidated)) {
|
|
3820
3841
|
this.state.rebuildVersions = Object.fromEntries(state.topics.filter((topic) => topic.invalidated).flatMap((topic) => Object.entries(topic.sourceVersions).filter(([id, version]) => state.projectedVersions[id] !== version)));
|
|
3821
3842
|
}
|
|
@@ -3851,9 +3872,23 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
3851
3872
|
bootstrap() {
|
|
3852
3873
|
return this.state.bootstrap ? structuredClone(this.state.bootstrap) : null;
|
|
3853
3874
|
}
|
|
3875
|
+
needsInitialization() {
|
|
3876
|
+
return this.state.bootstrap?.projectorVersion !== MEMORY_TOPIC_PROJECTOR_VERSION || this.state.sectionMembershipVersion !== 1;
|
|
3877
|
+
}
|
|
3854
3878
|
initializeBootstrap(events, now) {
|
|
3855
|
-
if (this.state.bootstrap?.projectorVersion === MEMORY_TOPIC_PROJECTOR_VERSION) return;
|
|
3856
3879
|
this.synchronize(events, now);
|
|
3880
|
+
if (this.state.sectionMembershipVersion !== 1) {
|
|
3881
|
+
const versions = sourceVersions(new Map(events.filter(visible).map((event) => [event.id, event])));
|
|
3882
|
+
const missing = {};
|
|
3883
|
+
for (const topic of this.state.topics.filter((topic2) => !topic2.invalidated)) {
|
|
3884
|
+
const assigned = new Set(memoryTopicMembershipSections(topic).flatMap((section) => section.sourceEventIds));
|
|
3885
|
+
const ids = topic.sourceEventIds.filter((id) => !assigned.has(id) && versions.has(id) && this.state.projectedVersions[id] === versions.get(id));
|
|
3886
|
+
if (ids.length) missing[topic.id] = Object.fromEntries(ids.map((id) => [id, versions.get(id)]));
|
|
3887
|
+
}
|
|
3888
|
+
this.state.sectionBackfill = missing;
|
|
3889
|
+
this.state.sectionMembershipVersion = 1;
|
|
3890
|
+
}
|
|
3891
|
+
if (this.state.bootstrap?.projectorVersion === MEMORY_TOPIC_PROJECTOR_VERSION) return;
|
|
3857
3892
|
this.state.bootstrap = {
|
|
3858
3893
|
projectorVersion: MEMORY_TOPIC_PROJECTOR_VERSION,
|
|
3859
3894
|
sourceVersions: Object.fromEntries(sourceVersions(new Map(events.filter(visible).map((event) => [event.id, event])))),
|
|
@@ -3869,7 +3904,7 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
3869
3904
|
const versions = sourceVersions(sources2);
|
|
3870
3905
|
if (this.state.bootstrap && this.state.bootstrap.projectorVersion !== MEMORY_TOPIC_PROJECTOR_VERSION || this.state.topics.some((topic) => topic.projectorVersion !== MEMORY_TOPIC_PROJECTOR_VERSION) || this.state.jobs.some((job) => job.projectorVersion !== MEMORY_TOPIC_PROJECTOR_VERSION)) {
|
|
3871
3906
|
const fromV2 = (!this.state.bootstrap || this.state.bootstrap.projectorVersion === 2) && this.state.topics.every((topic) => topic.projectorVersion === 2) && this.state.jobs.every((job) => job.projectorVersion === 2);
|
|
3872
|
-
const chapters = fromV2 ? this.state.topics.filter((topic) => !topic.invalidated && topic.title.trim() && topic.description.trim() && topic.sourceEventIds.length > 0 && topic.sourceEventIds.every((id) => sources2.has(id) && topic.sourceVersions[id] === versions.get(id)) && Object.entries(topic.dependencyVersions ?? topic.sourceVersions).every(([id, version]) => versions.get(id) === version)).map((topic) => ({ ...topic, overview: [], projectorVersion: MEMORY_TOPIC_PROJECTOR_VERSION, updatedAt: now })) : [];
|
|
3907
|
+
const chapters = fromV2 ? this.state.topics.filter((topic) => !topic.invalidated && topic.title.trim() && topic.description.trim() && topic.sourceEventIds.length > 0 && topic.sourceEventIds.every((id) => sources2.has(id) && topic.sourceVersions[id] === versions.get(id)) && Object.entries(topic.dependencyVersions ?? topic.sourceVersions).every(([id, version]) => versions.get(id) === version)).map((topic) => ({ ...topic, overview: [], sections: [], projectorVersion: MEMORY_TOPIC_PROJECTOR_VERSION, updatedAt: now })) : [];
|
|
3873
3908
|
this.state = { topics: chapters, jobs: [], projectedVersions: {} };
|
|
3874
3909
|
}
|
|
3875
3910
|
for (const topic of this.state.topics) {
|
|
@@ -3878,6 +3913,7 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
3878
3913
|
topic.title = "";
|
|
3879
3914
|
topic.description = "";
|
|
3880
3915
|
topic.overview = [];
|
|
3916
|
+
topic.sections = [];
|
|
3881
3917
|
for (const id of topic.sourceEventIds) if (sources2.has(id)) {
|
|
3882
3918
|
const version = versions.get(id);
|
|
3883
3919
|
if (topic.sourceVersions[id] === version && this.state.projectedVersions[id] === version) {
|
|
@@ -3908,6 +3944,12 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
3908
3944
|
for (const [id, version] of Object.entries(this.state.rebuildVersions ?? {})) {
|
|
3909
3945
|
if (versions.get(id) !== version || this.state.projectedVersions[id] === version) delete this.state.rebuildVersions[id];
|
|
3910
3946
|
}
|
|
3947
|
+
for (const [topicId, pending] of Object.entries(this.state.sectionBackfill ?? {})) {
|
|
3948
|
+
const topic = this.state.topics.find((topic2) => topic2.id === topicId && !topic2.invalidated);
|
|
3949
|
+
const assigned = new Set(topic ? memoryTopicMembershipSections(topic).flatMap((section) => section.sourceEventIds) : []);
|
|
3950
|
+
for (const [id, version] of Object.entries(pending)) if (!topic || !topic.sourceEventIds.includes(id) || versions.get(id) !== version || assigned.has(id)) delete pending[id];
|
|
3951
|
+
if (!Object.keys(pending).length) delete this.state.sectionBackfill[topicId];
|
|
3952
|
+
}
|
|
3911
3953
|
const bootstrap = this.state.bootstrap;
|
|
3912
3954
|
if (bootstrap && bootstrap.projectorVersion === MEMORY_TOPIC_PROJECTOR_VERSION) {
|
|
3913
3955
|
const outstanding = Object.entries(bootstrap.sourceVersions).filter(([id, version]) => versions.get(id) === version && this.state.projectedVersions[id] !== version);
|
|
@@ -3928,29 +3970,36 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
3928
3970
|
job.updatedAt = now;
|
|
3929
3971
|
}
|
|
3930
3972
|
}
|
|
3931
|
-
|
|
3973
|
+
pendingWork(events) {
|
|
3932
3974
|
const sources2 = new Map(events.filter(visible).map((event) => [event.id, event]));
|
|
3933
3975
|
const versions = sourceVersions(sources2);
|
|
3934
3976
|
const dirtyIds = new Set(this.state.topics.filter((topic) => topic.projectorVersion !== MEMORY_TOPIC_PROJECTOR_VERSION || Object.entries(topic.dependencyVersions ?? topic.sourceVersions).some(([id, version]) => versions.get(id) !== version)).flatMap((topic) => topic.sourceEventIds));
|
|
3935
|
-
|
|
3977
|
+
const blocked = (id, version, topicId) => this.state.jobs.some((job) => !job.superseded && job.projectorVersion === MEMORY_TOPIC_PROJECTOR_VERSION && job.sectionBackfillTopicId === topicId && job.sourceVersions[id] === version && jobInputsCurrent(job, versions) && (job.status === "running" || job.status === "pending" || job.status === "failed" && (job.attempts >= TOPIC_MAX_ATTEMPTS || job.nextRetryAt !== null)));
|
|
3978
|
+
return events.filter(visible).sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id)).flatMap((event) => {
|
|
3936
3979
|
const version = versions.get(event.id);
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3980
|
+
const work = [];
|
|
3981
|
+
if ((this.state.projectedVersions[event.id] !== version || dirtyIds.has(event.id)) && !blocked(event.id, version)) {
|
|
3982
|
+
work.push({ event });
|
|
3983
|
+
}
|
|
3984
|
+
for (const [topicId, pending] of Object.entries(this.state.sectionBackfill ?? {})) {
|
|
3985
|
+
if (pending[event.id] === version && !blocked(event.id, version, topicId)) work.push({ event, sectionBackfillTopicId: topicId });
|
|
3986
|
+
}
|
|
3987
|
+
return work;
|
|
3988
|
+
});
|
|
3940
3989
|
}
|
|
3941
|
-
isBootstrap(id, version) {
|
|
3990
|
+
isBootstrap(id, version, sectionBackfillTopicId) {
|
|
3942
3991
|
const bootstrap = this.state.bootstrap;
|
|
3943
|
-
return this.state.rebuildVersions?.[id] === version || !bootstrap || bootstrap.projectorVersion !== MEMORY_TOPIC_PROJECTOR_VERSION || bootstrap.status !== "completed" && bootstrap.sourceVersions[id] === version;
|
|
3992
|
+
return sectionBackfillTopicId !== void 0 || this.state.rebuildVersions?.[id] === version || !bootstrap || bootstrap.projectorVersion !== MEMORY_TOPIC_PROJECTOR_VERSION || bootstrap.status !== "completed" && bootstrap.sourceVersions[id] === version;
|
|
3944
3993
|
}
|
|
3945
|
-
matchesMode(ids, versions, mode) {
|
|
3946
|
-
return mode === "all" || ids.some((id) => this.isBootstrap(id, versions.get(id)) === (mode === "bootstrap"));
|
|
3994
|
+
matchesMode(ids, versions, mode, sectionBackfillTopicId) {
|
|
3995
|
+
return mode === "all" || ids.some((id) => this.isBootstrap(id, versions.get(id), sectionBackfillTopicId) === (mode === "bootstrap"));
|
|
3947
3996
|
}
|
|
3948
3997
|
hasPending(events, now, mode = "all") {
|
|
3949
3998
|
const sources2 = new Map(events.filter(visible).map((event) => [event.id, event]));
|
|
3950
3999
|
const versions = sourceVersions(sources2);
|
|
3951
4000
|
const running = this.state.jobs.find((job) => job.status === "running");
|
|
3952
4001
|
if (running) return Date.parse(running.leaseUntil ?? running.updatedAt) <= now || !jobInputsCurrent(running, versions);
|
|
3953
|
-
return this.
|
|
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));
|
|
3954
4003
|
}
|
|
3955
4004
|
claim(events, now, mode = "all") {
|
|
3956
4005
|
if (this.state.bootstrap?.projectorVersion !== MEMORY_TOPIC_PROJECTOR_VERSION) {
|
|
@@ -3961,9 +4010,9 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
3961
4010
|
if (this.state.jobs.some((job2) => job2.status === "running")) return null;
|
|
3962
4011
|
const sources2 = new Map(events.filter(visible).map((event) => [event.id, event]));
|
|
3963
4012
|
const versions = sourceVersions(sources2);
|
|
3964
|
-
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)).sort((a, b) => Number(this.isBootstrap(a.sourceEventIds[0], versions.get(a.sourceEventIds[0]))) - Number(this.isBootstrap(b.sourceEventIds[0], versions.get(b.sourceEventIds[0]))))[0];
|
|
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];
|
|
3965
4014
|
let job = eligible;
|
|
3966
|
-
if (job && this.isBootstrap(job.sourceEventIds[0], versions.get(job.sourceEventIds[0])) && mode === "all" && this.
|
|
4015
|
+
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;
|
|
3967
4016
|
if (job?.status === "failed") {
|
|
3968
4017
|
const previous = job;
|
|
3969
4018
|
previous.nextRetryAt = null;
|
|
@@ -3983,9 +4032,10 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
3983
4032
|
this.state.jobs.push(job);
|
|
3984
4033
|
}
|
|
3985
4034
|
if (!job) {
|
|
3986
|
-
const pending = this.
|
|
4035
|
+
const pending = this.pendingWork(events).filter(({ event, sectionBackfillTopicId }) => this.matchesMode([event.id], versions, mode, sectionBackfillTopicId)).sort((a, b) => Number(this.isBootstrap(a.event.id, versions.get(a.event.id), a.sectionBackfillTopicId)) - Number(this.isBootstrap(b.event.id, versions.get(b.event.id), b.sectionBackfillTopicId)));
|
|
3987
4036
|
const first = pending[0];
|
|
3988
|
-
const
|
|
4037
|
+
const backfillTopicId = first?.sectionBackfillTopicId;
|
|
4038
|
+
const sameKind = pending.filter(({ event, sectionBackfillTopicId }) => first && sectionBackfillTopicId === backfillTopicId && this.isBootstrap(event.id, versions.get(event.id), sectionBackfillTopicId) === this.isBootstrap(first.event.id, versions.get(first.event.id), backfillTopicId)).slice(0, TOPIC_BATCH_LIMIT).map(({ event }) => event);
|
|
3989
4039
|
const batch2 = [];
|
|
3990
4040
|
let eventBudget = 16e3;
|
|
3991
4041
|
for (const event of sameKind) {
|
|
@@ -4012,6 +4062,7 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4012
4062
|
createdAt: now,
|
|
4013
4063
|
updatedAt: now
|
|
4014
4064
|
};
|
|
4065
|
+
if (backfillTopicId) job.sectionBackfillTopicId = backfillTopicId;
|
|
4015
4066
|
this.state.jobs.push(job);
|
|
4016
4067
|
}
|
|
4017
4068
|
const batch = job.sourceEventIds.map((id) => sources2.get(id));
|
|
@@ -4030,21 +4081,30 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4030
4081
|
}).join(" "), 4]
|
|
4031
4082
|
])).map(({ item, score }) => [item.id, score]));
|
|
4032
4083
|
const overlap = (topic) => topic.sourceEventIds.filter((id) => batchIds.has(id)).length;
|
|
4033
|
-
const candidates = eligibleTopics.sort((a, b) => overlap(b) - overlap(a) || (lexicalScores.get(b.id) ?? 0) - (lexicalScores.get(a.id) ?? 0) || b.updatedAt.localeCompare(a.updatedAt) || a.id.localeCompare(b.id)).slice(0, TOPIC_CANDIDATE_LIMIT);
|
|
4084
|
+
const candidates = eligibleTopics.filter((topic) => !job.sectionBackfillTopicId || topic.id === job.sectionBackfillTopicId).sort((a, b) => overlap(b) - overlap(a) || (lexicalScores.get(b.id) ?? 0) - (lexicalScores.get(a.id) ?? 0) || b.updatedAt.localeCompare(a.updatedAt) || a.id.localeCompare(b.id)).slice(0, TOPIC_CANDIDATE_LIMIT);
|
|
4034
4085
|
const existingTopics = [];
|
|
4035
4086
|
let remaining = 3e4 - JSON.stringify(batch.map(compactEvent)).length;
|
|
4036
4087
|
for (const topic of candidates) {
|
|
4037
|
-
const overview2 = topic.overview.slice(0, 4);
|
|
4088
|
+
const overview2 = job.sectionBackfillTopicId ? [] : topic.overview.slice(0, 4);
|
|
4038
4089
|
const exposed = unique2([
|
|
4039
4090
|
...overview2.flatMap((part) => part.sourceEventIds),
|
|
4040
4091
|
...topic.sourceEventIds.filter((id) => batchIds.has(id))
|
|
4041
4092
|
]);
|
|
4093
|
+
const allTitles = sectionTitles(topic);
|
|
4094
|
+
let titleBudget = 4e3;
|
|
4095
|
+
const shownTitles = job.sectionBackfillTopicId ? allTitles.filter((title, index) => {
|
|
4096
|
+
const cost2 = JSON.stringify(title).length;
|
|
4097
|
+
if (index >= 120 || cost2 > titleBudget) return false;
|
|
4098
|
+
titleBudget -= cost2;
|
|
4099
|
+
return true;
|
|
4100
|
+
}) : allTitles;
|
|
4042
4101
|
const candidate = {
|
|
4043
4102
|
id: topic.id,
|
|
4044
4103
|
title: topic.title,
|
|
4045
4104
|
description: topic.description,
|
|
4046
4105
|
overview: overview2,
|
|
4047
|
-
sectionTitles:
|
|
4106
|
+
sectionTitles: shownTitles,
|
|
4107
|
+
...shownTitles.length < allTitles.length ? { sectionTitlesOmitted: allTitles.length - shownTitles.length } : {},
|
|
4048
4108
|
sourceEventIds: exposed,
|
|
4049
4109
|
totalSourceEvents: topic.sourceEventIds.length
|
|
4050
4110
|
};
|
|
@@ -4057,7 +4117,16 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4057
4117
|
const compact = compactEvent(event);
|
|
4058
4118
|
return compact.title !== event.title || compact.summary !== event.summary || JSON.stringify(compact.temporal) !== JSON.stringify(topicTemporal(event)) || JSON.stringify(compact.tags) !== JSON.stringify(event.tags);
|
|
4059
4119
|
}).map((event) => event.id);
|
|
4060
|
-
job.
|
|
4120
|
+
if (job.sectionBackfillTopicId && !existingTopics.some((topic) => topic.id === job.sectionBackfillTopicId)) {
|
|
4121
|
+
throw new Error("Section backfill chapter cannot fit the projection input budget.");
|
|
4122
|
+
}
|
|
4123
|
+
job.context = {
|
|
4124
|
+
jobId: job.id,
|
|
4125
|
+
events: batch.map(compactEvent),
|
|
4126
|
+
existingTopics,
|
|
4127
|
+
truncatedEventIds,
|
|
4128
|
+
...job.sectionBackfillTopicId ? { sectionBackfillTopicId: job.sectionBackfillTopicId } : {}
|
|
4129
|
+
};
|
|
4061
4130
|
const storedById = new Map(this.state.topics.map((topic) => [topic.id, topic]));
|
|
4062
4131
|
job.candidateVersions = Object.fromEntries(existingTopics.map((topic) => [topic.id, digest(storedById.get(topic.id))]));
|
|
4063
4132
|
const dependencyIds = unique2([...job.sourceEventIds, ...existingTopics.flatMap((topic) => [
|
|
@@ -4070,7 +4139,7 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4070
4139
|
job.lastError = null;
|
|
4071
4140
|
job.nextRetryAt = null;
|
|
4072
4141
|
job.updatedAt = now;
|
|
4073
|
-
if (this.state.bootstrap.status === "pending" && this.matchesMode(job.sourceEventIds, versions, "bootstrap")) {
|
|
4142
|
+
if (this.state.bootstrap.status === "pending" && this.matchesMode(job.sourceEventIds, versions, "bootstrap", job.sectionBackfillTopicId)) {
|
|
4074
4143
|
this.state.bootstrap.status = "running";
|
|
4075
4144
|
}
|
|
4076
4145
|
job.leaseUntil = new Date(Date.parse(now) + TOPIC_LEASE_MS).toISOString();
|
|
@@ -4087,6 +4156,9 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4087
4156
|
throw new Error("Topic projection source version changed; stale result rejected.");
|
|
4088
4157
|
}
|
|
4089
4158
|
if (!Array.isArray(result?.topics) || result.topics.length < 1 || result.topics.length > 12) throw new Error("Topic projection must return 1-12 topics.");
|
|
4159
|
+
if (job.sectionBackfillTopicId && (result.topics.length !== 1 || result.topics[0].topicId !== job.sectionBackfillTopicId || result.topics[0].sections === void 0)) {
|
|
4160
|
+
throw new Error("Invalid section backfill: explicit sections must cover the existing chapter.");
|
|
4161
|
+
}
|
|
4090
4162
|
const candidates = new Map(job.context.existingTopics.map((topic) => [topic.id, topic]));
|
|
4091
4163
|
const allowed = /* @__PURE__ */ new Set([...job.sourceEventIds, ...job.context.existingTopics.flatMap((topic) => topic.sourceEventIds)]);
|
|
4092
4164
|
const batchIds = new Set(job.sourceEventIds);
|
|
@@ -4133,16 +4205,44 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4133
4205
|
if (part.sourceEventIds.length > 12 || part.sourceEventIds.some((eventId) => !ids.includes(eventId))) throw new Error("Overview references must belong to returned topic membership.");
|
|
4134
4206
|
if (part.sourceEventIds.some((eventId) => !batchIds.has(eventId)) && !candidates.get(topicId)?.overview.some((old) => digest(old) === digest(part))) throw new Error("Old overview evidence must be preserved verbatim; new sections need batch events.");
|
|
4135
4207
|
}
|
|
4208
|
+
if (proposal.sections !== void 0 && !Array.isArray(proposal.sections)) throw new Error("Invalid topic sections.");
|
|
4209
|
+
const assignments = memoryTopicMembershipSections(proposal);
|
|
4210
|
+
const sectionLabels = /* @__PURE__ */ new Set();
|
|
4211
|
+
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 Error("Invalid or duplicate topic section title.");
|
|
4213
|
+
sectionLabels.add(label(section.title));
|
|
4214
|
+
checkIds(section.sourceEventIds, "section");
|
|
4215
|
+
if (section.sourceEventIds.some((eventId) => !ids.includes(eventId))) throw new Error("Section references must belong to returned topic membership.");
|
|
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 Error("Old section membership must be preserved; reassignment needs batch events.");
|
|
4217
|
+
}
|
|
4218
|
+
if (proposal.sections !== void 0 && ids.some((eventId) => batchIds.has(eventId) && !assignments.some((section) => section.sourceEventIds.includes(eventId)))) throw new Error("Topic sections omitted batch events.");
|
|
4136
4219
|
const membership = unique2([...(existing?.sourceEventIds ?? []).filter((eventId) => sources2.has(eventId)), ...ids]);
|
|
4220
|
+
const assignedIds = new Set(proposal.sections === void 0 ? [] : assignments.flatMap((section) => section.sourceEventIds).filter((eventId) => batchIds.has(eventId)));
|
|
4221
|
+
const sections = existing && !existing.invalidated ? memoryTopicMembershipSections(existing) : [];
|
|
4222
|
+
const memberIds = new Set(membership);
|
|
4223
|
+
for (const section of sections) section.sourceEventIds = section.sourceEventIds.filter((eventId) => memberIds.has(eventId) && !assignedIds.has(eventId));
|
|
4224
|
+
for (const assignment of assignments) {
|
|
4225
|
+
const section = sections.find((current) => label(current.title) === label(assignment.title));
|
|
4226
|
+
if (section) section.sourceEventIds = unique2([...section.sourceEventIds, ...assignment.sourceEventIds]);
|
|
4227
|
+
else sections.push({ title: assignment.title.trim(), sourceEventIds: [...assignment.sourceEventIds] });
|
|
4228
|
+
}
|
|
4229
|
+
const finalSections = sections.filter((section) => section.sourceEventIds.length > 0);
|
|
4230
|
+
const titles = finalSections.map((section) => section.title);
|
|
4137
4231
|
const inherited = existing && !existing.invalidated ? existing.overview : [];
|
|
4138
|
-
|
|
4139
|
-
|
|
4232
|
+
if (proposal.sections !== void 0) for (const part of proposal.overview) {
|
|
4233
|
+
if (!inherited.some((old) => digest(old) === digest(part)) && !memoryTopicOverviewMatchesSection(part, titles)) {
|
|
4234
|
+
throw new Error("New topic overview must match a final topic section.");
|
|
4235
|
+
}
|
|
4236
|
+
}
|
|
4237
|
+
const overview2 = unique2(proposal.overview.filter((part) => memoryTopicOverviewMatchesSection(part, titles)).map((part) => JSON.stringify(part))).map((part) => JSON.parse(part));
|
|
4238
|
+
for (const part of inherited) if (memoryTopicOverviewMatchesSection(part, titles) && !overview2.some((current) => digest(current) === digest(part))) overview2.push(structuredClone(part));
|
|
4140
4239
|
proposals.push({
|
|
4141
4240
|
id: topicId,
|
|
4142
|
-
title: proposal.title.trim(),
|
|
4143
|
-
description: proposal.description.trim(),
|
|
4241
|
+
title: job.sectionBackfillTopicId ? existing.title : proposal.title.trim(),
|
|
4242
|
+
description: job.sectionBackfillTopicId ? existing.description : proposal.description.trim(),
|
|
4144
4243
|
sourceEventIds: membership,
|
|
4145
4244
|
overview: overview2,
|
|
4245
|
+
sections: finalSections,
|
|
4146
4246
|
sourceVersions: Object.fromEntries(membership.map((eventId) => [eventId, versions.get(eventId)])),
|
|
4147
4247
|
dependencyVersions: { ...!existing?.invalidated ? existing?.dependencyVersions ?? {} : {}, ...job.dependencyVersions },
|
|
4148
4248
|
projectorVersion: MEMORY_TOPIC_PROJECTOR_VERSION,
|
|
@@ -4156,6 +4256,11 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4156
4256
|
const index = this.state.topics.findIndex((current) => current.id === topic.id);
|
|
4157
4257
|
if (index < 0) this.state.topics.push(topic);
|
|
4158
4258
|
else this.state.topics[index] = topic;
|
|
4259
|
+
const pending = this.state.sectionBackfill?.[topic.id];
|
|
4260
|
+
if (pending) {
|
|
4261
|
+
for (const eventId of memoryTopicMembershipSections(topic).flatMap((section) => section.sourceEventIds)) delete pending[eventId];
|
|
4262
|
+
if (!Object.keys(pending).length) delete this.state.sectionBackfill[topic.id];
|
|
4263
|
+
}
|
|
4159
4264
|
}
|
|
4160
4265
|
Object.assign(this.state.projectedVersions, job.sourceVersions);
|
|
4161
4266
|
for (const [eventId, version] of Object.entries(job.sourceVersions)) {
|
|
@@ -4193,7 +4298,7 @@ var MemoryTopicDirectory = class _MemoryTopicDirectory {
|
|
|
4193
4298
|
const previous = this.requireJob(id);
|
|
4194
4299
|
const sources2 = new Map(events.filter(visible).map((event) => [event.id, event]));
|
|
4195
4300
|
const versions = sourceVersions(sources2);
|
|
4196
|
-
if (previous.superseded || previous.status !== "failed" || previous.attempts < TOPIC_MAX_ATTEMPTS || previous.nextRetryAt !== null || !jobInputsCurrent(previous, versions) || previous.sourceEventIds.some((eventId) => this.state.projectedVersions[eventId] === versions.get(eventId))) {
|
|
4301
|
+
if (previous.superseded || previous.status !== "failed" || previous.attempts < TOPIC_MAX_ATTEMPTS || previous.nextRetryAt !== null || !jobInputsCurrent(previous, versions) || previous.sourceEventIds.some((eventId) => this.state.projectedVersions[eventId] === versions.get(eventId) && (!previous.sectionBackfillTopicId || this.state.sectionBackfill?.[previous.sectionBackfillTopicId]?.[eventId] !== versions.get(eventId)))) {
|
|
4197
4302
|
throw new Error("Topic retry conflict: failure is no longer current or retryable.");
|
|
4198
4303
|
}
|
|
4199
4304
|
const job = {
|
|
@@ -4518,7 +4623,7 @@ var StrataGate = class _StrataGate {
|
|
|
4518
4623
|
}
|
|
4519
4624
|
async initializeTopicBootstrap() {
|
|
4520
4625
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
4521
|
-
if (this.topicDirectory.
|
|
4626
|
+
if (!this.topicDirectory.needsInitialization()) return;
|
|
4522
4627
|
try {
|
|
4523
4628
|
await this.commitMutation(() => this.topicDirectory.initializeBootstrap(this.listAllEvents(), toUtc8Iso(this.now())));
|
|
4524
4629
|
return;
|
|
@@ -7411,7 +7516,7 @@ var TOPIC_OVERVIEW = {
|
|
|
7411
7516
|
additionalProperties: false,
|
|
7412
7517
|
properties: {
|
|
7413
7518
|
kind: { type: "string", enum: TOPIC_OVERVIEW_KINDS, required: true },
|
|
7414
|
-
title: { type: "string", description: "Lasting category inside the chapter
|
|
7519
|
+
title: { type: "string", description: "Lasting category inside the chapter. Must match a declared or inherited section title; if omitted, the kind default must match one. Never a single Event, bug, version, date or task title; at most 80 characters." },
|
|
7415
7520
|
text: { type: "string", description: "Source-grounded scope, historical progress, decision, change, or unresolved question; preserve time, uncertainty, plans, cancellation, and disputes.", required: true },
|
|
7416
7521
|
sourceEventIds: { ...STRING_ARRAY, required: true }
|
|
7417
7522
|
}
|
|
@@ -7428,6 +7533,14 @@ var TOPIC_PROJECTOR_PARAMETERS = {
|
|
|
7428
7533
|
title: { type: "string", description: "A broad lasting chapter label (project, life area, relationship), not a single task, release, UI page, or factual conclusion.", required: true },
|
|
7429
7534
|
description: { type: "string", description: "Describe which records this topic covers; navigation only, not a claim about current truth.", required: true },
|
|
7430
7535
|
sourceEventIds: { ...STRING_ARRAY, description: "Exact supplied Event ids assigned to this topic; existing unseen topic members are retained by the store.", required: true },
|
|
7536
|
+
sections: { type: "array", required: true, items: {
|
|
7537
|
+
type: "object",
|
|
7538
|
+
additionalProperties: false,
|
|
7539
|
+
properties: {
|
|
7540
|
+
title: { type: "string", description: "Reuse a lasting section title from sectionTitles when applicable.", required: true },
|
|
7541
|
+
sourceEventIds: { ...STRING_ARRAY, description: "Batch Event ids routed to this section, regardless of overview citations.", required: true }
|
|
7542
|
+
}
|
|
7543
|
+
} },
|
|
7431
7544
|
overview: { type: "array", items: TOPIC_OVERVIEW, required: true }
|
|
7432
7545
|
}
|
|
7433
7546
|
}
|
|
@@ -7441,7 +7554,7 @@ events.catalogHints \u4EC5\u662F\u53EF\u9009 routing hint\uFF08\u5206\u7C7B\u5BF
|
|
|
7441
7554
|
\u8FD9\u4E9B\u4E8B\u4EF6\u5361\u662F\u6709\u754C\u7684\u5BFC\u822A\u6750\u6599\uFF0C\u6807\u9898\u6216\u6458\u8981\u53EF\u80FD\u7701\u7565\u5C3E\u90E8\uFF1B\u4E0D\u8981\u56E0\u672A\u770B\u5230\u5426\u5B9A\u3001\u540E\u7EED\u66F4\u65B0\u6216\u9650\u5236\u6761\u4EF6\u5C31\u63A8\u65AD\u5F53\u524D\u4E8B\u5B9E\u3001\u5B8C\u6210\u72B6\u6001\u6216\u6CA1\u6709\u4E89\u8BAE\u3002\u5B8C\u6574\u4E8B\u5B9E\u4ECD\u987B\u5C55\u5F00\u4E8B\u4EF6\u548C\u539F\u6587\u53D6\u8BC1\u3002status\u3001supersededBy\u3001temporal.status \u53CA\u5176\u660E\u786E\u5173\u7CFB\u63D0\u4F9B\u7684\u5386\u53F2\u3001\u8BA1\u5212\u3001\u53D6\u6D88\u548C\u51B2\u7A81\u6807\u8BB0\u5FC5\u987B\u4FDD\u7559\uFF1B\u6750\u6599\u4E0D\u8DB3\u65F6\u53EA\u5199\u8986\u76D6\u8303\u56F4\u6216\u5F85\u786E\u8BA4\uFF0C\u4E0D\u8865\u5199\u7ED3\u8BBA\u3002
|
|
7442
7555
|
truncatedEventIds \u660E\u786E\u5217\u51FA\u672A\u5B8C\u6574\u63D0\u4F9B\u7684\u4E8B\u4EF6\uFF1A\u5F15\u7528\u5176\u4E2D\u4EFB\u4F55\u4E8B\u4EF6\u7684\u6982\u8981\u6BB5\u53EA\u80FD\u662F scope\uFF0C\u53EA\u8BF4\u660E\u8D44\u6599\u8303\u56F4\uFF0C\u4E0D\u5199\u5386\u53F2\u3001\u51B3\u5B9A\u3001\u53D8\u5316\u6216\u5F85\u786E\u8BA4\u7684\u4E8B\u5B9E\uFF1B\u6DF7\u5408\u5F15\u7528\u5176\u4ED6\u4E8B\u4EF6\u4E5F\u4E0D\u80FD\u653E\u5BBD\u6B64\u9650\u5236\u3002
|
|
7443
7556
|
|
|
7444
|
-
Topic \u662F\u957F\u671F\u7684\u5927\u7AE0\u8282\uFF0C\u6309\u9879\u76EE\u3001\u751F\u6D3B\u9886\u57DF\u6216\u957F\u671F\u5173\u7CFB\u7EC4\u7EC7\uFF0C\u800C\u4E0D\u662F\u6BCF\u4E2A\u4E8B\u4EF6\u6216\u5B50\u4EFB\u52A1\u4E00\u7AE0\u3002\u4F8B\u5982 StrataGate UI\u3001DSH \u517C\u5BB9\u3001Topic Directory\u3001Retrieval \u5E94\u5F52\u5165\u201CStrataGate\u201D\u540C\u4E00\u7AE0\uFF1B\u6C42\u804C\u6295\u9012\u3001\u9762\u8BD5\u548C\u5B9E\u4E60\u8FDB\u5C55\u5F52\u5165\u201C\u6C42\u804C\u4E0E\u804C\u4E1A\u53D1\u5C55\u201D\u3002existingTopics.sectionTitles \u5217\u51FA\u8BE5\u7AE0\u5168\u90E8\u5DF2\u6709\u5C0F\u8282\u6807\u9898\uFF0C\u4EC5\u4F9B\u5BFC\u822A\uFF0C\u4E0D\u662F\u6B63\u6587\u6216\u4E8B\u5B9E\u8BC1\u636E\u3002\u5DF2\u6709\u5C0F\u8282\u80FD\u5BB9\u7EB3\u672C\u6279\u4E8B\u9879\u65F6\u5FC5\u987B\u9010\u5B57\u590D\u7528\u539F title\uFF0C\u4E0D\u8981\u8FD1\u4E49\u6539\u540D\uFF1B\u4E0D\u80FD\u56E0\u4E3A overview \u53EA\u5C55\u793A\u524D\u51E0\u6BB5\u5C31\u91CD\u590D\u521B\u5EFA\u5C0F\u8282\u3002\u786E\u5B9E\u5C5E\u4E8E\u5DF2\u6709\u7C7B\u522B\u65E0\u6CD5\u5BB9\u7EB3\u7684\u65B0\u957F\u671F\u7C7B\u522B\u624D\u65B0\u589E\u5C0F\u8282\uFF1B\u5DF2\u6709\u7AE0\u4E5F\u5E94\u590D\u7528\u539F\u7AE0\u540D\u548C topicId\u3002\u5728\u7AE0\u5185\u7528
|
|
7557
|
+
Topic \u662F\u957F\u671F\u7684\u5927\u7AE0\u8282\uFF0C\u6309\u9879\u76EE\u3001\u751F\u6D3B\u9886\u57DF\u6216\u957F\u671F\u5173\u7CFB\u7EC4\u7EC7\uFF0C\u800C\u4E0D\u662F\u6BCF\u4E2A\u4E8B\u4EF6\u6216\u5B50\u4EFB\u52A1\u4E00\u7AE0\u3002\u4F8B\u5982 StrataGate UI\u3001DSH \u517C\u5BB9\u3001Topic Directory\u3001Retrieval \u5E94\u5F52\u5165\u201CStrataGate\u201D\u540C\u4E00\u7AE0\uFF1B\u6C42\u804C\u6295\u9012\u3001\u9762\u8BD5\u548C\u5B9E\u4E60\u8FDB\u5C55\u5F52\u5165\u201C\u6C42\u804C\u4E0E\u804C\u4E1A\u53D1\u5C55\u201D\u3002existingTopics.sectionTitles \u5217\u51FA\u8BE5\u7AE0\u5168\u90E8\u5DF2\u6709\u5C0F\u8282\u6807\u9898\uFF0C\u4EC5\u4F9B\u5BFC\u822A\uFF0C\u4E0D\u662F\u6B63\u6587\u6216\u4E8B\u5B9E\u8BC1\u636E\u3002\u5DF2\u6709\u5C0F\u8282\u80FD\u5BB9\u7EB3\u672C\u6279\u4E8B\u9879\u65F6\u5FC5\u987B\u9010\u5B57\u590D\u7528\u539F title\uFF0C\u4E0D\u8981\u8FD1\u4E49\u6539\u540D\uFF1B\u4E0D\u80FD\u56E0\u4E3A overview \u53EA\u5C55\u793A\u524D\u51E0\u6BB5\u5C31\u91CD\u590D\u521B\u5EFA\u5C0F\u8282\u3002\u786E\u5B9E\u5C5E\u4E8E\u5DF2\u6709\u7C7B\u522B\u65E0\u6CD5\u5BB9\u7EB3\u7684\u65B0\u957F\u671F\u7C7B\u522B\u624D\u65B0\u589E\u5C0F\u8282\uFF1B\u5DF2\u6709\u7AE0\u4E5F\u5E94\u590D\u7528\u539F\u7AE0\u540D\u548C topicId\u3002\u5728\u7AE0\u5185\u7528 sections.title \u533A\u5206\u957F\u671F\u7C7B\u522B\uFF0C\u4F8B\u5982\u201C\u7F3A\u9677\u6392\u67E5\u4E0E\u4FEE\u590D\u201D\u201C\u7248\u672C\u53D1\u5E03\u4E0E\u5B89\u88C5\u201D\u201C\u67B6\u6784\u4E0E\u914D\u7F6E\u51B3\u7B56\u201D\u201C\u754C\u9762\u4E0E\u4EA4\u4E92\u201D\u201CDSH \u517C\u5BB9\u201D\uFF1Boverview.title \u4F7F\u7528\u5BF9\u5E94\u5C0F\u8282\u6807\u9898\uFF0Ckind \u8868\u793A\u6BB5\u843D\u5185\u5BB9\u6027\u8D28\uFF0C\u4E0D\u662F\u62C6\u7AE0\u6216\u62C6\u8282\u4F9D\u636E\u3002\u4E0D\u540C\u9879\u76EE\u6216\u4E0D\u76F8\u5173\u9886\u57DF\u4ECD\u5206\u5F00\uFF0C\u4E0D\u80FD\u4E3A\u51CF\u5C11\u7AE0\u6570\u786C\u5408\u5E76\u3002\u6BCF\u6279\u901A\u5E38\u53EA\u65B0\u589E 0-2 \u4E2A\u5927\u7AE0\u8282\uFF1B12 \u662F\u4E92\u4E0D\u76F8\u5173\u9886\u57DF\u7684\u5B89\u5168\u4E0A\u9650\uFF0C\u4E0D\u662F\u521B\u5EFA\u76EE\u6807\u3002
|
|
7445
7558
|
\u6BCF\u4E2A\u672C\u6279\u4E8B\u4EF6\u5FC5\u987B\u81F3\u5C11\u5206\u914D\u7ED9\u4E00\u4E2A\u7AE0\u8282\uFF1B\u5373\u4F7F\u5C1A\u672A\u8FDB\u5165\u56FE\u8C31\u4E5F\u8981\u4FDD\u7559\u5165\u53E3\u3002\u540C\u4E00\u9879\u76EE\u4E0B\u4E0D\u540C\u4E8B\u4EF6\u3001\u65F6\u95F4\u548C\u51B3\u5B9A\u53EF\u4EE5\u5171\u7528\u7AE0\u8282\uFF0C\u5F52\u7C7B\u4E0D\u7B49\u4E8E\u628A\u4E8B\u5B9E\u5408\u5E76\u6216\u8BA4\u5B9A\u5B83\u4EEC\u76F8\u540C\u3002\u5148\u68C0\u67E5\u6240\u6709 existingTopics \u7684\u8303\u56F4\uFF0C\u80FD\u5BB9\u7EB3\u672C\u6279\u4E8B\u9879\u5C31\u4F18\u5148\u590D\u7528 topicId\uFF0C\u5373\u4F7F\u6807\u9898\u6CA1\u6709\u8BE5\u5B50\u4EFB\u52A1\u5173\u952E\u8BCD\uFF1B\u4E0D\u8981\u4E3A\u5B50\u529F\u80FD\u3001\u7248\u672C\u3001\u4E00\u6B21\u6295\u9012\u6216\u53D1\u5E03\u53E6\u5EFA\u7AE0\u8282\u3002\u540C\u540D\u7AE0\u8282\u53EA\u80FD\u8FD4\u56DE\u4E00\u6B21\u3002\u65B0\u589E\u7AE0\u8282\u7701\u7565 topicId\uFF1B\u65E7\u7AE0\u8282\u672A\u5C55\u793A\u7684\u6210\u5458\u7531\u5B58\u50A8\u5C42\u4FDD\u7559\uFF0C\u4E0D\u8981\u731C\u6D4B\u6216\u8865\u9F50\u5176\u5185\u5BB9\u3002\u6BCF\u4E2A\u7AE0\u8282 sourceEventIds \u53EA\u5199\u5B9E\u9645\u7ED9\u51FA\u7684\u4E8B\u4EF6\u7F16\u53F7\uFF1B\u6BCF\u6BB5\u6765\u6E90\u5FC5\u987B\u5C5E\u4E8E\u8BE5\u7AE0\u8282\u7684 sourceEventIds\u3002candidateTopicsOmitted \u8868\u793A\u53D7\u8F93\u5165\u9884\u7B97\u9650\u5236\u672A\u5C55\u793A\u7684\u5019\u9009\u6570\uFF0C\u4E0D\u80FD\u63A8\u65AD\u88AB\u7701\u7565\u7AE0\u8282\u7684\u5185\u5BB9\u3002
|
|
7446
7559
|
|
|
7447
7560
|
\u5BFC\u822A\u5C42\u7EA7\u662F\u7AE0 \u2192 \u8282 \u2192 Event\u3002Event \u4FDD\u6301\u5177\u4F53\u3001\u72EC\u7ACB\uFF1B\u8282\u5FC5\u987B\u80FD\u6301\u7EED\u5BB9\u7EB3\u540C\u7C7B\u4E8B\u4EF6\u3002\u5355\u4E2A bug\u3001\u7248\u672C\u53F7\u3001\u67D0\u5929\u8FDB\u5C55\u3001\u4E00\u6B21\u5B89\u88C5\u6216\u4E00\u4E2A\u4FEE\u590D\u65B9\u6848\u4E0D\u80FD\u5355\u72EC\u6210\u8282\uFF0C\u4E5F\u4E0D\u8981\u7167\u6284 Event \u6807\u9898\u4F5C\u4E3A\u8282\u6807\u9898\u3002\u4F8B\u5982\u201C0.2.4 \u63D0\u53D6\u6839\u56E0\u66B4\u9732\u8FC7\u7A0B\u201D\u201C0.2.6 \u63D0\u53D6\u4ECD 0 \u4EA7\u51FA\u201D\u201C0.2.7 \u4FEE\u590D\u65B9\u6848\u201D\u201C\u5C01\u5757\u5F02\u5E38\u5F85\u6392\u67E5\u201D\u90FD\u8FDB\u5165\u201C\u7F3A\u9677\u6392\u67E5\u4E0E\u4FEE\u590D\u201D\uFF1B\u201C\u4ECA\u65E5\u7248\u672C\u8FED\u4EE3 0.2.1\u21920.2.7\u201D\u201C0.2.8 \u5B89\u88C5\u4E0E\u9A8C\u8BC1\u201D\u8FDB\u5165\u201C\u7248\u672C\u53D1\u5E03\u4E0E\u5B89\u88C5\u201D\u3002\u5177\u4F53\u7248\u672C\u3001\u65F6\u95F4\u3001\u6839\u56E0\u3001\u5904\u7F6E\u8FC7\u7A0B\u548C\u72B6\u6001\u653E\u5728\u8282\u5185\u6982\u8981\u53CA\u539F Event \u4E2D\u3002\u201C\u8BA1\u5212\u4FEE\u590D\u201D\u201C\u4FEE\u590D\u4E2D\u201D\u201C\u5DF2\u4FEE\u590D\u201D\u5C5E\u4E8E\u540C\u4E00\u7C7B\u522B\u7684\u4E0D\u540C\u72B6\u6001\uFF0C\u4E0D\u53E6\u8D77\u8282\u3002\u540C\u4E00\u7C7B\u522B\u53EF\u4EE5\u6709\u591A\u4E2A\u6709\u6765\u6E90\u7684\u6BB5\u843D\uFF0C\u4E0D\u8981\u4E3A\u6BB5\u843D\u53E6\u8D77\u6807\u9898\u3002
|
|
@@ -7449,7 +7562,10 @@ Topic \u662F\u957F\u671F\u7684\u5927\u7AE0\u8282\uFF0C\u6309\u9879\u76EE\u3001\u
|
|
|
7449
7562
|
|
|
7450
7563
|
title \u662F\u53EF\u8BC6\u522B\u7684\u4E3B\u9898\u540D\u79F0\uFF1Bdescription \u7528\u4E00\u53E5\u8BDD\u8BF4\u660E\u8FD9\u91CC\u5B58\u6709\u54EA\u4E9B\u8D44\u6599\uFF0C\u4F8B\u5982\u201C\u5305\u542B\u90E8\u7F72\u9009\u578B\u3001\u8FC1\u79FB\u7ECF\u8FC7\u548C\u9057\u7559\u95EE\u9898\u201D\uFF0C\u4E0D\u8981\u628A\u5386\u53F2\u7ED3\u8BBA\u5199\u6210\u5F53\u524D\u4E8B\u5B9E\u3002overview \u7684 kind \u4EC5\u5141\u8BB8 scope\uFF08\u80CC\u666F\u6216\u8986\u76D6\u8303\u56F4\uFF09\u3001history\uFF08\u5386\u53F2\u8FDB\u5C55\uFF09\u3001decision\uFF08\u5F53\u65F6\u7684\u51B3\u5B9A\uFF09\u3001change\uFF08\u6709\u4F9D\u636E\u7684\u53D8\u5316\uFF09\u3001open-question\uFF08\u5F85\u786E\u8BA4\u4E8B\u9879\uFF09\u3002\u4FDD\u7559\u660E\u786E\u65F6\u70B9\u3001\u8FC7\u53BB\u5F0F\u3001\u8BA1\u5212\u3001\u53D6\u6D88\u3001\u4E89\u8BAE\u548C\u4E0D\u786E\u5B9A\u6027\uFF1B\u6CA1\u6709\u65B0\u7684\u652F\u6301\u4E0D\u80FD\u628A\u65E7\u4E8B\u5B9E\u5347\u683C\u4E3A\u5F53\u524D\u7ED3\u8BBA\uFF0C\u4E0D\u80FD\u628A\u8BA1\u5212\u5199\u6210\u5DF2\u5B8C\u6210\u3001\u5EFA\u8BAE\u5199\u6210\u5DF2\u51B3\u5B9A\u3001\u63A8\u6D4B\u5199\u6210\u539F\u56E0\u3002\u51B3\u5B9A\u66FE\u7ECF\u6210\u7ACB\u4E0D\u7B49\u4E8E\u73B0\u5728\u4ECD\u6709\u6548\u3002\u76F8\u5173\u4E8B\u4EF6\u77DB\u76FE\u65F6\u4FDD\u7559\u77DB\u76FE\u548C\u5F85\u786E\u8BA4\u72B6\u6001\u3002
|
|
7451
7564
|
|
|
7452
|
-
|
|
7565
|
+
sections \u72EC\u7ACB\u4FDD\u5B58\u4E8B\u4EF6\u7684\u5C0F\u8282\u5F52\u5C5E\uFF0C\u4E0E overview \u7684\u6765\u6E90\u5F15\u7528\u5206\u5F00\u3002\u6BCF\u4E2A\u5F52\u5165\u672C\u7AE0\u7684\u672C\u6279\u4E8B\u4EF6\u5FC5\u987B\u81F3\u5C11\u5206\u914D\u5230\u4E00\u4E2A sections \u5C0F\u8282\uFF0C\u5373\u4F7F\u603B\u89C8\u6CA1\u6709\u5F15\u7528\u5B83\u3002sections \u53EA\u5199\u672C\u6279\u4E8B\u4EF6\u7F16\u53F7\uFF1B\u65E7\u5F52\u5C5E\u7531\u5B58\u50A8\u5C42\u7EE7\u627F\u3002\u4F18\u5148\u9010\u5B57\u590D\u7528 existingTopics.sectionTitles \u4E2D\u9002\u5408\u7684\u6807\u9898\uFF1B\u5C0F\u8282\u6CBF\u7528\u4E0A\u8FF0\u957F\u671F\u7C7B\u522B\u89C4\u5219\u3002\u65B0 overview \u7684\u5C55\u793A\u6807\u9898\u5FC5\u987B\u5339\u914D\u672C\u8F6E sections.title \u6216\u91CD\u5206\u7C7B\u540E\u4ECD\u5B58\u5728\u7684\u5DF2\u6709\u5C0F\u8282\u6807\u9898\uFF0C\u4E0D\u80FD\u9760\u603B\u89C8\u5355\u72EC\u521B\u5EFA\u5C0F\u8282\uFF1B\u7701\u7565 title \u65F6\uFF0C\u5176 kind \u7684\u9ED8\u8BA4\u5C55\u793A\u6807\u9898\u4E5F\u5FC5\u987B\u6709\u5BF9\u5E94\u7684\u6700\u7EC8\u5C0F\u8282\u3002\u82E5\u91CD\u5206\u7C7B\u79FB\u8D70\u65E7\u5C0F\u8282\u7684\u5168\u90E8\u4E8B\u4EF6\uFF0C\u5B58\u50A8\u5C42\u4E22\u5F03\u8BE5\u8282\u7684\u65E7\u603B\u89C8\uFF0C\u5373\u4F7F\u672C\u8F6E\u539F\u6837\u56DE\u4F20\u4E5F\u4E0D\u4F1A\u4FDD\u7559\u3002overview.title \u53EA\u51B3\u5B9A\u6982\u62EC\u6587\u5B57\u663E\u793A\u5728\u54EA\u4E2A\u5C0F\u8282\uFF0C\u4E0D\u51B3\u5B9A\u4E8B\u4EF6\u5F52\u5C5E\uFF1B\u65E0\u9700\u4E3A\u6BCF\u6761\u4E8B\u4EF6\u5199\u603B\u89C8\uFF0C\u4E5F\u4E0D\u8981\u4E3A\u4E86\u8986\u76D6\u76EE\u5F55\u800C\u865A\u589E\u6765\u6E90\u5F15\u7528\u3002
|
|
7566
|
+
\u82E5\u8F93\u5165\u542B sectionBackfillTopicId\uFF0C\u8FD9\u662F\u5347\u7EA7\u540E\u7684\u65E7\u4E8B\u4EF6\u8865\u5F52\u7C7B\uFF1A\u53EA\u8FD4\u56DE\u8BE5 topicId \u7684\u4E00\u4E2A\u7AE0\u8282\uFF0C\u4FDD\u7559\u539F\u7AE0\u540D\u548C\u63CF\u8FF0\uFF0C\u4E3A\u5168\u90E8\u672C\u6279\u4E8B\u4EF6\u5199\u5165\u9002\u5F53\u7684\u5C0F\u8282\uFF1B\u4E0D\u5F97\u6362\u7AE0\u6216\u65B0\u5EFA\u7AE0\u3002\u5DF2\u6709\u5C0F\u8282\u6807\u9898\u53EF\u80FD\u53EA\u5C55\u793A\u524D 120 \u4E2A\uFF0C\u4F18\u5148\u590D\u7528\u9002\u5408\u7684\u5DF2\u6709\u6807\u9898\u3002\u6B64\u4EFB\u52A1\u65E0\u9700\u91CD\u5199\u603B\u89C8\uFF0Coverview \u53EF\u4EE5\u4E3A\u7A7A\uFF0C\u5B58\u50A8\u5C42\u4FDD\u7559\u6709\u6548\u65E7\u603B\u89C8\u3002
|
|
7567
|
+
|
|
7568
|
+
outputTokenBudget \u662F\u6574\u4E2A\u54CD\u5E94\u7684\u8F93\u51FA\u9884\u7B97\u3002\u4F18\u5148\u5B8C\u6574\u5206\u914D\u672C\u6279\u6240\u6709\u4E8B\u4EF6\u5230\u7AE0\u8282\u548C\u5C0F\u8282\uFF0C\u518D\u5199\u5FC5\u8981\u7684\u7B80\u77ED\u540D\u79F0\u3001\u8303\u56F4\u8BF4\u660E\u548C\u65B0\u589E\u6BB5\u3002\u5B58\u50A8\u5C42\u4F1A\u5728\u6700\u7EC8\u5C0F\u8282\u4ECD\u5B58\u5728\u65F6\u81EA\u52A8\u7EE7\u627F\u6709\u6548\u65E7\u6982\u8981\uFF0C\u65E0\u9700\u8F93\u51FA\u590D\u8FF0\uFF1B\u901A\u5E38\u53EA\u9700 0-2 \u6BB5\u65B0\u589E\u6982\u8981\uFF0C\u9884\u7B97\u4E0D\u8DB3\u65F6 overview \u53EF\u4E3A\u7A7A\uFF0Csections \u4ECD\u5FC5\u987B\u5B8C\u6574\u5F52\u7C7B\u672C\u6279\u4E8B\u4EF6\uFF0C\u4E0D\u622A\u65AD JSON \u6216\u9057\u6F0F\u4E8B\u4EF6\u3002
|
|
7453
7569
|
\u6BCF\u6279\u6700\u591A 12 \u4E2A\u4E3B\u9898\uFF1Btitle \u6700\u591A 120 \u4E2A\u5B57\u7B26\uFF0Cdescription \u6700\u591A 400 \u4E2A\u5B57\u7B26\uFF0C\u4F46\u5C3D\u91CF\u7528\u77ED\u540D\u79F0\u548C\u4E00\u53E5\u8303\u56F4\u8BF4\u660E\uFF1B\u6BCF\u4E2A\u4E3B\u9898\u5728\u672C\u6B21\u54CD\u5E94\u6700\u591A 8 \u6BB5\u6982\u8981\uFF1B\u8FD9\u4E0D\u662F\u7D2F\u8BA1\u7AE0\u8282\u7684\u6BB5\u6570\u6216\u8282\u6570\u4E0A\u9650\uFF0C\u6709\u6548\u65E7\u6BB5\u7531\u5B58\u50A8\u5C42\u4FDD\u7559\u3002\u6BCF\u6BB5\u6700\u591A 600 \u4E2A\u5B57\u7B26\u300112 \u4E2A\u6765\u6E90\u3002\u4E0D\u8981\u91CD\u590D\u6807\u9898\u3001\u76EE\u5F55\u8BF4\u660E\u3001\u5DF2\u6709\u6982\u8981\u6216\u65E0\u5173\u80CC\u666F\u3002\u4E0D\u8981\u751F\u6210\u7ECF\u9A8C\u5C42\u6216\u53E6\u5199\u65B0\u7684\u4E8B\u4EF6\u3002`;
|
|
7454
7570
|
var MAX_TOPIC_INPUT_TOKENS = 2e4;
|
|
7455
7571
|
var EXTERNAL_MEMORY_DECIDER_PARAMETERS = {
|
|
@@ -7594,6 +7710,9 @@ function topicProjectionPayload(context, outputTokenBudget) {
|
|
|
7594
7710
|
if (new Set(context.existingTopics.map(({ id }) => id)).size !== context.existingTopics.length) {
|
|
7595
7711
|
throw new Error("Topic projection input contains duplicate candidate topic ids");
|
|
7596
7712
|
}
|
|
7713
|
+
if (context.sectionBackfillTopicId && !context.existingTopics.some(({ id }) => id === context.sectionBackfillTopicId)) {
|
|
7714
|
+
throw new Error("Topic section backfill input must include its existing chapter");
|
|
7715
|
+
}
|
|
7597
7716
|
const events = context.events.map((event) => ({
|
|
7598
7717
|
id: event.id,
|
|
7599
7718
|
title: event.title,
|
|
@@ -7608,6 +7727,7 @@ function topicProjectionPayload(context, outputTokenBudget) {
|
|
|
7608
7727
|
for (; ; ) {
|
|
7609
7728
|
const payload = {
|
|
7610
7729
|
jobId: context.jobId,
|
|
7730
|
+
...context.sectionBackfillTopicId ? { sectionBackfillTopicId: context.sectionBackfillTopicId } : {},
|
|
7611
7731
|
evidenceCompleteness: "bounded-navigation-cards; retrieve Event and original-source evidence before relying on factual content",
|
|
7612
7732
|
outputTokenBudget,
|
|
7613
7733
|
events,
|
|
@@ -7619,7 +7739,8 @@ function topicProjectionPayload(context, outputTokenBudget) {
|
|
|
7619
7739
|
overview: topic.overview,
|
|
7620
7740
|
...topic.sectionTitles ? { sectionTitles: topic.sectionTitles } : {},
|
|
7621
7741
|
sourceEventIds: topic.sourceEventIds,
|
|
7622
|
-
totalSourceEvents: topic.totalSourceEvents
|
|
7742
|
+
totalSourceEvents: topic.totalSourceEvents,
|
|
7743
|
+
...topic.sectionTitlesOmitted ? { sectionTitlesOmitted: topic.sectionTitlesOmitted } : {}
|
|
7623
7744
|
})),
|
|
7624
7745
|
candidateTopicsOmitted: context.existingTopics.length - existingTopics.length
|
|
7625
7746
|
};
|
|
@@ -7627,7 +7748,7 @@ function topicProjectionPayload(context, outputTokenBudget) {
|
|
|
7627
7748
|
|
|
7628
7749
|
${JSON_RETRY_INSTRUCTION}`, tool: toolSchema("topicProjector"), payload }));
|
|
7629
7750
|
if (inputTokens <= MAX_TOPIC_INPUT_TOKENS) return { payload, shownContext: { ...context, existingTopics } };
|
|
7630
|
-
if (existingTopics.length > 0) existingTopics.pop();
|
|
7751
|
+
if (existingTopics.length > 0 && existingTopics.at(-1).id !== context.sectionBackfillTopicId) existingTopics.pop();
|
|
7631
7752
|
else throw new Error(`Topic projection input exceeds ${MAX_TOPIC_INPUT_TOKENS} estimated tokens; split the Event batch instead of truncating evidence`);
|
|
7632
7753
|
}
|
|
7633
7754
|
}
|
|
@@ -7655,14 +7776,18 @@ function parseTopicProjection(value, context) {
|
|
|
7655
7776
|
}
|
|
7656
7777
|
return value2;
|
|
7657
7778
|
};
|
|
7658
|
-
const
|
|
7779
|
+
const rawTopics = raw.topics;
|
|
7780
|
+
const topics = rawTopics.map((candidate) => {
|
|
7659
7781
|
const item = object(candidate);
|
|
7660
7782
|
const title = boundedText(item.title, "topic.title", 120);
|
|
7661
7783
|
const label = title.normalize("NFKC").replace(/\s+/g, " ").toLowerCase();
|
|
7662
7784
|
if (usedLabels.has(label)) fail("duplicate chapter label; combine its batch assignments");
|
|
7663
7785
|
usedLabels.add(label);
|
|
7664
|
-
if (Object.keys(item).some((key) => !["topicId", "title", "description", "sourceEventIds", "overview"].includes(key))) fail("unexpected topic field");
|
|
7786
|
+
if (Object.keys(item).some((key) => !["topicId", "title", "description", "sourceEventIds", "overview", "sections"].includes(key))) fail("unexpected topic field");
|
|
7665
7787
|
const topicId = item.topicId === void 0 ? void 0 : boundedText(item.topicId, "topicId", 200);
|
|
7788
|
+
if (context.sectionBackfillTopicId && (rawTopics.length !== 1 || topicId !== context.sectionBackfillTopicId)) {
|
|
7789
|
+
fail("section backfill must update only its existing chapter");
|
|
7790
|
+
}
|
|
7666
7791
|
if (topicId !== void 0 && (!candidates.has(topicId) || usedTopicIds.has(topicId))) fail("unknown or repeated topicId");
|
|
7667
7792
|
if (topicId !== void 0) usedTopicIds.add(topicId);
|
|
7668
7793
|
const existing = topicId === void 0 ? void 0 : candidates.get(topicId);
|
|
@@ -7670,7 +7795,24 @@ function parseTopicProjection(value, context) {
|
|
|
7670
7795
|
const sources2 = sourceIds(item.sourceEventIds, "topic.sourceEventIds", allowedIds);
|
|
7671
7796
|
if (!sources2.some((id) => eventIds.has(id))) fail("every returned topic must cover a supplied batch Event");
|
|
7672
7797
|
for (const id of sources2) if (eventIds.has(id)) covered.add(id);
|
|
7798
|
+
if (!Array.isArray(item.sections) || item.sections.length === 0) fail("sections must assign batch Events independently of overview");
|
|
7799
|
+
const sectionLabels = /* @__PURE__ */ new Set();
|
|
7800
|
+
const sections = item.sections.map((candidateSection) => {
|
|
7801
|
+
const section = object(candidateSection);
|
|
7802
|
+
if (Object.keys(section).some((key2) => !["title", "sourceEventIds"].includes(key2))) fail("unexpected section field");
|
|
7803
|
+
const title2 = boundedText(section.title, "section.title", 80);
|
|
7804
|
+
const key = title2.normalize("NFKC").replace(/\s+/g, " ").toLowerCase();
|
|
7805
|
+
if (sectionLabels.has(key)) fail("duplicate section title");
|
|
7806
|
+
sectionLabels.add(key);
|
|
7807
|
+
return { title: title2, sourceEventIds: sourceIds(section.sourceEventIds, "section.sourceEventIds", new Set(sources2.filter((id) => eventIds.has(id)))) };
|
|
7808
|
+
});
|
|
7809
|
+
const assigned = new Set(sections.flatMap((section) => section.sourceEventIds));
|
|
7810
|
+
if (sources2.some((id) => eventIds.has(id) && !assigned.has(id))) fail("every topic batch Event must be assigned to a section");
|
|
7673
7811
|
if (!Array.isArray(item.overview) || item.overview.length > 8) fail("overview must contain 0-8 source-grounded entries");
|
|
7812
|
+
const overviewTitles = [
|
|
7813
|
+
...sections.map((section) => section.title),
|
|
7814
|
+
...existing?.sectionTitles ?? memoryTopicMembershipSections(existing ?? { overview: [] }).map((section) => section.title)
|
|
7815
|
+
];
|
|
7674
7816
|
const overview2 = item.overview.map((candidateEntry) => {
|
|
7675
7817
|
const entry = object(candidateEntry);
|
|
7676
7818
|
if (Object.keys(entry).some((key) => !["kind", "title", "text", "sourceEventIds"].includes(key))) fail("unexpected overview field");
|
|
@@ -7679,16 +7821,20 @@ function parseTopicProjection(value, context) {
|
|
|
7679
7821
|
const paragraph = boundedText(entry.text, "overview.text", 600);
|
|
7680
7822
|
const paragraphSources = sourceIds(entry.sourceEventIds, "overview.sourceEventIds", new Set(sources2), 12);
|
|
7681
7823
|
if (entry.kind !== "scope" && paragraphSources.some((id) => truncatedIds.has(id))) fail("truncated Event evidence may only support scope entries");
|
|
7682
|
-
|
|
7824
|
+
const preserved = existing?.overview.some((old) => old.kind === entry.kind && old.title === sectionTitle && old.text === paragraph && old.sourceEventIds.length === paragraphSources.length && old.sourceEventIds.every((id) => paragraphSources.includes(id)));
|
|
7825
|
+
if (paragraphSources.some((id) => !eventIds.has(id)) && !preserved) {
|
|
7683
7826
|
fail("old overview evidence may only be preserved verbatim; rewritten entries must cite batch Events only");
|
|
7684
7827
|
}
|
|
7685
|
-
|
|
7828
|
+
const part = { kind: entry.kind, ...sectionTitle === void 0 ? {} : { title: sectionTitle }, text: paragraph, sourceEventIds: paragraphSources };
|
|
7829
|
+
if (!preserved && !memoryTopicOverviewMatchesSection(part, overviewTitles)) fail("new overview must match a declared or inherited section");
|
|
7830
|
+
return part;
|
|
7686
7831
|
});
|
|
7687
7832
|
return {
|
|
7688
7833
|
...topicId === void 0 ? {} : { topicId },
|
|
7689
7834
|
title,
|
|
7690
7835
|
description: boundedText(item.description, "topic.description", 400),
|
|
7691
7836
|
sourceEventIds: sources2,
|
|
7837
|
+
sections,
|
|
7692
7838
|
overview: overview2
|
|
7693
7839
|
};
|
|
7694
7840
|
});
|
|
@@ -8562,6 +8708,12 @@ function memoryTopicSections(topic) {
|
|
|
8562
8708
|
if (!memberPositions.has(id)) memberPositions.set(id, index);
|
|
8563
8709
|
});
|
|
8564
8710
|
const sections = /* @__PURE__ */ new Map();
|
|
8711
|
+
for (const membership of memoryTopicMembershipSections(topic)) {
|
|
8712
|
+
const key = memoryTopicSectionKey(membership.title);
|
|
8713
|
+
const section = sections.get(key) ?? { key, title: membership.title, paragraphs: [], sourceEventIds: [] };
|
|
8714
|
+
section.sourceEventIds.push(...membership.sourceEventIds.filter((id) => memberPositions.has(id)));
|
|
8715
|
+
sections.set(key, section);
|
|
8716
|
+
}
|
|
8565
8717
|
for (const part of topic.overview) {
|
|
8566
8718
|
const title = memoryTopicSectionTitle(part);
|
|
8567
8719
|
const identity = memoryTopicSectionKey(title);
|
|
@@ -8571,7 +8723,6 @@ function memoryTopicSections(topic) {
|
|
|
8571
8723
|
sections.set(identity, section);
|
|
8572
8724
|
}
|
|
8573
8725
|
section.paragraphs.push(part);
|
|
8574
|
-
for (const id of part.sourceEventIds) if (memberPositions.has(id)) section.sourceEventIds.push(id);
|
|
8575
8726
|
}
|
|
8576
8727
|
return [...sections.values()].map((section) => ({
|
|
8577
8728
|
section: {
|
|
@@ -8690,16 +8841,26 @@ ${[...selected.map(({ line: line2 }) => line2), line].join("\n")}`) <= MEMORY_DI
|
|
|
8690
8841
|
${selected.map(({ line }) => line).join("\n")}`.trim();
|
|
8691
8842
|
}
|
|
8692
8843
|
function boundedTopic(topic, envelope = {}) {
|
|
8844
|
+
const { sections: _sections, ...summaryTopic } = topic;
|
|
8845
|
+
const sectionIndex = memoryTopicSections(topic).map(({ title, sourceEventIds: sourceEventIds2 }) => ({ title, sourceEventCount: sourceEventIds2.length }));
|
|
8846
|
+
const sections = [];
|
|
8693
8847
|
const overview2 = [];
|
|
8694
8848
|
const sourceEventIds = topic.sourceEventIds.slice(0, 12);
|
|
8695
|
-
const result = (parts) => ({
|
|
8696
|
-
...
|
|
8849
|
+
const result = (parts, selectedSections = sections) => ({
|
|
8850
|
+
...summaryTopic,
|
|
8697
8851
|
sourceEventIds,
|
|
8698
8852
|
overview: parts,
|
|
8853
|
+
sections: selectedSections,
|
|
8854
|
+
totalSections: sectionIndex.length,
|
|
8855
|
+
omittedSections: sectionIndex.length - selectedSections.length,
|
|
8699
8856
|
totalSourceEvents: topic.sourceEventIds.length,
|
|
8700
8857
|
omittedSourceEvents: topic.sourceEventIds.length - sourceEventIds.length,
|
|
8701
8858
|
omittedOverviewParagraphs: topic.overview.length - parts.length
|
|
8702
8859
|
});
|
|
8860
|
+
for (const section of sectionIndex) {
|
|
8861
|
+
if (estimateTokens(JSON.stringify({ ...envelope, topic: result(overview2, [...sections, section]) })) > TOPIC_OVERVIEW_TOKEN_BUDGET) break;
|
|
8862
|
+
sections.push(section);
|
|
8863
|
+
}
|
|
8703
8864
|
for (const paragraph of topic.overview) {
|
|
8704
8865
|
if (estimateTokens(JSON.stringify({ ...envelope, topic: result([...overview2, paragraph]) })) <= TOPIC_OVERVIEW_TOKEN_BUDGET) overview2.push(paragraph);
|
|
8705
8866
|
}
|
|
@@ -11496,7 +11657,7 @@ function currentPluginVersion() {
|
|
|
11496
11657
|
return "unknown";
|
|
11497
11658
|
}
|
|
11498
11659
|
var STRATAGATE_DSH_VERSION = currentPluginVersion();
|
|
11499
|
-
var TOPIC_DIRECTORY_VIEW_VERSION =
|
|
11660
|
+
var TOPIC_DIRECTORY_VIEW_VERSION = 5;
|
|
11500
11661
|
function graphProjectionIsProcessing(job) {
|
|
11501
11662
|
return derivationJobState(job, false) === "processing" || derivationJobState(job, false) === "retryable";
|
|
11502
11663
|
}
|
|
@@ -11802,12 +11963,25 @@ function topicDirectoryProjection(snapshot, agentMemoryWeight) {
|
|
|
11802
11963
|
const pending = visibleEvents.filter(({ id }) => pendingIds.has(id)).sort((left, right) => left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id));
|
|
11803
11964
|
const versions = new Map(visibleEvents.map((event) => [event.id, memoryTopicEventFingerprint(event)]));
|
|
11804
11965
|
const frozen = state.bootstrap?.projectorVersion === MEMORY_TOPIC_PROJECTOR_VERSION ? state.bootstrap : null;
|
|
11966
|
+
const relationKey = (topicId, id, version) => JSON.stringify([topicId ?? null, id, version]);
|
|
11967
|
+
const backfillRelations = Object.entries(state.sectionBackfill ?? {}).flatMap(([topicId, pending2]) => Object.entries(pending2).filter(([id, version]) => versions.get(id) === version).map(([id, version]) => ({ topicId, id, version })));
|
|
11805
11968
|
const budgetedVersions = { ...frozen?.sourceVersions ?? {}, ...state.rebuildVersions };
|
|
11806
11969
|
const history = frozen ? Object.entries(budgetedVersions).filter(([id, version]) => versions.get(id) === version) : [];
|
|
11807
|
-
const
|
|
11970
|
+
const ordinaryOutstanding = history.filter(([id, version]) => state.projectedVersions[id] !== version);
|
|
11971
|
+
const outstanding = /* @__PURE__ */ new Set([...ordinaryOutstanding.map(([id]) => id), ...backfillRelations.map(({ id }) => id)]);
|
|
11972
|
+
const outstandingWork = /* @__PURE__ */ new Set([
|
|
11973
|
+
...ordinaryOutstanding.map(([id, version]) => relationKey(void 0, id, version)),
|
|
11974
|
+
...backfillRelations.map(({ topicId, id, version }) => relationKey(topicId, id, version))
|
|
11975
|
+
]);
|
|
11976
|
+
const failedWork = /* @__PURE__ */ new Set();
|
|
11808
11977
|
const failures = state.jobs.flatMap((job) => {
|
|
11809
11978
|
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 [];
|
|
11810
|
-
const ids = job.sourceEventIds.filter((id) =>
|
|
11979
|
+
const ids = job.sourceEventIds.filter((id) => {
|
|
11980
|
+
const key = relationKey(job.sectionBackfillTopicId, id, job.sourceVersions[id]);
|
|
11981
|
+
if (!outstandingWork.has(key)) return false;
|
|
11982
|
+
failedWork.add(key);
|
|
11983
|
+
return true;
|
|
11984
|
+
});
|
|
11811
11985
|
return ids.length > 0 ? [{
|
|
11812
11986
|
jobId: job.id,
|
|
11813
11987
|
eventIds: ids,
|
|
@@ -11817,11 +11991,14 @@ function topicDirectoryProjection(snapshot, agentMemoryWeight) {
|
|
|
11817
11991
|
updatedAt: job.updatedAt
|
|
11818
11992
|
}] : [];
|
|
11819
11993
|
});
|
|
11994
|
+
const total = (/* @__PURE__ */ new Set([...history.map(([id]) => id), ...backfillRelations.map(({ id }) => id)])).size;
|
|
11995
|
+
const failedRelations = backfillRelations.filter(({ topicId, id, version }) => failedWork.has(relationKey(topicId, id, version))).length;
|
|
11820
11996
|
const bootstrap = frozen ? {
|
|
11821
|
-
status: Object.keys(state.rebuildVersions ?? {}).length > 0
|
|
11822
|
-
total
|
|
11823
|
-
completed:
|
|
11997
|
+
status: backfillRelations.length > 0 && outstandingWork.size === failedWork.size ? "failed" : Object.keys(state.rebuildVersions ?? {}).length > 0 || backfillRelations.length > 0 ? outstandingWork.size === failedWork.size ? "completed" : "running" : frozen.status,
|
|
11998
|
+
total,
|
|
11999
|
+
completed: total - outstanding.size,
|
|
11824
12000
|
failedEvents: new Set(failures.flatMap(({ eventIds }) => eventIds)).size,
|
|
12001
|
+
sectionBackfill: { pendingRelations: backfillRelations.length, failedRelations },
|
|
11825
12002
|
failures
|
|
11826
12003
|
} : null;
|
|
11827
12004
|
const revision = createHash4("sha256").update(JSON.stringify({
|
|
@@ -11847,7 +12024,10 @@ function topicDirectoryProjection(snapshot, agentMemoryWeight) {
|
|
|
11847
12024
|
description: topic.description,
|
|
11848
12025
|
createdAt: topic.createdAt,
|
|
11849
12026
|
updatedAt: topic.updatedAt,
|
|
11850
|
-
coverage: { ...topic.coverage
|
|
12027
|
+
coverage: { ...topic.coverage, unassignedEvents: (() => {
|
|
12028
|
+
const assigned = new Set(memoryTopicSections(topic).flatMap((section) => section.sourceEventIds));
|
|
12029
|
+
return topic.sourceEventIds.filter((id) => !assigned.has(id)).length;
|
|
12030
|
+
})() },
|
|
11851
12031
|
sections: memoryTopicSectionNavigation(topic),
|
|
11852
12032
|
overview: topic.overview.map((part) => ({
|
|
11853
12033
|
kind: part.kind,
|
|
@@ -11924,7 +12104,7 @@ async function topicEvents(runtime, url) {
|
|
|
11924
12104
|
if (!topic) throw new AdminHttpError(404, "Unknown or unavailable memory topic");
|
|
11925
12105
|
if (sectionKey === null) ids = topic.sourceEventIds;
|
|
11926
12106
|
else if (sectionKey === "uncovered") {
|
|
11927
|
-
const covered = new Set(topic.
|
|
12107
|
+
const covered = new Set(memoryTopicSections(topic).flatMap((section) => section.sourceEventIds));
|
|
11928
12108
|
ids = topic.sourceEventIds.filter((id) => !covered.has(id));
|
|
11929
12109
|
} else {
|
|
11930
12110
|
const section = memoryTopicSections(topic).find((candidate) => candidate.key === sectionKey);
|
|
@@ -12813,7 +12993,7 @@ async function dashboard(runtime, url, ifNoneMatch) {
|
|
|
12813
12993
|
namespace: selected.namespace,
|
|
12814
12994
|
revision: selected.revision,
|
|
12815
12995
|
overview: overviewValue,
|
|
12816
|
-
processing: Number(selectedOverview?.processingJobs ?? 0) > 0 || topicDirectoryData.bootstrap !== null && topicDirectoryData.bootstrap.total > 0 && topicDirectoryData.bootstrap.status
|
|
12996
|
+
processing: Number(selectedOverview?.processingJobs ?? 0) > 0 || topicDirectoryData.bootstrap !== null && topicDirectoryData.bootstrap.total > 0 && (topicDirectoryData.bootstrap.status === "pending" || topicDirectoryData.bootstrap.status === "running"),
|
|
12817
12997
|
data: {
|
|
12818
12998
|
events: eventResult.items ?? [],
|
|
12819
12999
|
graph: graphResult,
|