@harness-mix/cli 0.1.12 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -2
- package/NOTICE +14 -0
- package/README.md +4 -24
- package/docs/multi-agent-collaboration.md +1 -1
- package/licenses/anthropic-codex-theme-MIT.txt +21 -0
- package/licenses/codex-dream-skin-MIT.txt +21 -0
- package/output/native-build/renderer-extension.js +703 -58
- package/package.json +192 -189
- package/scripts/collaboration-test.cjs +262 -200
- package/scripts/collaboration-ui-smoke.cjs +85 -77
- package/scripts/core-concurrency-test.cjs +23 -1
- package/scripts/e2e-pi-cancel.cjs +99 -0
- package/scripts/native-file-change-test.cjs +42 -2
- package/scripts/native-sidebar-test.cjs +8 -0
- package/scripts/native-vendor-adapters-test.cjs +67 -1
- package/scripts/pets-test.cjs +63 -0
- package/scripts/pi-cancel-race-test.cjs +102 -0
- package/scripts/stuck-turn-test.cjs +73 -0
- package/scripts/workspace-test.cjs +9 -0
- package/src/assets/skins/dream-skin/NOTICE.md +13 -0
- package/src/assets/skins/dream-skin/themes/gothic-void-crusade/hero.webp +0 -0
- package/src/assets/skins/dream-skin/themes/gothic-void-crusade/theme.json +34 -0
- package/src/main/adapters/claude.js +9 -6
- package/src/main/adapters/pi-family.js +41 -8
- package/src/main/harness-adapter/event-normalizer.js +270 -258
- package/src/main/host/collaboration-mcp.cjs +8 -1
- package/src/main/host/collaboration-tools.js +1 -1
- package/src/main/host/collaboration.js +81 -11
- package/src/main/host/runtime.js +1420 -1341
- package/src/main/native/pets.js +79 -2
- package/src/main/native/protocol.js +1160 -1145
- package/src/main/native/thread-list.js +6 -1
- package/src/main/workspace/core-review.js +75 -0
- package/src/main/workspace/diff.js +5 -1
- package/src/main/workspace/file-changes.js +9 -4
- package/src/main/workspace/review-controller.js +115 -102
- package/src/main/workspace/review.js +90 -88
- package/src/native-ui/renderer-extension/dist/contract-audit.js +16637 -0
- package/src/native-ui/renderer-extension/dist/index.js +34853 -0
- package/src/native-ui/renderer-extension/dist/production.js +34263 -0
- package/src/native-ui/renderer-extension/dist/renderer-binding-probe.js +34254 -0
- package/src/native-ui/renderer-extension/dist/types/renderer-binding-probe.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-model-client.d.ts +11 -0
- package/src/native-ui/renderer-extension/dist/types/renderer-model-client.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-pet-switcher.d.ts +73 -0
- package/src/native-ui/renderer-extension/dist/types/renderer-pet-switcher.d.ts.map +1 -0
- package/src/native-ui/renderer-extension/dist/types/renderer-settings-lifecycle.d.ts +1 -0
- package/src/native-ui/renderer-extension/dist/types/renderer-settings-lifecycle.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/renderer-team-cards.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/collaboration-page.d.ts +12 -0
- package/src/native-ui/renderer-extension/dist/types/settings/collaboration-page.d.ts.map +1 -0
- package/src/native-ui/renderer-extension/dist/types/settings/icons.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/icons.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/localization.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts +5 -3
- package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/pet-market.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/pet-market.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/pets-client.d.ts +9 -0
- package/src/native-ui/renderer-extension/dist/types/settings/pets-client.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/skin-runtime.d.ts +1 -1
- package/src/native-ui/renderer-extension/dist/types/settings/skin-runtime.d.ts.map +1 -1
- package/src/native-ui/renderer-extension/dist/types/tsconfig.tsbuildinfo +1 -1
- package/src/native-ui/renderer-extension/src/renderer-binding-probe.ts +3181 -3171
- package/src/native-ui/renderer-extension/src/renderer-model-client.ts +14 -0
- package/src/native-ui/renderer-extension/src/renderer-pet-switcher.ts +308 -0
- package/src/native-ui/renderer-extension/src/renderer-settings-lifecycle.ts +2 -0
- package/src/native-ui/renderer-extension/src/renderer-team-cards.ts +76 -21
- package/src/native-ui/renderer-extension/src/settings/collaboration-page.ts +139 -0
- package/src/native-ui/renderer-extension/src/settings/icons.ts +155 -153
- package/src/native-ui/renderer-extension/src/settings/localization.ts +785 -783
- package/src/native-ui/renderer-extension/src/settings/pages.ts +694 -681
- package/src/native-ui/renderer-extension/src/settings/pet-market.css +110 -1
- package/src/native-ui/renderer-extension/src/settings/pet-market.ts +276 -22
- package/src/native-ui/renderer-extension/src/settings/pets-client.ts +32 -0
- package/src/native-ui/renderer-extension/src/settings/shell.css +2294 -2255
- package/src/native-ui/renderer-extension/src/settings/skin-runtime.ts +51 -1
- package/src/native-ui/renderer-extension/test/renderer-model-client.test.ts +2 -0
- package/src/native-ui/renderer-extension/test/renderer-pet-switcher.test.ts +400 -0
- package/src/native-ui/renderer-extension/test/settings/localization.test.ts +80 -79
- package/src/native-ui/renderer-extension/test/settings/pet-market.test.ts +378 -0
- package/src/native-ui/renderer-extension/test/settings/pets-client.test.ts +83 -0
- package/src/native-ui/renderer-extension/test/settings/shell.test.ts +3 -0
- package/src/native-ui/renderer-extension/test/settings/skin-runtime.test.ts +15 -1
|
@@ -1,258 +1,270 @@
|
|
|
1
|
-
// Event Normalizer:把 Runtime 现有的统一事件(legacy kind)标准化为 CoreEvent(§12)。
|
|
2
|
-
// 第一阶段不重写任何 Adapter,只在公共入口处 normalize。
|
|
3
|
-
// 本模块对 Harness 名称零感知:输入已经是 Adapter 转换后的统一事件。
|
|
4
|
-
const { classifyError, codexErrorInfoKey } = require('./error-kind');
|
|
5
|
-
|
|
6
|
-
class EventNormalizer {
|
|
7
|
-
/**
|
|
8
|
-
* @param {{ threadId: string, source?: string }} context source 仅为溯源标签,不参与逻辑分支
|
|
9
|
-
*/
|
|
10
|
-
constructor({ threadId, source } = {}) {
|
|
11
|
-
this.threadId = threadId;
|
|
12
|
-
this.source = source ?? null;
|
|
13
|
-
this.turnId = null;
|
|
14
|
-
this.turnActive = false;
|
|
15
|
-
this.currentMessageItemId = null; // 当前 streaming 的 agent_message
|
|
16
|
-
this.currentReasoningItemId = null; // 当前 streaming 的 reasoning
|
|
17
|
-
this.segmentSequence = 0;
|
|
18
|
-
this.activeSlot = null;
|
|
19
|
-
this.interactions = new Map();
|
|
20
|
-
this.toolItems = new Map(); // toolCallId -> itemId
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** Turn 开始:先产出 user_message(已完成),后续 delta 归入本 Turn */
|
|
24
|
-
beginTurn(turnId, userText, attachments = []) {
|
|
25
|
-
this.turnId = turnId;
|
|
26
|
-
this.currentNativeRef = undefined;
|
|
27
|
-
this.turnActive = true;
|
|
28
|
-
this.currentMessageItemId = null;
|
|
29
|
-
this.currentReasoningItemId = null;
|
|
30
|
-
this.toolItems.clear();
|
|
31
|
-
this.interactions.clear();
|
|
32
|
-
this.activeSlot = null;
|
|
33
|
-
this.segmentSequence = 0;
|
|
34
|
-
const itemId = `item_user_${turnId}`;
|
|
35
|
-
return [
|
|
36
|
-
this.#event({ type: 'item.started', itemId, payload: { type: 'user_message', content: userText, ...(attachments.length ? { attachments } : {}) } }),
|
|
37
|
-
this.#event({ type: 'item.completed', itemId }),
|
|
38
|
-
];
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* legacy 统一事件 → CoreEvent 序列(一个 legacy 事件可展开为多个 CoreEvent)
|
|
43
|
-
* @returns Array<Partial<CoreEvent>>
|
|
44
|
-
*/
|
|
45
|
-
normalize(legacy) {
|
|
46
|
-
if (!legacy || typeof legacy !== 'object') return [];
|
|
47
|
-
if (!this.turnActive && ['text-delta', 'thinking-delta', 'tool', 'artifact', 'approval', 'interaction-responded', 'plan', 'file-change'].includes(legacy.kind)) return [];
|
|
48
|
-
this.currentNativeRef = legacy.nativeRef;
|
|
49
|
-
switch (legacy.kind) {
|
|
50
|
-
case 'text-delta':
|
|
51
|
-
return this.#
|
|
52
|
-
case 'thinking-delta':
|
|
53
|
-
return this.#delta('reasoning', 'currentReasoningItemId', legacy.text);
|
|
54
|
-
case 'tool':
|
|
55
|
-
return this.#tool(legacy);
|
|
56
|
-
case 'artifact':
|
|
57
|
-
return this.#artifact(legacy);
|
|
58
|
-
case 'approval':
|
|
59
|
-
if (this.interactions.has(legacy.requestId)) return [];
|
|
60
|
-
this.interactions.set(legacy.requestId, `interaction_${this.turnId}_${legacy.requestId}`);
|
|
61
|
-
return [this.#approval(legacy), this.#event({ type: 'turn.waiting' })];
|
|
62
|
-
case 'interaction-responded': {
|
|
63
|
-
const itemId = this.interactions.get(legacy.requestId);
|
|
64
|
-
if (!itemId) return [];
|
|
65
|
-
this.interactions.delete(legacy.requestId);
|
|
66
|
-
return [this.#event({ type: 'item.updated', itemId, payload: { interactionStatus: 'responded' } }),
|
|
67
|
-
this.#event({ type: 'item.completed', itemId }),
|
|
68
|
-
...(this.interactions.size ? [] : [this.#event({ type: 'turn.resumed' })])];
|
|
69
|
-
}
|
|
70
|
-
case 'compaction':
|
|
71
|
-
case 'context-compaction':
|
|
72
|
-
return this.#compaction(legacy);
|
|
73
|
-
case 'plan':
|
|
74
|
-
return [this.#event({ type: 'plan.updated', payload: { entries: legacy.entries ?? [] } })];
|
|
75
|
-
case 'file-change':
|
|
76
|
-
return [this.#event({ type: 'files.updated', payload: { source: legacy.source ?? 'native', changes: legacy.changes ?? [] } })];
|
|
77
|
-
case 'usage':
|
|
78
|
-
return [this.#event({ type: 'usage.updated', payload: { ...(legacy.usage ?? {}) } })];
|
|
79
|
-
case 'session':
|
|
80
|
-
return [this.#event({
|
|
81
|
-
type: 'thread.updated',
|
|
82
|
-
payload: {
|
|
83
|
-
...(legacy.nativeSessionId ? { nativeSessionRef: { sessionId: String(legacy.nativeSessionId) } } : {}),
|
|
84
|
-
...(legacy.model ? { metadata: { model: legacy.model } } : {}),
|
|
85
|
-
},
|
|
86
|
-
})];
|
|
87
|
-
case 'status':
|
|
88
|
-
case 'notice':
|
|
89
|
-
return this.#notice(legacy);
|
|
90
|
-
case 'completed':
|
|
91
|
-
// 结算闸门:Turn 已结算后迟到的重复结算事件不再产生 CoreEvent(如 cancel 后
|
|
92
|
-
// 原生流仍送达 agent_settled / result),防止影子侧出现非法状态迁移。
|
|
93
|
-
if (!this.turnActive) return [];
|
|
94
|
-
this.turnActive = false;
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
itemId
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
];
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
1
|
+
// Event Normalizer:把 Runtime 现有的统一事件(legacy kind)标准化为 CoreEvent(§12)。
|
|
2
|
+
// 第一阶段不重写任何 Adapter,只在公共入口处 normalize。
|
|
3
|
+
// 本模块对 Harness 名称零感知:输入已经是 Adapter 转换后的统一事件。
|
|
4
|
+
const { classifyError, codexErrorInfoKey } = require('./error-kind');
|
|
5
|
+
|
|
6
|
+
class EventNormalizer {
|
|
7
|
+
/**
|
|
8
|
+
* @param {{ threadId: string, source?: string }} context source 仅为溯源标签,不参与逻辑分支
|
|
9
|
+
*/
|
|
10
|
+
constructor({ threadId, source } = {}) {
|
|
11
|
+
this.threadId = threadId;
|
|
12
|
+
this.source = source ?? null;
|
|
13
|
+
this.turnId = null;
|
|
14
|
+
this.turnActive = false;
|
|
15
|
+
this.currentMessageItemId = null; // 当前 streaming 的 agent_message
|
|
16
|
+
this.currentReasoningItemId = null; // 当前 streaming 的 reasoning
|
|
17
|
+
this.segmentSequence = 0;
|
|
18
|
+
this.activeSlot = null;
|
|
19
|
+
this.interactions = new Map();
|
|
20
|
+
this.toolItems = new Map(); // toolCallId -> itemId
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Turn 开始:先产出 user_message(已完成),后续 delta 归入本 Turn */
|
|
24
|
+
beginTurn(turnId, userText, attachments = []) {
|
|
25
|
+
this.turnId = turnId;
|
|
26
|
+
this.currentNativeRef = undefined;
|
|
27
|
+
this.turnActive = true;
|
|
28
|
+
this.currentMessageItemId = null;
|
|
29
|
+
this.currentReasoningItemId = null;
|
|
30
|
+
this.toolItems.clear();
|
|
31
|
+
this.interactions.clear();
|
|
32
|
+
this.activeSlot = null;
|
|
33
|
+
this.segmentSequence = 0;
|
|
34
|
+
const itemId = `item_user_${turnId}`;
|
|
35
|
+
return [
|
|
36
|
+
this.#event({ type: 'item.started', itemId, payload: { type: 'user_message', content: userText, ...(attachments.length ? { attachments } : {}) } }),
|
|
37
|
+
this.#event({ type: 'item.completed', itemId }),
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* legacy 统一事件 → CoreEvent 序列(一个 legacy 事件可展开为多个 CoreEvent)
|
|
43
|
+
* @returns Array<Partial<CoreEvent>>
|
|
44
|
+
*/
|
|
45
|
+
normalize(legacy) {
|
|
46
|
+
if (!legacy || typeof legacy !== 'object') return [];
|
|
47
|
+
if (!this.turnActive && ['text-delta', 'thinking-delta', 'tool', 'artifact', 'approval', 'interaction-responded', 'plan', 'file-change'].includes(legacy.kind)) return [];
|
|
48
|
+
this.currentNativeRef = legacy.nativeRef;
|
|
49
|
+
switch (legacy.kind) {
|
|
50
|
+
case 'text-delta':
|
|
51
|
+
return this.#textDelta(legacy.text);
|
|
52
|
+
case 'thinking-delta':
|
|
53
|
+
return this.#delta('reasoning', 'currentReasoningItemId', legacy.text);
|
|
54
|
+
case 'tool':
|
|
55
|
+
return this.#tool(legacy);
|
|
56
|
+
case 'artifact':
|
|
57
|
+
return this.#artifact(legacy);
|
|
58
|
+
case 'approval':
|
|
59
|
+
if (this.interactions.has(legacy.requestId)) return [];
|
|
60
|
+
this.interactions.set(legacy.requestId, `interaction_${this.turnId}_${legacy.requestId}`);
|
|
61
|
+
return [this.#approval(legacy), this.#event({ type: 'turn.waiting' })];
|
|
62
|
+
case 'interaction-responded': {
|
|
63
|
+
const itemId = this.interactions.get(legacy.requestId);
|
|
64
|
+
if (!itemId) return [];
|
|
65
|
+
this.interactions.delete(legacy.requestId);
|
|
66
|
+
return [this.#event({ type: 'item.updated', itemId, payload: { interactionStatus: 'responded' } }),
|
|
67
|
+
this.#event({ type: 'item.completed', itemId }),
|
|
68
|
+
...(this.interactions.size ? [] : [this.#event({ type: 'turn.resumed' })])];
|
|
69
|
+
}
|
|
70
|
+
case 'compaction':
|
|
71
|
+
case 'context-compaction':
|
|
72
|
+
return this.#compaction(legacy);
|
|
73
|
+
case 'plan':
|
|
74
|
+
return [this.#event({ type: 'plan.updated', payload: { entries: legacy.entries ?? [] } })];
|
|
75
|
+
case 'file-change':
|
|
76
|
+
return [this.#event({ type: 'files.updated', payload: { source: legacy.source ?? 'native', changes: legacy.changes ?? [] } })];
|
|
77
|
+
case 'usage':
|
|
78
|
+
return [this.#event({ type: 'usage.updated', payload: { ...(legacy.usage ?? {}) } })];
|
|
79
|
+
case 'session':
|
|
80
|
+
return [this.#event({
|
|
81
|
+
type: 'thread.updated',
|
|
82
|
+
payload: {
|
|
83
|
+
...(legacy.nativeSessionId ? { nativeSessionRef: { sessionId: String(legacy.nativeSessionId) } } : {}),
|
|
84
|
+
...(legacy.model ? { metadata: { model: legacy.model } } : {}),
|
|
85
|
+
},
|
|
86
|
+
})];
|
|
87
|
+
case 'status':
|
|
88
|
+
case 'notice':
|
|
89
|
+
return this.#notice(legacy);
|
|
90
|
+
case 'completed':
|
|
91
|
+
// 结算闸门:Turn 已结算后迟到的重复结算事件不再产生 CoreEvent(如 cancel 后
|
|
92
|
+
// 原生流仍送达 agent_settled / result),防止影子侧出现非法状态迁移。
|
|
93
|
+
if (!this.turnActive) return [];
|
|
94
|
+
this.turnActive = false;
|
|
95
|
+
// 最后一段 agent_message 标记 phase:'final':Desktop 回合结算后把此前所有
|
|
96
|
+
// progress 段与工具项折叠进「用时」栏,仅保留该段作为唯一正式结论。
|
|
97
|
+
return [
|
|
98
|
+
...(!legacy.finalAnswer || legacy.stopReason && legacy.stopReason !== 'completed' || !this.currentMessageItemId ? [] : [this.#event({ type: 'item.updated', itemId: this.currentMessageItemId, payload: { phase: 'final' } })]),
|
|
99
|
+
this.#event({ type: 'turn.completed', payload: { stopReason: legacy.stopReason ?? 'completed' } }),
|
|
100
|
+
];
|
|
101
|
+
case 'error':
|
|
102
|
+
if (!this.turnActive) return [];
|
|
103
|
+
this.turnActive = false;
|
|
104
|
+
return [
|
|
105
|
+
this.#event({ type: 'turn.failed', payload: {
|
|
106
|
+
message: String(legacy.message ?? 'unknown error'),
|
|
107
|
+
errorKind: classifyError(legacy),
|
|
108
|
+
...(codexErrorInfoKey(legacy.codexErrorInfo) ? { codexErrorInfo: codexErrorInfoKey(legacy.codexErrorInfo) } : {}),
|
|
109
|
+
} }),
|
|
110
|
+
];
|
|
111
|
+
default:
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* ---------------- 内部 ---------------- */
|
|
117
|
+
|
|
118
|
+
#event({ type, itemId, payload, nativeRef }) {
|
|
119
|
+
return {
|
|
120
|
+
threadId: this.threadId,
|
|
121
|
+
turnId: this.turnId,
|
|
122
|
+
itemId: itemId ?? null,
|
|
123
|
+
source: this.source,
|
|
124
|
+
nativeRef: { ...this.currentNativeRef, ...nativeRef },
|
|
125
|
+
type,
|
|
126
|
+
payload: payload ?? {},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
#textDelta(text) {
|
|
131
|
+
// 正文 delta 一律即时流式发出:开场白与工具间解说段以 phase:'progress' 实时可见
|
|
132
|
+
// (回合结构 = progress 段与工具项交错),回合结算时仅最后一段升级为 'final',
|
|
133
|
+
// 由 Codex Desktop 原生把 progress 段随执行区折叠进「用时 XXm XXs」栏。
|
|
134
|
+
return this.#delta('agent_message', 'currentMessageItemId', text);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#delta(itemType, slot, text) {
|
|
138
|
+
if (typeof text !== 'string' || !text) return [];
|
|
139
|
+
const closed = this.activeSlot && this.activeSlot !== slot ? this.#closeSegment() : [];
|
|
140
|
+
if (!this[slot]) {
|
|
141
|
+
const itemId = this.#newSegment(slot, itemType);
|
|
142
|
+
return [
|
|
143
|
+
...closed,
|
|
144
|
+
this.#event({ type: 'item.started', itemId, payload: { type: itemType, ...(itemType === 'agent_message' ? { phase: 'progress' } : {}) } }),
|
|
145
|
+
this.#event({ type: 'item.delta', itemId, payload: { text } }),
|
|
146
|
+
];
|
|
147
|
+
}
|
|
148
|
+
return [this.#event({ type: 'item.delta', itemId: this[slot], payload: { text } })];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 同一 Turn 可能有多段 message/reasoning(与工具调用交替),用唯一后缀区分
|
|
152
|
+
#newSegment(slot, itemType) {
|
|
153
|
+
this.activeSlot = slot;
|
|
154
|
+
this[slot] = `item_${itemType}_${this.turnId}_${++this.segmentSequence}`;
|
|
155
|
+
return this[slot];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
#closeSegment() {
|
|
159
|
+
const itemId = this.activeSlot ? this[this.activeSlot] : null;
|
|
160
|
+
this.currentMessageItemId = null;
|
|
161
|
+
this.currentReasoningItemId = null;
|
|
162
|
+
this.activeSlot = null;
|
|
163
|
+
return itemId ? [this.#event({ type: 'item.completed', itemId })] : [];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
#tool(legacy) {
|
|
167
|
+
const key = legacy.toolCallId ?? `anon:${legacy.title ?? 'tool'}`;
|
|
168
|
+
let itemId = this.toolItems.get(key);
|
|
169
|
+
const events = [];
|
|
170
|
+
const state = legacy.state ?? 'running';
|
|
171
|
+
if (!itemId) {
|
|
172
|
+
itemId = `item_tool_${this.turnId}_${this.toolItems.size}`;
|
|
173
|
+
this.toolItems.set(key, itemId);
|
|
174
|
+
events.push(...this.#closeSegment());
|
|
175
|
+
// 新工具出现意味着上一段 message/reasoning 已结束,后续 delta 开新段
|
|
176
|
+
this.currentMessageItemId = null;
|
|
177
|
+
this.currentReasoningItemId = null;
|
|
178
|
+
events.push(this.#event({
|
|
179
|
+
type: 'item.started',
|
|
180
|
+
itemId,
|
|
181
|
+
nativeRef: legacy.toolCallId ? { toolCallId: String(legacy.toolCallId) } : undefined,
|
|
182
|
+
payload: { type: 'tool_call', title: legacy.title ?? '工具', state,
|
|
183
|
+
...(legacy.collaboration ? { collaboration: legacy.collaboration } : {}) },
|
|
184
|
+
}));
|
|
185
|
+
}
|
|
186
|
+
const patch = {};
|
|
187
|
+
if (legacy.collaboration) patch.collaboration = legacy.collaboration;
|
|
188
|
+
if (typeof legacy.path === 'string' && legacy.path) patch.path = legacy.path;
|
|
189
|
+
for (const k of ['detail', 'input', 'output']) {
|
|
190
|
+
if (typeof legacy[k] === 'string') patch[k] = legacy[k];
|
|
191
|
+
else if (legacy[k] != null) patch[k] = JSON.stringify(legacy[k]);
|
|
192
|
+
}
|
|
193
|
+
if (legacy.title) patch.title = legacy.title;
|
|
194
|
+
patch.state = state;
|
|
195
|
+
events.push(this.#event({ type: 'item.updated', itemId, payload: patch }));
|
|
196
|
+
if (state !== 'running') {
|
|
197
|
+
events.push(this.#event({ type: 'item.completed', itemId, payload: { state } }));
|
|
198
|
+
}
|
|
199
|
+
return events;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
#artifact(legacy) {
|
|
203
|
+
const a = legacy.artifact;
|
|
204
|
+
if (!a || typeof a !== 'object') return [];
|
|
205
|
+
// 产物挂在当前 agent_message 上;若尚无消息段则先建段
|
|
206
|
+
if (!this.currentMessageItemId) {
|
|
207
|
+
const itemId = this.#newSegment('currentMessageItemId', 'agent_message');
|
|
208
|
+
return [
|
|
209
|
+
this.#event({ type: 'item.started', itemId, payload: { type: 'agent_message' } }),
|
|
210
|
+
this.#event({
|
|
211
|
+
type: 'item.updated',
|
|
212
|
+
itemId,
|
|
213
|
+
payload: { artifacts: [{ id: a.id, type: a.type, name: a.name, mime: a.mime, uri: a.uri, data: a.data }] },
|
|
214
|
+
}),
|
|
215
|
+
];
|
|
216
|
+
}
|
|
217
|
+
return [this.#event({
|
|
218
|
+
type: 'item.updated',
|
|
219
|
+
itemId: this.currentMessageItemId,
|
|
220
|
+
payload: { artifacts: [{ id: a.id, type: a.type, name: a.name, mime: a.mime, uri: a.uri, data: a.data }] },
|
|
221
|
+
})];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
#approval(legacy) {
|
|
225
|
+
const itemType = ['input', 'editor'].includes(legacy.method) ? 'question' : 'approval';
|
|
226
|
+
return this.#event({
|
|
227
|
+
type: 'item.started',
|
|
228
|
+
itemId: this.interactions.get(legacy.requestId),
|
|
229
|
+
nativeRef: legacy.requestId ? { interactionId: String(legacy.nativeRef?.interactionId ?? legacy.requestId) } : undefined,
|
|
230
|
+
payload: {
|
|
231
|
+
type: itemType,
|
|
232
|
+
requestId: legacy.requestId,
|
|
233
|
+
method: legacy.method,
|
|
234
|
+
title: legacy.title,
|
|
235
|
+
message: legacy.message,
|
|
236
|
+
options: legacy.options,
|
|
237
|
+
placeholder: legacy.placeholder,
|
|
238
|
+
interactionStatus: 'pending',
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
#notice(legacy) {
|
|
244
|
+
const text = legacy.text;
|
|
245
|
+
if (typeof text !== 'string' || !text) return [];
|
|
246
|
+
const itemId = `item_notice_${this.turnId ?? this.threadId}_${++this.segmentSequence}`;
|
|
247
|
+
return [
|
|
248
|
+
this.#event({ type: 'item.started', itemId, payload: { type: 'notice', level: legacy.level ?? 'info', content: text } }),
|
|
249
|
+
this.#event({ type: 'item.completed', itemId }),
|
|
250
|
+
];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
#compaction(legacy) {
|
|
254
|
+
const closed = this.activeSlot ? this.#closeSegment() : [];
|
|
255
|
+
const itemId = `item_compaction_${this.turnId ?? this.threadId}_${++this.segmentSequence}`;
|
|
256
|
+
const payload = {
|
|
257
|
+
type: 'context_compaction',
|
|
258
|
+
...(legacy.tokensBefore != null ? { tokensBefore: legacy.tokensBefore } : {}),
|
|
259
|
+
...(legacy.tokensAfter != null ? { tokensAfter: legacy.tokensAfter } : {}),
|
|
260
|
+
...(legacy.summary ? { summary: legacy.summary } : {}),
|
|
261
|
+
};
|
|
262
|
+
return [
|
|
263
|
+
...closed,
|
|
264
|
+
this.#event({ type: 'item.started', itemId, payload }),
|
|
265
|
+
this.#event({ type: 'item.completed', itemId, payload }),
|
|
266
|
+
];
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
module.exports = { EventNormalizer };
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
const readline = require('node:readline');
|
|
2
2
|
const { tools } = require('./collaboration-tools');
|
|
3
3
|
|
|
4
|
+
// Host 通过 HARNESS_MIX_COLLAB_TEAM=0 表达「Agent Team 已在设置中停用」:
|
|
5
|
+
// 此时对模型隐藏团队工具,只保留一次性委派类工具。
|
|
6
|
+
const TEAM_TOOLS = new Set(['create_agent_team', 'assign_team_task', 'get_team_state', 'update_team_task', 'send_team_message']);
|
|
7
|
+
const visibleTools = process.env.HARNESS_MIX_COLLAB_TEAM === '0'
|
|
8
|
+
? tools.filter((tool) => !TEAM_TOOLS.has(tool.name))
|
|
9
|
+
: tools;
|
|
10
|
+
|
|
4
11
|
async function call(name, args) {
|
|
5
12
|
const response = await fetch(process.env.HARNESS_MIX_COLLAB_URL, {
|
|
6
13
|
method: 'POST', headers: { Authorization: `Bearer ${process.env.HARNESS_MIX_COLLAB_KEY}`, 'Content-Type': 'application/json' },
|
|
@@ -14,7 +21,7 @@ async function call(name, args) {
|
|
|
14
21
|
async function dispatch(message) {
|
|
15
22
|
if (message.method === 'initialize') return { protocolVersion: '2024-11-05', capabilities: { tools: {} }, serverInfo: { name: 'harness-mix', version: '0.1.0' } };
|
|
16
23
|
if (message.method === 'ping') return {};
|
|
17
|
-
if (message.method === 'tools/list') return { tools };
|
|
24
|
+
if (message.method === 'tools/list') return { tools: visibleTools };
|
|
18
25
|
if (message.method === 'tools/call') {
|
|
19
26
|
try { return { content: [{ type: 'text', text: JSON.stringify(await call(message.params.name, message.params.arguments ?? {})) }] }; }
|
|
20
27
|
catch (error) { return { isError: true, content: [{ type: 'text', text: error.message }] }; }
|
|
@@ -5,7 +5,7 @@ const teamId = { type: 'string', minLength: 1 };
|
|
|
5
5
|
const memberId = { type: 'string', minLength: 1 };
|
|
6
6
|
const tools = [
|
|
7
7
|
{ name: 'list_agents', description: 'List native Harnesses available for delegation and their IDs.', inputSchema: object({}) },
|
|
8
|
-
{ name: 'delegate_to_agent', description: 'Start an independent native Harness session asynchronously. For Agent Team work, pass team_id, member_id and team_task_id so the native session becomes that durable teammate and can use the shared task graph and mailbox. An @Agent mention explicitly assigns that agent its associated work. Include all necessary context. By default workers share the lead working directory, so later reviewers see earlier edits. Wait for development before starting dependent review; do not edit overlapping files concurrently. Use isolation=worktree for independent isolated experiments (auto also isolates Git projects). Returns task_id; collect results before finishing.', inputSchema: object({ agent_type: taskId, task, isolation: { type: 'string', enum: ['auto', 'worktree', 'shared'] }, team_id: teamId, member_id: memberId, team_task_id: taskId }, ['agent_type', 'task']) },
|
|
8
|
+
{ name: 'delegate_to_agent', description: 'Start an independent native Harness session asynchronously. For Agent Team work, pass team_id, member_id and team_task_id so the native session becomes that durable teammate and can use the shared task graph and mailbox. An @Agent mention explicitly assigns that agent its associated work. Include all necessary context. By default workers share the lead working directory, so later reviewers see earlier edits; whenever another session is actively running in that directory, new workers start isolated automatically instead. Wait for development before starting dependent review; do not edit overlapping files concurrently. Use isolation=worktree for independent isolated experiments (auto also isolates Git projects). Returns task_id; collect results before finishing.', inputSchema: object({ agent_type: taskId, task, isolation: { type: 'string', enum: ['auto', 'worktree', 'shared'] }, team_id: teamId, member_id: memberId, team_task_id: taskId }, ['agent_type', 'task']) },
|
|
9
9
|
{ name: 'create_agent_team', description: 'Create a durable multi-Harness Agent Team with one lead and up to six named members with explicit roles. This creates shared team state, not worker sessions; assign tasks and delegate them to members next.', inputSchema: object({ name: { type: 'string', minLength: 1, maxLength: 120 }, goal: task, members: { type: 'array', minItems: 1, maxItems: 6, items: object({ name: { type: 'string', minLength: 1, maxLength: 80 }, role: { type: 'string', minLength: 1, maxLength: 240 }, agent_type: taskId }, ['name', 'role', 'agent_type']) } }, ['name', 'goal', 'members']) },
|
|
10
10
|
{ name: 'assign_team_task', description: 'Add a task to the shared team task graph. Dependencies must already exist. The assignee is a team member id or unique member name.', inputSchema: object({ team_id: teamId, title: { type: 'string', minLength: 1, maxLength: 160 }, description: task, assignee: memberId, depends_on: { type: 'array', maxItems: 16, items: taskId } }, ['team_id', 'title', 'description', 'assignee']) },
|
|
11
11
|
{ name: 'get_team_state', description: 'Read the current team roster, shared task graph and recent mailbox messages. Available to the lead and native teammate sessions. The projection includes a coarse phase (forming/running/waiting/completed), per-status progress counters, and each task\'s dependency depth so parallel lanes and pipeline stages are visible without walking depends_on by hand.', inputSchema: object({ team_id: teamId }, ['team_id']) },
|