@arcfold/dsh 0.0.0-stage → 0.4.0
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/LICENSE +21 -0
- package/README.md +8 -2
- package/THIRD_PARTY_NOTICES.md +11 -0
- package/cordis.patch.yml +138 -0
- package/package.json +70 -4
- package/patches/deepseek-0.2.2-rc.1-transport.json +65 -0
- package/patches/deepseek-0.2.2-rc.1-transport.patch +61 -0
- package/presets/arcfold.patch.yml +110 -0
- package/runtime/README.md +19 -0
- package/runtime/bootstrap.json +15 -0
- package/runtime/desktop-python.tar.gz +0 -0
- package/runtime/host/package.json +11 -0
- package/runtime/host/pnpm-workspace.yaml +9 -0
- package/runtime/initial-policy.json +94 -0
- package/runtime/profile/pnpm-workspace.yaml +7 -0
- package/scripts/exact-package-patch.mjs +106 -0
- package/scripts/patch-deepseek-transport.mjs +14 -0
- package/src/agent.mjs +56 -0
- package/src/browse-scope.mjs +14 -0
- package/src/budget.mjs +369 -0
- package/src/business.mjs +334 -0
- package/src/context-material.mjs +119 -0
- package/src/deepseek-budget-plugin.mjs +58 -0
- package/src/deepseek-provider.mjs +57 -0
- package/src/deepseek-receipt.mjs +97 -0
- package/src/deepseek-transport.mjs +97 -0
- package/src/desktop/adapter.mjs +16 -0
- package/src/desktop/bootstrap.mjs +84 -0
- package/src/desktop/configuration.mjs +117 -0
- package/src/desktop/provider.mjs +46 -0
- package/src/desktop/python-runtime.mjs +105 -0
- package/src/desktop/setup-service.mjs +81 -0
- package/src/desktop/vendor/LICENSE.deepseek +21 -0
- package/src/desktop/vendor/NOTICE.md +5 -0
- package/src/desktop/vendor/deepseek-0.2.2-rc.1.mjs +2919 -0
- package/src/evidence-ownership.mjs +51 -0
- package/src/evidence.mjs +35 -0
- package/src/goal-context.mjs +52 -0
- package/src/goal-execution-tool.mjs +74 -0
- package/src/goal-execution.mjs +333 -0
- package/src/goal-tool.mjs +56 -0
- package/src/goal-work-control.mjs +63 -0
- package/src/goal-work.mjs +257 -0
- package/src/goals.mjs +578 -0
- package/src/importer.mjs +298 -0
- package/src/index.mjs +53 -0
- package/src/learning-contracts.mjs +220 -0
- package/src/learning-defaults.mjs +98 -0
- package/src/learning-goals.mjs +165 -0
- package/src/learning-plugin.mjs +28 -0
- package/src/learning-runner.mjs +275 -0
- package/src/learning.mjs +534 -0
- package/src/legacy-export.mjs +332 -0
- package/src/legacy-import-source.mjs +356 -0
- package/src/metered-provider-host.mjs +101 -0
- package/src/metered-transport.mjs +435 -0
- package/src/method-context.mjs +57 -0
- package/src/method-lifecycle.mjs +34 -0
- package/src/method-tool.mjs +61 -0
- package/src/methods.mjs +425 -0
- package/src/model-selection.mjs +41 -0
- package/src/native-context-call.mjs +88 -0
- package/src/native-continuation.mjs +217 -0
- package/src/native-request-authority.mjs +90 -0
- package/src/openai-models.mjs +4 -0
- package/src/openai-receipt.mjs +98 -0
- package/src/openai-transport.mjs +167 -0
- package/src/openai-usage.mjs +194 -0
- package/src/opportunities.mjs +295 -0
- package/src/preparation-artifacts.mjs +213 -0
- package/src/preparation-plugin.mjs +290 -0
- package/src/runtime-command.mjs +264 -0
- package/src/runtime-config.mjs +417 -0
- package/src/runtime-guard.mjs +284 -0
- package/src/runtime-host.mjs +212 -0
- package/src/runtime-init.mjs +278 -0
- package/src/runtime-installation.mjs +518 -0
- package/src/runtime-lifecycle.mjs +227 -0
- package/src/runtime-model-settings.mjs +111 -0
- package/src/runtime-readiness.mjs +285 -0
- package/src/runtime-workspace.mjs +18 -0
- package/src/session-bridge.mjs +365 -0
- package/src/store.mjs +394 -0
- package/src/tree/LICENSE +21 -0
- package/src/tree/attribution.mjs +120 -0
- package/src/tree/clusters.mjs +61 -0
- package/src/tree/consolidation.mjs +103 -0
- package/src/tree/context.mjs +211 -0
- package/src/tree/contracts.mjs +142 -0
- package/src/tree/experience.mjs +140 -0
- package/src/tree/experiment-budget.mjs +84 -0
- package/src/tree/hooks.mjs +254 -0
- package/src/tree/host-context.mjs +107 -0
- package/src/tree/host-process.mjs +288 -0
- package/src/tree/host.mjs +37 -0
- package/src/tree/improvement.mjs +106 -0
- package/src/tree/items.mjs +294 -0
- package/src/tree/jobs.mjs +184 -0
- package/src/tree/learning.mjs +202 -0
- package/src/tree/metrics.mjs +203 -0
- package/src/tree/model-owner.mjs +254 -0
- package/src/tree/observations.mjs +73 -0
- package/src/tree/prompts.mjs +500 -0
- package/src/tree/reports.mjs +67 -0
- package/src/tree/repository.mjs +236 -0
- package/src/tree/runtime.mjs +745 -0
- package/src/tree/script-writer.mjs +149 -0
- package/src/tree/scripts.mjs +208 -0
- package/src/tree/signals.mjs +118 -0
- package/src/tree/snapshots.mjs +379 -0
- package/src/tree/token-counter.mjs +75 -0
- package/src/tree/token-counter.py +37 -0
- package/src/tree/tokenizer-resources/LICENSE +21 -0
- package/src/tree/tokenizer-resources/README.md +31 -0
- package/src/tree/tokenizer-resources/deepseek_recipe-0.2.0-cp310-abi3-macosx_11_0_arm64.whl +0 -0
- package/src/tree/tokenizer-resources/manifest.json +61 -0
- package/src/tree/tokenizer-resources/native.pyi +1042 -0
- package/src/tree/tokenizer-resources/tokenizer-NOTICE.md +26 -0
- package/src/tree/tokenizer-resources/tokenizer-v4-NOTICE.md +27 -0
- package/src/tree/tokenizer-resources/tokenizer-v4.json +267359 -0
- package/src/tree/tokenizer-resources/tokenizer.json +267359 -0
- package/src/tree/tools.mjs +99 -0
- package/src/tree/tree.mjs +202 -0
- package/src/tree/ui.mjs +285 -0
- package/src/tree/verification.mjs +143 -0
- package/src/understandings.mjs +309 -0
- package/src/web-budget-recovery.mjs +181 -0
package/src/learning.mjs
ADDED
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
import { worksForDelivery, assertWork, relationState } from './goal-work.mjs';
|
|
2
|
+
import { assertNativeContinuationStored } from './native-continuation.mjs';
|
|
3
|
+
// Business-only migration of 3.0 learning.mjs material/commitSection/save*.
|
|
4
|
+
// DSH and the host own execution, retries, dispatch identities and durability.
|
|
5
|
+
import { canonicalJson, identity } from './store.mjs';
|
|
6
|
+
import { messageText } from './business.mjs';
|
|
7
|
+
import { sourceText, evidenceProvenance } from './evidence.mjs';
|
|
8
|
+
import { pauseMethod } from './method-lifecycle.mjs';
|
|
9
|
+
import { usageForVersion } from './methods.mjs';
|
|
10
|
+
import { GoalService, goalRepairAllowed } from './goals.mjs';
|
|
11
|
+
import { ContractError, digest, clone, LEARN_PROMPT, LEARN_SCHEMA, LEARN_SECTIONS,
|
|
12
|
+
parseLearn, assertVisibleSources, requiresUserSource, projectUnderstanding } from './learning-contracts.mjs';
|
|
13
|
+
import { applyGoalChanges, invalidateDependents } from './learning-goals.mjs';
|
|
14
|
+
|
|
15
|
+
export { LEARN_PROMPT, LEARN_SCHEMA } from './learning-contracts.mjs';
|
|
16
|
+
const groupName = kind => 'existing' + kind[0].toUpperCase() + kind.slice(1);
|
|
17
|
+
const unavailable = row => !row || row.deleted || row.withdrawn || row.useProhibited ||
|
|
18
|
+
['withdrawn', 'superseded', 'rejected', 'dismissed'].includes(row.status);
|
|
19
|
+
const atSequence = (row, cutoff) => Number.isSafeInteger(row?.createdSequence) &&
|
|
20
|
+
Number.isSafeInteger(row.updatedSequence) && row.createdSequence <= cutoff && row.updatedSequence <= cutoff;
|
|
21
|
+
const fail = (code, message) => { throw new ContractError(code, message); };
|
|
22
|
+
const recordRef = (kind, record) => ({ kind, id: record.id, revision: record.revision, hash: digest(record) });
|
|
23
|
+
const errorView = error => ({ code: error.code || 'learning_commit_failed', message: error.message,
|
|
24
|
+
...(error.details ? { details: error.details } : {}) });
|
|
25
|
+
|
|
26
|
+
function sourceCurrent(store, source) {
|
|
27
|
+
const control = source && store.get('source_controls', source.id);
|
|
28
|
+
return !unavailable(source) && !unavailable(control || {}) && control?.status !== 'paused';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function sourceBefore(store, source, boundary) {
|
|
32
|
+
if (!sourceCurrent(store, source) || !atSequence(source, boundary.sequence)) return false;
|
|
33
|
+
const ref = source.provenance?.sourceRef;
|
|
34
|
+
if (ref?.sessionId === boundary.sessionId && ref.seq > boundary.throughSeq) return false;
|
|
35
|
+
return typeof source.occurredAt === 'string' && Number.isFinite(Date.parse(source.occurredAt)) &&
|
|
36
|
+
Date.parse(source.occurredAt) <= Date.parse(boundary.occurredAt);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Unlike a method dependency, a paused goal root can still be discussed.
|
|
40
|
+
function evidenceBundle(store, row, kind, boundary, path = new Set()) {
|
|
41
|
+
const key = `${kind}:${row?.id}`;
|
|
42
|
+
const rejected = kind === 'opportunities'
|
|
43
|
+
? !row || row.deleted || row.withdrawn || row.useProhibited : unavailable(row);
|
|
44
|
+
if (rejected || row.status === 'paused' && !['goals', 'opportunities'].includes(kind) ||
|
|
45
|
+
!atSequence(row, boundary.sequence) || path.has(key) ||
|
|
46
|
+
!Array.isArray(row.sourceIds) || !row.sourceIds.length) return null;
|
|
47
|
+
const next = new Set([...path, key]), sources = new Map(), records = new Map([[key, recordRef(kind, row)]]);
|
|
48
|
+
for (const id of row.sourceIds) {
|
|
49
|
+
const source = store.get('events', id);
|
|
50
|
+
if (!sourceBefore(store, source, boundary)) return null;
|
|
51
|
+
sources.set(id, source);
|
|
52
|
+
}
|
|
53
|
+
for (const ref of row.dependencies || []) {
|
|
54
|
+
const depKind = ref.kind || ['events', 'understandings', 'methods'].find(k => store.get(k, ref.id));
|
|
55
|
+
if (!['events', 'understandings', 'methods'].includes(depKind)) return null;
|
|
56
|
+
const dependency = store.get(depKind, ref.id);
|
|
57
|
+
if (!dependency || dependency.revision !== ref.revision) return null;
|
|
58
|
+
if (depKind === 'events') {
|
|
59
|
+
if (!sourceBefore(store, dependency, boundary)) return null;
|
|
60
|
+
sources.set(dependency.id, dependency);
|
|
61
|
+
} else {
|
|
62
|
+
const nested = evidenceBundle(store, dependency, depKind, boundary, next);
|
|
63
|
+
if (!nested) return null;
|
|
64
|
+
for (const source of nested.sources) sources.set(source.id, source);
|
|
65
|
+
for (const version of nested.records) records.set(`${version.kind}:${version.id}`, version);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return { sources: [...sources.values()], records: [...records.values()] };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function sourceView(source) {
|
|
72
|
+
return { id: source.id, revision: source.revision, role: source.role,
|
|
73
|
+
content: source.message ? messageText(source.message) : sourceText(source),
|
|
74
|
+
occurredAt: source.occurredAt, conversationId: source.conversationId ?? null,
|
|
75
|
+
provenance: evidenceProvenance(source.provenance),
|
|
76
|
+
...(source.message?.content.some(block => !['text', 'reasoning'].includes(block.type))
|
|
77
|
+
? { nonTextBlocks: source.message.content.filter(block => !['text', 'reasoning'].includes(block.type)) } : {}),
|
|
78
|
+
...(source.role === 'assistant' ? { contentAuthor: 'assistant', userEndorsement: 'not_established_by_this_record' } : {}) };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function toolTrace(source) {
|
|
82
|
+
return source.role === 'tool' ? { id: source.id, sourceId: source.id,
|
|
83
|
+
toolCallId: source.message?.toolCallId ?? null, isError: source.message?.isError === true } : null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export class LearningService {
|
|
87
|
+
constructor({ business, maxCharacters = 180000 }) {
|
|
88
|
+
if (!business?.storage || !Number.isSafeInteger(maxCharacters) || maxCharacters < 1000 || maxCharacters > 180000)
|
|
89
|
+
throw new ContractError('invalid_learning_config', '需要业务存储和有效材料容量');
|
|
90
|
+
this.business = business; this.store = business.storage; this.maxCharacters = maxCharacters;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
prepare(pendingId) {
|
|
94
|
+
identity(pendingId, 'Pending learning id');
|
|
95
|
+
return this.store.transaction(() => {
|
|
96
|
+
const existing = this.store.get('learning_inputs', pendingId);
|
|
97
|
+
if (existing) {
|
|
98
|
+
this.assertInput(existing);
|
|
99
|
+
this.assertVersions(existing);
|
|
100
|
+
return existing;
|
|
101
|
+
}
|
|
102
|
+
const pending = this.store.get('learning_pending', pendingId);
|
|
103
|
+
if (!pending || pending.status !== 'pending') fail('invalid_learning_pending', '没有尚待处理的学习业务记录');
|
|
104
|
+
const delivery = this.store.get('deliveries', pending.deliveryId);
|
|
105
|
+
if (!delivery || delivery.kind !== 'formal_reply' || delivery.sessionId !== pending.sessionId ||
|
|
106
|
+
delivery.throughSeq !== pending.throughSeq) fail('invalid_learning_delivery', '学习须对应真实正式交付');
|
|
107
|
+
const boundary = { sequence: delivery.createdSequence, sessionId: delivery.sessionId,
|
|
108
|
+
throughSeq: delivery.throughSeq, occurredAt: delivery.occurredAt };
|
|
109
|
+
const sources = new Map(), versions = new Map();
|
|
110
|
+
if (delivery.nativeContinuation) assertNativeContinuationStored(this.store, delivery.nativeContinuation);
|
|
111
|
+
for (const id of [...new Set([...delivery.sourceEventIds,
|
|
112
|
+
...((delivery.nativeContinuation?.activeDelegation ?? delivery.nativeContinuation?.initialDelegation)?.sourceIds || [])])]) {
|
|
113
|
+
const source = this.store.get('events', id);
|
|
114
|
+
if (!sourceBefore(this.store, source, boundary)) fail('source_changed', '正式交付原件已失效或越过截止');
|
|
115
|
+
sources.set(id, source);
|
|
116
|
+
}
|
|
117
|
+
if (!sources.has(delivery.eventId)) fail('invalid_learning_delivery', '正式回复原件缺失');
|
|
118
|
+
const material = { version: 'arcfold-dsh-learning-v1', pendingId, sessionId: delivery.sessionId,
|
|
119
|
+
deliveryId: delivery.id, deliveredAnswerId: delivery.eventId,
|
|
120
|
+
inputEventIds: delivery.inputEventIds, inputEventId: delivery.inputEventIds.at(-1) || null,
|
|
121
|
+
boundary, authority: 'answer-already-delivered; source-grounded-business-interpretations; no-native-execution-or-user-endorsement-authority',
|
|
122
|
+
...(delivery.nativeContinuation ? { nativeContinuation: delivery.nativeContinuation } : {}),
|
|
123
|
+
sources: [], existingUnderstandings: [], existingMethods: [], existingOpportunities: [], existingGoals: [],
|
|
124
|
+
coverage: {}, toolReceipts: [], backgroundAtTurnEnd: delivery.backgroundAtTurnEnd };
|
|
125
|
+
for (const kind of LEARN_SECTIONS) {
|
|
126
|
+
const coverage = material.coverage[kind] = { available: 0, included: 0, omitted: 0 };
|
|
127
|
+
for (const row of this.store.listAll(kind)) {
|
|
128
|
+
if (row.scope?.conversationId && row.scope.conversationId !== delivery.sessionId ||
|
|
129
|
+
kind === 'opportunities' && row.conversationId !== delivery.sessionId) continue;
|
|
130
|
+
coverage.available++;
|
|
131
|
+
const validStatus = kind === 'understandings' ? row.status === 'active'
|
|
132
|
+
: kind === 'methods' ? ['trial', 'active'].includes(row.status) : true;
|
|
133
|
+
const bundle = validStatus && evidenceBundle(this.store, row, kind, boundary);
|
|
134
|
+
if (!bundle) { coverage.omitted++; continue; }
|
|
135
|
+
for (const source of bundle.sources) sources.set(source.id, source);
|
|
136
|
+
for (const ref of bundle.records) versions.set(`${ref.kind}:${ref.id}`, ref);
|
|
137
|
+
// Progress and each relationship have their own sources and versions.
|
|
138
|
+
const view = clone(row);
|
|
139
|
+
// Usage is a projection of adoption receipts, never a method revision.
|
|
140
|
+
if (kind === 'methods') view.trialUses = usageForVersion(this.store, row, { cutoffSequence: boundary.sequence }).trialUses;
|
|
141
|
+
if (kind === 'goals') {
|
|
142
|
+
const validPart = part => {
|
|
143
|
+
if (!part) return false;
|
|
144
|
+
if (!part.sourceIds?.length) return part.content === '';
|
|
145
|
+
const partBundle = evidenceBundle(this.store, { ...part, id: `part:${row.id}`,
|
|
146
|
+
createdSequence: row.createdSequence, updatedSequence: row.updatedSequence }, 'goal_part', boundary);
|
|
147
|
+
if (!partBundle) return false;
|
|
148
|
+
for (const source of partBundle.sources) sources.set(source.id, source);
|
|
149
|
+
for (const ref of partBundle.records.filter(ref => ref.kind !== 'goal_part')) versions.set(`${ref.kind}:${ref.id}`, ref);
|
|
150
|
+
return true;
|
|
151
|
+
};
|
|
152
|
+
if (!validPart(view.progress)) view.progress = null;
|
|
153
|
+
view.relations = (view.relations || []).filter(validPart).map(relation => {
|
|
154
|
+
const target = this.store.get('goals', relation.targetId);
|
|
155
|
+
return { ...relation, state: unavailable(target) ? 'unavailable'
|
|
156
|
+
: target.definitionVersion !== relation.targetDefinitionVersion ||
|
|
157
|
+
(relation.sourceDefinitionVersion ?? row.definitionVersion) !== row.definitionVersion ? 'needs_review' : 'current' };
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
material[groupName(kind)].push(view); coverage.included++;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const goals = new GoalService({ business: this.business });
|
|
164
|
+
const inputMessageIds = delivery.inputEventIds.map(id => this.store.get('events', id)?.message?.id).filter(Boolean);
|
|
165
|
+
material.goalDiscussions = goals.discussions({ sessionId: delivery.sessionId, inputMessageIds,
|
|
166
|
+
cutoffSequence: boundary.sequence }).map(binding => {
|
|
167
|
+
const source = this.store.get('events', binding.sourceId);
|
|
168
|
+
if (!material.inputEventIds.includes(source?.id) || !sourceBefore(this.store, source, boundary))
|
|
169
|
+
fail('source_changed', '目标讨论没有本次真实用户原件');
|
|
170
|
+
// Admission status is a later transport observation, not mutable user intent.
|
|
171
|
+
versions.set(`goal_discussions:${binding.id}`, { kind: 'goal_discussions', id: binding.id,
|
|
172
|
+
requestHash: binding.requestHash });
|
|
173
|
+
if (binding.mode === 'repair') {
|
|
174
|
+
const goal = this.store.get('goals', binding.goalId);
|
|
175
|
+
if (!goalRepairAllowed(goal) || !atSequence(goal, boundary.sequence))
|
|
176
|
+
fail('source_changed', '目标修复资格已改变');
|
|
177
|
+
material.existingGoals = material.existingGoals.filter(row => row.id !== goal.id);
|
|
178
|
+
material.existingGoals.push({ id: goal.id, revision: goal.revision,
|
|
179
|
+
definitionVersion: goal.definitionVersion, status: goal.status, repairOnly: true,
|
|
180
|
+
reason: goal.sourceInvalidation.reason || 'source_dependency', repairSourceId: source.id });
|
|
181
|
+
versions.set(`goals:${goal.id}`, recordRef('goals', goal));
|
|
182
|
+
}
|
|
183
|
+
return { id: binding.id, goalId: binding.goalId, goalRevision: binding.goalRevision,
|
|
184
|
+
definitionVersion: binding.definitionVersion, mode: binding.mode,
|
|
185
|
+
sourceId: source.id, messageId: binding.messageId };
|
|
186
|
+
});
|
|
187
|
+
material.goalLinks = this.store.listAll('goal_links', { where: { sessionId: delivery.sessionId,
|
|
188
|
+
startSeq: delivery.startSeq } }).flatMap(link => {
|
|
189
|
+
if (link.turn !== delivery.turn || link.createdSequence > boundary.sequence) return [];
|
|
190
|
+
goals.assertCore(link);
|
|
191
|
+
const outcome = this.store.get('goal_link_outcomes', link.id);
|
|
192
|
+
// The immutable association predates delivery; only this exact derived
|
|
193
|
+
// end reference may have been saved after delivery.createdSequence.
|
|
194
|
+
if (!outcome || outcome.deliveryId !== delivery.id || outcome.coreHash !== link.coreHash ||
|
|
195
|
+
outcome.sessionId !== delivery.sessionId || outcome.turn !== delivery.turn ||
|
|
196
|
+
outcome.startSeq !== delivery.startSeq || outcome.endEventId !== delivery.endEventId) return [];
|
|
197
|
+
versions.set(`goal_links:${link.id}`, recordRef('goal_links', link));
|
|
198
|
+
versions.set(`goal_link_outcomes:${link.id}`, recordRef('goal_link_outcomes', outcome));
|
|
199
|
+
const entry = { linkId: link.id, goalId: link.goalId, goalRevision: link.goalRevision,
|
|
200
|
+
definitionVersion: link.definitionVersion, deliveryId: delivery.id, outcome: outcome.outcome,
|
|
201
|
+
contributionEffect: 'not_assessed', claimType: 'assistant_inference' };
|
|
202
|
+
const records = [link.bundle.goal, ...link.bundle.relatedGoals];
|
|
203
|
+
const current = records.every(shown => {
|
|
204
|
+
const row = this.store.get('goals', shown.id);
|
|
205
|
+
return row?.revision === shown.revision && atSequence(row, boundary.sequence) &&
|
|
206
|
+
(shown.repairOnly ? goalRepairAllowed(row) : Boolean(evidenceBundle(this.store, row, 'goals', boundary)));
|
|
207
|
+
}) && link.bundle.sources.every(shown => {
|
|
208
|
+
const row = this.store.get('events', shown.id);
|
|
209
|
+
return row?.revision === shown.revision && sourceBefore(this.store, row, boundary);
|
|
210
|
+
});
|
|
211
|
+
if (!current) return [{ ...entry, availability: 'source_or_definition_changed', selection: null }];
|
|
212
|
+
for (const shown of link.bundle.sources) sources.set(shown.id, this.store.get('events', shown.id));
|
|
213
|
+
for (const shown of records) versions.set(`goals:${shown.id}`, recordRef('goals', this.store.get('goals', shown.id)));
|
|
214
|
+
return [{ ...entry, availability: 'current', selection: link.selection,
|
|
215
|
+
inputEvidence: this.store.listAll('goal_inputs', { where: { linkId: link.id } })
|
|
216
|
+
.filter(row => row.createdSequence <= boundary.sequence)
|
|
217
|
+
.map(({ id, step, requestIdentity, inputHash, evidence }) => ({ id, step, requestIdentity, inputHash, evidence })) }];
|
|
218
|
+
});
|
|
219
|
+
material.goalWorks = worksForDelivery(this.store, delivery).map(work => {
|
|
220
|
+
assertWork(this.store, work);
|
|
221
|
+
if (!work.state) fail('goal_work_changed', '工作缺少截止前的原生状态');
|
|
222
|
+
for (const id of work.sourceIds) {
|
|
223
|
+
const source = this.store.get('events', id);
|
|
224
|
+
if (!sourceBefore(this.store, source, boundary)) fail('source_changed', '工作委托来源不在此次交付截止内');
|
|
225
|
+
sources.set(id, source);
|
|
226
|
+
}
|
|
227
|
+
versions.set('goal_works:' + work.id, recordRef('goal_works', this.store.get('goal_works', work.id)));
|
|
228
|
+
return { id: work.id, sessionId: work.sessionId, nativeGoalId: work.nativeGoalId,
|
|
229
|
+
...work.state, id: work.id, relationId: work.relationId,
|
|
230
|
+
relation: relationState(this.store, work, work.relationId && this.store.get('goal_work_links', work.relationId), work.state),
|
|
231
|
+
sourceIds: work.sourceIds,
|
|
232
|
+
originKind: work.originKind, authority: 'historical-work-facts; no-new-permission' };
|
|
233
|
+
});
|
|
234
|
+
material.sources = [...sources.values()].sort((a, b) => a.createdSequence - b.createdSequence).map(sourceView);
|
|
235
|
+
material.toolReceipts = [...sources.values()].map(toolTrace).filter(Boolean);
|
|
236
|
+
material.existingUnderstandings = material.existingUnderstandings.map(row => projectUnderstanding(row, material.sources));
|
|
237
|
+
for (const source of sources.values()) {
|
|
238
|
+
versions.set(`events:${source.id}`, recordRef('events', source));
|
|
239
|
+
const control = this.store.get('source_controls', source.id);
|
|
240
|
+
versions.set(`source_controls:${source.id}`, { kind: 'source_controls', id: source.id,
|
|
241
|
+
revision: control?.revision ?? null, hash: control ? digest(control) : null });
|
|
242
|
+
}
|
|
243
|
+
const inputText = JSON.stringify(material);
|
|
244
|
+
if (LEARN_PROMPT.length + inputText.length > this.maxCharacters ||
|
|
245
|
+
Buffer.byteLength(LEARN_PROMPT + inputText) > 512000)
|
|
246
|
+
fail('learning_capacity', '完整学习材料超过容量,未截断原件或冻结部分输入');
|
|
247
|
+
return this.store.save('learning_inputs', { id: pendingId, pendingId, deliveryId: delivery.id,
|
|
248
|
+
material, versions: [...versions.values()], prompt: LEARN_PROMPT, schema: LEARN_SCHEMA,
|
|
249
|
+
inputText, inputHash: digest(inputText), materialHash: digest(material), promptHash: digest(LEARN_PROMPT) });
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
assertInput(input) {
|
|
254
|
+
if (input.inputText !== JSON.stringify(input.material) || input.inputHash !== digest(input.inputText) ||
|
|
255
|
+
input.materialHash !== digest(input.material) || input.promptHash !== digest(input.prompt))
|
|
256
|
+
fail('learning_input_changed', '冻结学习输入身份不一致');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
ownChanges(pendingId) {
|
|
260
|
+
const changes = new Map();
|
|
261
|
+
for (const section of this.store.listAll('learning_sections', { where: { pendingId } }))
|
|
262
|
+
if (section.status === 'completed') for (const change of section.changes) changes.set(`${change.kind}:${change.id}`, change);
|
|
263
|
+
return changes;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
assertVersions(input) {
|
|
267
|
+
if (input.material.nativeContinuation) assertNativeContinuationStored(this.store, input.material.nativeContinuation);
|
|
268
|
+
const own = this.ownChanges(input.pendingId);
|
|
269
|
+
for (const ref of input.versions) {
|
|
270
|
+
if (ref.kind === 'goal_discussions') {
|
|
271
|
+
const current = this.store.get(ref.kind, ref.id);
|
|
272
|
+
if (!current || current.requestHash !== ref.requestHash || digest(current.request) !== ref.requestHash)
|
|
273
|
+
fail('source_changed', '真实用户目标选择已变化');
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
const current = this.store.get(ref.kind, ref.id), accepted = own.get(`${ref.kind}:${ref.id}`) || ref;
|
|
277
|
+
if ((current?.revision ?? null) !== accepted.revision || (current ? digest(current) : null) !== accepted.hash)
|
|
278
|
+
fail('source_changed', `学习材料版本已变化:${ref.kind}/${ref.id}`);
|
|
279
|
+
if (ref.kind === 'events' && !sourceCurrent(this.store, current)) fail('source_changed', '学习原件已撤回');
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
assertTarget(input, kind, id) {
|
|
284
|
+
const shown = input.material[groupName(kind)]?.find(row => row.id === id);
|
|
285
|
+
const current = this.store.get(kind, id);
|
|
286
|
+
if (kind === 'goals' && shown?.repairOnly === true) {
|
|
287
|
+
const discussion = input.material.goalDiscussions.find(row => row.goalId === id && row.mode === 'repair');
|
|
288
|
+
if (!current || current.revision !== shown.revision || !goalRepairAllowed(current) ||
|
|
289
|
+
!atSequence(current, input.material.boundary.sequence) || !discussion ||
|
|
290
|
+
!input.material.inputEventIds.includes(discussion.sourceId) ||
|
|
291
|
+
!sourceBefore(this.store, this.store.get('events', discussion.sourceId), input.material.boundary))
|
|
292
|
+
fail('learning_conflict', '目标修复缺少当前明确选回或新原话');
|
|
293
|
+
return current;
|
|
294
|
+
}
|
|
295
|
+
if (!shown || !current || current.revision !== shown.revision ||
|
|
296
|
+
!evidenceBundle(this.store, current, kind, input.material.boundary))
|
|
297
|
+
fail('learning_conflict', '目标或依赖未显示、已改变或已失效');
|
|
298
|
+
return current;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
sourceReferences(input, ids, requireUser = false) {
|
|
302
|
+
const refs = assertVisibleSources(ids, input.material.sources, { requireUser });
|
|
303
|
+
for (const ref of refs) {
|
|
304
|
+
const current = this.store.get('events', ref.id);
|
|
305
|
+
if (current?.revision !== ref.revision || !sourceBefore(this.store, current, input.material.boundary))
|
|
306
|
+
fail('source_changed', '提议引用的原件已变化或越过截止');
|
|
307
|
+
}
|
|
308
|
+
return refs;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
understandingReferences(input, ids) {
|
|
312
|
+
if (new Set(ids).size !== ids.length) fail('invalid_source', '理解依赖不可重复');
|
|
313
|
+
return ids.map(id => {
|
|
314
|
+
const current = this.assertTarget(input, 'understandings', id);
|
|
315
|
+
if (current.status !== 'active') fail('learning_conflict', '理解依赖已失效');
|
|
316
|
+
return { kind: 'understandings', id, revision: current.revision };
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** Only consumes a flushed host-bound auxiliary result; never starts a session. */
|
|
321
|
+
commit(pendingId, { sessionId, events }) {
|
|
322
|
+
const input = this.store.get('learning_inputs', pendingId);
|
|
323
|
+
if (!input) fail('learning_not_prepared', '尚未冻结学习输入');
|
|
324
|
+
this.assertInput(input);
|
|
325
|
+
const run = this.store.get('learning_runs', pendingId);
|
|
326
|
+
if (!run || run.sessionId !== sessionId || !['submitted', 'settled'].includes(run.state) ||
|
|
327
|
+
run.inputHash !== input.inputHash || run.message?.id !== run.messageId)
|
|
328
|
+
fail('learning_run_mismatch', '辅助结果没有匹配的可信宿主登记');
|
|
329
|
+
const output = auxiliaryOutput(input, run, events);
|
|
330
|
+
const outputId = pendingId;
|
|
331
|
+
this.store.transaction(() => {
|
|
332
|
+
const prior = this.store.get('learning_outputs', outputId);
|
|
333
|
+
if (prior && prior.outputHash !== output.outputHash) fail('learning_output_conflict', '同次学习不能改换输出或辅助事件');
|
|
334
|
+
if (!prior) this.store.save('learning_outputs', { id: outputId, pendingId, ...output });
|
|
335
|
+
});
|
|
336
|
+
let parsed;
|
|
337
|
+
try {
|
|
338
|
+
if (output.error) throw new ContractError(output.error.code, output.error.message);
|
|
339
|
+
parsed = parseLearn(output.rawText);
|
|
340
|
+
} catch (error) {
|
|
341
|
+
for (const section of LEARN_SECTIONS) this.recordFailure(pendingId, section, error);
|
|
342
|
+
return this.finish(pendingId);
|
|
343
|
+
}
|
|
344
|
+
for (const section of LEARN_SECTIONS) {
|
|
345
|
+
if (this.store.get('learning_sections', `${pendingId}:${section}`)) continue;
|
|
346
|
+
try {
|
|
347
|
+
if (parsed.issues[section].length) throw new ContractError('invalid_output', '学习分区格式不符合合同', parsed.issues[section]);
|
|
348
|
+
this.commitSection(input, section, parsed.value[section]);
|
|
349
|
+
} catch (error) { this.recordFailure(pendingId, section, error); }
|
|
350
|
+
}
|
|
351
|
+
return this.finish(pendingId);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
commitSection(input, section, rows) {
|
|
355
|
+
return this.store.transaction(() => {
|
|
356
|
+
this.assertVersions(input);
|
|
357
|
+
const before = new Map(LEARN_SECTIONS.flatMap(kind => this.store.listAll(kind)
|
|
358
|
+
.map(row => [`${kind}:${row.id}`, recordRef(kind, row)])));
|
|
359
|
+
let ids;
|
|
360
|
+
if (section === 'goals') ids = applyGoalChanges(this.store, {
|
|
361
|
+
pendingId: input.pendingId, material: input.material, rows,
|
|
362
|
+
assertTarget: (kind, id) => this.assertTarget(input, kind, id),
|
|
363
|
+
assertSources: (ids, required) => this.sourceReferences(input, ids, required),
|
|
364
|
+
understandingRefs: ids => this.understandingReferences(input, ids),
|
|
365
|
+
});
|
|
366
|
+
else {
|
|
367
|
+
const targets = new Set();
|
|
368
|
+
ids = rows.map((row, index) => {
|
|
369
|
+
if (row.targetId && targets.has(row.targetId)) fail('invalid_output', '同一分区不能重复修改一个目标');
|
|
370
|
+
if (row.targetId) targets.add(row.targetId);
|
|
371
|
+
const refs = section === 'opportunities' && row.operation === 'reuse' ? []
|
|
372
|
+
: this.sourceReferences(input, row.sourceIds, requiresUserSource(section, row));
|
|
373
|
+
return section === 'understandings' ? this.saveUnderstanding(input, row, index, refs)
|
|
374
|
+
: section === 'methods' ? this.saveMethod(input, row, index, refs)
|
|
375
|
+
: this.saveOpportunity(input, row, index, refs);
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
const changes = LEARN_SECTIONS.flatMap(kind => this.store.listAll(kind).flatMap(row => {
|
|
379
|
+
const previous = before.get(`${kind}:${row.id}`);
|
|
380
|
+
return previous?.hash !== digest(row) ? [recordRef(kind, row)] : [];
|
|
381
|
+
}));
|
|
382
|
+
return this.store.save('learning_sections', { id: `${input.pendingId}:${section}`, pendingId: input.pendingId,
|
|
383
|
+
section, status: 'completed', ids, changes });
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
recordFailure(pendingId, section, error) {
|
|
388
|
+
const id = `${pendingId}:${section}`;
|
|
389
|
+
this.store.transaction(() => {
|
|
390
|
+
if (!this.store.get('learning_sections', id)) this.store.save('learning_sections', {
|
|
391
|
+
id, pendingId, section, status: 'failed', ids: [], changes: [], error: errorView(error) });
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
finish(pendingId) {
|
|
396
|
+
return this.store.transaction(() => {
|
|
397
|
+
const sections = this.store.listAll('learning_sections', { where: { pendingId } });
|
|
398
|
+
if (sections.length !== 4) fail('incomplete_learning', '学习分区尚未全部结算');
|
|
399
|
+
const completed = sections.filter(section => section.status === 'completed').length;
|
|
400
|
+
const status = completed === 4 ? 'completed' : completed ? 'partial' : 'failed';
|
|
401
|
+
const pending = this.store.get('learning_pending', pendingId);
|
|
402
|
+
if (!pending) fail('invalid_learning_pending', '学习待办已缺失');
|
|
403
|
+
const result = { status, sections: Object.fromEntries(sections.map(section => [section.section, section])) };
|
|
404
|
+
if (pending.status !== status || digest(pending.result || null) !== digest(result))
|
|
405
|
+
this.store.save('learning_pending', { ...pending, status, result }, { expectedRevision: pending.revision });
|
|
406
|
+
return result;
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
saveUnderstanding(input, row, index, dependencies) {
|
|
411
|
+
const old = row.operation === 'create' ? null : this.assertTarget(input, 'understandings', row.targetId);
|
|
412
|
+
if (row.operation === 'create' && row.targetId || !row.content.trim()) fail('invalid_output', '理解内容或目标编号不合法');
|
|
413
|
+
if (old && old.status !== 'active') fail('learning_conflict', '只可修订有效理解');
|
|
414
|
+
if (old) {
|
|
415
|
+
this.store.save('understandings', { ...old, status: row.operation === 'withdraw' ? 'withdrawn' : 'superseded',
|
|
416
|
+
changeSourceIds: row.sourceIds, changedByRunId: input.pendingId }, { expectedRevision: old.revision });
|
|
417
|
+
invalidateDependents(this.store, { ids: [old.id], pendingId: input.pendingId,
|
|
418
|
+
reason: `understanding_${row.operation}_by_learning` });
|
|
419
|
+
}
|
|
420
|
+
if (row.operation === 'withdraw') return old.id;
|
|
421
|
+
const saved = this.store.save('understandings', projectUnderstanding({
|
|
422
|
+
id: 'understanding-' + digest([input.pendingId, index]).slice(0, 32), content: row.content, claimType: row.claimType,
|
|
423
|
+
scope: { ...(row.scope === 'conversation' ? { conversationId: input.material.sessionId } : {}), topics: row.topics },
|
|
424
|
+
status: 'active', sourceIds: row.sourceIds, dependencies, version: (old?.version || 0) + 1,
|
|
425
|
+
previousId: old?.id || null, learnedFromRunId: input.pendingId, authority: 'revisable-understanding; not-permission',
|
|
426
|
+
}, input.material.sources));
|
|
427
|
+
return saved.id;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
saveMethod(input, row, index, dependencies) {
|
|
431
|
+
const old = row.operation === 'create' ? null : this.assertTarget(input, 'methods', row.targetId);
|
|
432
|
+
if (row.operation === 'create' && row.targetId ||
|
|
433
|
+
![row.title, row.guidance, row.appliesTo, row.doesNotApplyTo].every(value => value.trim()))
|
|
434
|
+
fail('invalid_output', '方法须有完整内容、适用与不适用条件和合法目标编号');
|
|
435
|
+
dependencies.push(...this.understandingReferences(input, row.understandingIds));
|
|
436
|
+
if (old) {
|
|
437
|
+
pauseMethod(this.store, old, { reason: row.operation === 'pause' ? row.guidance : 'superseded',
|
|
438
|
+
changes: { changeSourceIds: row.sourceIds }, pendingId: input.pendingId });
|
|
439
|
+
invalidateDependents(this.store, { ids: [old.id], pendingId: input.pendingId, reason: `method_${row.operation}_by_learning` });
|
|
440
|
+
}
|
|
441
|
+
if (row.operation === 'pause') return old.id;
|
|
442
|
+
const signature = digest({ title: row.title, guidance: row.guidance, appliesTo: row.appliesTo, doesNotApplyTo: row.doesNotApplyTo });
|
|
443
|
+
// Include paused/exhausted historical methods; a new id cannot buy a new trial.
|
|
444
|
+
const duplicate = this.store.listAll('methods').find(method => method.signature === signature ||
|
|
445
|
+
digest({ title: method.title ?? '', guidance: method.guidance ?? '', appliesTo: method.appliesTo ?? '', doesNotApplyTo: method.doesNotApplyTo ?? '' }) === signature);
|
|
446
|
+
if (duplicate) return duplicate.id;
|
|
447
|
+
return this.store.save('methods', { id: 'method-' + digest([input.pendingId, index]).slice(0, 32),
|
|
448
|
+
title: row.title, guidance: row.guidance, appliesTo: row.appliesTo, doesNotApplyTo: row.doesNotApplyTo,
|
|
449
|
+
origin: 'inferred', proposedOrigin: row.origin, sourceIds: row.sourceIds, dependencies,
|
|
450
|
+
status: 'trial', maxTrialUses: 3, validated: false, signature,
|
|
451
|
+
version: (old?.version || 0) + 1, previousId: old?.id || null, learnedFromRunId: input.pendingId,
|
|
452
|
+
evidence: [{ kind: 'learning-source', runId: input.pendingId, sourceIds: row.sourceIds }],
|
|
453
|
+
authority: 'work-guidance-only; no-new-tool-or-permission' }).id;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
saveOpportunity(input, row, index, dependencies) {
|
|
457
|
+
if (row.operation === 'reuse') {
|
|
458
|
+
const old = this.assertTarget(input, 'opportunities', row.targetId);
|
|
459
|
+
if (row.sourceIds.length || [row.description, row.rationale, row.suggestedPreparation].some(value => value !== ''))
|
|
460
|
+
fail('invalid_output', '复用机会只能关联旧编号,不能更新内容或恢复执行');
|
|
461
|
+
return old.id;
|
|
462
|
+
}
|
|
463
|
+
if (row.targetId || ![row.description, row.rationale, row.suggestedPreparation].every(value => value.trim()))
|
|
464
|
+
fail('invalid_output', '机会须有完整内容且新建不可指向旧编号');
|
|
465
|
+
const signature = digest({ conversationId: input.material.sessionId, description: row.description,
|
|
466
|
+
rationale: row.rationale, suggestedPreparation: row.suggestedPreparation, sourceIds: [...row.sourceIds].sort() });
|
|
467
|
+
const duplicate = this.store.listAll('opportunities').find(item => item.signature === signature);
|
|
468
|
+
if (duplicate) return duplicate.id;
|
|
469
|
+
return this.store.save('opportunities', { id: 'opportunity-' + digest([input.pendingId, index]).slice(0, 32),
|
|
470
|
+
conversationId: input.material.sessionId, description: row.description, rationale: row.rationale,
|
|
471
|
+
suggestedPreparation: row.suggestedPreparation, sourceIds: row.sourceIds, dependencies,
|
|
472
|
+
status: 'pending', preparationStatus: 'not_authorized', preparationRunId: null, artifactIds: [], signature,
|
|
473
|
+
learnedFromRunId: input.pendingId, authority: 'unendorsed-option; no-execution-authority' }).id;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function auxiliaryOutput(input, run, rawEvents) {
|
|
478
|
+
if (!Array.isArray(rawEvents) || !rawEvents.length) fail('invalid_auxiliary_history', '辅助会话原事件缺失');
|
|
479
|
+
const events = clone(rawEvents);
|
|
480
|
+
for (const [seq, event] of events.entries()) if (event.seq !== seq || typeof event.type !== 'string' || !event.data)
|
|
481
|
+
fail('invalid_auxiliary_history', '辅助会话必须提供从零开始的完整原事件');
|
|
482
|
+
const starts = events.filter(event => event.type === 'turn/start');
|
|
483
|
+
const ends = events.filter(event => event.type === 'turn/end');
|
|
484
|
+
const originals = events.filter(event => event.type === 'user/message' && event.data.id === run.messageId);
|
|
485
|
+
const expected = run.message;
|
|
486
|
+
if (expected.source?.kind !== 'arcfold/learning' || expected.source.pendingId !== input.pendingId ||
|
|
487
|
+
expected.source.deliveryId !== input.deliveryId || messageText(expected) !== input.inputText ||
|
|
488
|
+
originals.length !== 1 || canonicalJson(originals[0].data) !== canonicalJson(expected))
|
|
489
|
+
fail('learning_input_mismatch', '实际辅助消息与冻结材料或业务归属不一致');
|
|
490
|
+
const final = events.findLast(event => event.type === 'assistant/message');
|
|
491
|
+
const rawText = final ? messageText(final.data.message) : '';
|
|
492
|
+
const error = (code, message) => ({ code, message });
|
|
493
|
+
let problem = null;
|
|
494
|
+
if (starts.length !== 1 || ends.length !== 1 || starts[0].seq >= ends[0].seq ||
|
|
495
|
+
starts[0].data.turn !== ends[0].data.turn || events.some(event => event.type === 'session/end-seed' && event.data.inherited))
|
|
496
|
+
problem = error('invalid_auxiliary_turn', '学习只接受一轮非继承的完整辅助输出');
|
|
497
|
+
else if (ends[0].data.reason?.kind !== 'completed') problem = error('auxiliary_incomplete', '辅助会话未正常完成');
|
|
498
|
+
else if (!final || final.seq <= starts[0].seq || final.seq >= ends[0].seq ||
|
|
499
|
+
final.data.turn !== starts[0].data.turn || final.data.interrupted || final.data.message?.source?.kind !== 'model' ||
|
|
500
|
+
!rawText.trim()) problem = error('auxiliary_incomplete', '学习没有完整模型正文');
|
|
501
|
+
else if (events.some(event => ['tool/call', 'tool/result', 'job/registered', 'subagent/catalog'].includes(event.type)) ||
|
|
502
|
+
events.some(event => event.type === 'assistant/message' && event.data.message.content.some(block => block.type === 'tool-call')))
|
|
503
|
+
problem = error('auxiliary_tools_forbidden', '学习业务不接受工具或后台执行分支');
|
|
504
|
+
else if (events.some(event => event.type === 'user/message' && event.data.id !== run.messageId &&
|
|
505
|
+
event.data.source?.kind !== 'runtime-context'))
|
|
506
|
+
problem = error('auxiliary_extra_input', '冻结输入之后辅助会话又收到其他消息');
|
|
507
|
+
else if (originals[0].seq <= starts[0].seq || originals[0].seq >= final.seq)
|
|
508
|
+
problem = error('auxiliary_input_order', '冻结输入不在本轮模型输出之前');
|
|
509
|
+
if (!problem) {
|
|
510
|
+
let openStep = null;
|
|
511
|
+
for (const event of events.filter(event => event.seq > starts[0].seq && event.seq < ends[0].seq)) {
|
|
512
|
+
if (event.type === 'step/start') {
|
|
513
|
+
if (openStep !== null || event.data.turn !== starts[0].data.turn || !Number.isSafeInteger(event.data.step)) {
|
|
514
|
+
problem = error('auxiliary_step_boundary', '辅助步骤边界不完整'); break;
|
|
515
|
+
}
|
|
516
|
+
openStep = event.data.step;
|
|
517
|
+
} else if (event.type === 'step/end') {
|
|
518
|
+
if (openStep === null || event.data.step !== openStep || event.data.turn !== starts[0].data.turn) {
|
|
519
|
+
problem = error('auxiliary_step_boundary', '辅助步骤边界不完整'); break;
|
|
520
|
+
}
|
|
521
|
+
openStep = null;
|
|
522
|
+
} else if (['assistant/message', 'assistant/attempt'].includes(event.type) &&
|
|
523
|
+
(openStep === null || event.data.step !== openStep || event.data.turn !== starts[0].data.turn)) {
|
|
524
|
+
problem = error('auxiliary_step_boundary', '辅助模型输出不属于已进入的步骤'); break;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (openStep !== null) problem = error('auxiliary_step_boundary', '辅助步骤尚未关闭');
|
|
528
|
+
}
|
|
529
|
+
return { sessionId: run.sessionId, messageId: run.messageId,
|
|
530
|
+
throughSeq: events.at(-1).seq, eventCount: events.length,
|
|
531
|
+
sourceRef: { sessionId: run.sessionId, seq: final?.seq ?? null, type: final?.type ?? null },
|
|
532
|
+
rawText, eventHash: final ? digest(final) : null,
|
|
533
|
+
outputHash: digest({ sessionId: run.sessionId, events }), error: problem };
|
|
534
|
+
}
|