dsh-data-domain-knowledge 0.1.1-alpha.2 → 0.1.1-alpha.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/README.md +23 -16
- package/lib/client.js +596 -841
- package/lib/contract-refs.d.ts +0 -1
- package/lib/doc-page.js +7 -7
- package/lib/pages.js +928 -1145
- package/lib/types/agent-behavior-rules.d.ts.map +1 -1
- package/lib/types/agent-behavior-rules.js +1 -2
- package/lib/types/agent-behavior-rules.js.map +1 -1
- package/lib/types/axis-contract.js +1 -1
- package/lib/types/axis-contract.js.map +1 -1
- package/lib/types/card-organize.d.ts +2 -1
- package/lib/types/card-organize.d.ts.map +1 -1
- package/lib/types/card-organize.js +5 -3
- package/lib/types/card-organize.js.map +1 -1
- package/lib/types/config-schema.d.ts +1 -1
- package/lib/types/config-schema.js +1 -1
- package/lib/types/convert/host.d.ts +2 -3
- package/lib/types/convert/host.d.ts.map +1 -1
- package/lib/types/convert/host.js +5 -2
- package/lib/types/convert/host.js.map +1 -1
- package/lib/types/convert/ingest.d.ts +2 -0
- package/lib/types/convert/ingest.d.ts.map +1 -1
- package/lib/types/convert/ingest.js +1 -0
- package/lib/types/convert/ingest.js.map +1 -1
- package/lib/types/diagnosis/types.d.ts +1 -1
- package/lib/types/diagnosis/types.js +1 -1
- package/lib/types/images.js +1 -1
- package/lib/types/index.d.ts +0 -4
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +30 -41
- package/lib/types/index.js.map +1 -1
- package/lib/types/knowledge-contract.d.ts +6 -0
- package/lib/types/knowledge-contract.d.ts.map +1 -1
- package/lib/types/knowledge-contract.js +6 -2
- package/lib/types/knowledge-contract.js.map +1 -1
- package/lib/types/layout.d.ts +9 -26
- package/lib/types/layout.d.ts.map +1 -1
- package/lib/types/layout.js +10 -28
- package/lib/types/layout.js.map +1 -1
- package/lib/types/materialize-snapshot.d.ts +5 -0
- package/lib/types/materialize-snapshot.d.ts.map +1 -1
- package/lib/types/migrate.d.ts +3 -1
- package/lib/types/migrate.d.ts.map +1 -1
- package/lib/types/migrate.js +3 -1
- package/lib/types/migrate.js.map +1 -1
- package/lib/types/organize-contract.d.ts.map +1 -1
- package/lib/types/organize-contract.js +2 -1
- package/lib/types/organize-contract.js.map +1 -1
- package/lib/types/pipeline/engine.d.ts +1 -0
- package/lib/types/pipeline/engine.d.ts.map +1 -1
- package/lib/types/pipeline/engine.js +9 -3
- package/lib/types/pipeline/engine.js.map +1 -1
- package/lib/types/pipeline/types.d.ts +6 -0
- package/lib/types/pipeline/types.d.ts.map +1 -1
- package/lib/types/pipeline/types.js.map +1 -1
- package/lib/types/present.d.ts +1 -1
- package/lib/types/present.js +1 -1
- package/lib/types/preset-ids.d.ts +0 -1
- package/lib/types/preset-ids.d.ts.map +1 -1
- package/lib/types/preset-ids.js +0 -3
- package/lib/types/preset-ids.js.map +1 -1
- package/lib/types/query-analysis.d.ts +0 -5
- package/lib/types/query-analysis.d.ts.map +1 -1
- package/lib/types/query-analysis.js +0 -16
- package/lib/types/query-analysis.js.map +1 -1
- package/lib/types/recall-delivery.d.ts +36 -0
- package/lib/types/recall-delivery.d.ts.map +1 -0
- package/lib/types/recall-delivery.js +16 -0
- package/lib/types/recall-delivery.js.map +1 -0
- package/lib/types/runtime.d.ts +26 -12
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/runtime.js +231 -106
- package/lib/types/runtime.js.map +1 -1
- package/lib/types/settings-volatile.d.ts +0 -7
- package/lib/types/settings-volatile.d.ts.map +1 -1
- package/lib/types/settings-volatile.js +0 -7
- package/lib/types/settings-volatile.js.map +1 -1
- package/lib/types/source-identity.d.ts +3 -0
- package/lib/types/source-identity.d.ts.map +1 -0
- package/lib/types/source-identity.js +5 -0
- package/lib/types/source-identity.js.map +1 -0
- package/lib/types/store/axis-references.d.ts.map +1 -1
- package/lib/types/store/axis-references.js +5 -3
- package/lib/types/store/axis-references.js.map +1 -1
- package/lib/types/store/knowledge-db.d.ts +1 -1
- package/lib/types/store/knowledge-db.d.ts.map +1 -1
- package/lib/types/store/knowledge-db.js +16 -2
- package/lib/types/store/knowledge-db.js.map +1 -1
- package/lib/types/types.d.ts +5 -4
- package/lib/types/types.d.ts.map +1 -1
- package/lib/types/types.js +6 -3
- package/lib/types/types.js.map +1 -1
- package/lib/types/wiki-availability.d.ts +11 -0
- package/lib/types/wiki-availability.d.ts.map +1 -0
- package/lib/types/wiki-availability.js +39 -0
- package/lib/types/wiki-availability.js.map +1 -0
- package/lib/types/wiki-finalize.d.ts +4 -2
- package/lib/types/wiki-finalize.d.ts.map +1 -1
- package/lib/types/wiki-finalize.js +9 -27
- package/lib/types/wiki-finalize.js.map +1 -1
- package/lib/types/wiki-links.d.ts +35 -0
- package/lib/types/wiki-links.d.ts.map +1 -0
- package/lib/types/wiki-links.js +129 -0
- package/lib/types/wiki-links.js.map +1 -0
- package/package.json +16 -16
- package/scripts/doctor.mjs +0 -7
- package/scripts/setup-embedding.sh +0 -0
- package/src/agent-behavior-rules.ts +1 -2
- package/src/axis-contract.ts +1 -1
- package/src/card-organize.ts +5 -3
- package/src/client/AttributionEventGuide.tsx +3 -3
- package/src/client/AxisMatrix.tsx +86 -19
- package/src/client/DirectoryPicker.tsx +0 -2
- package/src/client/GuideArchitectureCanvas.tsx +5 -12
- package/src/client/ImportFilesDialog.tsx +1 -0
- package/src/client/InfoHint.tsx +1 -1
- package/src/client/KnowledgeBaseTab.tsx +113 -39
- package/src/client/KnowledgePanel.tsx +16 -9
- package/src/client/OperationLogTab.tsx +1 -1
- package/src/client/RecallPanel.tsx +5 -16
- package/src/client/TabGuide.tsx +3 -7
- package/src/client/WikiFlowPanel.tsx +10 -11
- package/src/client/arch/NodeLogicPanel.tsx +3 -3
- package/src/client/arch/model.ts +5 -113
- package/src/client/architecture.css +1 -1
- package/src/client/architecture.tsx +13 -27
- package/src/client/attribution/AttributionTab.tsx +141 -10
- package/src/client/pages.css +2 -2
- package/src/client/pages.tsx +4 -11
- package/src/client/styles.css +15 -96
- package/src/config-schema.ts +1 -1
- package/src/convert/host.ts +7 -5
- package/src/convert/ingest.ts +3 -0
- package/src/diagnosis/types.ts +1 -1
- package/src/images.ts +1 -1
- package/src/index.ts +27 -49
- package/src/knowledge-contract.ts +7 -2
- package/src/layout.ts +10 -32
- package/src/materialize-snapshot.ts +1 -0
- package/src/migrate.ts +3 -1
- package/src/organize-contract.ts +2 -1
- package/src/pipeline/engine.ts +9 -3
- package/src/pipeline/types.ts +2 -0
- package/src/present.ts +1 -1
- package/src/preset-ids.ts +0 -4
- package/src/query-analysis.ts +0 -20
- package/src/recall-delivery.ts +19 -0
- package/src/runtime.ts +220 -102
- package/src/settings-volatile.ts +0 -7
- package/src/source-identity.ts +4 -0
- package/src/store/axis-references.ts +5 -3
- package/src/store/knowledge-db.ts +14 -2
- package/src/types.ts +6 -4
- package/src/wiki-availability.ts +37 -0
- package/src/wiki-finalize.ts +9 -25
- package/src/wiki-links.ts +120 -0
- package/lib/types/intent/engine.d.ts +0 -122
- package/lib/types/intent/engine.d.ts.map +0 -1
- package/lib/types/intent/engine.js +0 -488
- package/lib/types/intent/engine.js.map +0 -1
- package/lib/types/intent/host.d.ts +0 -68
- package/lib/types/intent/host.d.ts.map +0 -1
- package/lib/types/intent/host.js +0 -249
- package/lib/types/intent/host.js.map +0 -1
- package/lib/types/intent/scope.d.ts +0 -25
- package/lib/types/intent/scope.d.ts.map +0 -1
- package/lib/types/intent/scope.js +0 -116
- package/lib/types/intent/scope.js.map +0 -1
- package/lib/types/intent/store.d.ts +0 -33
- package/lib/types/intent/store.d.ts.map +0 -1
- package/lib/types/intent/store.js +0 -239
- package/lib/types/intent/store.js.map +0 -1
- package/lib/types/intent/tools.d.ts +0 -62
- package/lib/types/intent/tools.d.ts.map +0 -1
- package/lib/types/intent/tools.js +0 -130
- package/lib/types/intent/tools.js.map +0 -1
- package/lib/types/intent/types.d.ts +0 -170
- package/lib/types/intent/types.d.ts.map +0 -1
- package/lib/types/intent/types.js +0 -56
- package/lib/types/intent/types.js.map +0 -1
- package/src/client/IntentLogic.tsx +0 -192
- package/src/client/IntentTab.tsx +0 -343
- package/src/intent/README.md +0 -42
- package/src/intent/engine.ts +0 -515
- package/src/intent/host.ts +0 -315
- package/src/intent/scope.ts +0 -125
- package/src/intent/store.ts +0 -218
- package/src/intent/tools.ts +0 -154
- package/src/intent/types.ts +0 -229
package/src/intent/engine.ts
DELETED
|
@@ -1,515 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DEFAULT_ACTS,
|
|
3
|
-
DEFAULT_CATEGORIES,
|
|
4
|
-
DEFAULT_INTENT_SETTINGS,
|
|
5
|
-
DEFAULT_PREDICATES,
|
|
6
|
-
type IntentRecord,
|
|
7
|
-
type IntentRecordInput,
|
|
8
|
-
type IntentRef,
|
|
9
|
-
type IntentRelation,
|
|
10
|
-
type IntentResult,
|
|
11
|
-
type IntentSettings,
|
|
12
|
-
type IntentSlotMap,
|
|
13
|
-
type IntentSource,
|
|
14
|
-
type IntentStoreView,
|
|
15
|
-
type IntentTaskSummary,
|
|
16
|
-
type IntentTurnState,
|
|
17
|
-
} from './types.js'
|
|
18
|
-
import { envelopeSuccess } from '#contracts'
|
|
19
|
-
import type { LineageContext } from '#contracts'
|
|
20
|
-
|
|
21
|
-
function id(prefix = 'intent'): string {
|
|
22
|
-
return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function clamp01(value: unknown): number | undefined {
|
|
26
|
-
if (typeof value !== 'number' || !Number.isFinite(value)) return undefined
|
|
27
|
-
return Math.max(0, Math.min(1, value))
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function slotsOf(value: unknown): IntentSlotMap {
|
|
31
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) return {}
|
|
32
|
-
return Object.fromEntries(Object.entries(value as Record<string, unknown>).filter(([, item]) => item !== null && item !== undefined))
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function refsOf(value: unknown): IntentRef[] {
|
|
36
|
-
if (!Array.isArray(value)) return []
|
|
37
|
-
return value.flatMap((ref): IntentRef[] => {
|
|
38
|
-
if (typeof ref === 'string' && ref.trim()) return [{ id: ref.trim() }]
|
|
39
|
-
if (ref && typeof ref === 'object' && typeof (ref as { id?: unknown }).id === 'string') {
|
|
40
|
-
const item = ref as { id: string; kind?: unknown; label?: unknown }
|
|
41
|
-
return [{ id: item.id, ...(typeof item.kind === 'string' ? { kind: item.kind } : {}), ...(typeof item.label === 'string' ? { label: item.label } : {}) }]
|
|
42
|
-
}
|
|
43
|
-
return []
|
|
44
|
-
})
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function safeLineage(value: unknown): Partial<LineageContext> {
|
|
48
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) return {}
|
|
49
|
-
const record = value as Record<string, unknown>
|
|
50
|
-
const result: Partial<LineageContext> = {}
|
|
51
|
-
for (const key of ['sessionId', 'turnId', 'intentId', 'taskId', 'subtaskId', 'parentSubtaskId', 'runId', 'traceId', 'domain'] as const) {
|
|
52
|
-
if (typeof record[key] === 'string' && record[key].trim()) result[key] = record[key].trim().slice(0, 160)
|
|
53
|
-
}
|
|
54
|
-
return result
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** 标量字段的同类兜底:类型不对就退回默认值(引用对象、`undefined`、字符串都算不对)。 */
|
|
58
|
-
function numberOr(value: unknown, fallback: number): number {
|
|
59
|
-
return typeof value === 'number' && Number.isFinite(value) ? value : fallback
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function booleanOr(value: unknown, fallback: boolean): boolean {
|
|
63
|
-
return typeof value === 'boolean' ? value : fallback
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function normalizeSettings(settings: IntentSettings): IntentSettings {
|
|
67
|
-
return {
|
|
68
|
-
...settings,
|
|
69
|
-
clarifyThreshold: numberOr(settings.clarifyThreshold, DEFAULT_INTENT_SETTINGS.clarifyThreshold),
|
|
70
|
-
ttlSeconds: numberOr(settings.ttlSeconds, DEFAULT_INTENT_SETTINGS.ttlSeconds),
|
|
71
|
-
fallbackEnabled: booleanOr(settings.fallbackEnabled, DEFAULT_INTENT_SETTINGS.fallbackEnabled),
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Bounded audit store: one record per session turn, expiring after the configured TTL.
|
|
77
|
-
*
|
|
78
|
-
* 0033 起它同时是**工作集**与**留痕的入口**,两者的分工要说清:
|
|
79
|
-
*
|
|
80
|
-
* - 内存里这份是有 TTL 的**工作集**(默认 600 秒):血缘判定只关心最近的轮次,
|
|
81
|
-
* 一个十轮之前就结束的会话不该让新的一轮接到它的 task 上。
|
|
82
|
-
* - 磁盘上那份(`intent/intent.db`)是**留痕**,当前版本便于读取,历史版本只追加、不随 TTL 消失。
|
|
83
|
-
* `sink` 在每次 `save` 时被调用,`hydrate` 在启动时把留痕读回工作集 ——
|
|
84
|
-
* 重启之后同一个会话的血缘因此还在(在 TTL 窗口内)。
|
|
85
|
-
*
|
|
86
|
-
* 为什么不干脆让 `records()` 直接读文件:那会让 TTL 名存实亡(文件永远有),
|
|
87
|
-
* 于是「工作集」这个概念消失,血缘会接到很久以前的 task 上。两件事分开,
|
|
88
|
-
* 各自的契约才都成立。
|
|
89
|
-
*/
|
|
90
|
-
export class IntentStore {
|
|
91
|
-
private readonly sessions = new Map<string, { records: IntentRecord[]; touched: number }>()
|
|
92
|
-
/**
|
|
93
|
-
* 每条被保存的记录都会经过它(落盘留痕)。
|
|
94
|
-
*
|
|
95
|
-
* 同步签名、由调用方自己吞异常:`save` 在记录路径的关键路径上,而留痕失败
|
|
96
|
-
* 不该让这一轮的意图记录失败 —— 「记录不拦截请求」这条契约对留痕也成立。
|
|
97
|
-
*/
|
|
98
|
-
private sink?: (record: IntentRecord) => void
|
|
99
|
-
constructor(private ttlSeconds = DEFAULT_INTENT_SETTINGS.ttlSeconds, private readonly now = () => Date.now()) {}
|
|
100
|
-
|
|
101
|
-
/** 装上一个留痕出口。多次调用以最后一次为准。 */
|
|
102
|
-
setSink(sink: (record: IntentRecord) => void): void {
|
|
103
|
-
this.sink = sink
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* 把留痕读回工作集(启动时调用一次)。
|
|
108
|
-
*
|
|
109
|
-
* `touched` 用记录自己的 `updatedAt`,于是「重启」不会把 TTL 归零:一份十小时
|
|
110
|
-
* 前的记录在 TTL 600 秒下本来就该过期,重启不该让它复活。
|
|
111
|
-
*/
|
|
112
|
-
hydrate(records: IntentRecord[]): number {
|
|
113
|
-
let loaded = 0
|
|
114
|
-
for (const record of records) {
|
|
115
|
-
const item = this.sessions.get(record.sessionKey) ?? { records: [], touched: 0 }
|
|
116
|
-
const index = item.records.findIndex(existing => existing.turnKey === record.turnKey)
|
|
117
|
-
if (index >= 0) item.records[index] = record
|
|
118
|
-
else item.records.push(record)
|
|
119
|
-
item.records = item.records.slice(-100)
|
|
120
|
-
const at = Date.parse(String(record.updatedAt ?? record.createdAt))
|
|
121
|
-
item.touched = Math.max(item.touched, Number.isFinite(at) ? at : this.now())
|
|
122
|
-
this.sessions.set(record.sessionKey, item)
|
|
123
|
-
loaded += 1
|
|
124
|
-
}
|
|
125
|
-
return loaded
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
setTtl(seconds: number): void {
|
|
129
|
-
this.ttlSeconds = Math.max(1, Math.min(86400, Math.round(seconds)))
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
private entry(sessionKey: string) {
|
|
133
|
-
const current = this.sessions.get(sessionKey)
|
|
134
|
-
if (!current || this.now() - current.touched > this.ttlSeconds * 1000) {
|
|
135
|
-
const fresh: { records: IntentRecord[]; touched: number } = { records: [], touched: this.now() }
|
|
136
|
-
this.sessions.set(sessionKey, fresh)
|
|
137
|
-
return fresh
|
|
138
|
-
}
|
|
139
|
-
current.touched = this.now()
|
|
140
|
-
return current
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/** Upsert by turn key so repeated records within one turn stay a single fact. */
|
|
144
|
-
save(record: IntentRecord): IntentRecord {
|
|
145
|
-
const item = this.entry(record.sessionKey)
|
|
146
|
-
const index = item.records.findIndex(existing => existing.turnKey === record.turnKey)
|
|
147
|
-
if (index >= 0) item.records[index] = record
|
|
148
|
-
else item.records.push(record)
|
|
149
|
-
item.records = item.records.slice(-100)
|
|
150
|
-
try { this.sink?.(record) } catch { /* 留痕失败不影响这一轮 */ }
|
|
151
|
-
return record
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
records(sessionKey: string): IntentRecord[] {
|
|
155
|
-
return this.entry(sessionKey).records
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
last(sessionKey: string): IntentRecord | undefined {
|
|
159
|
-
return this.entry(sessionKey).records.at(-1)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
findByTurn(sessionKey: string, turnKey: string): IntentRecord | undefined {
|
|
163
|
-
return this.entry(sessionKey).records.find(record => record.turnKey === turnKey)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
taskExists(sessionKey: string, taskId: string): boolean {
|
|
167
|
-
return this.entry(sessionKey).records.some(record => record.lineage.taskId === taskId)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
lastSubtaskOf(sessionKey: string, taskId: string): string | undefined {
|
|
171
|
-
return this.entry(sessionKey).records.filter(record => record.lineage.taskId === taskId).at(-1)?.lineage.subtaskId
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
expiresAt(sessionKey: string): string | undefined {
|
|
175
|
-
const item = this.entry(sessionKey)
|
|
176
|
-
return item.records.length > 0 ? new Date(item.touched + this.ttlSeconds * 1000).toISOString() : undefined
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
clear(sessionKey: string): void {
|
|
180
|
-
this.sessions.delete(sessionKey)
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/** When the session was last touched (ms since epoch); 0 when unknown. */
|
|
184
|
-
touchedAt(sessionKey: string): number {
|
|
185
|
-
return this.sessions.get(sessionKey)?.touched ?? 0
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/** Live session keys, most recently touched first. Expired sessions are skipped. */
|
|
189
|
-
sessionKeys(): string[] {
|
|
190
|
-
const now = this.now()
|
|
191
|
-
const live: Array<{ key: string; touched: number }> = []
|
|
192
|
-
for (const [key, item] of this.sessions) {
|
|
193
|
-
if (now - item.touched > this.ttlSeconds * 1000) continue
|
|
194
|
-
live.push({ key, touched: item.touched })
|
|
195
|
-
}
|
|
196
|
-
return live.sort((left, right) => right.touched - left.touched).map(item => item.key)
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Intent recorder. Every entry point is total: an unrecognized act, an unknown
|
|
202
|
-
* task reference, or a missing classification degrade into warnings on a stored
|
|
203
|
-
* record. Nothing here blocks, confirms, or rewrites the user's request.
|
|
204
|
-
*/
|
|
205
|
-
export class IntentEngine {
|
|
206
|
-
readonly store: IntentStore
|
|
207
|
-
private settings: IntentSettings
|
|
208
|
-
private readonly turns = new Map<string, IntentTurnState>()
|
|
209
|
-
|
|
210
|
-
constructor(settings: Partial<IntentSettings> = {}, store?: IntentStore) {
|
|
211
|
-
this.settings = normalizeSettings({ ...DEFAULT_INTENT_SETTINGS, ...settings })
|
|
212
|
-
this.store = store ?? new IntentStore(this.settings.ttlSeconds)
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
setSettings(settings: Partial<IntentSettings>): void {
|
|
216
|
-
this.settings = normalizeSettings({ ...this.settings, ...settings })
|
|
217
|
-
this.store.setTtl(this.settings.ttlSeconds)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
getSettings(): IntentSettings {
|
|
221
|
-
return this.settings
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Open a turn and allocate its provisional ids before the model records.
|
|
226
|
-
* Downstream calls dispatched in parallel with the record can already carry
|
|
227
|
-
* an id; the record later commits the authoritative task binding.
|
|
228
|
-
*/
|
|
229
|
-
beginTurn(sessionKey: string, turn: number, userInput: string, eligible = true): IntentTurnState {
|
|
230
|
-
const state: IntentTurnState = {
|
|
231
|
-
turn,
|
|
232
|
-
userInput: userInput.slice(0, 2000),
|
|
233
|
-
subtaskId: id('subtask'),
|
|
234
|
-
taskId: this.store.last(sessionKey)?.lineage.taskId,
|
|
235
|
-
eligible,
|
|
236
|
-
dataToolUsed: false,
|
|
237
|
-
committed: false,
|
|
238
|
-
}
|
|
239
|
-
this.turns.set(sessionKey, state)
|
|
240
|
-
return state
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* 用户消息可能在 `turn/start` 之后才到达。
|
|
245
|
-
*
|
|
246
|
-
* 宿主事件顺序不是固定的:有些会话先发 `turn/start`,再发 `user/message`。如果只在
|
|
247
|
-
* `beginTurn` 读取一次原文,就会把整轮意图永久记录成空字符串。收到真实用户消息后,
|
|
248
|
-
* 只更新尚未提交的当前轮次;已经记录的判断不再被后来事件改写。
|
|
249
|
-
*/
|
|
250
|
-
updateTurnInput(sessionKey: string, userInput: string): boolean {
|
|
251
|
-
const state = this.turns.get(sessionKey)
|
|
252
|
-
if (!state || state.committed || !userInput.trim()) return false
|
|
253
|
-
state.userInput = userInput.slice(0, 2000)
|
|
254
|
-
return true
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/** Mark the current turn as a data turn without classifying it. */
|
|
258
|
-
markDataIntent(sessionKey: string, turn?: number): boolean {
|
|
259
|
-
const state = this.turns.get(sessionKey)
|
|
260
|
-
if (!state || state.committed || (turn !== undefined && state.turn !== turn)) return false
|
|
261
|
-
state.eligible = true
|
|
262
|
-
return true
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/** Mark the current turn as a data turn based on actual data-tool use. */
|
|
266
|
-
markDataToolUse(sessionKey: string, turn?: number): boolean {
|
|
267
|
-
const state = this.turns.get(sessionKey)
|
|
268
|
-
if (!state || state.committed || (turn !== undefined && state.turn !== turn)) return false
|
|
269
|
-
state.eligible = true
|
|
270
|
-
state.dataToolUsed = true
|
|
271
|
-
return true
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/** Clear a text-only gate when the model explicitly identifies a non-data turn. */
|
|
275
|
-
clearDataIntent(sessionKey: string, turn?: number): boolean {
|
|
276
|
-
const state = this.turns.get(sessionKey)
|
|
277
|
-
if (!state || state.committed || (turn !== undefined && state.turn !== turn)) return false
|
|
278
|
-
if (!state.dataToolUsed) state.eligible = false
|
|
279
|
-
return true
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/** Whether the current turn is eligible for data intent recording. */
|
|
283
|
-
isDataIntentTurn(sessionKey: string, turn?: number): boolean {
|
|
284
|
-
const state = this.turns.get(sessionKey)
|
|
285
|
-
return Boolean(state && !state.committed && state.eligible && (turn === undefined || state.turn === turn))
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/** Backfill one `unknown` record only when an eligible data turn has no model record. */
|
|
289
|
-
fallbackWhenMissing(sessionKey: string, turn: number): IntentResult | undefined {
|
|
290
|
-
if (!this.settings.fallbackEnabled) return undefined
|
|
291
|
-
const state = this.turns.get(sessionKey)
|
|
292
|
-
if (!state || state.turn !== turn || state.committed) return undefined
|
|
293
|
-
if (!state.eligible) {
|
|
294
|
-
this.turns.delete(sessionKey)
|
|
295
|
-
return undefined
|
|
296
|
-
}
|
|
297
|
-
const result = this.record({
|
|
298
|
-
userInput: state.userInput,
|
|
299
|
-
act: 'unknown',
|
|
300
|
-
category: 'unknown',
|
|
301
|
-
relation: 'unknown',
|
|
302
|
-
sessionKey,
|
|
303
|
-
}, 'fallback')
|
|
304
|
-
if (result.ok) {
|
|
305
|
-
state.committed = true
|
|
306
|
-
this.turns.delete(sessionKey)
|
|
307
|
-
}
|
|
308
|
-
return result
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/** Record the model's intent judgement. The host applies scope before calling this method. */
|
|
312
|
-
record(input: IntentRecordInput, source: IntentSource = 'model'): IntentResult {
|
|
313
|
-
const sessionKey = input.sessionKey?.trim() || 'default'
|
|
314
|
-
const timestamp = new Date(input.now ?? Date.now()).toISOString()
|
|
315
|
-
const warnings: string[] = []
|
|
316
|
-
const state = this.turns.get(sessionKey)
|
|
317
|
-
const turnKey = state ? `${sessionKey}#${state.turn}` : `${sessionKey}#${input.turnId?.trim() || 'manual'}`
|
|
318
|
-
const existing = this.store.findByTurn(sessionKey, turnKey)
|
|
319
|
-
const lineageInput = safeLineage(input.lineage)
|
|
320
|
-
|
|
321
|
-
const userInput = input.userInput?.trim() ? input.userInput : state?.userInput ?? ''
|
|
322
|
-
const act = input.act?.trim() || 'unknown'
|
|
323
|
-
const category = input.category?.trim() || undefined
|
|
324
|
-
const confidence = clamp01(input.confidence)
|
|
325
|
-
if (!userInput.trim()) warnings.push('user_input 为空,仅记录了一条空意图')
|
|
326
|
-
if (act !== 'unknown' && !DEFAULT_ACTS.some(item => item.id === act && item.enabled)) warnings.push(`act "${act}" 不在内置 act 列表中,已按原样记录`)
|
|
327
|
-
if (category && !DEFAULT_CATEGORIES.some(item => item.id === category && item.enabled)) warnings.push(`category "${category}" 不是内置分类,已按原样记录`)
|
|
328
|
-
const lowConfidence = confidence !== undefined && confidence < this.settings.clarifyThreshold
|
|
329
|
-
if (lowConfidence) warnings.push(`confidence ${confidence.toFixed(2)} 低于阈值 ${this.settings.clarifyThreshold.toFixed(2)},已标记 lowConfidence`)
|
|
330
|
-
|
|
331
|
-
const requestedTaskId = lineageInput.taskId
|
|
332
|
-
let relation: IntentRelation = input.relation ?? (requestedTaskId ? 'subtask_of' : 'new_task')
|
|
333
|
-
let taskId: string | undefined
|
|
334
|
-
let parentSubtaskId: string | undefined
|
|
335
|
-
if (relation === 'subtask_of') {
|
|
336
|
-
if (requestedTaskId && this.store.taskExists(sessionKey, requestedTaskId)) {
|
|
337
|
-
taskId = requestedTaskId
|
|
338
|
-
parentSubtaskId = this.store.lastSubtaskOf(sessionKey, requestedTaskId)
|
|
339
|
-
} else {
|
|
340
|
-
warnings.push(requestedTaskId
|
|
341
|
-
? `task_id "${requestedTaskId}" 在本会话不存在,本轮已按 new_task 记录`
|
|
342
|
-
: 'relation=subtask_of 缺少 task_id,本轮已按 new_task 记录')
|
|
343
|
-
relation = 'new_task'
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
if (!taskId) {
|
|
347
|
-
taskId = relation === 'new_task'
|
|
348
|
-
? id('task')
|
|
349
|
-
: existing?.lineage.taskId ?? state?.taskId ?? id('task')
|
|
350
|
-
}
|
|
351
|
-
const subtaskId = existing?.lineage.subtaskId ?? state?.subtaskId ?? id('subtask')
|
|
352
|
-
const runId = lineageInput.runId ?? existing?.lineage.runId ?? id('run')
|
|
353
|
-
const intentId = existing?.id ?? id('intent')
|
|
354
|
-
const turnId = input.turnId?.trim() || existing?.turnId || (state ? `turn-${state.turn}` : id('turn'))
|
|
355
|
-
|
|
356
|
-
const record: IntentRecord = {
|
|
357
|
-
id: intentId,
|
|
358
|
-
sessionKey,
|
|
359
|
-
turnKey,
|
|
360
|
-
turnId,
|
|
361
|
-
userInput,
|
|
362
|
-
act,
|
|
363
|
-
...(category ? { category } : {}),
|
|
364
|
-
...(input.predicate?.trim() ? { predicate: input.predicate.trim() } : {}),
|
|
365
|
-
slots: slotsOf(input.slots),
|
|
366
|
-
...(confidence !== undefined ? { confidence } : {}),
|
|
367
|
-
...(lowConfidence ? { lowConfidence: true } : {}),
|
|
368
|
-
refs: refsOf(input.refs),
|
|
369
|
-
relation,
|
|
370
|
-
source,
|
|
371
|
-
createdAt: existing?.createdAt ?? timestamp,
|
|
372
|
-
updatedAt: timestamp,
|
|
373
|
-
lineage: {
|
|
374
|
-
...lineageInput,
|
|
375
|
-
sessionId: lineageInput.sessionId ?? sessionKey,
|
|
376
|
-
turnId,
|
|
377
|
-
intentId,
|
|
378
|
-
taskId,
|
|
379
|
-
subtaskId,
|
|
380
|
-
...(parentSubtaskId ? { parentSubtaskId } : {}),
|
|
381
|
-
runId,
|
|
382
|
-
},
|
|
383
|
-
meta: {},
|
|
384
|
-
}
|
|
385
|
-
this.store.save(record)
|
|
386
|
-
if (state) {
|
|
387
|
-
state.committed = true
|
|
388
|
-
state.taskId = taskId
|
|
389
|
-
state.subtaskId = subtaskId
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// Built through the shared constructor. This literal used to omit
|
|
393
|
-
// `error: null`, which made an intent result the one envelope in the repo
|
|
394
|
-
// that did not satisfy spec/contracts/envelope.json.
|
|
395
|
-
return envelopeSuccess({
|
|
396
|
-
intentId,
|
|
397
|
-
taskId,
|
|
398
|
-
subtaskId,
|
|
399
|
-
...(parentSubtaskId ? { parentSubtaskId } : {}),
|
|
400
|
-
runId,
|
|
401
|
-
turnId,
|
|
402
|
-
act,
|
|
403
|
-
...(category ? { category } : {}),
|
|
404
|
-
relation,
|
|
405
|
-
source,
|
|
406
|
-
lowConfidence,
|
|
407
|
-
activeTasks: this.tasks(sessionKey),
|
|
408
|
-
}, warnings)
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/** Ids a downstream tool call in this session should carry. */
|
|
412
|
-
activeLineage(sessionKey: string): { lineage: Partial<LineageContext>; committed: boolean } | undefined {
|
|
413
|
-
const last = this.store.last(sessionKey)
|
|
414
|
-
if (last) return { lineage: { ...last.lineage }, committed: this.turns.get(sessionKey)?.committed ?? true }
|
|
415
|
-
const state = this.turns.get(sessionKey)
|
|
416
|
-
if (!state) return undefined
|
|
417
|
-
return { lineage: { sessionId: sessionKey, ...(state.taskId ? { taskId: state.taskId } : {}), subtaskId: state.subtaskId }, committed: false }
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
tasks(sessionKey: string): IntentTaskSummary[] {
|
|
421
|
-
const now = Date.now()
|
|
422
|
-
const seen = new Map<string, IntentTaskSummary>()
|
|
423
|
-
for (const record of this.store.records(sessionKey)) {
|
|
424
|
-
const taskId = record.lineage.taskId
|
|
425
|
-
if (!taskId) continue
|
|
426
|
-
seen.set(taskId, {
|
|
427
|
-
taskId,
|
|
428
|
-
...(record.lineage.subtaskId ? { lastSubtaskId: record.lineage.subtaskId } : {}),
|
|
429
|
-
...(record.category ? { category: record.category } : {}),
|
|
430
|
-
ageSeconds: Math.max(0, Math.round((now - Date.parse(record.updatedAt)) / 1000)),
|
|
431
|
-
})
|
|
432
|
-
}
|
|
433
|
-
return [...seen.values()].slice(-3).reverse()
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
view(sessionKey = 'default', includePrompt = false): IntentResult {
|
|
437
|
-
const last = this.store.last(sessionKey)
|
|
438
|
-
const view: IntentStoreView = {
|
|
439
|
-
sessionKey,
|
|
440
|
-
...(last?.lineage.taskId ? { activeTaskId: last.lineage.taskId } : {}),
|
|
441
|
-
...(last ? { activeIntentId: last.id } : {}),
|
|
442
|
-
tasks: this.tasks(sessionKey),
|
|
443
|
-
recent: this.store.records(sessionKey).slice(-10).reverse(),
|
|
444
|
-
...(this.store.expiresAt(sessionKey) ? { expiresAt: this.store.expiresAt(sessionKey) } : {}),
|
|
445
|
-
}
|
|
446
|
-
return envelopeSuccess({ store: view, ...(includePrompt ? { prompt: this.routingPrompt() } : {}) })
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
/**
|
|
450
|
-
* Session-level index for the 意图 panel: every live session with its last
|
|
451
|
-
* classification and active task ids. Records themselves stay per-session.
|
|
452
|
-
*/
|
|
453
|
-
overview(): { sessions: Array<Record<string, unknown>> } {
|
|
454
|
-
const sessions = this.store.sessionKeys().map(sessionKey => {
|
|
455
|
-
const last = this.store.last(sessionKey)
|
|
456
|
-
return {
|
|
457
|
-
sessionKey,
|
|
458
|
-
recordCount: this.store.records(sessionKey).length,
|
|
459
|
-
lastAt: new Date(this.store.touchedAt(sessionKey)).toISOString(),
|
|
460
|
-
tasks: this.tasks(sessionKey),
|
|
461
|
-
...(last ? { activeIntentId: last.id } : {}),
|
|
462
|
-
...(last?.act ? { lastAct: last.act } : {}),
|
|
463
|
-
...(last?.category ? { lastCategory: last.category } : {}),
|
|
464
|
-
...(last?.relation ? { lastRelation: last.relation } : {}),
|
|
465
|
-
...(last?.lineage.taskId ? { activeTaskId: last.lineage.taskId } : {}),
|
|
466
|
-
...(last && last.confidence !== undefined ? { lastConfidence: last.confidence } : {}),
|
|
467
|
-
}
|
|
468
|
-
})
|
|
469
|
-
return { sessions }
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
/**
|
|
473
|
-
* Model-facing contract: record first, then continue; the record never gates the request.
|
|
474
|
-
*
|
|
475
|
-
* 这段文本就是 `intent_record` 的**工具描述**(0023 起不再单独注入 system prompt 段),
|
|
476
|
-
* 分类规则固定随工具在场,不再通过设置或注册表变更。
|
|
477
|
-
*
|
|
478
|
-
* 分类判定是**两段式**的(issue 0023):
|
|
479
|
-
* 1. 先拿用户意思去比对预置分类的 `description` / `examples`,命中就**直接用该 id**;
|
|
480
|
-
* 2. 全不命中才允许自行判断(新起一个 id 或落 `unknown`)。
|
|
481
|
-
*
|
|
482
|
-
* 这样预置项就是"先验",而不是一份仅供参考的清单——模型不必每轮从零分类。
|
|
483
|
-
*/
|
|
484
|
-
routingPrompt(): string {
|
|
485
|
-
const routes = DEFAULT_CATEGORIES
|
|
486
|
-
.map(category => {
|
|
487
|
-
const head = `- ${category.id}(${category.label})→ ${category.route}${category.description ? `:${category.description}` : ''}`
|
|
488
|
-
const examples = (category.examples ?? []).filter(Boolean)
|
|
489
|
-
return examples.length > 0 ? `${head}\n 例如:${examples.join(' / ')}` : head
|
|
490
|
-
})
|
|
491
|
-
.join('\n')
|
|
492
|
-
return [
|
|
493
|
-
'本工具支持 standard preset 的顶层会话。先判断本轮是否属于数据 / 知识域;只有属于数据域时,才调用 intent_record 落一条数据意图审计;寒暄、闲聊、Agent 自身说明、纯写作、代码和一般运维请求不要调用,也不要补 unknown。记录不改变、不阻塞、也不需要用户确认。',
|
|
494
|
-
'如果当前请求会使用数据、知识库、表格、报告、SourceRaw、WikiCard、查询、分析、导入、编译或召回工具,它属于数据域;否则按普通对话处理。',
|
|
495
|
-
'当前轮是新的任务用 relation=new_task;如果是在延续某个已有任务(追问、补充、纠正),用 relation=subtask_of 并带上该任务的 task_id;无法判断时省略 relation。',
|
|
496
|
-
'',
|
|
497
|
-
'分类判定分两步,必须先匹配预置、再自行判断:',
|
|
498
|
-
'1) 先把用户的意思和下面的「预置分类」逐条比对——看定义,也看"例如"里的说法。命中就直接用该分类的 id,不要另起一个近义的新 id,也不要因为措辞不同就换一个分类。',
|
|
499
|
-
'2) 所有预置分类都不命中时,才自行判断:可以按语义新起一个短语作为分类 id,或落 unknown。新起的 id 用英文小写下划线,并在 slots 里带上关键参数。',
|
|
500
|
-
'',
|
|
501
|
-
'预置分类(category 参数传 id):',
|
|
502
|
-
routes,
|
|
503
|
-
'',
|
|
504
|
-
'act 回答"这一轮用户在做什么",挑最贴近的一个:',
|
|
505
|
-
DEFAULT_ACTS
|
|
506
|
-
.map(def => `- ${def.id}${def.label && def.label !== def.id ? `(${def.label})` : ''}${def.description ? `:${def.description}` : ''}`)
|
|
507
|
-
.join('\n'),
|
|
508
|
-
`可用 predicate:${DEFAULT_PREDICATES.join('、')}`,
|
|
509
|
-
].join('\n')
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
export function createIntentEngine(settings?: Partial<IntentSettings>): IntentEngine {
|
|
514
|
-
return new IntentEngine(settings)
|
|
515
|
-
}
|