@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
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { canonicalJson } from './store.mjs';
|
|
2
|
+
import { sourceAvailable } from './evidence.mjs';
|
|
3
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
4
|
+
import { HarnessError } from '@deepseek-ai/dsh-llm';
|
|
5
|
+
import { resolveNativeContextCall, belongsToSelectionBatch } from './native-context-call.mjs';
|
|
6
|
+
const reject = message => { throw new HarnessError(message, 'ARCFOLD_WORK_RESUME_DENIED'); };
|
|
7
|
+
export const name = 'arcfold-work-control';
|
|
8
|
+
export const inject = ['tools', 'agents', 'goals', 'arcfoldBridge', 'arcfoldGoals', 'arcfoldWork', 'systemPrompt'];
|
|
9
|
+
export function assertResumeInput({ events, startSeq, callSeq, goal }) {
|
|
10
|
+
const pause = events.findLast(e => e.type === 'goal/change' && e.data.goal?.id === goal.id && ['pause', 'block'].includes(e.data.operation));
|
|
11
|
+
const inputs = events.filter(e => e.seq > startSeq && e.seq < callSeq && e.type === 'user/message' &&
|
|
12
|
+
e.data.role === 'user' && e.data.source?.kind === 'user' && e.data.source.principal === undefined);
|
|
13
|
+
let selected;
|
|
14
|
+
const valid = inputs.some(input => {
|
|
15
|
+
const received = events.flatMap(e => e.type === 'agent/inbox/spliced'
|
|
16
|
+
? (e.data.inserted || []).filter(message => message.id === input.data.id).map(message => ({ event: e, message })) : []).at(-1);
|
|
17
|
+
const valid = received && received.event.seq < input.seq && received.event.seq > (pause?.seq ?? -1) &&
|
|
18
|
+
canonicalJson(received.message) === canonicalJson(input.data);
|
|
19
|
+
if (valid) selected = { input, received };
|
|
20
|
+
return valid;
|
|
21
|
+
});
|
|
22
|
+
if (!valid) reject('Resuming requires a new owner message received after the last pause or block, in this current turn.');
|
|
23
|
+
return selected;
|
|
24
|
+
}
|
|
25
|
+
export function apply(ctx) {
|
|
26
|
+
ctx.on('tools/pre-execute', async (exec, next) => {
|
|
27
|
+
if (!belongsToSelectionBatch(exec, 'arcfold_resume_work')) return next();
|
|
28
|
+
try {
|
|
29
|
+
const call = await resolveNativeContextCall(ctx, exec, reject);
|
|
30
|
+
if (call.batch.length !== 1 || call.batch[0].name !== 'arcfold_resume_work')
|
|
31
|
+
return { kind: 'deny', reason: 'Resume the work alone; no other tool in this batch is admitted.' };
|
|
32
|
+
} catch (error) { return { kind: 'deny', reason: error.message }; }
|
|
33
|
+
return next();
|
|
34
|
+
});
|
|
35
|
+
ctx.systemPrompt.section({ name: 'arcfold:work-resume', order: 51, interpolate: false,
|
|
36
|
+
text: '用户在本轮新原话中要求继续当前已暂停或冷恢复的工作时,先get_goal核当前编号、版本和Arcfold阶段,再单独调用arcfold_resume_work。它只恢复已有具体阶段;依据用户原话判断,程序续轮和背景学习不构成用户要求。要改方向先用arcfold_revise_work明确新阶段,再按用户要求恢复;本轮结束仍须提交阶段推进决定。' });
|
|
37
|
+
ctx.tools.register(defineTool({
|
|
38
|
+
name: 'arcfold_resume_work',
|
|
39
|
+
description: 'Resume the current delegated native work when the new owner message asks to continue it. Call alone, with get_goal current id/revision. Does not create, edit or broaden work; automatic rounds cannot invoke it.',
|
|
40
|
+
parameters: { goal_id: { type: 'string', required: true }, revision: { type: 'integer', required: true } },
|
|
41
|
+
output: { schema: { type: 'json' }, render: (_args, value) => [{ type: 'text', text: JSON.stringify(value) }] },
|
|
42
|
+
async execute(args, exec) {
|
|
43
|
+
const call = await resolveNativeContextCall(ctx, exec, reject);
|
|
44
|
+
if (call.batch.length !== 1 || call.batch[0].name !== 'arcfold_resume_work') reject('Resume this work alone.');
|
|
45
|
+
const goal = ctx.goals.get(call.agent);
|
|
46
|
+
if (!goal || goal.id !== args.goal_id || goal.revision !== args.revision ||
|
|
47
|
+
!(['paused', 'blocked'].includes(goal.phase) || goal.phase === 'active' && goal.activation === 'disarmed'))
|
|
48
|
+
reject('The current native work no longer matches the requested stopped revision.');
|
|
49
|
+
const { input } = assertResumeInput({ events: call.events, startSeq: call.startSeq, callSeq: call.call.seq, goal });
|
|
50
|
+
const originals = ctx.arcfoldBridge.storage.listAll('events', { where: { sessionId: call.sessionId } })
|
|
51
|
+
.filter(row => row.message?.id === input.data.id && canonicalJson(row.message) === canonicalJson(input.data));
|
|
52
|
+
if (originals.length !== 1 || !sourceAvailable(ctx.arcfoldBridge.storage, originals[0]))
|
|
53
|
+
reject('The new owner resume message was withdrawn or no longer has its exact business original.');
|
|
54
|
+
exec.signal.throwIfAborted();
|
|
55
|
+
const stage = ctx.arcfoldWork.current(call.agent);
|
|
56
|
+
const revisedHere = stage?.origin.operation === 'revise' && stage.origin.turn === call.turn;
|
|
57
|
+
return { ...ctx.arcfoldWork.resume(call.agent, { goalId: goal.id, revision: goal.revision,
|
|
58
|
+
sourceRefs: ctx.arcfoldWork.sources(call), currentTurn: call.turn, callId: call.callId, callSeq: call.call.seq, allowRevisedTurn: revisedHere }),
|
|
59
|
+
attribution: 'model-interpreted-current-owner-request; no-new-delegation' };
|
|
60
|
+
},
|
|
61
|
+
presentCall: () => ({ card: 'generic', title: 'Continue this work', kind: 'other' }),
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// Goal/work associations are background interpretations, never a scheduler.
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { canonicalJson, DomainError } from './store.mjs';
|
|
4
|
+
import { nativeGoalOrigin } from './native-continuation.mjs';
|
|
5
|
+
import { sourceAvailable, dependenciesCurrent } from './evidence.mjs';
|
|
6
|
+
import { hasSuccessfulGoalReceipt } from './goal-context.mjs';
|
|
7
|
+
const hash = x => createHash('sha256').update(canonicalJson(x)).digest('hex');
|
|
8
|
+
const copy = x => structuredClone(x);
|
|
9
|
+
const fail = message => { throw new DomainError('goal_work_changed', message); };
|
|
10
|
+
export const workId = (sessionId, nativeGoalId) => 'goal-work-' + hash([sessionId, nativeGoalId]);
|
|
11
|
+
export const nativeDefinitionHash = goal => hash({ objective: goal.objective, maxGoalRounds: goal.maxGoalRounds });
|
|
12
|
+
const captureCursors = new WeakMap();
|
|
13
|
+
const coreOf = ({ revision, createdAt, updatedAt, createdSequence, updatedSequence, ...core }) => core;
|
|
14
|
+
function immutable(store, kind, value) {
|
|
15
|
+
const old = store.get(kind, value.id);
|
|
16
|
+
if (old) { if (canonicalJson(coreOf(old)) !== canonicalJson(value)) fail('工作原件身份冲突。'); return old; }
|
|
17
|
+
return store.save(kind, value);
|
|
18
|
+
}
|
|
19
|
+
function nativeRef(store, sessionId, event) {
|
|
20
|
+
const rows = store.listAll('dsh_events', { where: { sessionId, seq: event.seq } });
|
|
21
|
+
if (rows.length !== 1 || rows[0].sha256 !== hash(event)) fail('工作缺少已持久保存的原生原件。');
|
|
22
|
+
return { id: rows[0].id, seq: event.seq, sha256: rows[0].sha256, type: event.type };
|
|
23
|
+
}
|
|
24
|
+
export function assertWork(store, work) {
|
|
25
|
+
if (!work) fail('工作不存在。');
|
|
26
|
+
for (const ref of work.nativeEvents) {
|
|
27
|
+
const row = store.get('dsh_events', ref.id);
|
|
28
|
+
if (!row || row.sessionId !== work.sessionId || row.seq !== ref.seq || row.sha256 !== ref.sha256 ||
|
|
29
|
+
store.get('source_controls', ref.id)?.withdrawn || store.get('source_controls', ref.id)?.useProhibited)
|
|
30
|
+
fail('工作的原生委托来源已经变化或撤回。');
|
|
31
|
+
}
|
|
32
|
+
for (const ref of work.sourceRefs) {
|
|
33
|
+
const row = store.get('events', ref.id);
|
|
34
|
+
if (!sourceAvailable(store, row) || row.revision !== ref.revision || hash(coreOf(row)) !== ref.hash)
|
|
35
|
+
fail('工作的原话来源已经变化或撤回。');
|
|
36
|
+
}
|
|
37
|
+
return work;
|
|
38
|
+
}
|
|
39
|
+
export function workState(store, id, cutoff = store.currentSequence()) {
|
|
40
|
+
return store.listAll('goal_work_observations', { where: { workId: id } })
|
|
41
|
+
.filter(row => row.createdSequence <= cutoff).sort((a, b) => a.nativeSeq - b.nativeSeq).at(-1) ?? null;
|
|
42
|
+
}
|
|
43
|
+
export function workRelation(store, id, cutoff = store.currentSequence()) {
|
|
44
|
+
return store.listAll('goal_work_links', { where: { workId: id } })
|
|
45
|
+
.filter(row => row.createdSequence <= cutoff).sort((a, b) => a.createdSequence - b.createdSequence).at(-1) ?? null;
|
|
46
|
+
}
|
|
47
|
+
export function relationState(store, work, relation, state) {
|
|
48
|
+
if (!relation) return null;
|
|
49
|
+
const goal = store.get('goals', relation.goalId);
|
|
50
|
+
let status = 'current';
|
|
51
|
+
try { assertWork(store, work); } catch { status = 'unavailable'; }
|
|
52
|
+
if (!goal || goal.withdrawn || goal.deleted || goal.useProhibited ||
|
|
53
|
+
!dependenciesCurrent(store, { ...goal, status: 'active' }, store.currentSequence()) ||
|
|
54
|
+
relation.sourceRefs.some(ref => {
|
|
55
|
+
const row = store.get('events', ref.id);
|
|
56
|
+
return !sourceAvailable(store, row) || row.revision !== ref.revision;
|
|
57
|
+
})) status = 'unavailable';
|
|
58
|
+
else if (status === 'current' && (goal.definitionVersion !== relation.definitionVersion ||
|
|
59
|
+
state?.definitionHash !== relation.nativeDefinitionHash)) status = 'needs_review';
|
|
60
|
+
return { goalId: relation.goalId, definitionVersion: relation.definitionVersion, reason: status === 'unavailable' ? '' : relation.reason,
|
|
61
|
+
state: status, claimType: 'inference', relationId: relation.id };
|
|
62
|
+
}
|
|
63
|
+
export function linkWork(store, { work, goal, state, reason, sourceIds, origin, expectedRelationId }) {
|
|
64
|
+
assertWork(store, work);
|
|
65
|
+
if (!goal || !reason?.trim() || !sourceIds?.length || new Set(sourceIds).size !== sourceIds.length)
|
|
66
|
+
fail('工作联系需要完整目标、理由与原件。');
|
|
67
|
+
if (goal.scope?.conversationId && goal.scope.conversationId !== work.sessionId) fail('工作与目标不在同一范围。');
|
|
68
|
+
if (expectedRelationId !== undefined && (workRelation(store, work.id)?.id ?? null) !== expectedRelationId)
|
|
69
|
+
fail('学习期间已有更新的工作联系,旧结果不能覆盖。');
|
|
70
|
+
const latest = workState(store, work.id);
|
|
71
|
+
if (!state || !latest || state.definitionHash !== latest.definitionHash) fail('工作定义已经变化。');
|
|
72
|
+
const sourceRefs = sourceIds.map(id => {
|
|
73
|
+
const row = store.get('events', id);
|
|
74
|
+
if (!sourceAvailable(store, row)) fail('联系来源已不可用。');
|
|
75
|
+
return { id, revision: row.revision };
|
|
76
|
+
});
|
|
77
|
+
const id = 'work-link-' + hash([work.id, origin]);
|
|
78
|
+
return immutable(store, 'goal_work_links', { id, workId: work.id, sessionId: work.sessionId,
|
|
79
|
+
goalId: goal.id, definitionVersion: goal.definitionVersion, nativeGoalId: work.nativeGoalId,
|
|
80
|
+
nativeRevision: state.nativeRevision, nativeDefinitionHash: state.definitionHash, reason, sourceRefs, origin,
|
|
81
|
+
authority: 'model-association-only; no-new-work-or-control' });
|
|
82
|
+
}
|
|
83
|
+
/** Called only after the existing native durable-prefix barrier, including turn-end reconciliation. */
|
|
84
|
+
export function captureWorks(store, sessionId, events) {
|
|
85
|
+
let cursors = captureCursors.get(store);
|
|
86
|
+
if (!cursors) captureCursors.set(store, cursors = new Map());
|
|
87
|
+
const tail = events.at(-1)?.seq ?? -1;
|
|
88
|
+
if (cursors.get(sessionId) === tail) return;
|
|
89
|
+
for (const created of events.filter(e => e.type === 'goal/change' && e.data.operation === 'create')) {
|
|
90
|
+
const nativeGoalId = created.data.goal?.id;
|
|
91
|
+
if (!nativeGoalId) continue;
|
|
92
|
+
const id = workId(sessionId, nativeGoalId);
|
|
93
|
+
let work = store.get('goal_works', id);
|
|
94
|
+
if (!work) {
|
|
95
|
+
let origin;
|
|
96
|
+
try { origin = nativeGoalOrigin({ store, sessionId, events, goalId: nativeGoalId }); }
|
|
97
|
+
catch (error) { if (error.code !== 'ARCFOLD_NATIVE_CONTINUATION_INVALID') throw error; continue; }
|
|
98
|
+
if (!origin) continue; // Unfinished/ambiguous originals remain visible as unverified work.
|
|
99
|
+
const sourceIds = origin.inputs.map(row => row.id);
|
|
100
|
+
if (origin.kind === 'native-command') {
|
|
101
|
+
const event = origin.events.find(e => e.type === 'command/run');
|
|
102
|
+
const ref = nativeRef(store, sessionId, event);
|
|
103
|
+
const source = immutable(store, 'events', { id: 'goal-command-source-' + hash([sessionId, event.seq]),
|
|
104
|
+
sessionId, conversationId: sessionId, role: 'user', author: 'user',
|
|
105
|
+
content: event.data.args, occurredAt: new Date(event.time).toISOString(),
|
|
106
|
+
provenance: { kind: 'native_user_command', sourceRef: { sessionId, ...ref },
|
|
107
|
+
command: event.data.name, commandId: event.data.commandId } });
|
|
108
|
+
sourceIds.push(source.id);
|
|
109
|
+
}
|
|
110
|
+
work = immutable(store, 'goal_works', { id, sessionId, nativeGoalId,
|
|
111
|
+
createSeq: created.seq, originKind: origin.kind,
|
|
112
|
+
nativeEvents: origin.events.map(e => nativeRef(store, sessionId, e)),
|
|
113
|
+
sourceIds, sourceRefs: sourceIds.map(id => {
|
|
114
|
+
const row = store.get('events', id); return { id, revision: row.revision, hash: hash(coreOf(row)) };
|
|
115
|
+
}), authority: 'verified-native-delegation; association-not-yet-inferred' });
|
|
116
|
+
}
|
|
117
|
+
const previous = workState(store, id);
|
|
118
|
+
let state = previous && { id: nativeGoalId, revision: previous.nativeRevision, objective: previous.objective,
|
|
119
|
+
phase: previous.phase, maxGoalRounds: previous.maxGoalRounds, blockedReason: previous.blockedReason }, roundsStarted = previous?.roundsStarted ?? 0;
|
|
120
|
+
for (const event of events.filter(e => e.seq > (previous?.nativeSeq ?? -1))) {
|
|
121
|
+
if (event.type === 'goal/change' && event.data.goal?.id === nativeGoalId) {
|
|
122
|
+
state = { ...event.data.goal }; roundsStarted = event.data.roundsStarted ?? roundsStarted;
|
|
123
|
+
} else if (event.type === 'goal/change' && event.data.cleared?.id === nativeGoalId) {
|
|
124
|
+
if (state) state = { ...state, revision: event.data.cleared.revision, phase: 'cleared' }; else continue;
|
|
125
|
+
} else if (event.type === 'user/message' && event.data.source?.kind === 'goal' &&
|
|
126
|
+
event.data.source.goalId === nativeGoalId) {
|
|
127
|
+
if (!state) continue; roundsStarted = Math.max(roundsStarted, event.data.source.round);
|
|
128
|
+
} else continue;
|
|
129
|
+
immutable(store, 'goal_work_observations', { id: 'work-observation-' + hash([id, event.seq]),
|
|
130
|
+
workId: id, sessionId, nativeSeq: event.seq, nativeRef: nativeRef(store, sessionId, event),
|
|
131
|
+
nativeGoalId, nativeRevision: state.revision, objective: state.objective, phase: state.phase,
|
|
132
|
+
maxGoalRounds: state.maxGoalRounds, roundsStarted, definitionHash: nativeDefinitionHash(state),
|
|
133
|
+
...(state.phase === 'blocked' && state.blockedReason ? { blockedReason: copy(state.blockedReason) } : {}) });
|
|
134
|
+
}
|
|
135
|
+
for (const link of store.listAll('goal_links', { where: { sessionId } })) {
|
|
136
|
+
const start = events.find(e => e.seq === link.startSeq && e.type === 'turn/start');
|
|
137
|
+
if (!start || !hasSuccessfulGoalReceipt(events, link)) continue;
|
|
138
|
+
const endSeq = events.find(e => e.seq > start.seq && e.type === 'turn/end')?.seq ?? Infinity;
|
|
139
|
+
const belongs = created.seq > start.seq && created.seq < endSeq || events.some(e => e.seq > start.seq &&
|
|
140
|
+
e.seq < endSeq && e.type === 'user/message' && e.data.source?.kind === 'goal' && e.data.source.goalId === nativeGoalId);
|
|
141
|
+
if (!belongs) continue;
|
|
142
|
+
const relationId = 'work-link-' + hash([id, { kind: 'native_goal_selection', linkId: link.id }]);
|
|
143
|
+
if (store.get('goal_work_links', relationId)) continue;
|
|
144
|
+
const goal = store.get('goals', link.goalId);
|
|
145
|
+
if (goal?.definitionVersion !== link.definitionVersion) continue;
|
|
146
|
+
const receiptSeq = events.find(e => e.type === 'tool/result' && e.data.message?.toolCallId === link.callId)?.seq;
|
|
147
|
+
const atSelection = store.listAll('goal_work_observations', { where: { workId: id } })
|
|
148
|
+
.filter(row => row.nativeSeq <= Math.max(created.seq, receiptSeq ?? -1))
|
|
149
|
+
.sort((a, b) => a.nativeSeq - b.nativeSeq).at(-1);
|
|
150
|
+
try { linkWork(store, { work, goal, state: atSelection, reason: link.selection.reason,
|
|
151
|
+
sourceIds: link.selection.sourceIds, origin: { kind: 'native_goal_selection', linkId: link.id } }); }
|
|
152
|
+
catch (error) { if (error.code !== 'goal_work_changed') throw error; }
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (store.depth === 0) cursors.set(sessionId, tail);
|
|
156
|
+
}
|
|
157
|
+
export function worksForDelivery(store, delivery) {
|
|
158
|
+
const cutoff = delivery.createdSequence;
|
|
159
|
+
return store.listAll('goal_works', { where: { sessionId: delivery.sessionId } }).filter(work =>
|
|
160
|
+
work.createdSequence <= cutoff && (delivery.nativeContinuation?.goalId === work.nativeGoalId ||
|
|
161
|
+
store.listAll('goal_work_observations', { where: { workId: work.id } }).some(row =>
|
|
162
|
+
row.createdSequence <= cutoff && row.nativeSeq > delivery.startSeq && row.nativeSeq <= delivery.throughSeq)))
|
|
163
|
+
.map(work => ({ ...copy(work), state: workState(store, work.id, cutoff),
|
|
164
|
+
relationId: workRelation(store, work.id, cutoff)?.id ?? null }));
|
|
165
|
+
}
|
|
166
|
+
export function workViews(store, sessionId, native = null, goalId = null, limit = 20) {
|
|
167
|
+
const known = store.listAll('goal_works', { where: { sessionId } }).reverse();
|
|
168
|
+
const views = known.flatMap(work => {
|
|
169
|
+
const state = workState(store, work.id), relation = workRelation(store, work.id);
|
|
170
|
+
if (goalId && relation?.goalId !== goalId || !state) return [];
|
|
171
|
+
let valid = true; try { assertWork(store, work); } catch { valid = false; }
|
|
172
|
+
const current = native?.id === work.nativeGoalId ? native : null;
|
|
173
|
+
return [{ id: work.id, sessionId, nativeGoalId: work.nativeGoalId,
|
|
174
|
+
objective: current?.objective ?? state.objective, nativeRevision: current?.revision ?? state.nativeRevision,
|
|
175
|
+
phase: current?.phase ?? state.phase, activation: current?.activation ?? 'unavailable',
|
|
176
|
+
blockedReason: valid && (current ? current.phase === 'blocked' && current.blockedReason
|
|
177
|
+
: state.phase === 'blocked' && state.blockedReason) || null,
|
|
178
|
+
roundsStarted: current?.roundsStarted ?? state.roundsStarted, maxGoalRounds: current?.maxGoalRounds ?? state.maxGoalRounds,
|
|
179
|
+
relation: relationState(store, work, relation, current ? { ...state, definitionHash: nativeDefinitionHash(current) } : state), provenance: valid ? 'verified-native-delegation' : 'source-unavailable',
|
|
180
|
+
canStop: current?.phase === 'active', canResume: Boolean(valid && current &&
|
|
181
|
+
(['paused', 'blocked'].includes(current.phase) || current.phase === 'active' && current.activation === 'disarmed')) }];
|
|
182
|
+
});
|
|
183
|
+
if (!goalId && native && !known.some(work => work.nativeGoalId === native.id)) views.unshift({
|
|
184
|
+
id: workId(sessionId, native.id), sessionId, nativeGoalId: native.id, objective: native.objective,
|
|
185
|
+
nativeRevision: native.revision, phase: native.phase, activation: native.activation,
|
|
186
|
+
blockedReason: native.phase === 'blocked' ? native.blockedReason ?? null : null,
|
|
187
|
+
roundsStarted: native.roundsStarted, maxGoalRounds: native.maxGoalRounds, relation: null,
|
|
188
|
+
provenance: 'legacy-unverified', canStop: native.phase === 'active', canResume: false });
|
|
189
|
+
return limit === null ? views : views.slice(0, limit);
|
|
190
|
+
}
|
|
191
|
+
export function automaticWorkContext(goals, work, state, cutoff) {
|
|
192
|
+
const store = goals.store, relation = workRelation(store, work.id, cutoff);
|
|
193
|
+
if (relationState(store, work, relation, state)?.state !== 'current') return null;
|
|
194
|
+
const goal = store.get('goals', relation.goalId);
|
|
195
|
+
if (goal.status !== 'tracking' || goal.updatedSequence > cutoff) return null;
|
|
196
|
+
const selection = { id: goal.id, revision: goal.revision, definitionVersion: goal.definitionVersion,
|
|
197
|
+
reason: relation.reason, sourceIds: relation.sourceRefs.map(ref => ref.id) };
|
|
198
|
+
const bundle = goals.fullEntry(goal, { repairOnly: false }, selection,
|
|
199
|
+
{ sessionId: work.sessionId, material: { cutoffSequence: cutoff, inputMessageIds: [] } });
|
|
200
|
+
const understandings = [], visited = new Set();
|
|
201
|
+
function include(record) {
|
|
202
|
+
for (const ref of record.dependencies || []) {
|
|
203
|
+
if (ref.kind !== 'understandings' || visited.has(ref.id)) continue;
|
|
204
|
+
visited.add(ref.id);
|
|
205
|
+
const row = store.get('understandings', ref.id);
|
|
206
|
+
if (!row || row.revision !== ref.revision || !dependenciesCurrent(store, row, cutoff)) fail('目标依赖的理解已经变化。');
|
|
207
|
+
understandings.push(copy(row)); include(row);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
include(goal);
|
|
211
|
+
for (const related of bundle.relatedGoals) include(store.get('goals', related.id));
|
|
212
|
+
return { workId: work.id, relationId: relation.id, nativeDefinitionHash: state.definitionHash,
|
|
213
|
+
...bundle, understandings, authority: 'previously-established-model-association; no-new-delegation' };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export const goalContentHash = goal => hash({ id: goal.id, title: goal.title, outcome: goal.outcome,
|
|
217
|
+
definitionVersion: goal.definitionVersion, scope: goal.scope || {}, status: goal.status,
|
|
218
|
+
userControl: goal.userControl ?? null, sourceIds: goal.sourceIds || [], dependencies: goal.dependencies || [],
|
|
219
|
+
progress: goal.progress ?? null, relations: goal.relations || [], useProhibited: goal.useProhibited === true,
|
|
220
|
+
withdrawn: goal.withdrawn === true, deleted: goal.deleted === true });
|
|
221
|
+
const definition = row => ({ id: row.id, title: row.title, outcome: row.outcome, definitionVersion: row.definitionVersion,
|
|
222
|
+
scope: row.scope || {}, status: row.status, userControl: row.userControl ?? null,
|
|
223
|
+
sourceIds: row.sourceIds, dependencies: row.dependencies, relations: row.relations || [], useProhibited: row.useProhibited === true,
|
|
224
|
+
withdrawn: row.withdrawn === true, deleted: row.deleted === true });
|
|
225
|
+
/** A late progress-only update does not rewrite or invalidate a proven frozen definition. */
|
|
226
|
+
export function frozenGoalCurrent(store, shown, cutoff = store.currentSequence()) {
|
|
227
|
+
const current = store.get('goals', shown.id);
|
|
228
|
+
if (!current) return false;
|
|
229
|
+
const old = current.revision === shown.revision ? current : store.listAll('audit', { where: { goalId: shown.id } })
|
|
230
|
+
.flatMap(row => [row.before, row.after]).find(row => row?.revision === shown.revision);
|
|
231
|
+
return Boolean(old && (!shown.contentHash || goalContentHash(old) === shown.contentHash) &&
|
|
232
|
+
hash(definition(old)) === hash(definition(current)) &&
|
|
233
|
+
dependenciesCurrent(store, { ...old, status: 'active' }, cutoff) &&
|
|
234
|
+
!current.withdrawn && !current.deleted && !current.useProhibited);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function workContextCurrent(store, context) {
|
|
238
|
+
const work = store.get('goal_works', context.workId);
|
|
239
|
+
const relation = store.get('goal_work_links', context.relationId);
|
|
240
|
+
const state = workState(store, context.workId);
|
|
241
|
+
const currentRelation = workRelation(store, context.workId);
|
|
242
|
+
if (!currentRelation || currentRelation.goalId !== relation?.goalId ||
|
|
243
|
+
currentRelation.definitionVersion !== relation?.definitionVersion ||
|
|
244
|
+
currentRelation.nativeDefinitionHash !== relation?.nativeDefinitionHash ||
|
|
245
|
+
relationState(store, work, currentRelation, state)?.state !== 'current' ||
|
|
246
|
+
relationState(store, work, relation, state)?.state !== 'current' ||
|
|
247
|
+
state?.definitionHash !== context.nativeDefinitionHash) return false;
|
|
248
|
+
return [context.goal, ...context.relatedGoals, ...context.understandings].every(shown => {
|
|
249
|
+
const kind = shown.definitionVersion === undefined ? 'understandings' : 'goals';
|
|
250
|
+
const row = store.get(kind, shown.id);
|
|
251
|
+
return kind === 'goals' ? frozenGoalCurrent(store, shown) && row.status === 'tracking'
|
|
252
|
+
: row?.revision === shown.revision && dependenciesCurrent(store, row, store.currentSequence());
|
|
253
|
+
}) && context.sources.every(ref => {
|
|
254
|
+
const row = store.get('events', ref.id);
|
|
255
|
+
return row?.revision === ref.revision && sourceAvailable(store, row);
|
|
256
|
+
});
|
|
257
|
+
}
|