workspacecord 1.1.2 → 1.1.4
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/dist/{archive-manager-XM3UPOY2.js → archive-manager-FJU7YEEH.js} +4 -4
- package/dist/{attachment-cli-IGQBB6YJ.js → attachment-cli-AT4HXAGU.js} +2 -2
- package/dist/{chunk-54DP53ZK.js → chunk-5EMN2IL5.js} +29 -3
- package/dist/{chunk-L2ZJXV6H.js → chunk-66B64WL3.js} +1 -1
- package/dist/{chunk-QWPKAUSV.js → chunk-C4L34VJK.js} +8287 -7421
- package/dist/chunk-JR4B4L7I.js +5301 -0
- package/dist/{chunk-UEX7U2KW.js → chunk-MJ5JKFGS.js} +911 -232
- package/dist/{chunk-ZAQV2RZS.js → chunk-NNTMVOTM.js} +1054 -1310
- package/dist/{chunk-COXPTYH5.js → chunk-PWMEOBXG.js} +4 -4
- package/dist/{chunk-I6EOCCQV.js → chunk-SNPFYUQ3.js} +713 -628
- package/dist/{chunk-GMYN4SYT.js → chunk-SV7EHL3B.js} +3 -3
- package/dist/cli-framework-YF3LPLMT.js +18 -0
- package/dist/cli.js +10 -10
- package/dist/{codex-launcher-VDQ5VZPT.js → codex-launcher-CLGG4CVY.js} +1 -1
- package/dist/{codex-provider-NYI7KBGO.js → codex-provider-VLOS5QB6.js} +18 -4
- package/dist/{config-cli-RQR2ZRQ5.js → config-cli-7G5YWKJU.js} +2 -2
- package/dist/{panel-adapter-QTDL3S6O.js → panel-adapter-CLI4WDII.js} +4 -4
- package/dist/{project-cli-6P6ZWDR6.js → project-cli-ALKDLRMZ.js} +2 -2
- package/dist/{project-registry-OEVPECMS.js → project-registry-ZO3KSS25.js} +2 -2
- package/dist/sdk-C3D6X4EB.js +55 -0
- package/dist/{session-local-registration-RIO5EPZ5.js → session-local-registration-RL2A3QZB.js} +3 -3
- package/dist/{setup-KOS7SRSL.js → setup-GP3MML2R.js} +1 -1
- package/package.json +3 -3
- package/dist/chunk-RK6EIZOL.js +0 -589
- package/dist/cli-framework-SQM2465A.js +0 -18
- package/dist/sdk-V7A7IF7F.js +0 -43
|
@@ -1,26 +1,40 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
cleanupSessionAttachments
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-66B64WL3.js";
|
|
5
5
|
import {
|
|
6
|
+
STATE_COLORS,
|
|
7
|
+
STATE_LABELS,
|
|
8
|
+
createSession,
|
|
9
|
+
debouncedSaveSession,
|
|
10
|
+
endSession,
|
|
11
|
+
gateService,
|
|
6
12
|
getAllSessions,
|
|
7
13
|
getSession,
|
|
14
|
+
getSessionController,
|
|
8
15
|
getSessionPermissionSummary,
|
|
16
|
+
mapPlatformEventToState,
|
|
9
17
|
setCurrentInteractionMessage,
|
|
10
18
|
setStatusCardBinding,
|
|
19
|
+
stateMachine,
|
|
20
|
+
toPlatformEvent,
|
|
11
21
|
updateSession
|
|
12
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-JR4B4L7I.js";
|
|
13
23
|
import {
|
|
14
|
-
Store,
|
|
15
24
|
config,
|
|
16
25
|
truncate
|
|
17
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-MJ5JKFGS.js";
|
|
18
27
|
|
|
19
28
|
// ../bot/src/discord/status-card-projection-renderer.ts
|
|
20
29
|
var StatusCardProjectionRenderer = class {
|
|
21
30
|
#pending = /* @__PURE__ */ new Map();
|
|
22
31
|
async renderNow(sessionId, projection, context) {
|
|
23
32
|
if (!context) return;
|
|
33
|
+
if (process.env.E2E_DEBUG_RENDER === "1") {
|
|
34
|
+
console.log(
|
|
35
|
+
`[renderer] ${sessionId} todoList=${projection.todoList?.length ?? 0} denials=${projection.recentPermissionDenials?.length ?? 0} batch=${projection.batchApprovalMode}`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
24
38
|
try {
|
|
25
39
|
await context.statusCard.update(projection.state, {
|
|
26
40
|
turn: projection.turn,
|
|
@@ -30,7 +44,11 @@ var StatusCardProjectionRenderer = class {
|
|
|
30
44
|
provider: context.provider,
|
|
31
45
|
permissionsSummary: context.permissionsSummary,
|
|
32
46
|
verbose: context.verbose,
|
|
33
|
-
monitorGoal: context.monitorGoal
|
|
47
|
+
monitorGoal: context.monitorGoal,
|
|
48
|
+
todoList: projection.todoList,
|
|
49
|
+
recentPermissionDenials: projection.recentPermissionDenials,
|
|
50
|
+
batchApprovalMode: projection.batchApprovalMode,
|
|
51
|
+
pendingApprovals: projection.pendingApprovals
|
|
34
52
|
});
|
|
35
53
|
} catch (error) {
|
|
36
54
|
console.error(`\u72B6\u6001\u5361\u66F4\u65B0\u5931\u8D25 (${sessionId}):`, error);
|
|
@@ -66,1005 +84,788 @@ var StatusCardProjectionRenderer = class {
|
|
|
66
84
|
}
|
|
67
85
|
};
|
|
68
86
|
|
|
69
|
-
// ../bot/src/
|
|
70
|
-
import {
|
|
71
|
-
EmbedBuilder
|
|
72
|
-
} from "discord.js";
|
|
73
|
-
|
|
74
|
-
// ../state/src/types.ts
|
|
75
|
-
var STATE_PRIORITY = {
|
|
76
|
-
error: 9,
|
|
77
|
-
awaiting_human: 8,
|
|
78
|
-
stalled: 7,
|
|
79
|
-
summarizing: 6,
|
|
80
|
-
working: 5,
|
|
81
|
-
thinking: 4,
|
|
82
|
-
completed: 3,
|
|
83
|
-
idle: 2,
|
|
84
|
-
offline: 1
|
|
85
|
-
};
|
|
86
|
-
var STATE_LABELS = {
|
|
87
|
-
idle: "\u5F85\u547D",
|
|
88
|
-
thinking: "\u6B63\u5728\u601D\u8003",
|
|
89
|
-
working: "\u6B63\u5728\u6267\u884C",
|
|
90
|
-
awaiting_human: "\u7B49\u5F85\u4EBA\u5DE5\u5904\u7406",
|
|
91
|
-
summarizing: "\u6B63\u5728\u6574\u7406\u4E0A\u4E0B\u6587",
|
|
92
|
-
completed: "\u672C\u8F6E\u5DF2\u5B8C\u6210",
|
|
93
|
-
error: "\u51FA\u73B0\u5F02\u5E38",
|
|
94
|
-
stalled: "\u7591\u4F3C\u5361\u4F4F",
|
|
95
|
-
offline: "\u5DF2\u79BB\u7EBF"
|
|
96
|
-
};
|
|
97
|
-
var STATE_COLORS = {
|
|
98
|
-
idle: 8421504,
|
|
99
|
-
// 灰色
|
|
100
|
-
thinking: 3447003,
|
|
101
|
-
// 蓝色
|
|
102
|
-
working: 3066993,
|
|
103
|
-
// 绿色
|
|
104
|
-
awaiting_human: 15965202,
|
|
105
|
-
// 橙色
|
|
106
|
-
summarizing: 10181046,
|
|
107
|
-
// 紫色
|
|
108
|
-
completed: 2600544,
|
|
109
|
-
// 深绿
|
|
110
|
-
error: 15158332,
|
|
111
|
-
// 红色
|
|
112
|
-
stalled: 15105570,
|
|
113
|
-
// 深橙
|
|
114
|
-
offline: 9807270
|
|
115
|
-
// 浅灰
|
|
116
|
-
};
|
|
117
|
-
var PLATFORM_EVENT_TO_STATE = {
|
|
118
|
-
session_started: "idle",
|
|
119
|
-
session_idle: "idle",
|
|
120
|
-
thinking_started: "thinking",
|
|
121
|
-
work_started: "working",
|
|
122
|
-
awaiting_human: "awaiting_human",
|
|
123
|
-
human_resolved: "working",
|
|
124
|
-
compaction_started: "summarizing",
|
|
125
|
-
completed: "completed",
|
|
126
|
-
errored: "error",
|
|
127
|
-
stalled: "stalled",
|
|
128
|
-
session_ended: "offline"
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
// ../state/src/state-projections.ts
|
|
132
|
-
function toProjection(state) {
|
|
133
|
-
return {
|
|
134
|
-
state: state.displayState,
|
|
135
|
-
stateSource: state.stateSource,
|
|
136
|
-
confidence: state.confidence,
|
|
137
|
-
updatedAt: state.updatedAt,
|
|
138
|
-
turn: state.turn,
|
|
139
|
-
phase: state.phase,
|
|
140
|
-
humanResolved: state.humanResolved
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
function toPanelProjection(state) {
|
|
144
|
-
return {
|
|
145
|
-
...toProjection(state),
|
|
146
|
-
isWaitingHuman: state.lifecycle === "waiting_human" || state.gate === "pending" || state.displayState === "awaiting_human",
|
|
147
|
-
isCompleted: state.displayState === "completed" || state.lifecycle === "completed",
|
|
148
|
-
isError: state.displayState === "error" || state.lifecycle === "error",
|
|
149
|
-
isStalled: state.displayState === "stalled"
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
function resolveDisplayState(sessions) {
|
|
153
|
-
let best = "idle";
|
|
154
|
-
let bestPri = 0;
|
|
155
|
-
for (const state of sessions) {
|
|
156
|
-
const pri = STATE_PRIORITY[state.displayState] || 0;
|
|
157
|
-
if (pri > bestPri) {
|
|
158
|
-
best = state.displayState;
|
|
159
|
-
bestPri = pri;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return best;
|
|
163
|
-
}
|
|
164
|
-
function getStateLabel(state) {
|
|
165
|
-
return STATE_LABELS[state] || state;
|
|
166
|
-
}
|
|
167
|
-
function getStateColor(state) {
|
|
168
|
-
return STATE_COLORS[state] || 8421504;
|
|
169
|
-
}
|
|
87
|
+
// ../bot/src/output/event-handlers.ts
|
|
88
|
+
import { existsSync as existsSync2 } from "fs";
|
|
170
89
|
|
|
171
|
-
// ../
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
);
|
|
184
|
-
if (!allowTransition) {
|
|
185
|
-
return null;
|
|
186
|
-
}
|
|
187
|
-
const lifecycle = mapLifecycle(event, currentState, mappedState);
|
|
188
|
-
const execution = mapExecution(event, currentState, mappedState, lifecycle);
|
|
189
|
-
const gate = mapGate(event, currentState);
|
|
190
|
-
const turn = resolveTurn(event, currentState);
|
|
191
|
-
const humanResolved = resolveHumanResolved(event, currentState);
|
|
192
|
-
const phaseLabel = event.metadata?.phase ?? getStateLabel(mappedState);
|
|
193
|
-
return {
|
|
194
|
-
updates: {
|
|
195
|
-
lifecycle,
|
|
196
|
-
execution,
|
|
197
|
-
gate
|
|
198
|
-
},
|
|
199
|
-
metadata: {
|
|
200
|
-
displayState: mappedState,
|
|
201
|
-
stateSource: event.stateSource ?? "formal",
|
|
202
|
-
confidence: event.confidence,
|
|
203
|
-
updatedAt: event.timestamp,
|
|
204
|
-
turn,
|
|
205
|
-
phase: phaseLabel,
|
|
206
|
-
humanResolved
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
function mapLifecycle(event, current, mappedState) {
|
|
211
|
-
switch (event.type) {
|
|
212
|
-
case "session_started":
|
|
213
|
-
case "session_idle":
|
|
214
|
-
case "thinking_started":
|
|
215
|
-
case "work_started":
|
|
216
|
-
case "human_resolved":
|
|
217
|
-
case "compaction_started":
|
|
218
|
-
return "active";
|
|
219
|
-
case "awaiting_human":
|
|
220
|
-
return "waiting_human";
|
|
221
|
-
case "completed":
|
|
222
|
-
return "completed";
|
|
223
|
-
case "errored":
|
|
224
|
-
return "error";
|
|
225
|
-
case "stalled":
|
|
226
|
-
return current.lifecycle === "active" ? "paused" : current.lifecycle;
|
|
227
|
-
case "session_ended":
|
|
228
|
-
return current.lifecycle === "initializing" ? "initializing" : "paused";
|
|
229
|
-
default:
|
|
230
|
-
return mapLifecycleFromState(mappedState, current.lifecycle);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
function mapLifecycleFromState(mappedState, fallback) {
|
|
234
|
-
switch (mappedState) {
|
|
235
|
-
case "idle":
|
|
236
|
-
case "thinking":
|
|
237
|
-
case "working":
|
|
238
|
-
case "summarizing":
|
|
239
|
-
return "active";
|
|
240
|
-
case "awaiting_human":
|
|
241
|
-
return "waiting_human";
|
|
242
|
-
case "completed":
|
|
243
|
-
return "completed";
|
|
244
|
-
case "error":
|
|
245
|
-
return "error";
|
|
246
|
-
case "stalled":
|
|
247
|
-
return "paused";
|
|
248
|
-
case "offline":
|
|
249
|
-
return fallback === "initializing" ? "initializing" : "paused";
|
|
250
|
-
default:
|
|
251
|
-
return fallback;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
function mapExecution(event, current, mappedState, lifecycle) {
|
|
255
|
-
if (lifecycle !== "active") return null;
|
|
256
|
-
switch (event.type) {
|
|
257
|
-
case "session_started":
|
|
258
|
-
case "session_idle":
|
|
259
|
-
return "idle";
|
|
260
|
-
case "thinking_started":
|
|
261
|
-
return "thinking";
|
|
262
|
-
case "work_started":
|
|
263
|
-
return "tool_executing";
|
|
264
|
-
case "human_resolved":
|
|
265
|
-
return "tool_executing";
|
|
266
|
-
case "compaction_started":
|
|
267
|
-
return "thinking";
|
|
268
|
-
default:
|
|
269
|
-
return mapExecutionFromState(mappedState, current.execution);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
function mapExecutionFromState(mappedState, fallback) {
|
|
273
|
-
switch (mappedState) {
|
|
274
|
-
case "idle":
|
|
275
|
-
return "idle";
|
|
276
|
-
case "thinking":
|
|
277
|
-
case "summarizing":
|
|
278
|
-
return "thinking";
|
|
279
|
-
case "working":
|
|
280
|
-
return "tool_executing";
|
|
281
|
-
default:
|
|
282
|
-
return fallback;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
function mapGate(event, current) {
|
|
286
|
-
switch (event.type) {
|
|
287
|
-
case "awaiting_human":
|
|
288
|
-
return "pending";
|
|
289
|
-
case "human_resolved":
|
|
290
|
-
return event.metadata?.action === "reject" ? "rejected" : "approved";
|
|
291
|
-
case "session_idle":
|
|
292
|
-
if (event.metadata?.action === "reject" && current.gate === "pending") {
|
|
293
|
-
return "rejected";
|
|
294
|
-
}
|
|
295
|
-
return current.gate;
|
|
296
|
-
case "session_ended":
|
|
297
|
-
return current.gate === "pending" ? "invalidated" : current.gate;
|
|
298
|
-
default:
|
|
299
|
-
return current.gate;
|
|
90
|
+
// ../engine/src/session-context.ts
|
|
91
|
+
var EMPTY_PROJECTION = Object.freeze({
|
|
92
|
+
turn: 0,
|
|
93
|
+
humanResolved: false,
|
|
94
|
+
updatedAt: 0
|
|
95
|
+
});
|
|
96
|
+
function safeGetSessionController(sessionId) {
|
|
97
|
+
try {
|
|
98
|
+
const fn = getSessionController;
|
|
99
|
+
return typeof fn === "function" ? fn(sessionId) : void 0;
|
|
100
|
+
} catch {
|
|
101
|
+
return void 0;
|
|
300
102
|
}
|
|
301
103
|
}
|
|
302
|
-
function
|
|
303
|
-
|
|
304
|
-
|
|
104
|
+
function safeDebouncedSaveSession() {
|
|
105
|
+
try {
|
|
106
|
+
const fn = debouncedSaveSession;
|
|
107
|
+
if (typeof fn === "function") fn();
|
|
108
|
+
} catch {
|
|
305
109
|
}
|
|
306
|
-
return current.turn;
|
|
307
110
|
}
|
|
308
|
-
function
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// ../state/src/state-machine.ts
|
|
316
|
-
var LIFECYCLE_TRANSITIONS = {
|
|
317
|
-
initializing: ["active", "waiting_human", "paused", "completed", "error"],
|
|
318
|
-
active: ["waiting_human", "paused", "completed", "error"],
|
|
319
|
-
waiting_human: ["active", "paused", "error"],
|
|
320
|
-
paused: ["active", "completed", "error"],
|
|
321
|
-
completed: ["active"],
|
|
322
|
-
error: ["active", "completed"]
|
|
323
|
-
};
|
|
324
|
-
var EXECUTION_TRANSITIONS = {
|
|
325
|
-
idle: ["thinking", "tool_executing"],
|
|
326
|
-
thinking: ["tool_executing", "streaming_output", "idle"],
|
|
327
|
-
tool_executing: ["thinking", "streaming_output", "idle"],
|
|
328
|
-
streaming_output: ["idle", "thinking"]
|
|
329
|
-
};
|
|
330
|
-
var StateMachine = class {
|
|
331
|
-
sessions = /* @__PURE__ */ new Map();
|
|
332
|
-
transitionHistory = /* @__PURE__ */ new Map();
|
|
333
|
-
completedTimers = /* @__PURE__ */ new Map();
|
|
334
|
-
completedTimerTokens = /* @__PURE__ */ new Map();
|
|
335
|
-
completedTimerSequence = 0;
|
|
336
|
-
getState(sessionId) {
|
|
337
|
-
const existing = this.sessions.get(sessionId);
|
|
338
|
-
if (existing) return existing;
|
|
339
|
-
const defaultState = this.createDefaultState();
|
|
340
|
-
this.sessions.set(sessionId, defaultState);
|
|
341
|
-
return defaultState;
|
|
342
|
-
}
|
|
343
|
-
getSnapshot(sessionId) {
|
|
344
|
-
return toProjection(this.getState(sessionId));
|
|
345
|
-
}
|
|
346
|
-
getPanelProjection(sessionId) {
|
|
347
|
-
return toPanelProjection(this.getState(sessionId));
|
|
348
|
-
}
|
|
349
|
-
transition(sessionId, event, updates, metadata = {}) {
|
|
350
|
-
const current = this.getState(sessionId);
|
|
351
|
-
const timestamp = metadata.updatedAt ?? Date.now();
|
|
352
|
-
const target = {
|
|
353
|
-
...current,
|
|
354
|
-
lifecycle: updates.lifecycle ?? current.lifecycle,
|
|
355
|
-
execution: updates.execution !== void 0 ? updates.execution : current.execution,
|
|
356
|
-
gate: updates.gate !== void 0 ? updates.gate : current.gate,
|
|
357
|
-
displayState: metadata.displayState ?? current.displayState,
|
|
358
|
-
stateSource: metadata.stateSource ?? current.stateSource,
|
|
359
|
-
confidence: metadata.confidence ?? current.confidence,
|
|
360
|
-
updatedAt: timestamp,
|
|
361
|
-
turn: metadata.turn ?? current.turn,
|
|
362
|
-
phase: metadata.phase ?? current.phase,
|
|
363
|
-
humanResolved: metadata.humanResolved ?? current.humanResolved
|
|
364
|
-
};
|
|
365
|
-
if (updates.lifecycle && updates.lifecycle !== current.lifecycle) {
|
|
366
|
-
const allowed = LIFECYCLE_TRANSITIONS[current.lifecycle];
|
|
367
|
-
if (!allowed.includes(updates.lifecycle)) {
|
|
368
|
-
return {
|
|
369
|
-
success: false,
|
|
370
|
-
state: current,
|
|
371
|
-
error: `\u975E\u6CD5\u751F\u547D\u5468\u671F\u8F6C\u6362: ${current.lifecycle} -> ${updates.lifecycle}`
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
if (updates.execution !== void 0 && updates.execution !== current.execution) {
|
|
376
|
-
if (target.lifecycle !== "active" && updates.execution !== null) {
|
|
377
|
-
return {
|
|
378
|
-
success: false,
|
|
379
|
-
state: current,
|
|
380
|
-
error: `\u6267\u884C\u72B6\u6001\u4EC5\u5728 lifecycle=active \u65F6\u6709\u6548\uFF0C\u5F53\u524D lifecycle=${target.lifecycle}`
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
if (current.execution && updates.execution) {
|
|
384
|
-
const allowed = EXECUTION_TRANSITIONS[current.execution];
|
|
385
|
-
if (!allowed.includes(updates.execution)) {
|
|
386
|
-
return {
|
|
387
|
-
success: false,
|
|
388
|
-
state: current,
|
|
389
|
-
error: `\u975E\u6CD5\u6267\u884C\u72B6\u6001\u8F6C\u6362: ${current.execution} -> ${updates.execution}`
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
if (target.lifecycle !== "active" && target.execution !== null) {
|
|
395
|
-
target.execution = null;
|
|
396
|
-
}
|
|
397
|
-
if (this.isSameState(current, target)) {
|
|
398
|
-
return { success: true, state: current };
|
|
111
|
+
function safeGetProjection(sessionId) {
|
|
112
|
+
try {
|
|
113
|
+
const sm = stateMachine;
|
|
114
|
+
if (sm && typeof sm.getSnapshot === "function") {
|
|
115
|
+
return sm.getSnapshot(sessionId);
|
|
399
116
|
}
|
|
400
|
-
|
|
401
|
-
const transition = {
|
|
402
|
-
from: { ...current },
|
|
403
|
-
to: { ...target },
|
|
404
|
-
event,
|
|
405
|
-
timestamp,
|
|
406
|
-
sessionId
|
|
407
|
-
};
|
|
408
|
-
const history = this.transitionHistory.get(sessionId) || [];
|
|
409
|
-
history.push(transition);
|
|
410
|
-
if (history.length > 100) {
|
|
411
|
-
history.shift();
|
|
412
|
-
}
|
|
413
|
-
this.transitionHistory.set(sessionId, history);
|
|
414
|
-
console.log(
|
|
415
|
-
`[state-machine] ${sessionId} | ${event} | lifecycle: ${current.lifecycle} -> ${target.lifecycle} | execution: ${current.execution} -> ${target.execution} | gate: ${current.gate} -> ${target.gate} | display: ${current.displayState} -> ${target.displayState}`
|
|
416
|
-
);
|
|
417
|
-
return { success: true, state: target };
|
|
418
|
-
}
|
|
419
|
-
getTransitionHistory(sessionId) {
|
|
420
|
-
return this.transitionHistory.get(sessionId) || [];
|
|
117
|
+
} catch {
|
|
421
118
|
}
|
|
422
|
-
|
|
423
|
-
|
|
119
|
+
return EMPTY_PROJECTION;
|
|
120
|
+
}
|
|
121
|
+
var SessionSupervisor = class {
|
|
122
|
+
contexts = /* @__PURE__ */ new Map();
|
|
123
|
+
get(sessionId) {
|
|
124
|
+
const live = getSession(sessionId);
|
|
125
|
+
const cached = this.contexts.get(sessionId);
|
|
126
|
+
if (!live) {
|
|
127
|
+
if (cached) this.contexts.delete(sessionId);
|
|
128
|
+
return void 0;
|
|
129
|
+
}
|
|
130
|
+
if (cached) {
|
|
131
|
+
this.syncContext(cached);
|
|
132
|
+
return cached;
|
|
133
|
+
}
|
|
134
|
+
const ctx = this.build(live);
|
|
135
|
+
this.contexts.set(sessionId, ctx);
|
|
136
|
+
return ctx;
|
|
137
|
+
}
|
|
138
|
+
/** 枚举现有 context。用于 supervisor 层面的扫描(健康检查、空闲回收)。 */
|
|
139
|
+
all() {
|
|
140
|
+
return Array.from(this.contexts.values());
|
|
141
|
+
}
|
|
142
|
+
/** 释放某个 session 的 context(endSession 调用时触发)。 */
|
|
143
|
+
release(sessionId) {
|
|
144
|
+
this.contexts.delete(sessionId);
|
|
145
|
+
}
|
|
146
|
+
releaseAll() {
|
|
147
|
+
this.contexts.clear();
|
|
148
|
+
}
|
|
149
|
+
build(session) {
|
|
150
|
+
const ctx = {
|
|
151
|
+
sessionId: session.id,
|
|
152
|
+
session,
|
|
153
|
+
controller: safeGetSessionController(session.id),
|
|
154
|
+
projection: safeGetProjection(session.id),
|
|
155
|
+
save: () => safeDebouncedSaveSession(),
|
|
156
|
+
refresh: () => this.syncContext(ctx)
|
|
157
|
+
};
|
|
158
|
+
return ctx;
|
|
424
159
|
}
|
|
425
|
-
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
return toPri > fromPri;
|
|
160
|
+
syncContext(ctx) {
|
|
161
|
+
const live = getSession(ctx.sessionId);
|
|
162
|
+
if (live) {
|
|
163
|
+
ctx.session = live;
|
|
430
164
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
getStateLabel(state) {
|
|
434
|
-
return getStateLabel(state);
|
|
435
|
-
}
|
|
436
|
-
getStateColor(state) {
|
|
437
|
-
return getStateColor(state);
|
|
438
|
-
}
|
|
439
|
-
setTurn(sessionId, turn, event = "turn_set") {
|
|
440
|
-
const result = this.transition(sessionId, event, {}, { turn, humanResolved: false });
|
|
441
|
-
return toProjection(result.state);
|
|
442
|
-
}
|
|
443
|
-
incrementTurn(sessionId) {
|
|
444
|
-
const current = this.getState(sessionId);
|
|
445
|
-
const result = this.transition(sessionId, "turn_incremented", {}, {
|
|
446
|
-
turn: current.turn + 1,
|
|
447
|
-
humanResolved: false
|
|
448
|
-
});
|
|
449
|
-
return toProjection(result.state);
|
|
450
|
-
}
|
|
451
|
-
advanceTurnToIdle(sessionId) {
|
|
452
|
-
const current = this.getState(sessionId);
|
|
453
|
-
const incremented = this.transition(sessionId, "turn_incremented", {}, {
|
|
454
|
-
turn: current.turn + 1,
|
|
455
|
-
humanResolved: false
|
|
456
|
-
});
|
|
457
|
-
const baseState = incremented.success ? incremented.state : current;
|
|
458
|
-
const settled = this.transition(
|
|
459
|
-
sessionId,
|
|
460
|
-
"turn_completed",
|
|
461
|
-
{ lifecycle: "active", execution: "idle", gate: null },
|
|
462
|
-
{
|
|
463
|
-
displayState: "idle",
|
|
464
|
-
stateSource: "formal",
|
|
465
|
-
confidence: "high",
|
|
466
|
-
phase: getStateLabel("idle"),
|
|
467
|
-
humanResolved: false,
|
|
468
|
-
turn: baseState.turn
|
|
469
|
-
}
|
|
165
|
+
ctx.controller = safeGetSessionController(
|
|
166
|
+
ctx.sessionId
|
|
470
167
|
);
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
applyPlatformEvent(event) {
|
|
474
|
-
const current = this.getState(event.sessionId);
|
|
475
|
-
const mapped = mapEventToTransition(event, current, {
|
|
476
|
-
shouldTransition: this.shouldTransition.bind(this),
|
|
477
|
-
isSessionIdleTransitionAllowed: this.isSessionIdleTransitionAllowed.bind(this)
|
|
478
|
-
});
|
|
479
|
-
if (!mapped) return toProjection(current);
|
|
480
|
-
const mappedState = PLATFORM_EVENT_TO_STATE[event.type];
|
|
481
|
-
const shouldResetCompletedTimer = event.type === "session_started" || event.type === "session_ended" || event.type !== "completed" && mappedState !== "completed" && current.displayState === "completed";
|
|
482
|
-
if (shouldResetCompletedTimer) {
|
|
483
|
-
this.clearCompletedTimer(event.sessionId);
|
|
484
|
-
}
|
|
485
|
-
const result = this.transition(
|
|
486
|
-
event.sessionId,
|
|
487
|
-
event.type,
|
|
488
|
-
mapped.updates,
|
|
489
|
-
mapped.metadata
|
|
168
|
+
ctx.projection = safeGetProjection(
|
|
169
|
+
ctx.sessionId
|
|
490
170
|
);
|
|
491
|
-
if (!result.success) {
|
|
492
|
-
return toProjection(current);
|
|
493
|
-
}
|
|
494
|
-
if (mappedState === "completed") {
|
|
495
|
-
this.clearCompletedTimer(event.sessionId);
|
|
496
|
-
const timerToken = ++this.completedTimerSequence;
|
|
497
|
-
const completedTurn = result.state.turn;
|
|
498
|
-
const timer = setTimeout(() => {
|
|
499
|
-
this.applyPlatformEvent({
|
|
500
|
-
type: "session_idle",
|
|
501
|
-
sessionId: event.sessionId,
|
|
502
|
-
source: event.source,
|
|
503
|
-
stateSource: "formal",
|
|
504
|
-
confidence: "high",
|
|
505
|
-
timestamp: Date.now(),
|
|
506
|
-
metadata: {
|
|
507
|
-
phase: "\u5F85\u547D",
|
|
508
|
-
idleTimerToken: timerToken,
|
|
509
|
-
turn: completedTurn
|
|
510
|
-
}
|
|
511
|
-
});
|
|
512
|
-
this.clearCompletedTimer(event.sessionId);
|
|
513
|
-
}, 3e3);
|
|
514
|
-
this.completedTimers.set(event.sessionId, timer);
|
|
515
|
-
this.completedTimerTokens.set(event.sessionId, timerToken);
|
|
516
|
-
}
|
|
517
|
-
return toProjection(result.state);
|
|
518
|
-
}
|
|
519
|
-
clearSession(sessionId) {
|
|
520
|
-
this.sessions.delete(sessionId);
|
|
521
|
-
this.transitionHistory.delete(sessionId);
|
|
522
|
-
this.clearCompletedTimer(sessionId);
|
|
523
|
-
}
|
|
524
|
-
getSessionCount() {
|
|
525
|
-
return this.sessions.size;
|
|
526
|
-
}
|
|
527
|
-
createDefaultState() {
|
|
528
|
-
return {
|
|
529
|
-
lifecycle: "initializing",
|
|
530
|
-
execution: null,
|
|
531
|
-
gate: null,
|
|
532
|
-
displayState: "idle",
|
|
533
|
-
stateSource: "formal",
|
|
534
|
-
confidence: "high",
|
|
535
|
-
updatedAt: Date.now(),
|
|
536
|
-
turn: 0,
|
|
537
|
-
phase: STATE_LABELS.idle,
|
|
538
|
-
humanResolved: false
|
|
539
|
-
};
|
|
540
171
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
172
|
+
};
|
|
173
|
+
var sessionSupervisor = new SessionSupervisor();
|
|
174
|
+
function getSessionContext(sessionId) {
|
|
175
|
+
return sessionSupervisor.get(sessionId);
|
|
176
|
+
}
|
|
177
|
+
function getSessionView(sessionId) {
|
|
178
|
+
return sessionSupervisor.get(sessionId)?.session;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ../bot/src/subagent-manager.ts
|
|
182
|
+
import {
|
|
183
|
+
ChannelType,
|
|
184
|
+
ThreadAutoArchiveDuration
|
|
185
|
+
} from "discord.js";
|
|
186
|
+
|
|
187
|
+
// ../bot/src/discord/delivery-policy.ts
|
|
188
|
+
import { existsSync, statSync } from "fs";
|
|
189
|
+
var MAX_CHUNK_LIMIT = 2e3;
|
|
190
|
+
var MAX_ATTACHMENTS = 10;
|
|
191
|
+
var MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
|
|
192
|
+
function clampChunkLimit(limit) {
|
|
193
|
+
if (!Number.isFinite(limit)) return MAX_CHUNK_LIMIT;
|
|
194
|
+
return Math.max(1, Math.min(Math.trunc(limit), MAX_CHUNK_LIMIT));
|
|
195
|
+
}
|
|
196
|
+
function chunkText(text, limit, mode) {
|
|
197
|
+
const safeLimit = clampChunkLimit(limit);
|
|
198
|
+
if (text.length <= safeLimit) return [text];
|
|
199
|
+
const out = [];
|
|
200
|
+
let rest = text;
|
|
201
|
+
while (rest.length > safeLimit) {
|
|
202
|
+
let cut = safeLimit;
|
|
203
|
+
if (mode === "newline") {
|
|
204
|
+
const para = rest.lastIndexOf("\n\n", safeLimit);
|
|
205
|
+
const line = rest.lastIndexOf("\n", safeLimit);
|
|
206
|
+
const space = rest.lastIndexOf(" ", safeLimit);
|
|
207
|
+
cut = para > safeLimit / 2 ? para : line > safeLimit / 2 ? line : space > 0 ? space : safeLimit;
|
|
549
208
|
}
|
|
550
|
-
|
|
209
|
+
out.push(rest.slice(0, cut));
|
|
210
|
+
rest = rest.slice(cut).replace(/^\n+/, "");
|
|
551
211
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
212
|
+
if (rest) out.push(rest);
|
|
213
|
+
return out;
|
|
214
|
+
}
|
|
215
|
+
function validateOutboundAttachments(files) {
|
|
216
|
+
if (files.length > MAX_ATTACHMENTS) {
|
|
217
|
+
throw new Error(`\u6700\u591A\u53EA\u80FD\u53D1\u9001 ${MAX_ATTACHMENTS} \u4E2A\u9644\u4EF6`);
|
|
218
|
+
}
|
|
219
|
+
for (const filePath of files) {
|
|
220
|
+
if (!filePath.trim()) {
|
|
221
|
+
throw new Error("\u9644\u4EF6\u8DEF\u5F84\u65E0\u6548");
|
|
559
222
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
223
|
+
if (!existsSync(filePath)) continue;
|
|
224
|
+
const stats = statSync(filePath);
|
|
225
|
+
if (stats.isDirectory()) {
|
|
226
|
+
throw new Error(`\u9644\u4EF6\u8DEF\u5F84\u4E0D\u80FD\u662F\u76EE\u5F55: ${filePath}`);
|
|
563
227
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
return false;
|
|
228
|
+
if (stats.size > MAX_ATTACHMENT_BYTES) {
|
|
229
|
+
throw new Error(`\u9644\u4EF6 ${filePath} \u5927\u5C0F\u8D85\u8FC7 ${MAX_ATTACHMENT_BYTES} bytes`);
|
|
567
230
|
}
|
|
568
|
-
return true;
|
|
569
231
|
}
|
|
570
|
-
readNumericMetadata(metadata, field) {
|
|
571
|
-
const value = metadata?.[field];
|
|
572
|
-
return typeof value === "number" ? value : void 0;
|
|
573
|
-
}
|
|
574
|
-
};
|
|
575
|
-
var stateMachine = new StateMachine();
|
|
576
|
-
|
|
577
|
-
// ../state/src/event-normalizer.ts
|
|
578
|
-
var CLAUDE_EVENT_MAP = {
|
|
579
|
-
session_init: "idle",
|
|
580
|
-
text_delta: "thinking",
|
|
581
|
-
tool_start: "working",
|
|
582
|
-
tool_result: "working",
|
|
583
|
-
ask_user: "awaiting_human",
|
|
584
|
-
result: "completed",
|
|
585
|
-
error: "error"
|
|
586
|
-
};
|
|
587
|
-
function normalizeClaudeEvent(event, sessionId) {
|
|
588
|
-
const state = CLAUDE_EVENT_MAP[event.type];
|
|
589
|
-
if (!state) return null;
|
|
590
|
-
const platformType = mapToPlatformType(event.type);
|
|
591
|
-
if (!platformType) return null;
|
|
592
|
-
return {
|
|
593
|
-
type: platformType,
|
|
594
|
-
sessionId,
|
|
595
|
-
source: "claude",
|
|
596
|
-
stateSource: "formal",
|
|
597
|
-
confidence: "high",
|
|
598
|
-
metadata: event,
|
|
599
|
-
timestamp: Date.now()
|
|
600
|
-
};
|
|
601
232
|
}
|
|
602
|
-
function
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
result: "completed",
|
|
609
|
-
error: "errored"
|
|
610
|
-
};
|
|
611
|
-
return mapping[eventType] || null;
|
|
612
|
-
}
|
|
613
|
-
function normalizeCodexEvent(eventKey, sessionId, extra) {
|
|
614
|
-
const byEvent = mapCodexToPlatformType(eventKey);
|
|
615
|
-
const byState = mapCodexStateToPlatformType(extra.observedState);
|
|
616
|
-
const preferStateOverride = extra.observedState === "codex-permission";
|
|
617
|
-
const platformType = preferStateOverride ? byState ?? byEvent : byEvent ?? byState;
|
|
618
|
-
if (!platformType) return null;
|
|
619
|
-
const isPermissionEvent = eventKey === "codex-permission" || extra.observedState === "codex-permission";
|
|
620
|
-
const inferredFromState = Boolean(byState && !byEvent);
|
|
621
|
-
const stateSource = isPermissionEvent || inferredFromState ? "inferred" : "formal";
|
|
233
|
+
function buildDeliveryPlan(input) {
|
|
234
|
+
validateOutboundAttachments(input.files);
|
|
235
|
+
const chunks = chunkText(input.text, input.policy.textChunkLimit, input.policy.chunkMode);
|
|
236
|
+
const filesOnFirstChunk = input.files.slice(0, MAX_ATTACHMENTS);
|
|
237
|
+
const replyToMode = input.policy.replyToMode;
|
|
238
|
+
const replyToMessageId = input.mode === "system_notice" || input.mode === "summary" ? void 0 : replyToMode === "off" ? void 0 : input.replyToMessageId;
|
|
622
239
|
return {
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
240
|
+
sessionId: input.sessionId,
|
|
241
|
+
chatId: input.chatId,
|
|
242
|
+
replyToMessageId,
|
|
243
|
+
replyToMode,
|
|
244
|
+
editTargetMessageId: input.editTargetMessageId,
|
|
245
|
+
chunks,
|
|
246
|
+
filesOnFirstChunk,
|
|
247
|
+
mode: input.mode
|
|
630
248
|
};
|
|
631
249
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
"event_msg:task_complete": "completed",
|
|
643
|
-
"event_msg:context_compacted": "compaction_started",
|
|
644
|
-
"event_msg:turn_aborted": "session_idle",
|
|
645
|
-
"codex-turn-end": "completed",
|
|
646
|
-
"event_msg:error": "errored",
|
|
647
|
-
"stale-cleanup": "session_ended"
|
|
648
|
-
};
|
|
649
|
-
return mapping[eventKey] || null;
|
|
650
|
-
}
|
|
651
|
-
function mapCodexStateToPlatformType(state) {
|
|
652
|
-
if (!state) return null;
|
|
653
|
-
const mapping = {
|
|
654
|
-
thinking: "thinking_started",
|
|
655
|
-
working: "work_started",
|
|
656
|
-
sweeping: "compaction_started",
|
|
657
|
-
"codex-permission": "awaiting_human",
|
|
658
|
-
attention: "completed",
|
|
659
|
-
idle: "session_idle",
|
|
660
|
-
error: "errored",
|
|
661
|
-
sleeping: "session_ended"
|
|
662
|
-
};
|
|
663
|
-
return mapping[state] ?? null;
|
|
250
|
+
|
|
251
|
+
// ../bot/src/discord/delivery.ts
|
|
252
|
+
var sessionState = /* @__PURE__ */ new Map();
|
|
253
|
+
function getSessionState(sessionId) {
|
|
254
|
+
if (!sessionState.has(sessionId)) {
|
|
255
|
+
sessionState.set(sessionId, {});
|
|
256
|
+
}
|
|
257
|
+
const state = sessionState.get(sessionId);
|
|
258
|
+
state.lastUsed = Date.now();
|
|
259
|
+
return state;
|
|
664
260
|
}
|
|
665
|
-
function
|
|
666
|
-
|
|
667
|
-
const
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
if (obj.confidence !== "high" && obj.confidence !== "medium" && obj.confidence !== "low") {
|
|
672
|
-
return false;
|
|
261
|
+
function pruneStaleSessionStates(maxAgeMs = 5 * 60 * 1e3) {
|
|
262
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
263
|
+
for (const [sessionId, state] of sessionState) {
|
|
264
|
+
if (!state.lastUsed || state.lastUsed < cutoff) {
|
|
265
|
+
sessionState.delete(sessionId);
|
|
266
|
+
}
|
|
673
267
|
}
|
|
674
|
-
return typeof obj.timestamp === "number";
|
|
675
268
|
}
|
|
676
|
-
function
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
timestamp: event.timestamp || Date.now()
|
|
684
|
-
};
|
|
269
|
+
function cleanupSessionDeliveryState(sessionId) {
|
|
270
|
+
sessionState.delete(sessionId);
|
|
271
|
+
}
|
|
272
|
+
async function sendTyping(channel) {
|
|
273
|
+
try {
|
|
274
|
+
await channel.sendTyping?.();
|
|
275
|
+
} catch {
|
|
685
276
|
}
|
|
686
|
-
|
|
687
|
-
|
|
277
|
+
}
|
|
278
|
+
async function sendAckReaction(message, reaction) {
|
|
279
|
+
if (!reaction) return;
|
|
280
|
+
try {
|
|
281
|
+
await message.react(reaction);
|
|
282
|
+
} catch {
|
|
688
283
|
}
|
|
689
|
-
return normalizeClaudeEvent(event, sessionId);
|
|
690
284
|
}
|
|
691
|
-
|
|
692
|
-
|
|
285
|
+
var SEND_MAX_RETRIES = 3;
|
|
286
|
+
function isDiscordRateLimitError(err) {
|
|
287
|
+
if (!err || typeof err !== "object") return false;
|
|
288
|
+
const e = err;
|
|
289
|
+
return e.code === 429 || e.status === 429 || e.httpStatus === 429;
|
|
693
290
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
this.store = new Store("gates.json");
|
|
701
|
-
}
|
|
702
|
-
/** Load persisted gates from disk. Call during startup before using the registry. */
|
|
703
|
-
async init() {
|
|
291
|
+
function sleep(ms) {
|
|
292
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
293
|
+
}
|
|
294
|
+
async function sendWithBackoff(channel, payload) {
|
|
295
|
+
let attempt = 0;
|
|
296
|
+
while (true) {
|
|
704
297
|
try {
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
298
|
+
const message = await channel.send(payload);
|
|
299
|
+
return message.id;
|
|
300
|
+
} catch (error) {
|
|
301
|
+
if (!isDiscordRateLimitError(error) || attempt >= SEND_MAX_RETRIES) {
|
|
302
|
+
throw error;
|
|
710
303
|
}
|
|
711
|
-
|
|
712
|
-
console.
|
|
304
|
+
const delayMs = 250 * 2 ** attempt;
|
|
305
|
+
console.warn(
|
|
306
|
+
`[Delivery] Discord rate-limited (attempt ${attempt + 1}/${SEND_MAX_RETRIES}), retrying in ${delayMs}ms`
|
|
307
|
+
);
|
|
308
|
+
await sleep(delayMs);
|
|
309
|
+
attempt++;
|
|
713
310
|
}
|
|
714
311
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
create(params) {
|
|
722
|
-
const id = this.generateId();
|
|
723
|
-
const record = {
|
|
724
|
-
...params,
|
|
725
|
-
id,
|
|
726
|
-
version: 1,
|
|
727
|
-
createdAt: Date.now(),
|
|
728
|
-
status: "pending"
|
|
729
|
-
};
|
|
730
|
-
this.gates.set(id, record);
|
|
731
|
-
this._save();
|
|
732
|
-
return record;
|
|
733
|
-
}
|
|
734
|
-
// 获取门控记录
|
|
735
|
-
get(id) {
|
|
736
|
-
return this.gates.get(id);
|
|
737
|
-
}
|
|
738
|
-
// 获取会话的所有门控
|
|
739
|
-
getBySession(sessionId) {
|
|
740
|
-
return Array.from(this.gates.values()).filter((g) => g.sessionId === sessionId);
|
|
741
|
-
}
|
|
742
|
-
// 获取会话的活跃门控(pending 状态)
|
|
743
|
-
getActiveBySession(sessionId) {
|
|
744
|
-
return this.getBySession(sessionId).filter((g) => g.status === "pending");
|
|
745
|
-
}
|
|
746
|
-
// CAS 更新:使用乐观锁保证原子性
|
|
747
|
-
update(id, expectedVersion, updates) {
|
|
748
|
-
const current = this.gates.get(id);
|
|
749
|
-
if (!current) {
|
|
750
|
-
return {
|
|
751
|
-
success: false,
|
|
752
|
-
error: "not_found",
|
|
753
|
-
message: `Gate ${id} not found`
|
|
754
|
-
};
|
|
755
|
-
}
|
|
756
|
-
if (current.version !== expectedVersion) {
|
|
757
|
-
return {
|
|
758
|
-
success: false,
|
|
759
|
-
error: "version_conflict",
|
|
760
|
-
message: `Version conflict: expected ${expectedVersion}, got ${current.version}`
|
|
761
|
-
};
|
|
762
|
-
}
|
|
763
|
-
if (updates.status && !this.isValidTransition(current.status, updates.status)) {
|
|
764
|
-
return {
|
|
765
|
-
success: false,
|
|
766
|
-
error: "invalid_transition",
|
|
767
|
-
message: `Invalid transition: ${current.status} -> ${updates.status}`
|
|
768
|
-
};
|
|
769
|
-
}
|
|
770
|
-
const updated = {
|
|
771
|
-
...current,
|
|
772
|
-
...updates,
|
|
773
|
-
version: current.version + 1
|
|
774
|
-
};
|
|
775
|
-
this.gates.set(id, updated);
|
|
776
|
-
this._save();
|
|
777
|
-
return {
|
|
778
|
-
success: true,
|
|
779
|
-
record: updated
|
|
780
|
-
};
|
|
312
|
+
}
|
|
313
|
+
async function sendChunk(channel, chunk, options = {}) {
|
|
314
|
+
const payload = { content: chunk };
|
|
315
|
+
if (options.files?.length) payload.files = options.files;
|
|
316
|
+
if (options.replyToMessageId) {
|
|
317
|
+
payload.reply = { messageReference: options.replyToMessageId };
|
|
781
318
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
const updated = {
|
|
788
|
-
...gate,
|
|
789
|
-
status: "invalidated",
|
|
790
|
-
resolvedAt: Date.now(),
|
|
791
|
-
resolvedBy: reason,
|
|
792
|
-
version: gate.version + 1
|
|
793
|
-
};
|
|
794
|
-
this.gates.set(id, updated);
|
|
795
|
-
count++;
|
|
796
|
-
}
|
|
319
|
+
try {
|
|
320
|
+
return await sendWithBackoff(channel, payload);
|
|
321
|
+
} catch (error) {
|
|
322
|
+
if (!options.replyToMessageId) {
|
|
323
|
+
throw error;
|
|
797
324
|
}
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
// 删除门控记录
|
|
802
|
-
delete(id) {
|
|
803
|
-
const result = this.gates.delete(id);
|
|
804
|
-
if (result) this._save();
|
|
805
|
-
return result;
|
|
325
|
+
const fallbackPayload = { content: chunk };
|
|
326
|
+
if (options.files?.length) fallbackPayload.files = options.files;
|
|
327
|
+
return sendWithBackoff(channel, fallbackPayload);
|
|
806
328
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
};
|
|
820
|
-
this.gates.set(id, updated);
|
|
821
|
-
count++;
|
|
329
|
+
}
|
|
330
|
+
async function deliver(channel, plan) {
|
|
331
|
+
pruneStaleSessionStates();
|
|
332
|
+
const state = getSessionState(plan.sessionId);
|
|
333
|
+
if (plan.mode === "progress_update") {
|
|
334
|
+
const targetMessageId = plan.editTargetMessageId ?? state.recentProgressMessageId;
|
|
335
|
+
if (targetMessageId && plan.chunks.length === 1 && plan.filesOnFirstChunk.length === 0) {
|
|
336
|
+
try {
|
|
337
|
+
await channel.messages?.edit(targetMessageId, { content: plan.chunks[0] });
|
|
338
|
+
state.recentProgressMessageId = targetMessageId;
|
|
339
|
+
return [targetMessageId];
|
|
340
|
+
} catch {
|
|
822
341
|
}
|
|
823
342
|
}
|
|
824
|
-
this._save();
|
|
825
|
-
return count;
|
|
826
343
|
}
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
}
|
|
837
|
-
this._save();
|
|
838
|
-
return toArchive.length;
|
|
344
|
+
const ids = [];
|
|
345
|
+
const replyToMode = plan.replyToMode ?? "first";
|
|
346
|
+
for (let index = 0; index < plan.chunks.length; index++) {
|
|
347
|
+
const shouldReply = !!plan.replyToMessageId && replyToMode !== "off" && (replyToMode === "all" || index === 0);
|
|
348
|
+
const id = await sendChunk(channel, plan.chunks[index], {
|
|
349
|
+
replyToMessageId: shouldReply ? plan.replyToMessageId : void 0,
|
|
350
|
+
files: index === 0 ? plan.filesOnFirstChunk : void 0
|
|
351
|
+
});
|
|
352
|
+
ids.push(id);
|
|
839
353
|
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
354
|
+
if (ids.length > 0) {
|
|
355
|
+
if (plan.mode === "progress_update") {
|
|
356
|
+
state.recentProgressMessageId = ids[ids.length - 1];
|
|
357
|
+
} else {
|
|
358
|
+
state.recentFinalMessageId = ids[ids.length - 1];
|
|
359
|
+
}
|
|
843
360
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
361
|
+
return ids;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ../bot/src/discord/delivery-notices.ts
|
|
365
|
+
function getDeliveryPolicy() {
|
|
366
|
+
return {
|
|
367
|
+
textChunkLimit: config.textChunkLimit ?? 2e3,
|
|
368
|
+
chunkMode: config.chunkMode ?? "length",
|
|
369
|
+
replyToMode: config.replyToMode ?? "first",
|
|
370
|
+
ackReaction: config.ackReaction ?? "\u{1F440}"
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
async function sendSystemNotice(channel, sessionId, text, replyToMessageId) {
|
|
374
|
+
if (!text.trim()) return;
|
|
375
|
+
const plan = buildDeliveryPlan({
|
|
376
|
+
sessionId,
|
|
377
|
+
chatId: channel.id,
|
|
378
|
+
text,
|
|
379
|
+
files: [],
|
|
380
|
+
mode: "system_notice",
|
|
381
|
+
replyToMessageId,
|
|
382
|
+
policy: getDeliveryPolicy()
|
|
383
|
+
});
|
|
384
|
+
try {
|
|
385
|
+
await deliver(channel, plan);
|
|
386
|
+
} catch {
|
|
855
387
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
return validTransitions[from]?.includes(to) ?? false;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// ../bot/src/subagent-manager.ts
|
|
391
|
+
var SUBAGENT_IDLE_TIMEOUT_MS = 60 * 60 * 1e3;
|
|
392
|
+
function canSpawnSubagent(parentSession) {
|
|
393
|
+
return parentSession.subagentDepth < config.maxSubagentDepth;
|
|
394
|
+
}
|
|
395
|
+
async function spawnSubagent(parentSession, label, provider, sessionChannel) {
|
|
396
|
+
if (!canSpawnSubagent(parentSession)) {
|
|
397
|
+
console.warn(`[SubagentManager] Depth limit hit for session ${parentSession.id} (max depth ${config.maxSubagentDepth})`);
|
|
398
|
+
throw new Error(
|
|
399
|
+
`Max subagent depth (${config.maxSubagentDepth}) reached. Cannot spawn further subagents.`
|
|
400
|
+
);
|
|
870
401
|
}
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
402
|
+
const threadName = `[sub:${provider}] ${label}`.slice(0, 100);
|
|
403
|
+
const thread = await sessionChannel.threads.create({
|
|
404
|
+
name: threadName,
|
|
405
|
+
type: ChannelType.PublicThread,
|
|
406
|
+
autoArchiveDuration: ThreadAutoArchiveDuration.OneHour,
|
|
407
|
+
reason: `Subagent spawned by session ${parentSession.id}`
|
|
408
|
+
});
|
|
409
|
+
const session = await createSession({
|
|
410
|
+
channelId: thread.id,
|
|
411
|
+
// Subagent's primary ID is the Thread
|
|
412
|
+
categoryId: parentSession.categoryId,
|
|
413
|
+
projectName: parentSession.projectName,
|
|
414
|
+
agentLabel: label,
|
|
415
|
+
provider,
|
|
416
|
+
directory: parentSession.directory,
|
|
417
|
+
type: "subagent",
|
|
418
|
+
parentChannelId: parentSession.channelId,
|
|
419
|
+
// Parent session's TextChannel
|
|
420
|
+
subagentDepth: parentSession.subagentDepth + 1,
|
|
421
|
+
mode: parentSession.mode,
|
|
422
|
+
claudePermissionMode: provider === "claude" ? parentSession.claudePermissionMode : void 0
|
|
423
|
+
});
|
|
424
|
+
console.log(`[SubagentManager] Spawned subagent "${label}" session ${session.id} thread ${thread.id} (depth ${session.subagentDepth}, parent ${parentSession.id})`);
|
|
425
|
+
return session;
|
|
426
|
+
}
|
|
427
|
+
async function archiveSubagent(session, thread, summary) {
|
|
428
|
+
if (summary) {
|
|
429
|
+
await sendSystemNotice(thread, session.id, `*\u5B50\u4EFB\u52A1\u5B8C\u6210\uFF1A${summary}*`);
|
|
874
430
|
}
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
var GateCoordinator = class {
|
|
880
|
-
registry;
|
|
881
|
-
receiptHandles = /* @__PURE__ */ new Map();
|
|
882
|
-
timeoutTimers = /* @__PURE__ */ new Map();
|
|
883
|
-
constructor(registry) {
|
|
884
|
-
this.registry = registry;
|
|
431
|
+
try {
|
|
432
|
+
await thread.setArchived(true, "Subagent task completed");
|
|
433
|
+
} catch (error) {
|
|
434
|
+
console.warn(`[SubagentManager] Failed to archive thread ${thread.id} for subagent ${session.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
885
435
|
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
if (record.supportsRemoteDecision && record.isBlocking) {
|
|
892
|
-
this.setupTimeout(record.id);
|
|
893
|
-
}
|
|
894
|
-
return record;
|
|
436
|
+
try {
|
|
437
|
+
await endSession(session.id);
|
|
438
|
+
console.log(`[SubagentManager] Archived subagent ${session.id} thread ${thread.id}${summary ? ` \u2014 ${summary}` : ""}`);
|
|
439
|
+
} catch (error) {
|
|
440
|
+
console.warn(`[SubagentManager] Failed to end session for subagent ${session.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
895
441
|
}
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
442
|
+
}
|
|
443
|
+
function getSubagents(parentSession) {
|
|
444
|
+
return getAllSessions().filter(
|
|
445
|
+
(s) => s.type === "subagent" && s.parentChannelId === parentSession.channelId
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
async function runSubagentWatchdog(getThread) {
|
|
449
|
+
const now = Date.now();
|
|
450
|
+
const checked = /* @__PURE__ */ new Set();
|
|
451
|
+
let archived = 0;
|
|
452
|
+
let errors = 0;
|
|
453
|
+
for (const session of getSubagentSessions()) {
|
|
454
|
+
if (checked.has(session.id)) continue;
|
|
455
|
+
checked.add(session.id);
|
|
456
|
+
const idle = now - session.lastActivity;
|
|
457
|
+
if (idle < SUBAGENT_IDLE_TIMEOUT_MS) continue;
|
|
458
|
+
if (session.isGenerating) continue;
|
|
459
|
+
const thread = getThread(session.channelId);
|
|
460
|
+
if (!thread) {
|
|
461
|
+
await endSession(session.id).catch((e) => console.warn(`[SubagentWatchdog] Failed to end orphaned subagent ${session.id}: ${e.message}`));
|
|
462
|
+
console.log(`[SubagentWatchdog] Ended orphaned subagent ${session.id} \u2014 thread ${session.channelId} not found`);
|
|
463
|
+
continue;
|
|
904
464
|
}
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
const record = this.registry.get(gateId);
|
|
912
|
-
if (!record) {
|
|
913
|
-
return false;
|
|
465
|
+
try {
|
|
466
|
+
await archiveSubagent(session, thread, "Idle timeout reached.");
|
|
467
|
+
archived += 1;
|
|
468
|
+
} catch (error) {
|
|
469
|
+
console.error(`[SubagentWatchdog] Failed to archive idle subagent ${session.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
470
|
+
errors += 1;
|
|
914
471
|
}
|
|
915
|
-
const result = this.registry.update(gateId, record.version, { discordMessageId });
|
|
916
|
-
return result.success;
|
|
917
472
|
}
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
473
|
+
console.log(`[SubagentWatchdog] Watchdog run: checked ${checked.size} subagents, archived ${archived}, errors ${errors}`);
|
|
474
|
+
}
|
|
475
|
+
async function autoSpawnSubagentThread(parentSession, taskId, description, sessionChannel) {
|
|
476
|
+
if (parentSession.type === "subagent") return null;
|
|
477
|
+
if (!canSpawnSubagent(parentSession)) return null;
|
|
478
|
+
const threadName = `[task] ${description}`.slice(0, 100);
|
|
479
|
+
const thread = await sessionChannel.threads.create({
|
|
480
|
+
name: threadName,
|
|
481
|
+
type: ChannelType.PublicThread,
|
|
482
|
+
autoArchiveDuration: ThreadAutoArchiveDuration.OneHour,
|
|
483
|
+
reason: `Auto-created for task ${taskId} in session ${parentSession.id}`
|
|
484
|
+
});
|
|
485
|
+
let session;
|
|
486
|
+
try {
|
|
487
|
+
session = await createSession({
|
|
488
|
+
channelId: thread.id,
|
|
489
|
+
categoryId: parentSession.categoryId,
|
|
490
|
+
projectName: parentSession.projectName,
|
|
491
|
+
agentLabel: description,
|
|
492
|
+
provider: parentSession.provider,
|
|
493
|
+
directory: parentSession.directory,
|
|
494
|
+
type: "subagent",
|
|
495
|
+
parentChannelId: parentSession.channelId,
|
|
496
|
+
subagentDepth: (parentSession.subagentDepth || 0) + 1,
|
|
497
|
+
mode: parentSession.mode,
|
|
498
|
+
claudePermissionMode: parentSession.provider === "claude" ? parentSession.claudePermissionMode : void 0
|
|
934
499
|
});
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
if (handle) {
|
|
942
|
-
handle.resolve(action, "discord");
|
|
943
|
-
this.receiptHandles.delete(gateId);
|
|
944
|
-
handledByReceipt = true;
|
|
945
|
-
}
|
|
946
|
-
return { success: true, handledByReceipt };
|
|
500
|
+
} catch (err) {
|
|
501
|
+
console.warn(`[SubagentManager] createSession failed for auto-spawned task ${taskId}, deleting orphan thread ${thread.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
502
|
+
await thread.delete("Session creation failed").catch(
|
|
503
|
+
(deleteErr) => console.warn(`[SubagentManager] Failed to delete orphan thread ${thread.id}: ${deleteErr instanceof Error ? deleteErr.message : String(deleteErr)}`)
|
|
504
|
+
);
|
|
505
|
+
throw err;
|
|
947
506
|
}
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
507
|
+
console.log(`[SubagentManager] Auto-spawned thread for task ${taskId} session ${session.id} thread ${thread.id}`);
|
|
508
|
+
return { threadId: thread.id, session };
|
|
509
|
+
}
|
|
510
|
+
function getSubagentSessions() {
|
|
511
|
+
return getAllSessions().filter((s) => s.type === "subagent");
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// ../bot/src/codex-renderer.ts
|
|
515
|
+
import { EmbedBuilder } from "discord.js";
|
|
516
|
+
|
|
517
|
+
// ../bot/src/output/interaction-controls.ts
|
|
518
|
+
import {
|
|
519
|
+
ActionRowBuilder,
|
|
520
|
+
ButtonBuilder,
|
|
521
|
+
ButtonStyle,
|
|
522
|
+
StringSelectMenuBuilder,
|
|
523
|
+
EmbedBuilder as EmbedBuilder2
|
|
524
|
+
} from "discord.js";
|
|
525
|
+
|
|
526
|
+
// ../engine/src/output/answer-store.ts
|
|
527
|
+
var pendingAnswersStore = /* @__PURE__ */ new Map();
|
|
528
|
+
var questionCountStore = /* @__PURE__ */ new Map();
|
|
529
|
+
function setPendingAnswer(sessionId, questionIndex, answer) {
|
|
530
|
+
if (!pendingAnswersStore.has(sessionId)) {
|
|
531
|
+
pendingAnswersStore.set(sessionId, /* @__PURE__ */ new Map());
|
|
532
|
+
}
|
|
533
|
+
pendingAnswersStore.get(sessionId).set(questionIndex, answer);
|
|
534
|
+
}
|
|
535
|
+
function getPendingAnswers(sessionId) {
|
|
536
|
+
return pendingAnswersStore.get(sessionId);
|
|
537
|
+
}
|
|
538
|
+
function clearPendingAnswers(sessionId) {
|
|
539
|
+
pendingAnswersStore.delete(sessionId);
|
|
540
|
+
questionCountStore.delete(sessionId);
|
|
541
|
+
}
|
|
542
|
+
function getQuestionCount(sessionId) {
|
|
543
|
+
return questionCountStore.get(sessionId) || 0;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// ../bot/src/output/interaction-controls.ts
|
|
547
|
+
function resolveEffectiveClaudePermissionMode(currentMode, claudePermissionMode) {
|
|
548
|
+
if (!claudePermissionMode) return void 0;
|
|
549
|
+
return currentMode === "auto" ? "bypass" : claudePermissionMode;
|
|
550
|
+
}
|
|
551
|
+
function makeModeButtons(sessionId, currentMode, claudePermissionMode) {
|
|
552
|
+
const modes = [
|
|
553
|
+
{ id: "auto", label: "\u26A1 \u81EA\u52A8\u6A21\u5F0F" },
|
|
554
|
+
{ id: "plan", label: "\u{1F4CB} \u8BA1\u5212\u6A21\u5F0F" },
|
|
555
|
+
{ id: "normal", label: "\u{1F6E1}\uFE0F \u666E\u901A\u6A21\u5F0F" },
|
|
556
|
+
{ id: "monitor", label: "\u{1F9E0} \u76D1\u63A7\u6A21\u5F0F" }
|
|
557
|
+
];
|
|
558
|
+
const row = new ActionRowBuilder();
|
|
559
|
+
for (const m of modes) {
|
|
560
|
+
row.addComponents(
|
|
561
|
+
new ButtonBuilder().setCustomId(`mode:${sessionId}:${m.id}`).setLabel(m.id === currentMode ? `\u2713 ${m.label}` : m.label).setStyle(m.id === currentMode ? ButtonStyle.Success : ButtonStyle.Secondary).setDisabled(false)
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
const effectiveClaudePermissionMode = resolveEffectiveClaudePermissionMode(
|
|
565
|
+
currentMode,
|
|
566
|
+
claudePermissionMode
|
|
567
|
+
);
|
|
568
|
+
if (effectiveClaudePermissionMode) {
|
|
569
|
+
const permLabel = effectiveClaudePermissionMode === "bypass" ? "\u26A1 \u7ED5\u8FC7\u6743\u9650" : "\u{1F6E1}\uFE0F \u9700\u8981\u786E\u8BA4";
|
|
570
|
+
row.addComponents(
|
|
571
|
+
new ButtonBuilder().setCustomId(`perm-info:${sessionId}`).setLabel(permLabel).setStyle(
|
|
572
|
+
effectiveClaudePermissionMode === "bypass" ? ButtonStyle.Danger : ButtonStyle.Success
|
|
573
|
+
).setDisabled(true)
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
return row;
|
|
577
|
+
}
|
|
578
|
+
function shouldSuppressCommandExecution(command) {
|
|
579
|
+
return command.toLowerCase().includes("total-recall");
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// ../bot/src/output/event-handlers.ts
|
|
583
|
+
function providerSource(sessionId) {
|
|
584
|
+
const session = getSessionView(sessionId);
|
|
585
|
+
return session?.provider === "codex" ? "codex" : "claude";
|
|
586
|
+
}
|
|
587
|
+
var textDelta = (event, ctx) => {
|
|
588
|
+
ctx.streamer.append(event.text);
|
|
589
|
+
};
|
|
590
|
+
var askUser = async (event, ctx) => {
|
|
591
|
+
ctx.state.askedUser = true;
|
|
592
|
+
ctx.state.askUserQuestionsJson = event.questionsJson;
|
|
593
|
+
await ctx.streamer.discard();
|
|
594
|
+
const session = getSessionView(ctx.sessionId);
|
|
595
|
+
if (!session) return;
|
|
596
|
+
const source = providerSource(ctx.sessionId);
|
|
597
|
+
await updateSessionState(ctx.sessionId, {
|
|
598
|
+
type: "awaiting_human",
|
|
599
|
+
sessionId: ctx.sessionId,
|
|
600
|
+
source,
|
|
601
|
+
confidence: "high",
|
|
602
|
+
timestamp: Date.now(),
|
|
603
|
+
metadata: { detail: event.questionsJson }
|
|
604
|
+
});
|
|
605
|
+
await flushDigest(ctx.sessionId);
|
|
606
|
+
await handleAwaitingHuman(ctx.sessionId, event.questionsJson, { source });
|
|
607
|
+
};
|
|
608
|
+
var task = async (event, ctx) => {
|
|
609
|
+
await ctx.streamer.finalize();
|
|
610
|
+
queueDigest(ctx.sessionId, { kind: "tool", text: `\u4EFB\u52A1\u5DE5\u5177\uFF1A${event.action}` });
|
|
611
|
+
ctx.state.lastToolName = event.action;
|
|
612
|
+
};
|
|
613
|
+
var taskStarted = async (event, ctx) => {
|
|
614
|
+
await ctx.streamer.finalize();
|
|
615
|
+
queueDigest(ctx.sessionId, {
|
|
616
|
+
kind: "subagent",
|
|
617
|
+
text: `\u5B50\u4EE3\u7406\u542F\u52A8\uFF1A${truncate(event.description, 80)}`
|
|
618
|
+
});
|
|
619
|
+
const session = getSessionView(ctx.sessionId);
|
|
620
|
+
if (!session || ctx.channel.type === void 0) return;
|
|
621
|
+
autoSpawnSubagentThread(session, event.taskId, event.description, ctx.channel).then((result2) => {
|
|
622
|
+
if (result2) ctx.state.taskThreadMap.set(event.taskId, result2.threadId);
|
|
623
|
+
}).catch(
|
|
624
|
+
(err) => console.warn(
|
|
625
|
+
`[OutputHandler] Failed to auto-spawn thread for task ${event.taskId}: ${err.message}`
|
|
626
|
+
)
|
|
627
|
+
);
|
|
628
|
+
};
|
|
629
|
+
var taskProgress = (event, ctx) => {
|
|
630
|
+
if (event.summary) {
|
|
631
|
+
queueDigest(ctx.sessionId, {
|
|
632
|
+
kind: "subagent",
|
|
633
|
+
text: `\u5B50\u4EE3\u7406\u8FDB\u5C55\uFF1A${truncate(event.summary, 100)}`
|
|
964
634
|
});
|
|
965
|
-
if (!result.success) {
|
|
966
|
-
return { success: false, message: result.message, handledByReceipt: false };
|
|
967
|
-
}
|
|
968
|
-
this.clearTimeout(gateId);
|
|
969
|
-
const handle = this.receiptHandles.get(gateId);
|
|
970
|
-
let handledByReceipt = false;
|
|
971
|
-
if (handle) {
|
|
972
|
-
handle.resolve(action, "terminal");
|
|
973
|
-
this.receiptHandles.delete(gateId);
|
|
974
|
-
handledByReceipt = true;
|
|
975
|
-
}
|
|
976
|
-
return { success: true, handledByReceipt };
|
|
977
635
|
}
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
636
|
+
const threadId = ctx.state.taskThreadMap.get(event.taskId);
|
|
637
|
+
if (!threadId || !event.summary) return;
|
|
638
|
+
const thread = ctx.channel.threads?.cache.get(threadId);
|
|
639
|
+
if (!thread) return;
|
|
640
|
+
thread.send(`\u{1F4DD} ${truncate(event.summary, 1900)}`).catch(
|
|
641
|
+
(e) => console.warn(
|
|
642
|
+
`[OutputHandler] Failed to send progress to thread: ${e.message}`
|
|
643
|
+
)
|
|
644
|
+
);
|
|
645
|
+
};
|
|
646
|
+
var taskDone = async (event, ctx) => {
|
|
647
|
+
await ctx.streamer.finalize();
|
|
648
|
+
queueDigest(ctx.sessionId, {
|
|
649
|
+
kind: "subagent",
|
|
650
|
+
text: `\u5B50\u4EE3\u7406${event.status === "completed" ? "\u5B8C\u6210" : "\u7ED3\u675F"}\uFF1A${truncate(event.summary || "No summary.", 100)}`
|
|
651
|
+
});
|
|
652
|
+
const threadId = ctx.state.taskThreadMap.get(event.taskId);
|
|
653
|
+
if (!threadId) return;
|
|
654
|
+
const thread = ctx.channel.threads?.cache.get(threadId);
|
|
655
|
+
if (thread) {
|
|
656
|
+
const emoji = event.status === "completed" ? "\u2705" : "\u274C";
|
|
657
|
+
thread.send(`${emoji} \u5B50\u4EFB\u52A1${event.status === "completed" ? "\u5B8C\u6210" : "\u7ED3\u675F"}\uFF1A${truncate(event.summary || "", 1900)}`).catch(
|
|
658
|
+
(e) => console.warn(
|
|
659
|
+
`[OutputHandler] Failed to send task done to thread: ${e.message}`
|
|
660
|
+
)
|
|
661
|
+
);
|
|
986
662
|
}
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
if (timer) {
|
|
993
|
-
clearTimeout(timer);
|
|
994
|
-
this.timeoutTimers.delete(gateId);
|
|
995
|
-
}
|
|
663
|
+
ctx.state.taskThreadMap.delete(event.taskId);
|
|
664
|
+
};
|
|
665
|
+
var webSearch = (event, ctx) => {
|
|
666
|
+
if (ctx.verbose) {
|
|
667
|
+
queueDigest(ctx.sessionId, { kind: "search", text: `\u68C0\u7D22\uFF1A${truncate(event.query, 80)}` });
|
|
996
668
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
669
|
+
};
|
|
670
|
+
var toolStart = async (event, ctx) => {
|
|
671
|
+
await ctx.streamer.finalize();
|
|
672
|
+
queueDigest(ctx.sessionId, { kind: "tool", text: `\u5DE5\u5177\uFF1A${event.toolName}` });
|
|
673
|
+
ctx.state.lastToolName = event.toolName;
|
|
674
|
+
};
|
|
675
|
+
var toolResult = async (event, ctx) => {
|
|
676
|
+
await ctx.streamer.finalize();
|
|
677
|
+
if (ctx.verbose && event.result) {
|
|
678
|
+
queueDigest(ctx.sessionId, {
|
|
679
|
+
kind: "tool",
|
|
680
|
+
text: `\u5DE5\u5177\u7ED3\u679C\uFF1A${truncate(ctx.state.lastToolName || event.toolName || "tool", 60)}`
|
|
1009
681
|
});
|
|
1010
|
-
const handle = this.receiptHandles.get(gateId);
|
|
1011
|
-
if (handle) {
|
|
1012
|
-
handle.reject("\u5BA1\u6279\u8D85\u65F6\uFF085 \u5206\u949F\uFF09");
|
|
1013
|
-
this.receiptHandles.delete(gateId);
|
|
1014
|
-
}
|
|
1015
|
-
this.timeoutTimers.delete(gateId);
|
|
1016
682
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
const count = this.registry.invalidateAll("restart");
|
|
1022
|
-
console.log(`[GateCoordinator] Invalidated ${count} pending gates on restart`);
|
|
1023
|
-
const toUpdate = [];
|
|
1024
|
-
for (const gate of this.registry.getAll()) {
|
|
1025
|
-
if (gate.status === "invalidated" && gate.discordMessageId) {
|
|
1026
|
-
toUpdate.push({
|
|
1027
|
-
gateId: gate.id,
|
|
1028
|
-
discordMessageId: gate.discordMessageId
|
|
1029
|
-
});
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
this.receiptHandles.clear();
|
|
1033
|
-
for (const timer of this.timeoutTimers.values()) {
|
|
1034
|
-
clearTimeout(timer);
|
|
1035
|
-
}
|
|
1036
|
-
this.timeoutTimers.clear();
|
|
1037
|
-
return toUpdate;
|
|
683
|
+
};
|
|
684
|
+
var imageFile = (event, ctx) => {
|
|
685
|
+
if (existsSync2(event.filePath)) {
|
|
686
|
+
ctx.state.pendingAttachments.push(event.filePath);
|
|
1038
687
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
688
|
+
};
|
|
689
|
+
var commandExecution = (event, ctx) => {
|
|
690
|
+
ctx.state.commandCount++;
|
|
691
|
+
if (ctx.state.recentCommands.length < 8) ctx.state.recentCommands.push(event.command);
|
|
692
|
+
if (!shouldSuppressCommandExecution(event.command)) {
|
|
693
|
+
queueDigest(ctx.sessionId, {
|
|
694
|
+
kind: "command",
|
|
695
|
+
text: `\u547D\u4EE4\uFF1A${truncate(event.command, 80)}${event.exitCode !== null ? `\uFF08\u9000\u51FA\u7801 ${event.exitCode}\uFF09` : ""}`
|
|
696
|
+
});
|
|
1044
697
|
}
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
698
|
+
};
|
|
699
|
+
var fileChange = (event, ctx) => {
|
|
700
|
+
ctx.state.fileChangeCount += event.changes.length;
|
|
701
|
+
for (const change of event.changes) {
|
|
702
|
+
if (!change.filePath) continue;
|
|
703
|
+
if (ctx.state.changedFiles.includes(change.filePath)) continue;
|
|
704
|
+
if (ctx.state.changedFiles.length >= 12) break;
|
|
705
|
+
ctx.state.changedFiles.push(change.filePath);
|
|
706
|
+
}
|
|
707
|
+
queueDigest(ctx.sessionId, {
|
|
708
|
+
kind: "file",
|
|
709
|
+
text: `\u6587\u4EF6\u53D8\u66F4\uFF1A${event.changes.length} \u4E2A\uFF08\u6700\u8FD1\uFF1A${truncate(ctx.state.changedFiles.slice(-3).join(", "), 120)}\uFF09`
|
|
710
|
+
});
|
|
711
|
+
};
|
|
712
|
+
var reasoning = (event, ctx) => {
|
|
713
|
+
if (ctx.verbose) {
|
|
714
|
+
queueDigest(ctx.sessionId, { kind: "reasoning", text: `\u63A8\u7406\uFF1A${truncate(event.text, 100)}` });
|
|
1051
715
|
}
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
716
|
+
};
|
|
717
|
+
var todoList = async (event, ctx) => {
|
|
718
|
+
const completed = event.items.filter((item) => item.completed).length;
|
|
719
|
+
queueDigest(ctx.sessionId, {
|
|
720
|
+
kind: "todo",
|
|
721
|
+
text: `\u5F85\u529E\u66F4\u65B0\uFF1A${completed}/${event.items.length} \u5DF2\u5B8C\u6210`
|
|
722
|
+
});
|
|
723
|
+
await updateSessionState(ctx.sessionId, {
|
|
724
|
+
type: "todo_updated",
|
|
725
|
+
sessionId: ctx.sessionId,
|
|
726
|
+
source: providerSource(ctx.sessionId),
|
|
727
|
+
confidence: "high",
|
|
728
|
+
timestamp: Date.now(),
|
|
729
|
+
metadata: { items: event.items }
|
|
730
|
+
});
|
|
731
|
+
};
|
|
732
|
+
var MODE_LABELS = {
|
|
733
|
+
auto: "\u81EA\u52A8",
|
|
734
|
+
plan: "\u8BA1\u5212",
|
|
735
|
+
normal: "\u666E\u901A",
|
|
736
|
+
monitor: "\u76D1\u63A7"
|
|
737
|
+
};
|
|
738
|
+
var TERMINAL_REASON_LABELS = {
|
|
739
|
+
completed: "\u5DF2\u5B8C\u6210",
|
|
740
|
+
max_turns: "\u5DF2\u8FBE\u6700\u5927\u8F6E\u6B21",
|
|
741
|
+
aborted: "\u5DF2\u4E2D\u6B62",
|
|
742
|
+
rate_limited: "\u89E6\u53D1\u901F\u7387\u9650\u5236",
|
|
743
|
+
context_too_long: "\u4E0A\u4E0B\u6587\u8D85\u957F",
|
|
744
|
+
model_error: "\u6A21\u578B\u9519\u8BEF",
|
|
745
|
+
tool_deferred: "\u5DE5\u5177\u5BA1\u6279\u5EF6\u540E",
|
|
746
|
+
hook_stopped: "\u88AB\u94A9\u5B50\u963B\u65AD",
|
|
747
|
+
image_error: "\u56FE\u50CF\u9519\u8BEF",
|
|
748
|
+
error: "\u5F02\u5E38"
|
|
749
|
+
};
|
|
750
|
+
function formatTerminalReason(reason, fallbackSuccess) {
|
|
751
|
+
if (!reason) return fallbackSuccess ? "\u5DF2\u5B8C\u6210" : "\u5F02\u5E38";
|
|
752
|
+
return TERMINAL_REASON_LABELS[reason] ?? reason;
|
|
753
|
+
}
|
|
754
|
+
var result = async (event, ctx) => {
|
|
755
|
+
ctx.state.success = event.success;
|
|
756
|
+
const lastText = ctx.streamer.getText();
|
|
757
|
+
const cost = event.costUsd.toFixed(4);
|
|
758
|
+
const duration = event.durationMs ? `${(event.durationMs / 1e3).toFixed(1)}s` : "unknown";
|
|
759
|
+
const turns = event.numTurns || 0;
|
|
760
|
+
const modeLabel = MODE_LABELS[ctx.mode] || "\u81EA\u52A8";
|
|
761
|
+
const reasonLabel = formatTerminalReason(event.terminalReason, event.success);
|
|
762
|
+
const showReason = event.terminalReason && event.terminalReason !== "completed";
|
|
763
|
+
const reasonSuffix = showReason ? ` | ${reasonLabel}` : "";
|
|
764
|
+
const statusLine = event.success ? `-# $${cost} | ${duration} | ${turns} turns | ${modeLabel}${reasonSuffix}` : `-# ${reasonLabel} | $${cost} | ${duration} | ${turns} turns`;
|
|
765
|
+
ctx.streamer.append(`
|
|
766
|
+
${statusLine}`, { persist: false });
|
|
767
|
+
if (!event.success && event.errors.length) {
|
|
768
|
+
ctx.streamer.append(`
|
|
769
|
+
\`\`\`
|
|
770
|
+
${event.errors.join("\n")}
|
|
771
|
+
\`\`\``, { persist: false });
|
|
772
|
+
}
|
|
773
|
+
await ctx.streamer.finalize();
|
|
774
|
+
const session = getSessionView(ctx.sessionId);
|
|
775
|
+
if (!session) {
|
|
776
|
+
ctx.state.pendingAttachments = [];
|
|
777
|
+
return;
|
|
1057
778
|
}
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
779
|
+
if (ctx.mode === "monitor") {
|
|
780
|
+
await flushDigest(ctx.sessionId);
|
|
781
|
+
await updateSessionState(ctx.sessionId, {
|
|
782
|
+
type: "work_started",
|
|
783
|
+
sessionId: ctx.sessionId,
|
|
784
|
+
source: providerSource(ctx.sessionId),
|
|
785
|
+
confidence: "high",
|
|
786
|
+
timestamp: Date.now(),
|
|
787
|
+
metadata: {
|
|
788
|
+
phase: "\u7B49\u5F85\u76D1\u7763\u5224\u65AD",
|
|
789
|
+
summary: event.success ? "\u672C\u8F6E\u6267\u884C\u7ED3\u675F\uFF0C\u7B49\u5F85\u76D1\u7763\u5224\u65AD" : "\u672C\u8F6E\u6267\u884C\u5931\u8D25\uFF0C\u7B49\u5F85\u76D1\u7763\u5224\u65AD"
|
|
790
|
+
}
|
|
791
|
+
});
|
|
792
|
+
} else {
|
|
793
|
+
ctx.state.deferredResult = {
|
|
794
|
+
event,
|
|
795
|
+
text: lastText,
|
|
796
|
+
attachments: [...ctx.state.pendingAttachments]
|
|
797
|
+
};
|
|
1063
798
|
}
|
|
799
|
+
ctx.state.pendingAttachments = [];
|
|
800
|
+
};
|
|
801
|
+
var permissionDenied = async (event, ctx) => {
|
|
802
|
+
queueDigest(ctx.sessionId, {
|
|
803
|
+
kind: "denied",
|
|
804
|
+
text: `\u26D4 \u6743\u9650\u62D2\u7EDD\uFF1A${truncate(event.toolName, 40)} \u2014 ${truncate(event.reason, 80)}`
|
|
805
|
+
});
|
|
806
|
+
await updateSessionState(ctx.sessionId, {
|
|
807
|
+
type: "permission_denied",
|
|
808
|
+
sessionId: ctx.sessionId,
|
|
809
|
+
source: providerSource(ctx.sessionId),
|
|
810
|
+
confidence: "high",
|
|
811
|
+
timestamp: Date.now(),
|
|
812
|
+
metadata: {
|
|
813
|
+
toolName: event.toolName,
|
|
814
|
+
reason: event.reason,
|
|
815
|
+
source: event.source
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
};
|
|
819
|
+
var errorEvent = async (event, ctx) => {
|
|
820
|
+
console.warn(`[OutputHandler] Session ${ctx.sessionId}: error event \u2014 ${event.message}`);
|
|
821
|
+
ctx.state.hadError = true;
|
|
822
|
+
await ctx.streamer.finalize();
|
|
823
|
+
queueDigest(ctx.sessionId, { kind: "error", text: `\u9519\u8BEF\uFF1A${truncate(event.message, 120)}` });
|
|
824
|
+
if (ctx.mode === "monitor") return;
|
|
825
|
+
const session = getSessionView(ctx.sessionId);
|
|
826
|
+
if (!session) return;
|
|
827
|
+
await flushDigest(ctx.sessionId);
|
|
828
|
+
await updateSessionState(ctx.sessionId, {
|
|
829
|
+
type: "errored",
|
|
830
|
+
sessionId: ctx.sessionId,
|
|
831
|
+
source: providerSource(ctx.sessionId),
|
|
832
|
+
confidence: "high",
|
|
833
|
+
timestamp: Date.now(),
|
|
834
|
+
metadata: { errorMessage: event.message }
|
|
835
|
+
});
|
|
836
|
+
};
|
|
837
|
+
var sessionInit = () => {
|
|
838
|
+
};
|
|
839
|
+
var EVENT_HANDLERS = {
|
|
840
|
+
text_delta: textDelta,
|
|
841
|
+
ask_user: askUser,
|
|
842
|
+
task,
|
|
843
|
+
task_started: taskStarted,
|
|
844
|
+
task_progress: taskProgress,
|
|
845
|
+
task_done: taskDone,
|
|
846
|
+
web_search: webSearch,
|
|
847
|
+
tool_start: toolStart,
|
|
848
|
+
tool_result: toolResult,
|
|
849
|
+
image_file: imageFile,
|
|
850
|
+
command_execution: commandExecution,
|
|
851
|
+
file_change: fileChange,
|
|
852
|
+
reasoning,
|
|
853
|
+
todo_list: todoList,
|
|
854
|
+
permission_denied: permissionDenied,
|
|
855
|
+
result,
|
|
856
|
+
error: errorEvent,
|
|
857
|
+
session_init: sessionInit
|
|
1064
858
|
};
|
|
1065
|
-
|
|
859
|
+
async function dispatchEvent(event, ctx) {
|
|
860
|
+
const handler = EVENT_HANDLERS[event.type];
|
|
861
|
+
if (!handler) return;
|
|
862
|
+
await handler(event, ctx);
|
|
863
|
+
}
|
|
1066
864
|
|
|
1067
865
|
// ../bot/src/discord/status-card.ts
|
|
866
|
+
import {
|
|
867
|
+
EmbedBuilder as EmbedBuilder3
|
|
868
|
+
} from "discord.js";
|
|
1068
869
|
var StatusCard = class {
|
|
1069
870
|
messageId = null;
|
|
1070
871
|
channel;
|
|
@@ -1141,7 +942,7 @@ var StatusCard = class {
|
|
|
1141
942
|
}
|
|
1142
943
|
}
|
|
1143
944
|
buildEmbed(state, data) {
|
|
1144
|
-
const embed = new
|
|
945
|
+
const embed = new EmbedBuilder3().setColor(STATE_COLORS[state]).setTitle(`\u{1F916} ${STATE_LABELS[state]}`).addFields(
|
|
1145
946
|
{ name: "\u8F6E\u6B21", value: `#${data.turn}`, inline: true },
|
|
1146
947
|
{ name: "\u66F4\u65B0", value: `<t:${Math.floor(data.updatedAt / 1e3)}:R>`, inline: true }
|
|
1147
948
|
).setTimestamp();
|
|
@@ -1167,204 +968,102 @@ var StatusCard = class {
|
|
|
1167
968
|
if (data.permissionsSummary) {
|
|
1168
969
|
embed.addFields({ name: "\u6743\u9650", value: data.permissionsSummary, inline: false });
|
|
1169
970
|
}
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
console.warn(`[StatusCard] Phase contains unsuitable content, stripping: ${normalized.slice(0, 60)}...`);
|
|
1181
|
-
return "";
|
|
1182
|
-
}
|
|
1183
|
-
if (normalized.length > 200) {
|
|
1184
|
-
normalized = normalized.slice(0, 197) + "...";
|
|
1185
|
-
}
|
|
1186
|
-
return normalized;
|
|
1187
|
-
}
|
|
1188
|
-
validate(description) {
|
|
1189
|
-
if (!description) return;
|
|
1190
|
-
const normalized = description.trim();
|
|
1191
|
-
if (!normalized) return;
|
|
1192
|
-
if (normalized.length > 200) {
|
|
1193
|
-
throw new Error("\u72B6\u6001\u5361\u63CF\u8FF0\u8FC7\u957F\uFF0C\u5E94\u79FB\u81F3\u6458\u8981\u5361\u6216\u7ED3\u679C\u6D88\u606F");
|
|
1194
|
-
}
|
|
1195
|
-
if (normalized.includes("```")) {
|
|
1196
|
-
throw new Error("\u72B6\u6001\u5361\u4E0D\u5E94\u5305\u542B\u4EE3\u7801\u5757");
|
|
1197
|
-
}
|
|
1198
|
-
if (/diff --git/.test(normalized)) {
|
|
1199
|
-
throw new Error("\u72B6\u6001\u5361\u4E0D\u5E94\u5305\u542B diff");
|
|
1200
|
-
}
|
|
1201
|
-
if (this.isLikelyFileList(normalized)) {
|
|
1202
|
-
throw new Error("\u72B6\u6001\u5361\u4E0D\u5E94\u5305\u542B\u6587\u4EF6\u5217\u8868");
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
|
-
isLikelyFileList(text) {
|
|
1206
|
-
const lines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
1207
|
-
if (lines.length < 2) return false;
|
|
1208
|
-
return lines.every((line) => /^[-+*]\s+[\w./\\-]+$/.test(line));
|
|
1209
|
-
}
|
|
1210
|
-
};
|
|
1211
|
-
|
|
1212
|
-
// ../bot/src/discord/summary-handler.ts
|
|
1213
|
-
import "discord.js";
|
|
1214
|
-
|
|
1215
|
-
// ../bot/src/discord/delivery-policy.ts
|
|
1216
|
-
import { existsSync, statSync } from "fs";
|
|
1217
|
-
var MAX_CHUNK_LIMIT = 2e3;
|
|
1218
|
-
var MAX_ATTACHMENTS = 10;
|
|
1219
|
-
var MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
|
|
1220
|
-
function clampChunkLimit(limit) {
|
|
1221
|
-
if (!Number.isFinite(limit)) return MAX_CHUNK_LIMIT;
|
|
1222
|
-
return Math.max(1, Math.min(Math.trunc(limit), MAX_CHUNK_LIMIT));
|
|
1223
|
-
}
|
|
1224
|
-
function chunkText(text, limit, mode) {
|
|
1225
|
-
const safeLimit = clampChunkLimit(limit);
|
|
1226
|
-
if (text.length <= safeLimit) return [text];
|
|
1227
|
-
const out = [];
|
|
1228
|
-
let rest = text;
|
|
1229
|
-
while (rest.length > safeLimit) {
|
|
1230
|
-
let cut = safeLimit;
|
|
1231
|
-
if (mode === "newline") {
|
|
1232
|
-
const para = rest.lastIndexOf("\n\n", safeLimit);
|
|
1233
|
-
const line = rest.lastIndexOf("\n", safeLimit);
|
|
1234
|
-
const space = rest.lastIndexOf(" ", safeLimit);
|
|
1235
|
-
cut = para > safeLimit / 2 ? para : line > safeLimit / 2 ? line : space > 0 ? space : safeLimit;
|
|
1236
|
-
}
|
|
1237
|
-
out.push(rest.slice(0, cut));
|
|
1238
|
-
rest = rest.slice(cut).replace(/^\n+/, "");
|
|
1239
|
-
}
|
|
1240
|
-
if (rest) out.push(rest);
|
|
1241
|
-
return out;
|
|
1242
|
-
}
|
|
1243
|
-
function validateOutboundAttachments(files) {
|
|
1244
|
-
if (files.length > MAX_ATTACHMENTS) {
|
|
1245
|
-
throw new Error(`\u6700\u591A\u53EA\u80FD\u53D1\u9001 ${MAX_ATTACHMENTS} \u4E2A\u9644\u4EF6`);
|
|
1246
|
-
}
|
|
1247
|
-
for (const filePath of files) {
|
|
1248
|
-
if (!filePath.trim()) {
|
|
1249
|
-
throw new Error("\u9644\u4EF6\u8DEF\u5F84\u65E0\u6548");
|
|
1250
|
-
}
|
|
1251
|
-
if (!existsSync(filePath)) continue;
|
|
1252
|
-
const stats = statSync(filePath);
|
|
1253
|
-
if (stats.isDirectory()) {
|
|
1254
|
-
throw new Error(`\u9644\u4EF6\u8DEF\u5F84\u4E0D\u80FD\u662F\u76EE\u5F55: ${filePath}`);
|
|
1255
|
-
}
|
|
1256
|
-
if (stats.size > MAX_ATTACHMENT_BYTES) {
|
|
1257
|
-
throw new Error(`\u9644\u4EF6 ${filePath} \u5927\u5C0F\u8D85\u8FC7 ${MAX_ATTACHMENT_BYTES} bytes`);
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
function buildDeliveryPlan(input) {
|
|
1262
|
-
validateOutboundAttachments(input.files);
|
|
1263
|
-
const chunks = chunkText(input.text, input.policy.textChunkLimit, input.policy.chunkMode);
|
|
1264
|
-
const filesOnFirstChunk = input.files.slice(0, MAX_ATTACHMENTS);
|
|
1265
|
-
const replyToMode = input.policy.replyToMode;
|
|
1266
|
-
const replyToMessageId = input.mode === "system_notice" || input.mode === "summary" ? void 0 : replyToMode === "off" ? void 0 : input.replyToMessageId;
|
|
1267
|
-
return {
|
|
1268
|
-
sessionId: input.sessionId,
|
|
1269
|
-
chatId: input.chatId,
|
|
1270
|
-
replyToMessageId,
|
|
1271
|
-
replyToMode,
|
|
1272
|
-
editTargetMessageId: input.editTargetMessageId,
|
|
1273
|
-
chunks,
|
|
1274
|
-
filesOnFirstChunk,
|
|
1275
|
-
mode: input.mode
|
|
1276
|
-
};
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
// ../bot/src/discord/delivery.ts
|
|
1280
|
-
var sessionState = /* @__PURE__ */ new Map();
|
|
1281
|
-
function getSessionState(sessionId) {
|
|
1282
|
-
if (!sessionState.has(sessionId)) {
|
|
1283
|
-
sessionState.set(sessionId, {});
|
|
1284
|
-
}
|
|
1285
|
-
const state = sessionState.get(sessionId);
|
|
1286
|
-
state.lastUsed = Date.now();
|
|
1287
|
-
return state;
|
|
1288
|
-
}
|
|
1289
|
-
function pruneStaleSessionStates(maxAgeMs = 5 * 60 * 1e3) {
|
|
1290
|
-
const cutoff = Date.now() - maxAgeMs;
|
|
1291
|
-
for (const [sessionId, state] of sessionState) {
|
|
1292
|
-
if (!state.lastUsed || state.lastUsed < cutoff) {
|
|
1293
|
-
sessionState.delete(sessionId);
|
|
971
|
+
if (data.todoList && data.todoList.length > 0) {
|
|
972
|
+
const rendered = this.renderTodoList(data.todoList);
|
|
973
|
+
if (rendered) {
|
|
974
|
+
const completed = data.todoList.filter((t) => t.completed).length;
|
|
975
|
+
embed.addFields({
|
|
976
|
+
name: `\u5F85\u529E\uFF08${completed}/${data.todoList.length}\uFF09`,
|
|
977
|
+
value: rendered,
|
|
978
|
+
inline: false
|
|
979
|
+
});
|
|
980
|
+
}
|
|
1294
981
|
}
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
}
|
|
1305
|
-
}
|
|
1306
|
-
async function sendAckReaction(message, reaction) {
|
|
1307
|
-
if (!reaction) return;
|
|
1308
|
-
try {
|
|
1309
|
-
await message.react(reaction);
|
|
1310
|
-
} catch {
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
async function sendChunk(channel, chunk, options = {}) {
|
|
1314
|
-
const payload = { content: chunk };
|
|
1315
|
-
if (options.files?.length) payload.files = options.files;
|
|
1316
|
-
if (options.replyToMessageId) {
|
|
1317
|
-
payload.reply = { messageReference: options.replyToMessageId };
|
|
1318
|
-
}
|
|
1319
|
-
try {
|
|
1320
|
-
const message = await channel.send(payload);
|
|
1321
|
-
return message.id;
|
|
1322
|
-
} catch (error) {
|
|
1323
|
-
if (!options.replyToMessageId) {
|
|
1324
|
-
throw error;
|
|
982
|
+
if (data.batchApprovalMode) {
|
|
983
|
+
const pending = data.pendingApprovals ?? [];
|
|
984
|
+
const queueLine = pending.length === 0 ? "\u961F\u5217\u4E3A\u7A7A" : pending.slice(0, 5).map((a) => `\u2022 ${truncate(a.toolName, 32)} \u2014 ${truncate(a.detail, 80)}`).join("\n");
|
|
985
|
+
embed.addFields({
|
|
986
|
+
name: `\u6279\u91CF\u5BA1\u6279\uFF08${pending.length} \u5F85\u6279\uFF09`,
|
|
987
|
+
value: queueLine + (pending.length > 5 ? `
|
|
988
|
+
\u2026\u8FD8\u6709 ${pending.length - 5} \u6761` : ""),
|
|
989
|
+
inline: false
|
|
990
|
+
});
|
|
1325
991
|
}
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
return fallbackMessage.id;
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
async function deliver(channel, plan) {
|
|
1333
|
-
pruneStaleSessionStates();
|
|
1334
|
-
const state = getSessionState(plan.sessionId);
|
|
1335
|
-
if (plan.mode === "progress_update") {
|
|
1336
|
-
const targetMessageId = plan.editTargetMessageId ?? state.recentProgressMessageId;
|
|
1337
|
-
if (targetMessageId && plan.chunks.length === 1 && plan.filesOnFirstChunk.length === 0) {
|
|
1338
|
-
try {
|
|
1339
|
-
await channel.messages?.edit(targetMessageId, { content: plan.chunks[0] });
|
|
1340
|
-
state.recentProgressMessageId = targetMessageId;
|
|
1341
|
-
return [targetMessageId];
|
|
1342
|
-
} catch {
|
|
1343
|
-
}
|
|
992
|
+
if (data.recentPermissionDenials && data.recentPermissionDenials.length > 0) {
|
|
993
|
+
const lines = data.recentPermissionDenials.slice(0, 3).map((d) => `\u26D4 ${truncate(d.toolName, 28)} \u2014 ${truncate(d.reason, 80)}`).join("\n");
|
|
994
|
+
embed.addFields({ name: "\u6700\u8FD1\u62D2\u7EDD", value: lines, inline: false });
|
|
1344
995
|
}
|
|
996
|
+
return embed;
|
|
1345
997
|
}
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
const
|
|
1350
|
-
const
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
998
|
+
renderTodoList(items) {
|
|
999
|
+
const TOTAL_BUDGET = 950;
|
|
1000
|
+
const MAX_LINES = 8;
|
|
1001
|
+
const MAX_CHAR_PER_LINE = 180;
|
|
1002
|
+
const lines = [];
|
|
1003
|
+
let used = 0;
|
|
1004
|
+
let renderedCount = 0;
|
|
1005
|
+
for (const item of items.slice(0, MAX_LINES)) {
|
|
1006
|
+
const mark = item.completed ? "\u2611" : "\u2610";
|
|
1007
|
+
const line = `${mark} ${truncate(item.text, MAX_CHAR_PER_LINE)}`;
|
|
1008
|
+
const cost = (lines.length > 0 ? 1 : 0) + line.length;
|
|
1009
|
+
if (used + cost > TOTAL_BUDGET) break;
|
|
1010
|
+
lines.push(line);
|
|
1011
|
+
used += cost;
|
|
1012
|
+
renderedCount++;
|
|
1013
|
+
}
|
|
1014
|
+
const hidden = items.length - renderedCount;
|
|
1015
|
+
if (hidden > 0) {
|
|
1016
|
+
const more = `\u2026 \u8FD8\u6709 ${hidden} \u9879`;
|
|
1017
|
+
const cost = (lines.length > 0 ? 1 : 0) + more.length;
|
|
1018
|
+
if (used + cost <= TOTAL_BUDGET) lines.push(more);
|
|
1019
|
+
}
|
|
1020
|
+
return lines.join("\n");
|
|
1355
1021
|
}
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1022
|
+
/**
|
|
1023
|
+
* Sanitize phase text for status card display.
|
|
1024
|
+
* Returns cleaned text or empty string if unsuitable.
|
|
1025
|
+
*/
|
|
1026
|
+
sanitizePhase(description) {
|
|
1027
|
+
let normalized = description.trim();
|
|
1028
|
+
if (!normalized) return "";
|
|
1029
|
+
if (normalized.includes("```") || /diff --git/.test(normalized) || this.isLikelyFileList(normalized)) {
|
|
1030
|
+
console.warn(`[StatusCard] Phase contains unsuitable content, stripping: ${normalized.slice(0, 60)}...`);
|
|
1031
|
+
return "";
|
|
1032
|
+
}
|
|
1033
|
+
if (normalized.length > 200) {
|
|
1034
|
+
normalized = normalized.slice(0, 197) + "...";
|
|
1361
1035
|
}
|
|
1036
|
+
return normalized;
|
|
1362
1037
|
}
|
|
1363
|
-
|
|
1364
|
-
|
|
1038
|
+
validate(description) {
|
|
1039
|
+
if (!description) return;
|
|
1040
|
+
const normalized = description.trim();
|
|
1041
|
+
if (!normalized) return;
|
|
1042
|
+
if (normalized.length > 200) {
|
|
1043
|
+
throw new Error("\u72B6\u6001\u5361\u63CF\u8FF0\u8FC7\u957F\uFF0C\u5E94\u79FB\u81F3\u6458\u8981\u5361\u6216\u7ED3\u679C\u6D88\u606F");
|
|
1044
|
+
}
|
|
1045
|
+
if (normalized.includes("```")) {
|
|
1046
|
+
throw new Error("\u72B6\u6001\u5361\u4E0D\u5E94\u5305\u542B\u4EE3\u7801\u5757");
|
|
1047
|
+
}
|
|
1048
|
+
if (/diff --git/.test(normalized)) {
|
|
1049
|
+
throw new Error("\u72B6\u6001\u5361\u4E0D\u5E94\u5305\u542B diff");
|
|
1050
|
+
}
|
|
1051
|
+
if (this.isLikelyFileList(normalized)) {
|
|
1052
|
+
throw new Error("\u72B6\u6001\u5361\u4E0D\u5E94\u5305\u542B\u6587\u4EF6\u5217\u8868");
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
isLikelyFileList(text) {
|
|
1056
|
+
const lines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
1057
|
+
if (lines.length < 2) return false;
|
|
1058
|
+
return lines.every((line) => /^[-+*]\s+[\w./\\-]+$/.test(line));
|
|
1059
|
+
}
|
|
1060
|
+
};
|
|
1061
|
+
|
|
1062
|
+
// ../bot/src/discord/summary-handler.ts
|
|
1063
|
+
import "discord.js";
|
|
1365
1064
|
|
|
1366
1065
|
// ../bot/src/discord/digest-delivery.ts
|
|
1367
|
-
import { EmbedBuilder as
|
|
1066
|
+
import { EmbedBuilder as EmbedBuilder4 } from "discord.js";
|
|
1368
1067
|
var DigestDelivery = class {
|
|
1369
1068
|
channel;
|
|
1370
1069
|
messageIds = [];
|
|
@@ -1423,7 +1122,7 @@ var DigestDelivery = class {
|
|
|
1423
1122
|
return { oldMessageIds, newMessageIds };
|
|
1424
1123
|
}
|
|
1425
1124
|
buildDigestEmbed(content, index, total) {
|
|
1426
|
-
const embed = new
|
|
1125
|
+
const embed = new EmbedBuilder4().setColor(3447003).setDescription(content).setTimestamp();
|
|
1427
1126
|
if (index === 0) embed.setTitle("\u{1F4CC} \u6700\u8FD1\u6458\u8981");
|
|
1428
1127
|
if (total > 1) {
|
|
1429
1128
|
embed.setFooter({ text: `\u7B2C ${index + 1}/${total} \u90E8\u5206` });
|
|
@@ -1505,10 +1204,10 @@ var SummaryHandler = class {
|
|
|
1505
1204
|
|
|
1506
1205
|
// ../bot/src/discord/interaction-card.ts
|
|
1507
1206
|
import {
|
|
1508
|
-
EmbedBuilder as
|
|
1509
|
-
ActionRowBuilder,
|
|
1510
|
-
ButtonBuilder,
|
|
1511
|
-
ButtonStyle
|
|
1207
|
+
EmbedBuilder as EmbedBuilder5,
|
|
1208
|
+
ActionRowBuilder as ActionRowBuilder2,
|
|
1209
|
+
ButtonBuilder as ButtonBuilder2,
|
|
1210
|
+
ButtonStyle as ButtonStyle2
|
|
1512
1211
|
} from "discord.js";
|
|
1513
1212
|
var InteractionCard = class {
|
|
1514
1213
|
channel;
|
|
@@ -1518,16 +1217,16 @@ var InteractionCard = class {
|
|
|
1518
1217
|
}
|
|
1519
1218
|
async show(sessionId, turn, detail, options = {}) {
|
|
1520
1219
|
const customIdBase = `awaiting_human:${sessionId}:${turn}`;
|
|
1521
|
-
const embed = new
|
|
1220
|
+
const embed = new EmbedBuilder5().setTitle("\u23F8\uFE0F \u7B49\u5F85\u4EBA\u5DE5\u5904\u7406").setDescription(detail).setColor(16755200).setTimestamp();
|
|
1522
1221
|
embed.addFields(
|
|
1523
1222
|
{ name: "\u8F6E\u6B21", value: `#${turn}`, inline: true },
|
|
1524
1223
|
{ name: "\u7B49\u5F85\u81EA", value: `<t:${Math.floor(Date.now() / 1e3)}:R>`, inline: true }
|
|
1525
1224
|
);
|
|
1526
1225
|
let components = [];
|
|
1527
1226
|
if (options.remoteHumanControl !== false) {
|
|
1528
|
-
const row = new
|
|
1529
|
-
new
|
|
1530
|
-
new
|
|
1227
|
+
const row = new ActionRowBuilder2().addComponents(
|
|
1228
|
+
new ButtonBuilder2().setCustomId(`${customIdBase}:approve`).setLabel("\u5141\u8BB8\u7EE7\u7EED").setStyle(ButtonStyle2.Success),
|
|
1229
|
+
new ButtonBuilder2().setCustomId(`${customIdBase}:deny`).setLabel("\u62D2\u7EDD").setStyle(ButtonStyle2.Danger)
|
|
1531
1230
|
);
|
|
1532
1231
|
components = [row];
|
|
1533
1232
|
} else {
|
|
@@ -1795,24 +1494,98 @@ var PerformanceTracker = class {
|
|
|
1795
1494
|
};
|
|
1796
1495
|
var performanceTracker = new PerformanceTracker();
|
|
1797
1496
|
|
|
1798
|
-
// ../
|
|
1799
|
-
var
|
|
1800
|
-
var
|
|
1801
|
-
function
|
|
1802
|
-
|
|
1803
|
-
pendingAnswersStore.set(sessionId, /* @__PURE__ */ new Map());
|
|
1804
|
-
}
|
|
1805
|
-
pendingAnswersStore.get(sessionId).set(questionIndex, answer);
|
|
1497
|
+
// ../bot/src/panel/panel-state.ts
|
|
1498
|
+
var sessionPanels = /* @__PURE__ */ new Map();
|
|
1499
|
+
var sessionInitializationPromises = /* @__PURE__ */ new Map();
|
|
1500
|
+
function getPanel(sessionId) {
|
|
1501
|
+
return sessionPanels.get(sessionId);
|
|
1806
1502
|
}
|
|
1807
|
-
function
|
|
1808
|
-
|
|
1503
|
+
function setPanel(sessionId, panel) {
|
|
1504
|
+
sessionPanels.set(sessionId, panel);
|
|
1809
1505
|
}
|
|
1810
|
-
function
|
|
1811
|
-
|
|
1812
|
-
questionCountStore.delete(sessionId);
|
|
1506
|
+
function deletePanel(sessionId) {
|
|
1507
|
+
sessionPanels.delete(sessionId);
|
|
1813
1508
|
}
|
|
1814
|
-
function
|
|
1815
|
-
return
|
|
1509
|
+
function getAllPanels() {
|
|
1510
|
+
return sessionPanels.entries();
|
|
1511
|
+
}
|
|
1512
|
+
function getPanelCount() {
|
|
1513
|
+
return sessionPanels.size;
|
|
1514
|
+
}
|
|
1515
|
+
function getInitializationPromise(sessionId) {
|
|
1516
|
+
return sessionInitializationPromises.get(sessionId);
|
|
1517
|
+
}
|
|
1518
|
+
function setInitializationPromise(sessionId, promise) {
|
|
1519
|
+
sessionInitializationPromises.set(sessionId, promise);
|
|
1520
|
+
}
|
|
1521
|
+
function deleteInitializationPromise(sessionId) {
|
|
1522
|
+
sessionInitializationPromises.delete(sessionId);
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
// ../bot/src/panel/panel-relocation.ts
|
|
1526
|
+
var RELOCATION_TIMEOUT_MS = 15e3;
|
|
1527
|
+
async function relocateSessionPanelToBottom(sessionId, channel, initialize) {
|
|
1528
|
+
let panel = getPanel(sessionId);
|
|
1529
|
+
if (!panel && channel) {
|
|
1530
|
+
const session = getSessionView(sessionId);
|
|
1531
|
+
const initPromise = initialize(sessionId, channel, {
|
|
1532
|
+
statusCardMessageId: session?.statusCardMessageId,
|
|
1533
|
+
initialTurn: session?.currentTurn || 1
|
|
1534
|
+
});
|
|
1535
|
+
let timeoutHandle;
|
|
1536
|
+
const timeout = new Promise((_, reject) => {
|
|
1537
|
+
timeoutHandle = setTimeout(
|
|
1538
|
+
() => reject(new Error("Panel initialization timeout")),
|
|
1539
|
+
RELOCATION_TIMEOUT_MS
|
|
1540
|
+
);
|
|
1541
|
+
});
|
|
1542
|
+
try {
|
|
1543
|
+
await Promise.race([initPromise, timeout]);
|
|
1544
|
+
} finally {
|
|
1545
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
1546
|
+
}
|
|
1547
|
+
panel = getPanel(sessionId);
|
|
1548
|
+
}
|
|
1549
|
+
if (!panel) return;
|
|
1550
|
+
let statusRelocation = null;
|
|
1551
|
+
try {
|
|
1552
|
+
statusRelocation = await panel.statusCard.recreateAtBottom();
|
|
1553
|
+
} catch (error) {
|
|
1554
|
+
console.warn(`\u72B6\u6001\u6D88\u606F\u8FC1\u79FB\u5931\u8D25 (${sessionId})\uFF1A`, error);
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1557
|
+
let digestRelocation = { oldMessageIds: [], newMessageIds: [] };
|
|
1558
|
+
try {
|
|
1559
|
+
digestRelocation = await panel.summaryHandler.relocateDigestToBottom();
|
|
1560
|
+
} catch (error) {
|
|
1561
|
+
console.warn(`\u6458\u8981\u6D88\u606F\u8FC1\u79FB\u5931\u8D25 (${sessionId})\uFF1A`, error);
|
|
1562
|
+
if (statusRelocation?.oldMessageId && statusRelocation.newMessageId) {
|
|
1563
|
+
panel.statusCard.adopt(statusRelocation.oldMessageId);
|
|
1564
|
+
await panel.channel.messages.delete(statusRelocation.newMessageId).catch(
|
|
1565
|
+
(e) => console.warn(
|
|
1566
|
+
`[PanelAdapter] Failed to cleanup new status card (${sessionId}): ${e.message}`
|
|
1567
|
+
)
|
|
1568
|
+
);
|
|
1569
|
+
}
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
if (statusRelocation?.newMessageId) {
|
|
1573
|
+
setStatusCardBinding(sessionId, { messageId: statusRelocation.newMessageId });
|
|
1574
|
+
}
|
|
1575
|
+
if (statusRelocation?.oldMessageId) {
|
|
1576
|
+
await panel.channel.messages.delete(statusRelocation.oldMessageId).catch(
|
|
1577
|
+
(e) => console.warn(
|
|
1578
|
+
`[PanelAdapter] Failed to delete old status card (${sessionId}): ${e.message}`
|
|
1579
|
+
)
|
|
1580
|
+
);
|
|
1581
|
+
}
|
|
1582
|
+
for (const messageId of digestRelocation.oldMessageIds) {
|
|
1583
|
+
await panel.channel.messages.delete(messageId).catch(
|
|
1584
|
+
(e) => console.warn(
|
|
1585
|
+
`[PanelAdapter] Failed to delete old digest (${sessionId}): ${e.message}`
|
|
1586
|
+
)
|
|
1587
|
+
);
|
|
1588
|
+
}
|
|
1816
1589
|
}
|
|
1817
1590
|
|
|
1818
1591
|
// ../bot/src/bot-services-helpers.ts
|
|
@@ -1868,15 +1641,82 @@ async function notifyUnmanagedCodexHint(client, sessionId, channelId) {
|
|
|
1868
1641
|
}
|
|
1869
1642
|
}
|
|
1870
1643
|
|
|
1644
|
+
// ../bot/src/panel/panel-performance.ts
|
|
1645
|
+
var SESSION_INACTIVE_TIMEOUT_MS = 36e5;
|
|
1646
|
+
function createCleanupSessionPanel(renderer) {
|
|
1647
|
+
return function cleanupSessionPanel2(sessionId) {
|
|
1648
|
+
const panel = getPanel(sessionId);
|
|
1649
|
+
if (panel) panel.cleanup();
|
|
1650
|
+
deletePanel(sessionId);
|
|
1651
|
+
renderer.clear(sessionId);
|
|
1652
|
+
stateMachine.clearSession(sessionId);
|
|
1653
|
+
clearPendingAnswers(sessionId);
|
|
1654
|
+
cleanupSessionDeliveryState(sessionId);
|
|
1655
|
+
clearCodexHint(sessionId);
|
|
1656
|
+
cleanupSessionAttachments(sessionId).catch(
|
|
1657
|
+
(e) => console.warn(
|
|
1658
|
+
`[PanelAdapter] Failed to cleanup attachments (${sessionId}): ${e.message}`
|
|
1659
|
+
)
|
|
1660
|
+
);
|
|
1661
|
+
const activeGate = gateService.getActiveGateForSession(sessionId);
|
|
1662
|
+
if (activeGate) {
|
|
1663
|
+
gateService.resolveFromDiscord(activeGate.id, "reject").catch(
|
|
1664
|
+
(e) => console.warn(
|
|
1665
|
+
`[PanelAdapter] Failed to invalidate gate on cleanup (${sessionId}): ${e.message}`
|
|
1666
|
+
)
|
|
1667
|
+
);
|
|
1668
|
+
}
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
function createCleanupInactiveSessions(renderer) {
|
|
1672
|
+
return function cleanupInactiveSessions2() {
|
|
1673
|
+
const now = Date.now();
|
|
1674
|
+
for (const [sessionId, panel] of getAllPanels()) {
|
|
1675
|
+
if (now - panel.getLastActivity() > SESSION_INACTIVE_TIMEOUT_MS) {
|
|
1676
|
+
panel.cleanup();
|
|
1677
|
+
deletePanel(sessionId);
|
|
1678
|
+
renderer.clear(sessionId);
|
|
1679
|
+
console.log(`\u6E05\u7406\u5931\u6D3B\u4F1A\u8BDD\u72B6\u6001\u6295\u5F71: ${sessionId}`);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1684
|
+
function createGetPerformanceStats() {
|
|
1685
|
+
return function getPerformanceStats2() {
|
|
1686
|
+
let projectionCount = 0;
|
|
1687
|
+
for (const [, panel] of getAllPanels()) {
|
|
1688
|
+
if (panel.getCachedProjection() !== null) projectionCount++;
|
|
1689
|
+
}
|
|
1690
|
+
return {
|
|
1691
|
+
discoveryLatency: performanceTracker.getMetricStats("session_discovery_latency"),
|
|
1692
|
+
updateLatency: performanceTracker.getMetricStats("state_update_latency"),
|
|
1693
|
+
activeSessions: getPanelCount(),
|
|
1694
|
+
projectionCount
|
|
1695
|
+
};
|
|
1696
|
+
};
|
|
1697
|
+
}
|
|
1698
|
+
var cleanupInterval = null;
|
|
1699
|
+
function startPerformanceMonitoring(onTick) {
|
|
1700
|
+
if (cleanupInterval) return;
|
|
1701
|
+
cleanupInterval = setInterval(() => {
|
|
1702
|
+
onTick();
|
|
1703
|
+
performanceTracker.takeSnapshot();
|
|
1704
|
+
performanceTracker.cleanup();
|
|
1705
|
+
}, 6e4);
|
|
1706
|
+
}
|
|
1707
|
+
function stopPerformanceMonitoring() {
|
|
1708
|
+
if (cleanupInterval) {
|
|
1709
|
+
clearInterval(cleanupInterval);
|
|
1710
|
+
cleanupInterval = null;
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
function generatePerformanceReport() {
|
|
1714
|
+
return performanceTracker.generateReport();
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1871
1717
|
// ../bot/src/panel-adapter.ts
|
|
1872
|
-
var sessionPanels = /* @__PURE__ */ new Map();
|
|
1873
|
-
var sessionInitializationPromises = /* @__PURE__ */ new Map();
|
|
1874
1718
|
var BATCH_UPDATE_DELAY_MS = 500;
|
|
1875
1719
|
var statusCardProjectionRenderer = new StatusCardProjectionRenderer();
|
|
1876
|
-
var SESSION_INACTIVE_TIMEOUT_MS = 36e5;
|
|
1877
|
-
function getPanel(sessionId) {
|
|
1878
|
-
return sessionPanels.get(sessionId);
|
|
1879
|
-
}
|
|
1880
1720
|
function getSessionProjection(sessionId) {
|
|
1881
1721
|
return stateMachine.getSnapshot(sessionId);
|
|
1882
1722
|
}
|
|
@@ -1889,20 +1729,18 @@ function ensureProjectionTurn(sessionId, turn = 1, event = "turn_bootstrap") {
|
|
|
1889
1729
|
}
|
|
1890
1730
|
function cacheProjection(sessionId, projection) {
|
|
1891
1731
|
const panel = getPanel(sessionId);
|
|
1892
|
-
if (panel)
|
|
1893
|
-
panel.updateProjection(projection);
|
|
1894
|
-
}
|
|
1732
|
+
if (panel) panel.updateProjection(projection);
|
|
1895
1733
|
}
|
|
1896
|
-
|
|
1734
|
+
stateMachine.registerTurnStatePersister((sessionId, projection) => {
|
|
1897
1735
|
updateSession(sessionId, {
|
|
1898
1736
|
currentTurn: projection.turn,
|
|
1899
1737
|
humanResolved: projection.humanResolved
|
|
1900
1738
|
});
|
|
1901
|
-
}
|
|
1739
|
+
});
|
|
1902
1740
|
function createStatusCardProjectionContext(sessionId) {
|
|
1903
1741
|
const panel = getPanel(sessionId);
|
|
1904
1742
|
if (!panel) return void 0;
|
|
1905
|
-
const session =
|
|
1743
|
+
const session = getSessionView(sessionId);
|
|
1906
1744
|
return {
|
|
1907
1745
|
statusCard: panel.statusCard,
|
|
1908
1746
|
remoteHumanControl: session?.remoteHumanControl,
|
|
@@ -1929,17 +1767,16 @@ async function scheduleProjectionRender(sessionId, projection, updateKey) {
|
|
|
1929
1767
|
);
|
|
1930
1768
|
}
|
|
1931
1769
|
function resolveProviderSource(sessionId, fallback = "claude") {
|
|
1932
|
-
const session =
|
|
1770
|
+
const session = getSessionView(sessionId);
|
|
1933
1771
|
return session?.provider === "codex" ? "codex" : fallback;
|
|
1934
1772
|
}
|
|
1935
1773
|
async function initializeSessionPanel(sessionId, channel, options = {}) {
|
|
1936
1774
|
performanceTracker.startSessionDiscovery(sessionId);
|
|
1937
|
-
|
|
1938
|
-
if (existing) {
|
|
1775
|
+
if (getPanel(sessionId)) {
|
|
1939
1776
|
performanceTracker.endSessionDiscovery(sessionId, { cached: true });
|
|
1940
1777
|
return;
|
|
1941
1778
|
}
|
|
1942
|
-
const pendingInitialization =
|
|
1779
|
+
const pendingInitialization = getInitializationPromise(sessionId);
|
|
1943
1780
|
if (pendingInitialization) {
|
|
1944
1781
|
await pendingInitialization;
|
|
1945
1782
|
performanceTracker.endSessionDiscovery(sessionId, { cached: true });
|
|
@@ -1947,7 +1784,7 @@ async function initializeSessionPanel(sessionId, channel, options = {}) {
|
|
|
1947
1784
|
}
|
|
1948
1785
|
const initialization = (async () => {
|
|
1949
1786
|
const panel = new SessionPanelComponent(sessionId, channel);
|
|
1950
|
-
const session =
|
|
1787
|
+
const session = getSessionView(sessionId);
|
|
1951
1788
|
await panel.initialize({
|
|
1952
1789
|
statusCardMessageId: options.statusCardMessageId,
|
|
1953
1790
|
initialTurn: options.initialTurn,
|
|
@@ -1956,7 +1793,7 @@ async function initializeSessionPanel(sessionId, channel, options = {}) {
|
|
|
1956
1793
|
provider: session?.provider,
|
|
1957
1794
|
permissionsSummary: session ? getSessionPermissionSummary(session) : void 0
|
|
1958
1795
|
});
|
|
1959
|
-
|
|
1796
|
+
setPanel(sessionId, panel);
|
|
1960
1797
|
setStatusCardBinding(sessionId, {
|
|
1961
1798
|
messageId: panel.getMessageId() ?? options.statusCardMessageId
|
|
1962
1799
|
});
|
|
@@ -1967,24 +1804,22 @@ async function initializeSessionPanel(sessionId, channel, options = {}) {
|
|
|
1967
1804
|
);
|
|
1968
1805
|
cacheProjection(sessionId, projection);
|
|
1969
1806
|
})();
|
|
1970
|
-
|
|
1807
|
+
setInitializationPromise(sessionId, initialization);
|
|
1971
1808
|
try {
|
|
1972
1809
|
await initialization;
|
|
1973
1810
|
performanceTracker.endSessionDiscovery(sessionId, { cached: false });
|
|
1974
1811
|
} finally {
|
|
1975
|
-
|
|
1812
|
+
deleteInitializationPromise(sessionId);
|
|
1976
1813
|
}
|
|
1977
1814
|
}
|
|
1978
1815
|
async function registerExistingStatusCard(sessionId, channel, statusCardMessageId) {
|
|
1979
|
-
await initializeSessionPanel(sessionId, channel, {
|
|
1980
|
-
statusCardMessageId
|
|
1981
|
-
});
|
|
1816
|
+
await initializeSessionPanel(sessionId, channel, { statusCardMessageId });
|
|
1982
1817
|
}
|
|
1983
1818
|
async function updateSessionState(sessionId, event, options = {}) {
|
|
1984
1819
|
const updateKey = `${sessionId}:state`;
|
|
1985
1820
|
performanceTracker.startStateUpdate(updateKey);
|
|
1986
1821
|
if (!getPanel(sessionId) && options.channel) {
|
|
1987
|
-
const session2 =
|
|
1822
|
+
const session2 = getSessionView(sessionId);
|
|
1988
1823
|
await initializeSessionPanel(sessionId, options.channel, {
|
|
1989
1824
|
statusCardMessageId: session2?.statusCardMessageId,
|
|
1990
1825
|
initialTurn: session2?.currentTurn || 1
|
|
@@ -1999,7 +1834,7 @@ async function updateSessionState(sessionId, event, options = {}) {
|
|
|
1999
1834
|
performanceTracker.endStateUpdate(updateKey, { skipped: true });
|
|
2000
1835
|
return null;
|
|
2001
1836
|
}
|
|
2002
|
-
const session =
|
|
1837
|
+
const session = getSessionView(sessionId);
|
|
2003
1838
|
if (platformEvent.source === "codex" && platformEvent.type === "completed" && session?.isGenerating) {
|
|
2004
1839
|
performanceTracker.endStateUpdate(updateKey, { skipped: true });
|
|
2005
1840
|
return getSessionProjection(sessionId);
|
|
@@ -2008,12 +1843,12 @@ async function updateSessionState(sessionId, event, options = {}) {
|
|
|
2008
1843
|
const projection = stateMachine.applyPlatformEvent(platformEvent);
|
|
2009
1844
|
cacheProjection(sessionId, projection);
|
|
2010
1845
|
const stateChanged = projection.state !== previousProjection.state || projection.turn !== previousProjection.turn || projection.phase !== previousProjection.phase;
|
|
2011
|
-
|
|
1846
|
+
const contextChanged = projection.todoList !== previousProjection.todoList || projection.recentPermissionDenials !== previousProjection.recentPermissionDenials || projection.batchApprovalMode !== previousProjection.batchApprovalMode || projection.pendingApprovals !== previousProjection.pendingApprovals;
|
|
1847
|
+
if (stateChanged || contextChanged) {
|
|
2012
1848
|
await scheduleProjectionRender(sessionId, projection, updateKey);
|
|
2013
1849
|
} else {
|
|
2014
1850
|
performanceTracker.endStateUpdate(updateKey, { skipped: true });
|
|
2015
1851
|
}
|
|
2016
|
-
persistTurnState(sessionId, projection);
|
|
2017
1852
|
return projection;
|
|
2018
1853
|
}
|
|
2019
1854
|
async function handleResultEvent(sessionId, event, textContent, attachments = []) {
|
|
@@ -2035,10 +1870,13 @@ async function handleResultEvent(sessionId, event, textContent, attachments = []
|
|
|
2035
1870
|
metadata: { from: "result" }
|
|
2036
1871
|
});
|
|
2037
1872
|
} else if (!event.success) {
|
|
2038
|
-
const session =
|
|
1873
|
+
const session = getSessionView(sessionId);
|
|
2039
1874
|
const failureText = textContent.trim() || event.errors.join("\n").trim() || "\u4EFB\u52A1\u5931\u8D25";
|
|
1875
|
+
const reasonLabel = event.terminalReason && event.terminalReason !== "completed" ? formatTerminalReason(event.terminalReason, false) : null;
|
|
1876
|
+
const withReason = reasonLabel ? `\uFF08${reasonLabel}\uFF09
|
|
1877
|
+
${failureText}` : failureText;
|
|
2040
1878
|
await panel.summaryHandler.sendTurnFailure(
|
|
2041
|
-
|
|
1879
|
+
withReason,
|
|
2042
1880
|
projection.turn,
|
|
2043
1881
|
session?.lastInboundMessageId,
|
|
2044
1882
|
attachments
|
|
@@ -2053,7 +1891,7 @@ async function handleResultEvent(sessionId, event, textContent, attachments = []
|
|
|
2053
1891
|
});
|
|
2054
1892
|
} else {
|
|
2055
1893
|
const beforeProjection = getSessionProjection(sessionId);
|
|
2056
|
-
const session =
|
|
1894
|
+
const session = getSessionView(sessionId);
|
|
2057
1895
|
await panel.summaryHandler.sendTurnSummary(
|
|
2058
1896
|
textContent,
|
|
2059
1897
|
beforeProjection.turn,
|
|
@@ -2061,7 +1899,6 @@ async function handleResultEvent(sessionId, event, textContent, attachments = []
|
|
|
2061
1899
|
attachments
|
|
2062
1900
|
);
|
|
2063
1901
|
const projectionAfterTurn = stateMachine.advanceTurnToIdle(sessionId);
|
|
2064
|
-
persistTurnState(sessionId, projectionAfterTurn);
|
|
2065
1902
|
await renderProjectionToStatusCard(sessionId, projectionAfterTurn);
|
|
2066
1903
|
cacheProjection(sessionId, projectionAfterTurn);
|
|
2067
1904
|
}
|
|
@@ -2069,15 +1906,17 @@ async function handleResultEvent(sessionId, event, textContent, attachments = []
|
|
|
2069
1906
|
async function handleAwaitingHuman(sessionId, detail, options = {}) {
|
|
2070
1907
|
const panel = getPanel(sessionId);
|
|
2071
1908
|
if (!panel) return null;
|
|
2072
|
-
const session =
|
|
1909
|
+
const session = getSessionView(sessionId);
|
|
2073
1910
|
if (!panel.checkInteractionCooldown()) {
|
|
2074
|
-
console.warn(
|
|
1911
|
+
console.warn(
|
|
1912
|
+
`\u4EA4\u4E92\u5361\u521B\u5EFA\u9650\u6D41 (${sessionId}): \u8DDD\u4E0A\u6B21\u521B\u5EFA\u4EC5 ${panel.getTimeSinceLastInteraction()}ms`
|
|
1913
|
+
);
|
|
2075
1914
|
return null;
|
|
2076
1915
|
}
|
|
2077
1916
|
const projection = ensureProjectionTurn(sessionId, 1, "turn_bootstrap");
|
|
2078
1917
|
const provider = session?.provider ?? resolveProviderSource(sessionId);
|
|
2079
1918
|
const remoteHumanControl = session?.remoteHumanControl !== false;
|
|
2080
|
-
const gate =
|
|
1919
|
+
const gate = gateService.createGate({
|
|
2081
1920
|
sessionId,
|
|
2082
1921
|
provider,
|
|
2083
1922
|
type: "binary_approval",
|
|
@@ -2099,7 +1938,7 @@ async function handleAwaitingHuman(sessionId, detail, options = {}) {
|
|
|
2099
1938
|
remoteHumanControl,
|
|
2100
1939
|
provider
|
|
2101
1940
|
});
|
|
2102
|
-
|
|
1941
|
+
gateService.bindDiscordMessage(gate.id, messageId);
|
|
2103
1942
|
panel.recordInteractionCardTime();
|
|
2104
1943
|
cacheProjection(sessionId, getSessionProjection(sessionId));
|
|
2105
1944
|
updateSession(sessionId, {
|
|
@@ -2111,73 +1950,21 @@ async function handleAwaitingHuman(sessionId, detail, options = {}) {
|
|
|
2111
1950
|
setCurrentInteractionMessage(sessionId, messageId);
|
|
2112
1951
|
return messageId;
|
|
2113
1952
|
}
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
statusCardMessageId: session?.statusCardMessageId,
|
|
2121
|
-
initialTurn: session?.currentTurn || 1
|
|
2122
|
-
});
|
|
2123
|
-
let timeoutHandle;
|
|
2124
|
-
const timeout = new Promise((_, reject) => {
|
|
2125
|
-
timeoutHandle = setTimeout(
|
|
2126
|
-
() => reject(new Error("Panel initialization timeout")),
|
|
2127
|
-
RELOCATION_TIMEOUT_MS
|
|
2128
|
-
);
|
|
2129
|
-
});
|
|
2130
|
-
try {
|
|
2131
|
-
await Promise.race([initPromise, timeout]);
|
|
2132
|
-
} finally {
|
|
2133
|
-
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
2134
|
-
}
|
|
2135
|
-
panel = getPanel(sessionId);
|
|
2136
|
-
}
|
|
2137
|
-
if (!panel) return;
|
|
2138
|
-
let statusRelocation = null;
|
|
2139
|
-
try {
|
|
2140
|
-
statusRelocation = await panel.statusCard.recreateAtBottom();
|
|
2141
|
-
} catch (error) {
|
|
2142
|
-
console.warn(`\u72B6\u6001\u6D88\u606F\u8FC1\u79FB\u5931\u8D25 (${sessionId})\uFF1A`, error);
|
|
2143
|
-
return;
|
|
2144
|
-
}
|
|
2145
|
-
let digestRelocation = { oldMessageIds: [], newMessageIds: [] };
|
|
2146
|
-
try {
|
|
2147
|
-
digestRelocation = await panel.summaryHandler.relocateDigestToBottom();
|
|
2148
|
-
} catch (error) {
|
|
2149
|
-
console.warn(`\u6458\u8981\u6D88\u606F\u8FC1\u79FB\u5931\u8D25 (${sessionId})\uFF1A`, error);
|
|
2150
|
-
if (statusRelocation?.oldMessageId && statusRelocation.newMessageId) {
|
|
2151
|
-
panel.statusCard.adopt(statusRelocation.oldMessageId);
|
|
2152
|
-
await panel.channel.messages.delete(statusRelocation.newMessageId).catch((e) => console.warn(`[PanelAdapter] Failed to cleanup new status card (${sessionId}): ${e.message}`));
|
|
2153
|
-
}
|
|
2154
|
-
return;
|
|
2155
|
-
}
|
|
2156
|
-
if (statusRelocation?.newMessageId) {
|
|
2157
|
-
setStatusCardBinding(sessionId, {
|
|
2158
|
-
messageId: statusRelocation.newMessageId
|
|
2159
|
-
});
|
|
2160
|
-
}
|
|
2161
|
-
if (statusRelocation?.oldMessageId) {
|
|
2162
|
-
await panel.channel.messages.delete(statusRelocation.oldMessageId).catch((e) => console.warn(`[PanelAdapter] Failed to delete old status card (${sessionId}): ${e.message}`));
|
|
2163
|
-
}
|
|
2164
|
-
for (const messageId of digestRelocation.oldMessageIds) {
|
|
2165
|
-
await panel.channel.messages.delete(messageId).catch((e) => console.warn(`[PanelAdapter] Failed to delete old digest (${sessionId}): ${e.message}`));
|
|
2166
|
-
}
|
|
1953
|
+
async function relocateSessionPanelToBottom2(sessionId, channel) {
|
|
1954
|
+
await relocateSessionPanelToBottom(
|
|
1955
|
+
sessionId,
|
|
1956
|
+
channel,
|
|
1957
|
+
(sid, ch, options) => initializeSessionPanel(sid, ch, options)
|
|
1958
|
+
);
|
|
2167
1959
|
}
|
|
2168
1960
|
function queueDigest(sessionId, item) {
|
|
2169
|
-
|
|
2170
|
-
if (!panel) return;
|
|
2171
|
-
panel.queueDigest(item);
|
|
1961
|
+
getPanel(sessionId)?.queueDigest(item);
|
|
2172
1962
|
}
|
|
2173
1963
|
function getDigestQueue(sessionId) {
|
|
2174
|
-
|
|
2175
|
-
if (!panel) return [];
|
|
2176
|
-
return panel.getDigestQueue();
|
|
1964
|
+
return getPanel(sessionId)?.getDigestQueue() ?? [];
|
|
2177
1965
|
}
|
|
2178
1966
|
function clearDigestQueue(sessionId) {
|
|
2179
|
-
|
|
2180
|
-
if (panel) panel.clearDigestQueue();
|
|
1967
|
+
getPanel(sessionId)?.clearDigestQueue();
|
|
2181
1968
|
}
|
|
2182
1969
|
async function flushDigest(sessionId) {
|
|
2183
1970
|
const panel = getPanel(sessionId);
|
|
@@ -2193,66 +1980,16 @@ function mapPlatformEventTypeToUnifiedState(type) {
|
|
|
2193
1980
|
function getStateMachine() {
|
|
2194
1981
|
return stateMachine;
|
|
2195
1982
|
}
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
stateMachine.clearSession(sessionId);
|
|
2202
|
-
clearPendingAnswers(sessionId);
|
|
2203
|
-
cleanupSessionDeliveryState(sessionId);
|
|
2204
|
-
clearCodexHint(sessionId);
|
|
2205
|
-
cleanupSessionAttachments(sessionId).catch((e) => console.warn(`[PanelAdapter] Failed to cleanup attachments (${sessionId}): ${e.message}`));
|
|
2206
|
-
const activeGate = gateCoordinator.getActiveGateForSession(sessionId);
|
|
2207
|
-
if (activeGate) {
|
|
2208
|
-
gateCoordinator.resolveFromDiscord(activeGate.id, "reject").catch((e) => console.warn(`[PanelAdapter] Failed to invalidate gate on cleanup (${sessionId}): ${e.message}`));
|
|
2209
|
-
}
|
|
2210
|
-
}
|
|
2211
|
-
function cleanupInactiveSessions() {
|
|
2212
|
-
const now = Date.now();
|
|
2213
|
-
for (const [sessionId, panel] of sessionPanels) {
|
|
2214
|
-
if (now - panel.getLastActivity() > SESSION_INACTIVE_TIMEOUT_MS) {
|
|
2215
|
-
panel.cleanup();
|
|
2216
|
-
sessionPanels.delete(sessionId);
|
|
2217
|
-
statusCardProjectionRenderer.clear(sessionId);
|
|
2218
|
-
console.log(`\u6E05\u7406\u5931\u6D3B\u4F1A\u8BDD\u72B6\u6001\u6295\u5F71: ${sessionId}`);
|
|
2219
|
-
}
|
|
2220
|
-
}
|
|
2221
|
-
}
|
|
2222
|
-
function getPerformanceStats() {
|
|
2223
|
-
let projectionCount = 0;
|
|
2224
|
-
for (const panel of sessionPanels.values()) {
|
|
2225
|
-
if (panel.getCachedProjection() !== null) projectionCount++;
|
|
2226
|
-
}
|
|
2227
|
-
return {
|
|
2228
|
-
discoveryLatency: performanceTracker.getMetricStats("session_discovery_latency"),
|
|
2229
|
-
updateLatency: performanceTracker.getMetricStats("state_update_latency"),
|
|
2230
|
-
activeSessions: sessionPanels.size,
|
|
2231
|
-
projectionCount
|
|
2232
|
-
};
|
|
2233
|
-
}
|
|
2234
|
-
var cleanupInterval = null;
|
|
2235
|
-
function startPerformanceMonitoring() {
|
|
2236
|
-
if (cleanupInterval) return;
|
|
2237
|
-
cleanupInterval = setInterval(() => {
|
|
2238
|
-
cleanupInactiveSessions();
|
|
2239
|
-
performanceTracker.takeSnapshot();
|
|
2240
|
-
performanceTracker.cleanup();
|
|
2241
|
-
}, 6e4);
|
|
2242
|
-
}
|
|
2243
|
-
function stopPerformanceMonitoring() {
|
|
2244
|
-
if (cleanupInterval) {
|
|
2245
|
-
clearInterval(cleanupInterval);
|
|
2246
|
-
cleanupInterval = null;
|
|
2247
|
-
}
|
|
2248
|
-
}
|
|
2249
|
-
function generatePerformanceReport() {
|
|
2250
|
-
return performanceTracker.generateReport();
|
|
1983
|
+
var cleanupSessionPanel = createCleanupSessionPanel(statusCardProjectionRenderer);
|
|
1984
|
+
var cleanupInactiveSessions = createCleanupInactiveSessions(statusCardProjectionRenderer);
|
|
1985
|
+
var getPerformanceStats = createGetPerformanceStats();
|
|
1986
|
+
function startPerformanceMonitoring2() {
|
|
1987
|
+
startPerformanceMonitoring(() => cleanupInactiveSessions());
|
|
2251
1988
|
}
|
|
2252
1989
|
|
|
2253
1990
|
export {
|
|
2254
|
-
|
|
2255
|
-
|
|
1991
|
+
getSessionContext,
|
|
1992
|
+
getSessionView,
|
|
2256
1993
|
setPendingAnswer,
|
|
2257
1994
|
getPendingAnswers,
|
|
2258
1995
|
getQuestionCount,
|
|
@@ -2260,15 +1997,24 @@ export {
|
|
|
2260
1997
|
sendTyping,
|
|
2261
1998
|
sendAckReaction,
|
|
2262
1999
|
deliver,
|
|
2000
|
+
sendSystemNotice,
|
|
2001
|
+
spawnSubagent,
|
|
2002
|
+
getSubagents,
|
|
2003
|
+
runSubagentWatchdog,
|
|
2004
|
+
resolveEffectiveClaudePermissionMode,
|
|
2005
|
+
makeModeButtons,
|
|
2006
|
+
dispatchEvent,
|
|
2263
2007
|
cleanupOldMessages,
|
|
2264
2008
|
notifyUnmanagedCodexHint,
|
|
2009
|
+
stopPerformanceMonitoring,
|
|
2010
|
+
generatePerformanceReport,
|
|
2265
2011
|
getSessionProjection,
|
|
2266
2012
|
initializeSessionPanel,
|
|
2267
2013
|
registerExistingStatusCard,
|
|
2268
2014
|
updateSessionState,
|
|
2269
2015
|
handleResultEvent,
|
|
2270
2016
|
handleAwaitingHuman,
|
|
2271
|
-
relocateSessionPanelToBottom,
|
|
2017
|
+
relocateSessionPanelToBottom2 as relocateSessionPanelToBottom,
|
|
2272
2018
|
queueDigest,
|
|
2273
2019
|
getDigestQueue,
|
|
2274
2020
|
clearDigestQueue,
|
|
@@ -2278,7 +2024,5 @@ export {
|
|
|
2278
2024
|
cleanupSessionPanel,
|
|
2279
2025
|
cleanupInactiveSessions,
|
|
2280
2026
|
getPerformanceStats,
|
|
2281
|
-
startPerformanceMonitoring
|
|
2282
|
-
stopPerformanceMonitoring,
|
|
2283
|
-
generatePerformanceReport
|
|
2027
|
+
startPerformanceMonitoring2 as startPerformanceMonitoring
|
|
2284
2028
|
};
|