dominds 0.6.6 → 0.6.7
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.
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# Drive 逻辑上下文拼装现状与重构计划(2026-02)
|
|
2
|
+
|
|
3
|
+
状态:Draft
|
|
4
|
+
语义基线:以 `dominds/main/llm/driver.ts` 与 `dominds/main/persistence.ts` 当前实现为准。
|
|
5
|
+
|
|
6
|
+
## 1. 背景与目标
|
|
7
|
+
|
|
8
|
+
近期在对话 `@ux (39/12/417f4a49)` 中暴露出一个上下文一致性问题:同一主线对话内已经收到了 `@cmdr/@browser_tester` 的回贴并据此更新 Taskdoc,但后续回复仍声称“没看到原始回贴文本”。
|
|
9
|
+
|
|
10
|
+
这类问题的根因不是单条提示词,而是 **drive 内多轮生成的上下文来源语义不统一**:有的上下文是持久消息(`dlg.msgs`),有的是一次性注入(仅首轮),还有的是消费队列(take/commit/rollback)。
|
|
11
|
+
|
|
12
|
+
本文目标:
|
|
13
|
+
|
|
14
|
+
1. 固化“当前代码结构现状”的可审计视图。
|
|
15
|
+
2. 记录本轮已做的修补。
|
|
16
|
+
3. 制定一次更大范围的 drive logic 重构计划,使后续维护更清晰、不易回归。
|
|
17
|
+
|
|
18
|
+
## 2. 当前结构(代码现状)
|
|
19
|
+
|
|
20
|
+
### 2.1 核心文件与职责
|
|
21
|
+
|
|
22
|
+
- `main/llm/driver.ts`
|
|
23
|
+
- `_driveDialogStream()`:单次 drive 主循环,负责 prompt 处理、上下文拼装、LLM 调用、工具调用、suspend/continue。
|
|
24
|
+
- `supplyResponseToSupdialog()`:子对话回贴写入父对话响应队列、更新 pending、触发 auto-revive。
|
|
25
|
+
- `main/persistence.ts`
|
|
26
|
+
- `takeSubdialogResponses()/commitTakenSubdialogResponses()/rollbackTakenSubdialogResponses()`:子对话回贴队列的“取走-提交/回滚”机制。
|
|
27
|
+
- `rebuildFromEvents()`:重建 `dlg.msgs`(包含 `teammate_response_record -> tellask_result_msg` 映射)。
|
|
28
|
+
- `main/dialog.ts`
|
|
29
|
+
- `addChatMessages()`:运行时消息容器(in-memory)。
|
|
30
|
+
|
|
31
|
+
### 2.2 当前上下文来源(进入 LLM 的入口)
|
|
32
|
+
|
|
33
|
+
在 `_driveDialogStream()` 中,每轮 gen 的 `ctxMsgs` 由以下部分拼装:
|
|
34
|
+
|
|
35
|
+
1. `prependedContextMessages`(策略注入)
|
|
36
|
+
2. `memories`
|
|
37
|
+
3. `taskDocMsg`
|
|
38
|
+
4. `coursePrefixMsgs`
|
|
39
|
+
5. `dialogMsgsForContext`(来自 `dlg.msgs`)
|
|
40
|
+
6. `subdialogResponseContextMsgs`(来自 `takeSubdialogResponses` 的注入)
|
|
41
|
+
7. `internalDrivePromptMsg`(internal prompt 注入)
|
|
42
|
+
8. reminders + language guide(末尾插入)
|
|
43
|
+
|
|
44
|
+
要点:`dlg.msgs` 是“稳定上下文”;队列/内部 prompt 属于“drive 级上下文”。
|
|
45
|
+
|
|
46
|
+
## 3. 本轮修补(已落地)
|
|
47
|
+
|
|
48
|
+
### 3.1 已修问题
|
|
49
|
+
|
|
50
|
+
1. **同一 drive 多轮丢失 teammate response 上下文**
|
|
51
|
+
- 修复:把 `takeSubdialogResponses` 生成的 `subdialogResponseContextMsgs` 在同一 drive 内跨迭代保留,而非仅 `genIterNo===1` 注入。
|
|
52
|
+
|
|
53
|
+
2. **中断时误 commit 已 take 队列**
|
|
54
|
+
- 修复:在 interrupted 分支标记 `generationHadError = true`,确保 finally 走 rollback,不会把未稳定消费的队列当作已消费。
|
|
55
|
+
|
|
56
|
+
3. **internal prompt 语义收敛为 drive 级 priming**
|
|
57
|
+
- 修复:移除生命周期分支;`persistMode='internal'` 统一为 drive-scoped priming 注入。
|
|
58
|
+
- 语义:仅在当前 drive 生效、不入 `dlg.msgs`、不持久化、不渲染 UI。
|
|
59
|
+
- 依据:当前唯一使用场景是 Agent Priming,且明确要求 loop 迭代中持续可见。
|
|
60
|
+
|
|
61
|
+
4. **teammate response 稳定化到 `dlg.msgs`(与工具结果语义对齐)**
|
|
62
|
+
- 修复:当 take/commit 成功后,把该批回贴镜像为 `tellask_result_msg` 写入 `dlg.msgs`,让后续 drive 不依赖一次性队列注入。
|
|
63
|
+
|
|
64
|
+
5. **队列记录补齐状态字段**
|
|
65
|
+
- 修复:`subdialog-responses` 记录新增可选 `status`(`completed|failed`),并在镜像消息时使用该状态(默认 `completed`)。
|
|
66
|
+
|
|
67
|
+
## 4. 仍存在的结构债务
|
|
68
|
+
|
|
69
|
+
### 4.1 状态语义分散
|
|
70
|
+
|
|
71
|
+
同一类“队友回贴事实”同时存在于:
|
|
72
|
+
|
|
73
|
+
- `teammate_response_record`(事件持久层)
|
|
74
|
+
- `subdialog-responses.json`(消费队列)
|
|
75
|
+
- `dlg.msgs`(运行时上下文)
|
|
76
|
+
|
|
77
|
+
缺少单一“源事实 -> 视图派生”的规范,维护成本高。
|
|
78
|
+
|
|
79
|
+
### 4.2 拼装流程耦合过深
|
|
80
|
+
|
|
81
|
+
`_driveDialogStream()` 同时承担:
|
|
82
|
+
|
|
83
|
+
- prompt 生命周期管理
|
|
84
|
+
- 上下文装配
|
|
85
|
+
- policy 校验
|
|
86
|
+
- 流式/非流式分支
|
|
87
|
+
- 工具调用循环
|
|
88
|
+
- suspend/revive 与队列提交事务
|
|
89
|
+
|
|
90
|
+
单函数职责过重,不利于做语义回归验证。
|
|
91
|
+
|
|
92
|
+
### 4.3 缺少针对性回归测试矩阵
|
|
93
|
+
|
|
94
|
+
现有 tellask 测试覆盖“auto-revive 能跑通”,但对以下关键边界覆盖不足:
|
|
95
|
+
|
|
96
|
+
- 同一 drive 的多轮迭代上下文一致性
|
|
97
|
+
- interrupted + take queue 的 rollback 语义
|
|
98
|
+
- committed queue 镜像到 `dlg.msgs` 后的去重/恢复语义
|
|
99
|
+
|
|
100
|
+
## 5. 语义决策记录(本轮确认)
|
|
101
|
+
|
|
102
|
+
1. `persistMode='internal'` 不是“下一轮临时补丁”,而是 **drive 级 priming 通道**。
|
|
103
|
+
2. 当前无 `next_gen` 真实业务需求,先不保留该分支,避免语义包袱。
|
|
104
|
+
3. 如未来出现单轮内部提示需求,新增能力应基于明确场景和回归测试,不提前抽象。
|
|
105
|
+
|
|
106
|
+
## 6. 大重构计划(可执行蓝图)
|
|
107
|
+
|
|
108
|
+
### 阶段 A:上下文装配纯函数化(不改行为)
|
|
109
|
+
|
|
110
|
+
目标:把“怎么拼装 ctxMsgs”从 `_driveDialogStream()` 中剥离为可测试单元。
|
|
111
|
+
|
|
112
|
+
交付物:
|
|
113
|
+
|
|
114
|
+
1. 新建 `buildDriveContextSnapshot(...)`,输出不可变快照:
|
|
115
|
+
- `stableMessages`(来自 `dlg.msgs` + memories + taskdoc + coursePrefix)
|
|
116
|
+
- `transientMessages`(subdialog 注入、internal drive priming、reminder/language guide)
|
|
117
|
+
- `diagnostics`(各来源计数、关键 id、去重统计)
|
|
118
|
+
2. 新建 `renderDriveContextMessages(snapshot)`,仅负责有序拼接,不负责业务决策。
|
|
119
|
+
3. `_driveDialogStream()` 改为“准备输入 -> 调纯函数 -> 用输出”。
|
|
120
|
+
|
|
121
|
+
验收:
|
|
122
|
+
|
|
123
|
+
1. 对同一输入快照,render 结果稳定(可 snapshot test)。
|
|
124
|
+
2. 现有行为不变(含 remind/language guide 插入顺序)。
|
|
125
|
+
|
|
126
|
+
### 阶段 B:take/commit/rollback 事务边界显式化(核心)
|
|
127
|
+
|
|
128
|
+
目标:把“队友回贴消费”从散落标志位改为单一事务状态机。
|
|
129
|
+
|
|
130
|
+
交付物:
|
|
131
|
+
|
|
132
|
+
1. 引入 `TakenResponsesTxn`(建议放在 `main/llm/driver-context-txn.ts`):
|
|
133
|
+
- `begin(dlgId)`:take queue
|
|
134
|
+
- `asInjectedMessages()`:生成注入消息
|
|
135
|
+
- `markGenerationAttempted()`:至少发起过有效 LLM 请求
|
|
136
|
+
- `finalize(outcome)`:`commit|rollback`
|
|
137
|
+
2. 用显式 outcome 替代隐式 `generationHadError` 推断。
|
|
138
|
+
3. 把 interrupted 与 error 的 finalize 语义统一。
|
|
139
|
+
|
|
140
|
+
验收:
|
|
141
|
+
|
|
142
|
+
1. interrupted/error 必 rollback。
|
|
143
|
+
2. 成功且已尝试生成时 commit。
|
|
144
|
+
3. finalize 幂等(重复调用不会破坏状态)。
|
|
145
|
+
|
|
146
|
+
### 阶段 C:teammate response 单路径入上下文(减少双轨)
|
|
147
|
+
|
|
148
|
+
目标:统一 live 路径与 restore 路径的同构转换,避免“看过但丢失”或重复。
|
|
149
|
+
|
|
150
|
+
交付物:
|
|
151
|
+
|
|
152
|
+
1. 新建 `toTellaskResultMsg(record)` 转换器,统一以下两处:
|
|
153
|
+
- live commit 后镜像到 `dlg.msgs`
|
|
154
|
+
- `rebuildFromEvents()` 的重建映射
|
|
155
|
+
2. 显式去重键策略:
|
|
156
|
+
- 优先 `callId`
|
|
157
|
+
- 回退 `responseId`
|
|
158
|
+
- 再回退 `responderId + tellaskHead + completedAt`
|
|
159
|
+
3. diagnostics 中输出“去重前后数量”和命中键类型。
|
|
160
|
+
|
|
161
|
+
验收:
|
|
162
|
+
|
|
163
|
+
1. restore 后不重复。
|
|
164
|
+
2. live drive 与 restore drive 的输入上下文等价。
|
|
165
|
+
|
|
166
|
+
### 阶段 D:\_driveDialogStream 职责分层(可维护性)
|
|
167
|
+
|
|
168
|
+
目标:把巨函数拆成“稳定编排 + 可替换子模块”。
|
|
169
|
+
|
|
170
|
+
建议拆分模块:
|
|
171
|
+
|
|
172
|
+
1. `prepareDriveInputs(...)`
|
|
173
|
+
2. `buildGenerationContext(...)`
|
|
174
|
+
3. `runGenerationRound(...)`(流式/非流式统一返回形态)
|
|
175
|
+
4. `applyRoundSideEffects(...)`(tool outputs / tellask outputs / upNext)
|
|
176
|
+
5. `finalizeDrive(...)`
|
|
177
|
+
|
|
178
|
+
验收:
|
|
179
|
+
|
|
180
|
+
1. `_driveDialogStream` 主体降到“编排层”。
|
|
181
|
+
2. 单模块改动不需触碰整个 while-loop。
|
|
182
|
+
|
|
183
|
+
### 阶段 E:回归矩阵与复放样本
|
|
184
|
+
|
|
185
|
+
新增最小回归:
|
|
186
|
+
|
|
187
|
+
1. `drive-context:multi-iter-subdialog-response.ts`
|
|
188
|
+
2. `drive-context:interrupt-rollback.ts`
|
|
189
|
+
3. `drive-context:commit-mirror-msgs.ts`
|
|
190
|
+
4. `drive-context:no-duplicate-after-restore.ts`
|
|
191
|
+
5. `drive-context:internal-drive-priming-persists-across-iterations.ts`
|
|
192
|
+
|
|
193
|
+
新增复放样本:
|
|
194
|
+
|
|
195
|
+
1. 以 `39/12/417f4a49` 风格构造一条“多轮 + tellask + tool + continue”样本,断言模型能持续看到 teammate 原始回贴语义。
|
|
196
|
+
|
|
197
|
+
## 7. 目标接口草案(供后续实现)
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
type DriveTransientContext = {
|
|
201
|
+
subdialogResponseMsgs: ChatMessage[];
|
|
202
|
+
internalDrivePrimingMsg?: ChatMessage;
|
|
203
|
+
reminderMsgs: ChatMessage[];
|
|
204
|
+
languageGuideMsg: ChatMessage;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
type DriveContextSnapshot = {
|
|
208
|
+
stableMessages: ChatMessage[];
|
|
209
|
+
transient: DriveTransientContext;
|
|
210
|
+
diagnostics: {
|
|
211
|
+
counts: Record<string, number>;
|
|
212
|
+
dedupe: { before: number; after: number; by: string[] };
|
|
213
|
+
genseq?: number;
|
|
214
|
+
callIds: string[];
|
|
215
|
+
};
|
|
216
|
+
};
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
说明:
|
|
220
|
+
|
|
221
|
+
1. `internalDrivePrimingMsg` 仅允许 drive 级语义,不包含单轮模式。
|
|
222
|
+
2. snapshot 不做副作用,不访问持久层。
|
|
223
|
+
3. 所有副作用留在 driver 编排层执行。
|
|
224
|
+
|
|
225
|
+
## 8. 重构约束(必须保持)
|
|
226
|
+
|
|
227
|
+
1. 不改变现有 wire 协议事件名与基础语义(除非显式版本升级)。
|
|
228
|
+
2. 不引入 silent fallback;上下文事务异常需 loud 日志与可见错误信号。
|
|
229
|
+
3. 保持 TypeScript strict 与可静态验证属性;禁止 `any`。
|
|
230
|
+
4. 重构每阶段都要求:
|
|
231
|
+
- `pnpm -C dominds run lint:types` 通过
|
|
232
|
+
- tellask 相关回归通过
|
|
233
|
+
- 至少 1 条真实对话复放样本通过(含 auto-revive 场景)
|
|
234
|
+
|
|
235
|
+
## 9. 执行顺序建议
|
|
236
|
+
|
|
237
|
+
1. 先做阶段 A,先把“可观察性”补齐。
|
|
238
|
+
2. 再做阶段 B,这是一致性根基。
|
|
239
|
+
3. 再做阶段 C,统一消息路径。
|
|
240
|
+
4. 最后做阶段 D/E,收敛维护复杂度并补齐回归。
|
|
241
|
+
|
|
242
|
+
该顺序允许在每阶段都可独立回滚,降低一次性大改风险。
|
package/dist/llm/driver.js
CHANGED
|
@@ -1120,9 +1120,14 @@ async function _driveDialogStream(dlg, humanPrompt, driveOptions) {
|
|
|
1120
1120
|
let generationHadError = false;
|
|
1121
1121
|
let tookSubdialogResponses = false;
|
|
1122
1122
|
let takenSubdialogResponses = [];
|
|
1123
|
+
// Keep injected subdialog replies visible across all gen iterations in the same drive.
|
|
1124
|
+
// Without this, iteration #2 (e.g. after a function call) can lose reply context from iteration #1.
|
|
1125
|
+
let subdialogResponseContextMsgs = [];
|
|
1126
|
+
// Internal prompt is drive-scoped priming context and never persisted.
|
|
1127
|
+
let internalDrivePromptMsg;
|
|
1128
|
+
let committedTakenSubdialogResponses = false;
|
|
1123
1129
|
let genIterNo = 0;
|
|
1124
1130
|
let pendingPrompt = humanPrompt;
|
|
1125
|
-
let internalPromptForThisDrive;
|
|
1126
1131
|
let skipTaskdocForThisDrive = humanPrompt?.skipTaskdoc === true;
|
|
1127
1132
|
try {
|
|
1128
1133
|
while (true) {
|
|
@@ -1264,7 +1269,14 @@ async function _driveDialogStream(dlg, humanPrompt, driveOptions) {
|
|
|
1264
1269
|
});
|
|
1265
1270
|
}
|
|
1266
1271
|
if (persistMode === 'internal') {
|
|
1267
|
-
|
|
1272
|
+
const injected = currentPrompt.content.trim();
|
|
1273
|
+
internalDrivePromptMsg = injected
|
|
1274
|
+
? {
|
|
1275
|
+
type: 'environment_msg',
|
|
1276
|
+
role: 'user',
|
|
1277
|
+
content: injected,
|
|
1278
|
+
}
|
|
1279
|
+
: undefined;
|
|
1268
1280
|
}
|
|
1269
1281
|
else {
|
|
1270
1282
|
await dlg.addChatMessages({
|
|
@@ -1365,31 +1377,25 @@ async function _driveDialogStream(dlg, humanPrompt, driveOptions) {
|
|
|
1365
1377
|
dialogMsgsForContext,
|
|
1366
1378
|
});
|
|
1367
1379
|
if (genIterNo === 1 && takenSubdialogResponses.length > 0) {
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1380
|
+
subdialogResponseContextMsgs = takenSubdialogResponses.map((response) => ({
|
|
1381
|
+
type: 'environment_msg',
|
|
1382
|
+
role: 'user',
|
|
1383
|
+
content: (0, inter_dialog_format_1.formatTeammateResponseContent)({
|
|
1384
|
+
responderId: response.responderId,
|
|
1385
|
+
requesterId: response.originMemberId,
|
|
1386
|
+
originalCallHeadLine: response.tellaskHead,
|
|
1387
|
+
responseBody: response.response,
|
|
1388
|
+
language: (0, runtime_language_1.getWorkLanguage)(),
|
|
1389
|
+
}),
|
|
1390
|
+
}));
|
|
1391
|
+
}
|
|
1392
|
+
if (subdialogResponseContextMsgs.length > 0) {
|
|
1393
|
+
ctxMsgs.push(...subdialogResponseContextMsgs);
|
|
1381
1394
|
}
|
|
1382
1395
|
// Inject the internal (non-persisted) prompt at the end of the fresh user context
|
|
1383
|
-
// so it can steer
|
|
1384
|
-
if (
|
|
1385
|
-
|
|
1386
|
-
if (injected) {
|
|
1387
|
-
ctxMsgs.push({
|
|
1388
|
-
type: 'environment_msg',
|
|
1389
|
-
role: 'user',
|
|
1390
|
-
content: injected,
|
|
1391
|
-
});
|
|
1392
|
-
}
|
|
1396
|
+
// so it can steer this drive without polluting dialog history.
|
|
1397
|
+
if (internalDrivePromptMsg) {
|
|
1398
|
+
ctxMsgs.push(internalDrivePromptMsg);
|
|
1393
1399
|
}
|
|
1394
1400
|
await dlg.processReminderUpdates();
|
|
1395
1401
|
const renderedReminders = dlg.reminders.length > 0
|
|
@@ -2263,6 +2269,9 @@ async function _driveDialogStream(dlg, humanPrompt, driveOptions) {
|
|
|
2263
2269
|
: { kind: 'system_stop', detail: 'Aborted.' }
|
|
2264
2270
|
: undefined;
|
|
2265
2271
|
if (interruptedReason) {
|
|
2272
|
+
// If subdialog responses were taken during this drive, interruption means they
|
|
2273
|
+
// should be rolled back for retry instead of being committed as consumed.
|
|
2274
|
+
generationHadError = true;
|
|
2266
2275
|
finalRunState = { kind: 'interrupted', reason: interruptedReason };
|
|
2267
2276
|
(0, dialog_run_state_1.broadcastRunStateMarker)(dlg.id, { kind: 'interrupted', reason: interruptedReason });
|
|
2268
2277
|
return { lastAssistantSayingContent, interrupted: true };
|
|
@@ -2320,6 +2329,7 @@ async function _driveDialogStream(dlg, humanPrompt, driveOptions) {
|
|
|
2320
2329
|
}
|
|
2321
2330
|
else {
|
|
2322
2331
|
await persistence_1.DialogPersistence.commitTakenSubdialogResponses(dlg.id);
|
|
2332
|
+
committedTakenSubdialogResponses = true;
|
|
2323
2333
|
}
|
|
2324
2334
|
});
|
|
2325
2335
|
}
|
|
@@ -2330,6 +2340,31 @@ async function _driveDialogStream(dlg, humanPrompt, driveOptions) {
|
|
|
2330
2340
|
});
|
|
2331
2341
|
}
|
|
2332
2342
|
}
|
|
2343
|
+
if (committedTakenSubdialogResponses && takenSubdialogResponses.length > 0) {
|
|
2344
|
+
try {
|
|
2345
|
+
const mirroredMsgs = takenSubdialogResponses.map((response) => ({
|
|
2346
|
+
type: 'tellask_result_msg',
|
|
2347
|
+
role: 'tool',
|
|
2348
|
+
responderId: response.responderId,
|
|
2349
|
+
tellaskHead: response.tellaskHead,
|
|
2350
|
+
status: response.status ?? 'completed',
|
|
2351
|
+
content: (0, inter_dialog_format_1.formatTeammateResponseContent)({
|
|
2352
|
+
responderId: response.responderId,
|
|
2353
|
+
requesterId: response.originMemberId,
|
|
2354
|
+
originalCallHeadLine: response.tellaskHead,
|
|
2355
|
+
responseBody: response.response,
|
|
2356
|
+
language: (0, runtime_language_1.getWorkLanguage)(),
|
|
2357
|
+
}),
|
|
2358
|
+
}));
|
|
2359
|
+
await dlg.addChatMessages(...mirroredMsgs);
|
|
2360
|
+
}
|
|
2361
|
+
catch (err) {
|
|
2362
|
+
log_1.log.warn('Failed to mirror committed subdialog responses into dialog msgs', {
|
|
2363
|
+
dialogId: dlg.id.selfId,
|
|
2364
|
+
error: err,
|
|
2365
|
+
});
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2333
2368
|
}
|
|
2334
2369
|
} // Close while loop
|
|
2335
2370
|
// Dialog stream has completed - no need to mark queue as complete since we're using receivers
|
|
@@ -3004,6 +3039,7 @@ async function supplyResponseToSupdialog(parentDialog, subdialogId, responseText
|
|
|
3004
3039
|
subdialogId: subdialogId.selfId,
|
|
3005
3040
|
response: responseText,
|
|
3006
3041
|
completedAt,
|
|
3042
|
+
status,
|
|
3007
3043
|
callType,
|
|
3008
3044
|
tellaskHead,
|
|
3009
3045
|
responderId,
|
package/dist/persistence.js
CHANGED
|
@@ -264,6 +264,8 @@ function isSubdialogResponseRecord(value) {
|
|
|
264
264
|
return false;
|
|
265
265
|
if (typeof value.completedAt !== 'string')
|
|
266
266
|
return false;
|
|
267
|
+
if (value.status !== undefined && value.status !== 'completed' && value.status !== 'failed')
|
|
268
|
+
return false;
|
|
267
269
|
if (value.callType !== 'A' && value.callType !== 'B' && value.callType !== 'C')
|
|
268
270
|
return false;
|
|
269
271
|
if (typeof value.tellaskHead !== 'string')
|