workspacecord 1.1.1 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,16 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ cleanupSessionAttachments
4
+ } from "./chunk-BFINJJYL.js";
5
+ import {
6
+ STATE_COLORS,
7
+ STATE_LABELS,
8
+ debouncedSaveSession,
9
+ gateService,
3
10
  getAllSessions,
4
11
  getSession,
12
+ getSessionController,
5
13
  getSessionPermissionSummary,
14
+ mapPlatformEventToState,
6
15
  setCurrentInteractionMessage,
7
16
  setStatusCardBinding,
17
+ stateMachine,
18
+ toPlatformEvent,
8
19
  updateSession
9
- } from "./chunk-RK6EIZOL.js";
20
+ } from "./chunk-WON3DPE4.js";
10
21
  import {
11
- Store,
12
- config
13
- } from "./chunk-UEX7U2KW.js";
22
+ config,
23
+ truncate
24
+ } from "./chunk-IVXCJA5I.js";
14
25
 
15
26
  // ../bot/src/discord/status-card-projection-renderer.ts
16
27
  var StatusCardProjectionRenderer = class {
@@ -24,7 +35,9 @@ var StatusCardProjectionRenderer = class {
24
35
  phase: projection.phase,
25
36
  remoteHumanControl: context.remoteHumanControl,
26
37
  provider: context.provider,
27
- permissionsSummary: context.permissionsSummary
38
+ permissionsSummary: context.permissionsSummary,
39
+ verbose: context.verbose,
40
+ monitorGoal: context.monitorGoal
28
41
  });
29
42
  } catch (error) {
30
43
  console.error(`\u72B6\u6001\u5361\u66F4\u65B0\u5931\u8D25 (${sessionId}):`, error);
@@ -64,1001 +77,6 @@ var StatusCardProjectionRenderer = class {
64
77
  import {
65
78
  EmbedBuilder
66
79
  } from "discord.js";
67
-
68
- // ../state/src/types.ts
69
- var STATE_PRIORITY = {
70
- error: 9,
71
- awaiting_human: 8,
72
- stalled: 7,
73
- summarizing: 6,
74
- working: 5,
75
- thinking: 4,
76
- completed: 3,
77
- idle: 2,
78
- offline: 1
79
- };
80
- var STATE_LABELS = {
81
- idle: "\u5F85\u547D",
82
- thinking: "\u6B63\u5728\u601D\u8003",
83
- working: "\u6B63\u5728\u6267\u884C",
84
- awaiting_human: "\u7B49\u5F85\u4EBA\u5DE5\u5904\u7406",
85
- summarizing: "\u6B63\u5728\u6574\u7406\u4E0A\u4E0B\u6587",
86
- completed: "\u672C\u8F6E\u5DF2\u5B8C\u6210",
87
- error: "\u51FA\u73B0\u5F02\u5E38",
88
- stalled: "\u7591\u4F3C\u5361\u4F4F",
89
- offline: "\u5DF2\u79BB\u7EBF"
90
- };
91
- var STATE_COLORS = {
92
- idle: 8421504,
93
- // 灰色
94
- thinking: 3447003,
95
- // 蓝色
96
- working: 3066993,
97
- // 绿色
98
- awaiting_human: 15965202,
99
- // 橙色
100
- summarizing: 10181046,
101
- // 紫色
102
- completed: 2600544,
103
- // 深绿
104
- error: 15158332,
105
- // 红色
106
- stalled: 15105570,
107
- // 深橙
108
- offline: 9807270
109
- // 浅灰
110
- };
111
- var PLATFORM_EVENT_TO_STATE = {
112
- session_started: "idle",
113
- session_idle: "idle",
114
- thinking_started: "thinking",
115
- work_started: "working",
116
- awaiting_human: "awaiting_human",
117
- human_resolved: "working",
118
- compaction_started: "summarizing",
119
- completed: "completed",
120
- errored: "error",
121
- stalled: "stalled",
122
- session_ended: "offline"
123
- };
124
-
125
- // ../state/src/state-projections.ts
126
- function toProjection(state) {
127
- return {
128
- state: state.displayState,
129
- stateSource: state.stateSource,
130
- confidence: state.confidence,
131
- updatedAt: state.updatedAt,
132
- turn: state.turn,
133
- phase: state.phase,
134
- humanResolved: state.humanResolved
135
- };
136
- }
137
- function toPanelProjection(state) {
138
- return {
139
- ...toProjection(state),
140
- isWaitingHuman: state.lifecycle === "waiting_human" || state.gate === "pending" || state.displayState === "awaiting_human",
141
- isCompleted: state.displayState === "completed" || state.lifecycle === "completed",
142
- isError: state.displayState === "error" || state.lifecycle === "error",
143
- isStalled: state.displayState === "stalled"
144
- };
145
- }
146
- function resolveDisplayState(sessions) {
147
- let best = "idle";
148
- let bestPri = 0;
149
- for (const state of sessions) {
150
- const pri = STATE_PRIORITY[state.displayState] || 0;
151
- if (pri > bestPri) {
152
- best = state.displayState;
153
- bestPri = pri;
154
- }
155
- }
156
- return best;
157
- }
158
- function getStateLabel(state) {
159
- return STATE_LABELS[state] || state;
160
- }
161
- function getStateColor(state) {
162
- return STATE_COLORS[state] || 8421504;
163
- }
164
-
165
- // ../state/src/state-event-mapper.ts
166
- function mapEventToTransition(event, currentState, context) {
167
- const mappedState = PLATFORM_EVENT_TO_STATE[event.type];
168
- if (!mappedState) return null;
169
- if (event.type === "session_idle" && !context.isSessionIdleTransitionAllowed(event, currentState)) {
170
- return null;
171
- }
172
- const allowTransition = event.type === "human_resolved" || event.type === "completed" || event.type === "session_ended" || context.shouldTransition(
173
- currentState.displayState,
174
- mappedState,
175
- currentState.stateSource,
176
- event.stateSource ?? "formal"
177
- );
178
- if (!allowTransition) {
179
- return null;
180
- }
181
- const lifecycle = mapLifecycle(event, currentState, mappedState);
182
- const execution = mapExecution(event, currentState, mappedState, lifecycle);
183
- const gate = mapGate(event, currentState);
184
- const turn = resolveTurn(event, currentState);
185
- const humanResolved = resolveHumanResolved(event, currentState);
186
- const phaseLabel = event.metadata?.phase ?? getStateLabel(mappedState);
187
- return {
188
- updates: {
189
- lifecycle,
190
- execution,
191
- gate
192
- },
193
- metadata: {
194
- displayState: mappedState,
195
- stateSource: event.stateSource ?? "formal",
196
- confidence: event.confidence,
197
- updatedAt: event.timestamp,
198
- turn,
199
- phase: phaseLabel,
200
- humanResolved
201
- }
202
- };
203
- }
204
- function mapLifecycle(event, current, mappedState) {
205
- switch (event.type) {
206
- case "session_started":
207
- case "session_idle":
208
- case "thinking_started":
209
- case "work_started":
210
- case "human_resolved":
211
- case "compaction_started":
212
- return "active";
213
- case "awaiting_human":
214
- return "waiting_human";
215
- case "completed":
216
- return "completed";
217
- case "errored":
218
- return "error";
219
- case "stalled":
220
- return current.lifecycle === "active" ? "paused" : current.lifecycle;
221
- case "session_ended":
222
- return current.lifecycle === "initializing" ? "initializing" : "paused";
223
- default:
224
- return mapLifecycleFromState(mappedState, current.lifecycle);
225
- }
226
- }
227
- function mapLifecycleFromState(mappedState, fallback) {
228
- switch (mappedState) {
229
- case "idle":
230
- case "thinking":
231
- case "working":
232
- case "summarizing":
233
- return "active";
234
- case "awaiting_human":
235
- return "waiting_human";
236
- case "completed":
237
- return "completed";
238
- case "error":
239
- return "error";
240
- case "stalled":
241
- return "paused";
242
- case "offline":
243
- return fallback === "initializing" ? "initializing" : "paused";
244
- default:
245
- return fallback;
246
- }
247
- }
248
- function mapExecution(event, current, mappedState, lifecycle) {
249
- if (lifecycle !== "active") return null;
250
- switch (event.type) {
251
- case "session_started":
252
- case "session_idle":
253
- return "idle";
254
- case "thinking_started":
255
- return "thinking";
256
- case "work_started":
257
- return "tool_executing";
258
- case "human_resolved":
259
- return "tool_executing";
260
- case "compaction_started":
261
- return "thinking";
262
- default:
263
- return mapExecutionFromState(mappedState, current.execution);
264
- }
265
- }
266
- function mapExecutionFromState(mappedState, fallback) {
267
- switch (mappedState) {
268
- case "idle":
269
- return "idle";
270
- case "thinking":
271
- case "summarizing":
272
- return "thinking";
273
- case "working":
274
- return "tool_executing";
275
- default:
276
- return fallback;
277
- }
278
- }
279
- function mapGate(event, current) {
280
- switch (event.type) {
281
- case "awaiting_human":
282
- return "pending";
283
- case "human_resolved":
284
- return event.metadata?.action === "reject" ? "rejected" : "approved";
285
- case "session_idle":
286
- if (event.metadata?.action === "reject" && current.gate === "pending") {
287
- return "rejected";
288
- }
289
- return current.gate;
290
- case "session_ended":
291
- return current.gate === "pending" ? "invalidated" : current.gate;
292
- default:
293
- return current.gate;
294
- }
295
- }
296
- function resolveTurn(event, current) {
297
- if (event.type === "session_started" && current.turn <= 0) {
298
- return 1;
299
- }
300
- return current.turn;
301
- }
302
- function resolveHumanResolved(event, current) {
303
- if (event.type === "awaiting_human") return false;
304
- if (event.type === "human_resolved") return true;
305
- if (event.type === "session_idle" && event.metadata?.action === "reject") return true;
306
- return current.humanResolved;
307
- }
308
-
309
- // ../state/src/state-machine.ts
310
- var LIFECYCLE_TRANSITIONS = {
311
- initializing: ["active", "waiting_human", "paused", "completed", "error"],
312
- active: ["waiting_human", "paused", "completed", "error"],
313
- waiting_human: ["active", "paused", "error"],
314
- paused: ["active", "completed", "error"],
315
- completed: ["active"],
316
- error: ["active", "completed"]
317
- };
318
- var EXECUTION_TRANSITIONS = {
319
- idle: ["thinking", "tool_executing"],
320
- thinking: ["tool_executing", "streaming_output", "idle"],
321
- tool_executing: ["thinking", "streaming_output", "idle"],
322
- streaming_output: ["idle", "thinking"]
323
- };
324
- var StateMachine = class {
325
- sessions = /* @__PURE__ */ new Map();
326
- transitionHistory = /* @__PURE__ */ new Map();
327
- completedTimers = /* @__PURE__ */ new Map();
328
- completedTimerTokens = /* @__PURE__ */ new Map();
329
- completedTimerSequence = 0;
330
- getState(sessionId) {
331
- const existing = this.sessions.get(sessionId);
332
- if (existing) return existing;
333
- const defaultState = this.createDefaultState();
334
- this.sessions.set(sessionId, defaultState);
335
- return defaultState;
336
- }
337
- getSnapshot(sessionId) {
338
- return toProjection(this.getState(sessionId));
339
- }
340
- getPanelProjection(sessionId) {
341
- return toPanelProjection(this.getState(sessionId));
342
- }
343
- transition(sessionId, event, updates, metadata = {}) {
344
- const current = this.getState(sessionId);
345
- const timestamp = metadata.updatedAt ?? Date.now();
346
- const target = {
347
- ...current,
348
- lifecycle: updates.lifecycle ?? current.lifecycle,
349
- execution: updates.execution !== void 0 ? updates.execution : current.execution,
350
- gate: updates.gate !== void 0 ? updates.gate : current.gate,
351
- displayState: metadata.displayState ?? current.displayState,
352
- stateSource: metadata.stateSource ?? current.stateSource,
353
- confidence: metadata.confidence ?? current.confidence,
354
- updatedAt: timestamp,
355
- turn: metadata.turn ?? current.turn,
356
- phase: metadata.phase ?? current.phase,
357
- humanResolved: metadata.humanResolved ?? current.humanResolved
358
- };
359
- if (updates.lifecycle && updates.lifecycle !== current.lifecycle) {
360
- const allowed = LIFECYCLE_TRANSITIONS[current.lifecycle];
361
- if (!allowed.includes(updates.lifecycle)) {
362
- return {
363
- success: false,
364
- state: current,
365
- error: `\u975E\u6CD5\u751F\u547D\u5468\u671F\u8F6C\u6362: ${current.lifecycle} -> ${updates.lifecycle}`
366
- };
367
- }
368
- }
369
- if (updates.execution !== void 0 && updates.execution !== current.execution) {
370
- if (target.lifecycle !== "active" && updates.execution !== null) {
371
- return {
372
- success: false,
373
- state: current,
374
- error: `\u6267\u884C\u72B6\u6001\u4EC5\u5728 lifecycle=active \u65F6\u6709\u6548\uFF0C\u5F53\u524D lifecycle=${target.lifecycle}`
375
- };
376
- }
377
- if (current.execution && updates.execution) {
378
- const allowed = EXECUTION_TRANSITIONS[current.execution];
379
- if (!allowed.includes(updates.execution)) {
380
- return {
381
- success: false,
382
- state: current,
383
- error: `\u975E\u6CD5\u6267\u884C\u72B6\u6001\u8F6C\u6362: ${current.execution} -> ${updates.execution}`
384
- };
385
- }
386
- }
387
- }
388
- if (target.lifecycle !== "active" && target.execution !== null) {
389
- target.execution = null;
390
- }
391
- if (this.isSameState(current, target)) {
392
- return { success: true, state: current };
393
- }
394
- this.sessions.set(sessionId, target);
395
- const transition = {
396
- from: { ...current },
397
- to: { ...target },
398
- event,
399
- timestamp,
400
- sessionId
401
- };
402
- const history = this.transitionHistory.get(sessionId) || [];
403
- history.push(transition);
404
- if (history.length > 100) {
405
- history.shift();
406
- }
407
- this.transitionHistory.set(sessionId, history);
408
- console.log(
409
- `[state-machine] ${sessionId} | ${event} | lifecycle: ${current.lifecycle} -> ${target.lifecycle} | execution: ${current.execution} -> ${target.execution} | gate: ${current.gate} -> ${target.gate} | display: ${current.displayState} -> ${target.displayState}`
410
- );
411
- return { success: true, state: target };
412
- }
413
- getTransitionHistory(sessionId) {
414
- return this.transitionHistory.get(sessionId) || [];
415
- }
416
- resolveDisplayState() {
417
- return resolveDisplayState(this.sessions.values());
418
- }
419
- shouldTransition(from, to, fromSource = "formal", toSource = "formal") {
420
- const fromPri = STATE_PRIORITY[from] || 0;
421
- const toPri = STATE_PRIORITY[to] || 0;
422
- if (fromSource === "formal" && toSource === "inferred") {
423
- return toPri > fromPri;
424
- }
425
- return toPri >= fromPri;
426
- }
427
- getStateLabel(state) {
428
- return getStateLabel(state);
429
- }
430
- getStateColor(state) {
431
- return getStateColor(state);
432
- }
433
- setTurn(sessionId, turn, event = "turn_set") {
434
- const result = this.transition(sessionId, event, {}, { turn, humanResolved: false });
435
- return toProjection(result.state);
436
- }
437
- incrementTurn(sessionId) {
438
- const current = this.getState(sessionId);
439
- const result = this.transition(sessionId, "turn_incremented", {}, {
440
- turn: current.turn + 1,
441
- humanResolved: false
442
- });
443
- return toProjection(result.state);
444
- }
445
- advanceTurnToIdle(sessionId) {
446
- const current = this.getState(sessionId);
447
- const incremented = this.transition(sessionId, "turn_incremented", {}, {
448
- turn: current.turn + 1,
449
- humanResolved: false
450
- });
451
- const baseState = incremented.success ? incremented.state : current;
452
- const settled = this.transition(
453
- sessionId,
454
- "turn_completed",
455
- { lifecycle: "active", execution: "idle", gate: null },
456
- {
457
- displayState: "idle",
458
- stateSource: "formal",
459
- confidence: "high",
460
- phase: getStateLabel("idle"),
461
- humanResolved: false,
462
- turn: baseState.turn
463
- }
464
- );
465
- return toProjection(settled.success ? settled.state : baseState);
466
- }
467
- applyPlatformEvent(event) {
468
- const current = this.getState(event.sessionId);
469
- const mapped = mapEventToTransition(event, current, {
470
- shouldTransition: this.shouldTransition.bind(this),
471
- isSessionIdleTransitionAllowed: this.isSessionIdleTransitionAllowed.bind(this)
472
- });
473
- if (!mapped) return toProjection(current);
474
- const mappedState = PLATFORM_EVENT_TO_STATE[event.type];
475
- const shouldResetCompletedTimer = event.type === "session_started" || event.type === "session_ended" || event.type !== "completed" && mappedState !== "completed" && current.displayState === "completed";
476
- if (shouldResetCompletedTimer) {
477
- this.clearCompletedTimer(event.sessionId);
478
- }
479
- const result = this.transition(
480
- event.sessionId,
481
- event.type,
482
- mapped.updates,
483
- mapped.metadata
484
- );
485
- if (!result.success) {
486
- return toProjection(current);
487
- }
488
- if (mappedState === "completed") {
489
- this.clearCompletedTimer(event.sessionId);
490
- const timerToken = ++this.completedTimerSequence;
491
- const completedTurn = result.state.turn;
492
- const timer = setTimeout(() => {
493
- this.applyPlatformEvent({
494
- type: "session_idle",
495
- sessionId: event.sessionId,
496
- source: event.source,
497
- stateSource: "formal",
498
- confidence: "high",
499
- timestamp: Date.now(),
500
- metadata: {
501
- phase: "\u5F85\u547D",
502
- idleTimerToken: timerToken,
503
- turn: completedTurn
504
- }
505
- });
506
- this.clearCompletedTimer(event.sessionId);
507
- }, 3e3);
508
- this.completedTimers.set(event.sessionId, timer);
509
- this.completedTimerTokens.set(event.sessionId, timerToken);
510
- }
511
- return toProjection(result.state);
512
- }
513
- clearSession(sessionId) {
514
- this.sessions.delete(sessionId);
515
- this.transitionHistory.delete(sessionId);
516
- this.clearCompletedTimer(sessionId);
517
- }
518
- getSessionCount() {
519
- return this.sessions.size;
520
- }
521
- createDefaultState() {
522
- return {
523
- lifecycle: "initializing",
524
- execution: null,
525
- gate: null,
526
- displayState: "idle",
527
- stateSource: "formal",
528
- confidence: "high",
529
- updatedAt: Date.now(),
530
- turn: 0,
531
- phase: STATE_LABELS.idle,
532
- humanResolved: false
533
- };
534
- }
535
- isSameState(a, b) {
536
- return a.lifecycle === b.lifecycle && a.execution === b.execution && a.gate === b.gate && a.displayState === b.displayState && a.stateSource === b.stateSource && a.confidence === b.confidence && a.updatedAt === b.updatedAt && a.turn === b.turn && a.phase === b.phase && a.humanResolved === b.humanResolved;
537
- }
538
- clearCompletedTimer(sessionId) {
539
- const completedTimer = this.completedTimers.get(sessionId);
540
- if (completedTimer) {
541
- clearTimeout(completedTimer);
542
- this.completedTimers.delete(sessionId);
543
- }
544
- this.completedTimerTokens.delete(sessionId);
545
- }
546
- isSessionIdleTransitionAllowed(event, current) {
547
- if (current.displayState === "completed") {
548
- return true;
549
- }
550
- const idleTimerToken = this.readNumericMetadata(event.metadata, "idleTimerToken");
551
- if (idleTimerToken === void 0) {
552
- return false;
553
- }
554
- const activeTimerToken = this.completedTimerTokens.get(event.sessionId);
555
- if (activeTimerToken !== idleTimerToken) {
556
- return false;
557
- }
558
- const timerTurn = this.readNumericMetadata(event.metadata, "turn");
559
- if (timerTurn !== void 0 && timerTurn !== current.turn) {
560
- return false;
561
- }
562
- return true;
563
- }
564
- readNumericMetadata(metadata, field) {
565
- const value = metadata?.[field];
566
- return typeof value === "number" ? value : void 0;
567
- }
568
- };
569
- var stateMachine = new StateMachine();
570
-
571
- // ../state/src/event-normalizer.ts
572
- var CLAUDE_EVENT_MAP = {
573
- session_init: "idle",
574
- text_delta: "thinking",
575
- tool_start: "working",
576
- tool_result: "working",
577
- ask_user: "awaiting_human",
578
- result: "completed",
579
- error: "error"
580
- };
581
- function normalizeClaudeEvent(event, sessionId) {
582
- const state = CLAUDE_EVENT_MAP[event.type];
583
- if (!state) return null;
584
- const platformType = mapToPlatformType(event.type);
585
- if (!platformType) return null;
586
- return {
587
- type: platformType,
588
- sessionId,
589
- source: "claude",
590
- stateSource: "formal",
591
- confidence: "high",
592
- metadata: event,
593
- timestamp: Date.now()
594
- };
595
- }
596
- function mapToPlatformType(eventType) {
597
- const mapping = {
598
- session_init: "session_started",
599
- text_delta: "thinking_started",
600
- tool_start: "work_started",
601
- ask_user: "awaiting_human",
602
- result: "completed",
603
- error: "errored"
604
- };
605
- return mapping[eventType] || null;
606
- }
607
- function normalizeCodexEvent(eventKey, sessionId, extra) {
608
- const byEvent = mapCodexToPlatformType(eventKey);
609
- const byState = mapCodexStateToPlatformType(extra.observedState);
610
- const preferStateOverride = extra.observedState === "codex-permission";
611
- const platformType = preferStateOverride ? byState ?? byEvent : byEvent ?? byState;
612
- if (!platformType) return null;
613
- const isPermissionEvent = eventKey === "codex-permission" || extra.observedState === "codex-permission";
614
- const inferredFromState = Boolean(byState && !byEvent);
615
- const stateSource = isPermissionEvent || inferredFromState ? "inferred" : "formal";
616
- return {
617
- type: platformType,
618
- sessionId,
619
- source: "codex",
620
- stateSource,
621
- confidence: isPermissionEvent ? "medium" : "high",
622
- metadata: { eventKey, ...extra },
623
- timestamp: Date.now()
624
- };
625
- }
626
- function mapCodexToPlatformType(eventKey) {
627
- const mapping = {
628
- "session_meta": "session_started",
629
- "event_msg:task_started": "thinking_started",
630
- "event_msg:user_message": "thinking_started",
631
- "response_item:function_call": "work_started",
632
- "response_item:custom_tool_call": "work_started",
633
- "response_item:web_search_call": "work_started",
634
- "event_msg:exec_command_start": "work_started",
635
- "codex-permission": "awaiting_human",
636
- "event_msg:task_complete": "completed",
637
- "event_msg:context_compacted": "compaction_started",
638
- "event_msg:turn_aborted": "session_idle",
639
- "codex-turn-end": "completed",
640
- "event_msg:error": "errored",
641
- "stale-cleanup": "session_ended"
642
- };
643
- return mapping[eventKey] || null;
644
- }
645
- function mapCodexStateToPlatformType(state) {
646
- if (!state) return null;
647
- const mapping = {
648
- thinking: "thinking_started",
649
- working: "work_started",
650
- sweeping: "compaction_started",
651
- "codex-permission": "awaiting_human",
652
- attention: "completed",
653
- idle: "session_idle",
654
- error: "errored",
655
- sleeping: "session_ended"
656
- };
657
- return mapping[state] ?? null;
658
- }
659
- function isPlatformEvent(input) {
660
- if (!input || typeof input !== "object") return false;
661
- const obj = input;
662
- if (typeof obj.type !== "string") return false;
663
- if (typeof obj.sessionId !== "string") return false;
664
- if (obj.source !== "claude" && obj.source !== "codex") return false;
665
- if (obj.confidence !== "high" && obj.confidence !== "medium" && obj.confidence !== "low") {
666
- return false;
667
- }
668
- return typeof obj.timestamp === "number";
669
- }
670
- function toPlatformEvent(event, sessionId, source = "claude") {
671
- if (isPlatformEvent(event)) {
672
- return {
673
- ...event,
674
- sessionId: event.sessionId || sessionId,
675
- source: event.source || source,
676
- stateSource: event.stateSource ?? "formal",
677
- timestamp: event.timestamp || Date.now()
678
- };
679
- }
680
- if (source === "codex") {
681
- return null;
682
- }
683
- return normalizeClaudeEvent(event, sessionId);
684
- }
685
- function mapPlatformEventToState(type) {
686
- return PLATFORM_EVENT_TO_STATE[type] ?? null;
687
- }
688
-
689
- // ../state/src/human-gate.ts
690
- var HumanGateRegistry = class {
691
- gates = /* @__PURE__ */ new Map();
692
- store;
693
- constructor() {
694
- this.store = new Store("gates.json");
695
- }
696
- /** Load persisted gates from disk. Call during startup before using the registry. */
697
- async init() {
698
- try {
699
- const data = await this.store.read();
700
- if (data && Array.isArray(data)) {
701
- for (const record of data) {
702
- this.gates.set(record.id, record);
703
- }
704
- }
705
- } catch (err) {
706
- console.error("[HumanGateRegistry] Failed to load persisted gates:", err);
707
- }
708
- }
709
- async _save() {
710
- return this.store.write(Array.from(this.gates.values())).catch((err) => {
711
- console.error("[HumanGateRegistry] Failed to persist gates:", err);
712
- });
713
- }
714
- // 创建新门控
715
- create(params) {
716
- const id = this.generateId();
717
- const record = {
718
- ...params,
719
- id,
720
- version: 1,
721
- createdAt: Date.now(),
722
- status: "pending"
723
- };
724
- this.gates.set(id, record);
725
- this._save();
726
- return record;
727
- }
728
- // 获取门控记录
729
- get(id) {
730
- return this.gates.get(id);
731
- }
732
- // 获取会话的所有门控
733
- getBySession(sessionId) {
734
- return Array.from(this.gates.values()).filter((g) => g.sessionId === sessionId);
735
- }
736
- // 获取会话的活跃门控(pending 状态)
737
- getActiveBySession(sessionId) {
738
- return this.getBySession(sessionId).filter((g) => g.status === "pending");
739
- }
740
- // CAS 更新:使用乐观锁保证原子性
741
- update(id, expectedVersion, updates) {
742
- const current = this.gates.get(id);
743
- if (!current) {
744
- return {
745
- success: false,
746
- error: "not_found",
747
- message: `Gate ${id} not found`
748
- };
749
- }
750
- if (current.version !== expectedVersion) {
751
- return {
752
- success: false,
753
- error: "version_conflict",
754
- message: `Version conflict: expected ${expectedVersion}, got ${current.version}`
755
- };
756
- }
757
- if (updates.status && !this.isValidTransition(current.status, updates.status)) {
758
- return {
759
- success: false,
760
- error: "invalid_transition",
761
- message: `Invalid transition: ${current.status} -> ${updates.status}`
762
- };
763
- }
764
- const updated = {
765
- ...current,
766
- ...updates,
767
- version: current.version + 1
768
- };
769
- this.gates.set(id, updated);
770
- this._save();
771
- return {
772
- success: true,
773
- record: updated
774
- };
775
- }
776
- // 批量失效门控(用于重启后清理)
777
- invalidateAll(reason = "restart") {
778
- let count = 0;
779
- for (const [id, gate] of this.gates.entries()) {
780
- if (gate.status === "pending") {
781
- const updated = {
782
- ...gate,
783
- status: "invalidated",
784
- resolvedAt: Date.now(),
785
- resolvedBy: reason,
786
- version: gate.version + 1
787
- };
788
- this.gates.set(id, updated);
789
- count++;
790
- }
791
- }
792
- this._save();
793
- return count;
794
- }
795
- // 删除门控记录
796
- delete(id) {
797
- const result = this.gates.delete(id);
798
- if (result) this._save();
799
- return result;
800
- }
801
- // 清理过期门控(超过指定时间未解决)
802
- cleanupExpired(maxAgeMs = 5 * 60 * 1e3) {
803
- const now = Date.now();
804
- let count = 0;
805
- for (const [id, gate] of this.gates.entries()) {
806
- if (gate.status === "pending" && now - gate.createdAt >= maxAgeMs) {
807
- const updated = {
808
- ...gate,
809
- status: "expired",
810
- resolvedAt: now,
811
- resolvedBy: "timeout",
812
- version: gate.version + 1
813
- };
814
- this.gates.set(id, updated);
815
- count++;
816
- }
817
- }
818
- this._save();
819
- return count;
820
- }
821
- // 归档已解决的门控(保留最近 N 条)
822
- archiveResolved(keepCount = 100) {
823
- const resolved = Array.from(this.gates.values()).filter((g) => g.status !== "pending").sort((a, b) => (b.resolvedAt || 0) - (a.resolvedAt || 0));
824
- if (resolved.length <= keepCount) {
825
- return 0;
826
- }
827
- const toArchive = resolved.slice(keepCount);
828
- for (const gate of toArchive) {
829
- this.gates.delete(gate.id);
830
- }
831
- this._save();
832
- return toArchive.length;
833
- }
834
- // 获取所有门控(用于调试)
835
- getAll() {
836
- return Array.from(this.gates.values());
837
- }
838
- // 获取统计信息
839
- getStats() {
840
- const all = this.getAll();
841
- return {
842
- total: all.length,
843
- pending: all.filter((g) => g.status === "pending").length,
844
- approved: all.filter((g) => g.status === "approved").length,
845
- rejected: all.filter((g) => g.status === "rejected").length,
846
- expired: all.filter((g) => g.status === "expired").length,
847
- invalidated: all.filter((g) => g.status === "invalidated").length
848
- };
849
- }
850
- // 验证状态转换合法性
851
- isValidTransition(from, to) {
852
- const validTransitions = {
853
- pending: ["approved", "rejected", "expired", "invalidated"],
854
- approved: [],
855
- // 终态
856
- rejected: [],
857
- // 终态
858
- expired: [],
859
- // 终态
860
- invalidated: []
861
- // 终态
862
- };
863
- return validTransitions[from]?.includes(to) ?? false;
864
- }
865
- // 生成唯一 ID
866
- generateId() {
867
- return `gate_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
868
- }
869
- };
870
- var humanGateRegistry = new HumanGateRegistry();
871
-
872
- // ../state/src/gate-coordinator.ts
873
- var GateCoordinator = class {
874
- registry;
875
- receiptHandles = /* @__PURE__ */ new Map();
876
- timeoutTimers = /* @__PURE__ */ new Map();
877
- constructor(registry) {
878
- this.registry = registry;
879
- }
880
- /**
881
- * 创建新的人工门控
882
- */
883
- createGate(params) {
884
- const record = this.registry.create(params);
885
- if (record.supportsRemoteDecision && record.isBlocking) {
886
- this.setupTimeout(record.id);
887
- }
888
- return record;
889
- }
890
- /**
891
- * 注册回执句柄(仅内存,不持久化)
892
- */
893
- registerReceiptHandle(gateId, handle) {
894
- const record = this.registry.get(gateId);
895
- if (!record || record.status !== "pending") {
896
- handle.reject("\u95E8\u63A7\u4E0D\u5B58\u5728\u6216\u5DF2\u5904\u7406");
897
- return;
898
- }
899
- this.receiptHandles.set(gateId, { ...handle, gateId });
900
- }
901
- /**
902
- * 绑定 Discord 交互卡消息 ID
903
- */
904
- bindDiscordMessage(gateId, discordMessageId) {
905
- const record = this.registry.get(gateId);
906
- if (!record) {
907
- return false;
908
- }
909
- const result = this.registry.update(gateId, record.version, { discordMessageId });
910
- return result.success;
911
- }
912
- /**
913
- * 尝试通过 Discord 解决门控(CAS 更新)
914
- */
915
- async resolveFromDiscord(gateId, action) {
916
- const record = this.registry.get(gateId);
917
- if (!record) {
918
- return { success: false, message: "\u95E8\u63A7\u4E0D\u5B58\u5728", handledByReceipt: false };
919
- }
920
- if (record.status !== "pending") {
921
- return { success: false, message: "\u95E8\u63A7\u5DF2\u88AB\u5904\u7406", handledByReceipt: false };
922
- }
923
- const result = this.registry.update(record.id, record.version, {
924
- status: action === "approve" ? "approved" : "rejected",
925
- resolvedAt: Date.now(),
926
- resolvedBy: "discord",
927
- resolvedAction: action
928
- });
929
- if (!result.success) {
930
- return { success: false, message: result.message, handledByReceipt: false };
931
- }
932
- this.clearTimeout(gateId);
933
- const handle = this.receiptHandles.get(gateId);
934
- let handledByReceipt = false;
935
- if (handle) {
936
- handle.resolve(action, "discord");
937
- this.receiptHandles.delete(gateId);
938
- handledByReceipt = true;
939
- }
940
- return { success: true, handledByReceipt };
941
- }
942
- /**
943
- * 通知终端已处理(由钩子调用)
944
- */
945
- notifyTerminalResolved(gateId, action) {
946
- const record = this.registry.get(gateId);
947
- if (!record) {
948
- return { success: false, message: "\u95E8\u63A7\u4E0D\u5B58\u5728", handledByReceipt: false };
949
- }
950
- if (record.status !== "pending") {
951
- return { success: false, message: "\u95E8\u63A7\u5DF2\u88AB\u5904\u7406", handledByReceipt: false };
952
- }
953
- const result = this.registry.update(record.id, record.version, {
954
- status: action === "approve" ? "approved" : "rejected",
955
- resolvedAt: Date.now(),
956
- resolvedBy: "terminal",
957
- resolvedAction: action
958
- });
959
- if (!result.success) {
960
- return { success: false, message: result.message, handledByReceipt: false };
961
- }
962
- this.clearTimeout(gateId);
963
- const handle = this.receiptHandles.get(gateId);
964
- let handledByReceipt = false;
965
- if (handle) {
966
- handle.resolve(action, "terminal");
967
- this.receiptHandles.delete(gateId);
968
- handledByReceipt = true;
969
- }
970
- return { success: true, handledByReceipt };
971
- }
972
- /**
973
- * 设置超时定时器(5 分钟)
974
- */
975
- setupTimeout(gateId) {
976
- const timer = setTimeout(() => {
977
- this.handleTimeout(gateId);
978
- }, 5 * 60 * 1e3);
979
- this.timeoutTimers.set(gateId, timer);
980
- }
981
- /**
982
- * 清理超时定时器
983
- */
984
- clearTimeout(gateId) {
985
- const timer = this.timeoutTimers.get(gateId);
986
- if (timer) {
987
- clearTimeout(timer);
988
- this.timeoutTimers.delete(gateId);
989
- }
990
- }
991
- /**
992
- * 处理超时
993
- */
994
- handleTimeout(gateId) {
995
- const record = this.registry.get(gateId);
996
- if (!record || record.status !== "pending") {
997
- return;
998
- }
999
- this.registry.update(record.id, record.version, {
1000
- status: "expired",
1001
- resolvedAt: Date.now(),
1002
- resolvedBy: "timeout"
1003
- });
1004
- const handle = this.receiptHandles.get(gateId);
1005
- if (handle) {
1006
- handle.reject("\u5BA1\u6279\u8D85\u65F6\uFF085 \u5206\u949F\uFF09");
1007
- this.receiptHandles.delete(gateId);
1008
- }
1009
- this.timeoutTimers.delete(gateId);
1010
- }
1011
- /**
1012
- * 重启时失效所有待处理门控
1013
- */
1014
- invalidateAllOnRestart() {
1015
- const count = this.registry.invalidateAll("restart");
1016
- console.log(`[GateCoordinator] Invalidated ${count} pending gates on restart`);
1017
- const toUpdate = [];
1018
- for (const gate of this.registry.getAll()) {
1019
- if (gate.status === "invalidated" && gate.discordMessageId) {
1020
- toUpdate.push({
1021
- gateId: gate.id,
1022
- discordMessageId: gate.discordMessageId
1023
- });
1024
- }
1025
- }
1026
- this.receiptHandles.clear();
1027
- for (const timer of this.timeoutTimers.values()) {
1028
- clearTimeout(timer);
1029
- }
1030
- this.timeoutTimers.clear();
1031
- return toUpdate;
1032
- }
1033
- /**
1034
- * 获取门控记录
1035
- */
1036
- getGate(gateId) {
1037
- return this.registry.get(gateId);
1038
- }
1039
- /**
1040
- * 获取会话的活跃门控
1041
- */
1042
- getActiveGateForSession(sessionId) {
1043
- const active = this.registry.getActiveBySession(sessionId);
1044
- return active[0];
1045
- }
1046
- /**
1047
- * 清理过期门控
1048
- */
1049
- cleanupExpired() {
1050
- return this.registry.cleanupExpired();
1051
- }
1052
- /**
1053
- * 归档已解决门控,保留最近 N 条
1054
- */
1055
- archiveResolved(keepCount = 100) {
1056
- return this.registry.archiveResolved(keepCount);
1057
- }
1058
- };
1059
- var gateCoordinator = new GateCoordinator(humanGateRegistry);
1060
-
1061
- // ../bot/src/discord/status-card.ts
1062
80
  var StatusCard = class {
1063
81
  messageId = null;
1064
82
  channel;
@@ -1149,6 +167,15 @@ var StatusCard = class {
1149
167
  embed.addFields({ name: "\u9636\u6BB5", value: sanitizedPhase, inline: true });
1150
168
  }
1151
169
  }
170
+ if (data.verbose !== void 0) {
171
+ embed.addFields({ name: "\u8F93\u51FA", value: data.verbose ? "\u{1F50A} \u8BE6\u7EC6" : "\u{1F507} \u7CBE\u7B80", inline: true });
172
+ }
173
+ if (data.monitorGoal) {
174
+ embed.addFields({ name: "\u76D1\u63A7\u76EE\u6807", value: truncate(data.monitorGoal, 150) });
175
+ }
176
+ if (data.monitorIteration !== void 0 && data.maxMonitorIterations !== void 0) {
177
+ embed.addFields({ name: "\u8FED\u4EE3", value: `${data.monitorIteration}/${data.maxMonitorIterations}`, inline: true });
178
+ }
1152
179
  if (data.permissionsSummary) {
1153
180
  embed.addFields({ name: "\u6743\u9650", value: data.permissionsSummary, inline: false });
1154
181
  }
@@ -1295,6 +322,34 @@ async function sendAckReaction(message, reaction) {
1295
322
  } catch {
1296
323
  }
1297
324
  }
325
+ var SEND_MAX_RETRIES = 3;
326
+ function isDiscordRateLimitError(err) {
327
+ if (!err || typeof err !== "object") return false;
328
+ const e = err;
329
+ return e.code === 429 || e.status === 429 || e.httpStatus === 429;
330
+ }
331
+ function sleep(ms) {
332
+ return new Promise((resolve) => setTimeout(resolve, ms));
333
+ }
334
+ async function sendWithBackoff(channel, payload) {
335
+ let attempt = 0;
336
+ while (true) {
337
+ try {
338
+ const message = await channel.send(payload);
339
+ return message.id;
340
+ } catch (error) {
341
+ if (!isDiscordRateLimitError(error) || attempt >= SEND_MAX_RETRIES) {
342
+ throw error;
343
+ }
344
+ const delayMs = 250 * 2 ** attempt;
345
+ console.warn(
346
+ `[Delivery] Discord rate-limited (attempt ${attempt + 1}/${SEND_MAX_RETRIES}), retrying in ${delayMs}ms`
347
+ );
348
+ await sleep(delayMs);
349
+ attempt++;
350
+ }
351
+ }
352
+ }
1298
353
  async function sendChunk(channel, chunk, options = {}) {
1299
354
  const payload = { content: chunk };
1300
355
  if (options.files?.length) payload.files = options.files;
@@ -1302,16 +357,14 @@ async function sendChunk(channel, chunk, options = {}) {
1302
357
  payload.reply = { messageReference: options.replyToMessageId };
1303
358
  }
1304
359
  try {
1305
- const message = await channel.send(payload);
1306
- return message.id;
360
+ return await sendWithBackoff(channel, payload);
1307
361
  } catch (error) {
1308
362
  if (!options.replyToMessageId) {
1309
363
  throw error;
1310
364
  }
1311
365
  const fallbackPayload = { content: chunk };
1312
366
  if (options.files?.length) fallbackPayload.files = options.files;
1313
- const fallbackMessage = await channel.send(fallbackPayload);
1314
- return fallbackMessage.id;
367
+ return sendWithBackoff(channel, fallbackPayload);
1315
368
  }
1316
369
  }
1317
370
  async function deliver(channel, plan) {
@@ -1381,8 +434,7 @@ var DigestDelivery = class {
1381
434
  nextMessageIds.push(message.id);
1382
435
  }
1383
436
  for (const staleId of [...replacedMessageIds, ...this.messageIds.slice(chunks.length)]) {
1384
- await this.channel.messages.delete(staleId).catch(() => {
1385
- });
437
+ await this.channel.messages.delete(staleId).catch((e) => console.warn(`[DigestDelivery] Failed to delete message: ${e.message}`));
1386
438
  }
1387
439
  this.messageIds = nextMessageIds;
1388
440
  this.chunks = [...chunks];
@@ -1401,8 +453,7 @@ var DigestDelivery = class {
1401
453
  }
1402
454
  } catch (error) {
1403
455
  for (const messageId of newMessageIds) {
1404
- await this.channel.messages.delete(messageId).catch(() => {
1405
- });
456
+ await this.channel.messages.delete(messageId).catch((e) => console.warn(`[DigestDelivery] Failed to delete message: ${e.message}`));
1406
457
  }
1407
458
  throw error;
1408
459
  }
@@ -1506,6 +557,10 @@ var InteractionCard = class {
1506
557
  async show(sessionId, turn, detail, options = {}) {
1507
558
  const customIdBase = `awaiting_human:${sessionId}:${turn}`;
1508
559
  const embed = new EmbedBuilder3().setTitle("\u23F8\uFE0F \u7B49\u5F85\u4EBA\u5DE5\u5904\u7406").setDescription(detail).setColor(16755200).setTimestamp();
560
+ embed.addFields(
561
+ { name: "\u8F6E\u6B21", value: `#${turn}`, inline: true },
562
+ { name: "\u7B49\u5F85\u81EA", value: `<t:${Math.floor(Date.now() / 1e3)}:R>`, inline: true }
563
+ );
1509
564
  let components = [];
1510
565
  if (options.remoteHumanControl !== false) {
1511
566
  const row = new ActionRowBuilder().addComponents(
@@ -1607,7 +662,7 @@ var SessionPanelComponent = class {
1607
662
  }
1608
663
  this.digestQueue.push({ kind: item.kind, text });
1609
664
  if (this.digestQueue.length > MAX_DIGEST_QUEUE_SIZE) {
1610
- this.digestQueue.splice(0, this.digestQueue.length - MAX_DIGEST_QUEUE_SIZE);
665
+ this.digestQueue = this.digestQueue.slice(-MAX_DIGEST_QUEUE_SIZE);
1611
666
  }
1612
667
  }
1613
668
  getDigestQueue() {
@@ -1634,6 +689,97 @@ var SessionPanelComponent = class {
1634
689
  }
1635
690
  };
1636
691
 
692
+ // ../engine/src/session-context.ts
693
+ var EMPTY_PROJECTION = Object.freeze({
694
+ turn: 0,
695
+ humanResolved: false,
696
+ updatedAt: 0
697
+ });
698
+ function safeGetSessionController(sessionId) {
699
+ try {
700
+ const fn = getSessionController;
701
+ return typeof fn === "function" ? fn(sessionId) : void 0;
702
+ } catch {
703
+ return void 0;
704
+ }
705
+ }
706
+ function safeDebouncedSaveSession() {
707
+ try {
708
+ const fn = debouncedSaveSession;
709
+ if (typeof fn === "function") fn();
710
+ } catch {
711
+ }
712
+ }
713
+ function safeGetProjection(sessionId) {
714
+ try {
715
+ const sm = stateMachine;
716
+ if (sm && typeof sm.getSnapshot === "function") {
717
+ return sm.getSnapshot(sessionId);
718
+ }
719
+ } catch {
720
+ }
721
+ return EMPTY_PROJECTION;
722
+ }
723
+ var SessionSupervisor = class {
724
+ contexts = /* @__PURE__ */ new Map();
725
+ get(sessionId) {
726
+ const live = getSession(sessionId);
727
+ const cached = this.contexts.get(sessionId);
728
+ if (!live) {
729
+ if (cached) this.contexts.delete(sessionId);
730
+ return void 0;
731
+ }
732
+ if (cached) {
733
+ this.syncContext(cached);
734
+ return cached;
735
+ }
736
+ const ctx = this.build(live);
737
+ this.contexts.set(sessionId, ctx);
738
+ return ctx;
739
+ }
740
+ /** 枚举现有 context。用于 supervisor 层面的扫描(健康检查、空闲回收)。 */
741
+ all() {
742
+ return Array.from(this.contexts.values());
743
+ }
744
+ /** 释放某个 session 的 context(endSession 调用时触发)。 */
745
+ release(sessionId) {
746
+ this.contexts.delete(sessionId);
747
+ }
748
+ releaseAll() {
749
+ this.contexts.clear();
750
+ }
751
+ build(session) {
752
+ const ctx = {
753
+ sessionId: session.id,
754
+ session,
755
+ controller: safeGetSessionController(session.id),
756
+ projection: safeGetProjection(session.id),
757
+ save: () => safeDebouncedSaveSession(),
758
+ refresh: () => this.syncContext(ctx)
759
+ };
760
+ return ctx;
761
+ }
762
+ syncContext(ctx) {
763
+ const live = getSession(ctx.sessionId);
764
+ if (live) {
765
+ ctx.session = live;
766
+ }
767
+ ctx.controller = safeGetSessionController(
768
+ ctx.sessionId
769
+ );
770
+ ctx.projection = safeGetProjection(
771
+ ctx.sessionId
772
+ );
773
+ }
774
+ };
775
+ var sessionSupervisor = new SessionSupervisor();
776
+ function getSessionContext(sessionId) {
777
+ return sessionSupervisor.get(sessionId);
778
+ }
779
+ function getSessionView(sessionId) {
780
+ return sessionSupervisor.get(sessionId)?.session;
781
+ }
782
+
1637
783
  // ../bot/src/monitoring/performance-tracker.ts
1638
784
  import { performance } from "perf_hooks";
1639
785
  import { memoryUsage, cpuUsage } from "process";
@@ -1778,6 +924,100 @@ var PerformanceTracker = class {
1778
924
  };
1779
925
  var performanceTracker = new PerformanceTracker();
1780
926
 
927
+ // ../bot/src/panel/panel-state.ts
928
+ var sessionPanels = /* @__PURE__ */ new Map();
929
+ var sessionInitializationPromises = /* @__PURE__ */ new Map();
930
+ function getPanel(sessionId) {
931
+ return sessionPanels.get(sessionId);
932
+ }
933
+ function setPanel(sessionId, panel) {
934
+ sessionPanels.set(sessionId, panel);
935
+ }
936
+ function deletePanel(sessionId) {
937
+ sessionPanels.delete(sessionId);
938
+ }
939
+ function getAllPanels() {
940
+ return sessionPanels.entries();
941
+ }
942
+ function getPanelCount() {
943
+ return sessionPanels.size;
944
+ }
945
+ function getInitializationPromise(sessionId) {
946
+ return sessionInitializationPromises.get(sessionId);
947
+ }
948
+ function setInitializationPromise(sessionId, promise) {
949
+ sessionInitializationPromises.set(sessionId, promise);
950
+ }
951
+ function deleteInitializationPromise(sessionId) {
952
+ sessionInitializationPromises.delete(sessionId);
953
+ }
954
+
955
+ // ../bot/src/panel/panel-relocation.ts
956
+ var RELOCATION_TIMEOUT_MS = 15e3;
957
+ async function relocateSessionPanelToBottom(sessionId, channel, initialize) {
958
+ let panel = getPanel(sessionId);
959
+ if (!panel && channel) {
960
+ const session = getSessionView(sessionId);
961
+ const initPromise = initialize(sessionId, channel, {
962
+ statusCardMessageId: session?.statusCardMessageId,
963
+ initialTurn: session?.currentTurn || 1
964
+ });
965
+ let timeoutHandle;
966
+ const timeout = new Promise((_, reject) => {
967
+ timeoutHandle = setTimeout(
968
+ () => reject(new Error("Panel initialization timeout")),
969
+ RELOCATION_TIMEOUT_MS
970
+ );
971
+ });
972
+ try {
973
+ await Promise.race([initPromise, timeout]);
974
+ } finally {
975
+ if (timeoutHandle) clearTimeout(timeoutHandle);
976
+ }
977
+ panel = getPanel(sessionId);
978
+ }
979
+ if (!panel) return;
980
+ let statusRelocation = null;
981
+ try {
982
+ statusRelocation = await panel.statusCard.recreateAtBottom();
983
+ } catch (error) {
984
+ console.warn(`\u72B6\u6001\u6D88\u606F\u8FC1\u79FB\u5931\u8D25 (${sessionId})\uFF1A`, error);
985
+ return;
986
+ }
987
+ let digestRelocation = { oldMessageIds: [], newMessageIds: [] };
988
+ try {
989
+ digestRelocation = await panel.summaryHandler.relocateDigestToBottom();
990
+ } catch (error) {
991
+ console.warn(`\u6458\u8981\u6D88\u606F\u8FC1\u79FB\u5931\u8D25 (${sessionId})\uFF1A`, error);
992
+ if (statusRelocation?.oldMessageId && statusRelocation.newMessageId) {
993
+ panel.statusCard.adopt(statusRelocation.oldMessageId);
994
+ await panel.channel.messages.delete(statusRelocation.newMessageId).catch(
995
+ (e) => console.warn(
996
+ `[PanelAdapter] Failed to cleanup new status card (${sessionId}): ${e.message}`
997
+ )
998
+ );
999
+ }
1000
+ return;
1001
+ }
1002
+ if (statusRelocation?.newMessageId) {
1003
+ setStatusCardBinding(sessionId, { messageId: statusRelocation.newMessageId });
1004
+ }
1005
+ if (statusRelocation?.oldMessageId) {
1006
+ await panel.channel.messages.delete(statusRelocation.oldMessageId).catch(
1007
+ (e) => console.warn(
1008
+ `[PanelAdapter] Failed to delete old status card (${sessionId}): ${e.message}`
1009
+ )
1010
+ );
1011
+ }
1012
+ for (const messageId of digestRelocation.oldMessageIds) {
1013
+ await panel.channel.messages.delete(messageId).catch(
1014
+ (e) => console.warn(
1015
+ `[PanelAdapter] Failed to delete old digest (${sessionId}): ${e.message}`
1016
+ )
1017
+ );
1018
+ }
1019
+ }
1020
+
1781
1021
  // ../engine/src/output/answer-store.ts
1782
1022
  var pendingAnswersStore = /* @__PURE__ */ new Map();
1783
1023
  var questionCountStore = /* @__PURE__ */ new Map();
@@ -1851,15 +1091,82 @@ async function notifyUnmanagedCodexHint(client, sessionId, channelId) {
1851
1091
  }
1852
1092
  }
1853
1093
 
1094
+ // ../bot/src/panel/panel-performance.ts
1095
+ var SESSION_INACTIVE_TIMEOUT_MS = 36e5;
1096
+ function createCleanupSessionPanel(renderer) {
1097
+ return function cleanupSessionPanel2(sessionId) {
1098
+ const panel = getPanel(sessionId);
1099
+ if (panel) panel.cleanup();
1100
+ deletePanel(sessionId);
1101
+ renderer.clear(sessionId);
1102
+ stateMachine.clearSession(sessionId);
1103
+ clearPendingAnswers(sessionId);
1104
+ cleanupSessionDeliveryState(sessionId);
1105
+ clearCodexHint(sessionId);
1106
+ cleanupSessionAttachments(sessionId).catch(
1107
+ (e) => console.warn(
1108
+ `[PanelAdapter] Failed to cleanup attachments (${sessionId}): ${e.message}`
1109
+ )
1110
+ );
1111
+ const activeGate = gateService.getActiveGateForSession(sessionId);
1112
+ if (activeGate) {
1113
+ gateService.resolveFromDiscord(activeGate.id, "reject").catch(
1114
+ (e) => console.warn(
1115
+ `[PanelAdapter] Failed to invalidate gate on cleanup (${sessionId}): ${e.message}`
1116
+ )
1117
+ );
1118
+ }
1119
+ };
1120
+ }
1121
+ function createCleanupInactiveSessions(renderer) {
1122
+ return function cleanupInactiveSessions2() {
1123
+ const now = Date.now();
1124
+ for (const [sessionId, panel] of getAllPanels()) {
1125
+ if (now - panel.getLastActivity() > SESSION_INACTIVE_TIMEOUT_MS) {
1126
+ panel.cleanup();
1127
+ deletePanel(sessionId);
1128
+ renderer.clear(sessionId);
1129
+ console.log(`\u6E05\u7406\u5931\u6D3B\u4F1A\u8BDD\u72B6\u6001\u6295\u5F71: ${sessionId}`);
1130
+ }
1131
+ }
1132
+ };
1133
+ }
1134
+ function createGetPerformanceStats() {
1135
+ return function getPerformanceStats2() {
1136
+ let projectionCount = 0;
1137
+ for (const [, panel] of getAllPanels()) {
1138
+ if (panel.getCachedProjection() !== null) projectionCount++;
1139
+ }
1140
+ return {
1141
+ discoveryLatency: performanceTracker.getMetricStats("session_discovery_latency"),
1142
+ updateLatency: performanceTracker.getMetricStats("state_update_latency"),
1143
+ activeSessions: getPanelCount(),
1144
+ projectionCount
1145
+ };
1146
+ };
1147
+ }
1148
+ var cleanupInterval = null;
1149
+ function startPerformanceMonitoring(onTick) {
1150
+ if (cleanupInterval) return;
1151
+ cleanupInterval = setInterval(() => {
1152
+ onTick();
1153
+ performanceTracker.takeSnapshot();
1154
+ performanceTracker.cleanup();
1155
+ }, 6e4);
1156
+ }
1157
+ function stopPerformanceMonitoring() {
1158
+ if (cleanupInterval) {
1159
+ clearInterval(cleanupInterval);
1160
+ cleanupInterval = null;
1161
+ }
1162
+ }
1163
+ function generatePerformanceReport() {
1164
+ return performanceTracker.generateReport();
1165
+ }
1166
+
1854
1167
  // ../bot/src/panel-adapter.ts
1855
- var sessionPanels = /* @__PURE__ */ new Map();
1856
- var sessionInitializationPromises = /* @__PURE__ */ new Map();
1857
1168
  var BATCH_UPDATE_DELAY_MS = 500;
1858
1169
  var statusCardProjectionRenderer = new StatusCardProjectionRenderer();
1859
- var SESSION_INACTIVE_TIMEOUT_MS = 36e5;
1860
- function getPanel(sessionId) {
1861
- return sessionPanels.get(sessionId);
1862
- }
1863
1170
  function getSessionProjection(sessionId) {
1864
1171
  return stateMachine.getSnapshot(sessionId);
1865
1172
  }
@@ -1872,25 +1179,25 @@ function ensureProjectionTurn(sessionId, turn = 1, event = "turn_bootstrap") {
1872
1179
  }
1873
1180
  function cacheProjection(sessionId, projection) {
1874
1181
  const panel = getPanel(sessionId);
1875
- if (panel) {
1876
- panel.updateProjection(projection);
1877
- }
1182
+ if (panel) panel.updateProjection(projection);
1878
1183
  }
1879
- function persistTurnState(sessionId, projection) {
1184
+ stateMachine.registerTurnStatePersister((sessionId, projection) => {
1880
1185
  updateSession(sessionId, {
1881
1186
  currentTurn: projection.turn,
1882
1187
  humanResolved: projection.humanResolved
1883
1188
  });
1884
- }
1189
+ });
1885
1190
  function createStatusCardProjectionContext(sessionId) {
1886
1191
  const panel = getPanel(sessionId);
1887
1192
  if (!panel) return void 0;
1888
- const session = getSession(sessionId);
1193
+ const session = getSessionView(sessionId);
1889
1194
  return {
1890
1195
  statusCard: panel.statusCard,
1891
1196
  remoteHumanControl: session?.remoteHumanControl,
1892
1197
  provider: session?.provider,
1893
- permissionsSummary: session ? getSessionPermissionSummary(session) : void 0
1198
+ permissionsSummary: session ? getSessionPermissionSummary(session) : void 0,
1199
+ verbose: session?.verbose,
1200
+ monitorGoal: session?.monitorGoal
1894
1201
  };
1895
1202
  }
1896
1203
  async function renderProjectionToStatusCard(sessionId, projection) {
@@ -1910,17 +1217,16 @@ async function scheduleProjectionRender(sessionId, projection, updateKey) {
1910
1217
  );
1911
1218
  }
1912
1219
  function resolveProviderSource(sessionId, fallback = "claude") {
1913
- const session = getSession(sessionId);
1220
+ const session = getSessionView(sessionId);
1914
1221
  return session?.provider === "codex" ? "codex" : fallback;
1915
1222
  }
1916
1223
  async function initializeSessionPanel(sessionId, channel, options = {}) {
1917
1224
  performanceTracker.startSessionDiscovery(sessionId);
1918
- const existing = getPanel(sessionId);
1919
- if (existing) {
1225
+ if (getPanel(sessionId)) {
1920
1226
  performanceTracker.endSessionDiscovery(sessionId, { cached: true });
1921
1227
  return;
1922
1228
  }
1923
- const pendingInitialization = sessionInitializationPromises.get(sessionId);
1229
+ const pendingInitialization = getInitializationPromise(sessionId);
1924
1230
  if (pendingInitialization) {
1925
1231
  await pendingInitialization;
1926
1232
  performanceTracker.endSessionDiscovery(sessionId, { cached: true });
@@ -1928,7 +1234,7 @@ async function initializeSessionPanel(sessionId, channel, options = {}) {
1928
1234
  }
1929
1235
  const initialization = (async () => {
1930
1236
  const panel = new SessionPanelComponent(sessionId, channel);
1931
- const session = getSession(sessionId);
1237
+ const session = getSessionView(sessionId);
1932
1238
  await panel.initialize({
1933
1239
  statusCardMessageId: options.statusCardMessageId,
1934
1240
  initialTurn: options.initialTurn,
@@ -1937,7 +1243,7 @@ async function initializeSessionPanel(sessionId, channel, options = {}) {
1937
1243
  provider: session?.provider,
1938
1244
  permissionsSummary: session ? getSessionPermissionSummary(session) : void 0
1939
1245
  });
1940
- sessionPanels.set(sessionId, panel);
1246
+ setPanel(sessionId, panel);
1941
1247
  setStatusCardBinding(sessionId, {
1942
1248
  messageId: panel.getMessageId() ?? options.statusCardMessageId
1943
1249
  });
@@ -1948,24 +1254,22 @@ async function initializeSessionPanel(sessionId, channel, options = {}) {
1948
1254
  );
1949
1255
  cacheProjection(sessionId, projection);
1950
1256
  })();
1951
- sessionInitializationPromises.set(sessionId, initialization);
1257
+ setInitializationPromise(sessionId, initialization);
1952
1258
  try {
1953
1259
  await initialization;
1954
1260
  performanceTracker.endSessionDiscovery(sessionId, { cached: false });
1955
1261
  } finally {
1956
- sessionInitializationPromises.delete(sessionId);
1262
+ deleteInitializationPromise(sessionId);
1957
1263
  }
1958
1264
  }
1959
1265
  async function registerExistingStatusCard(sessionId, channel, statusCardMessageId) {
1960
- await initializeSessionPanel(sessionId, channel, {
1961
- statusCardMessageId
1962
- });
1266
+ await initializeSessionPanel(sessionId, channel, { statusCardMessageId });
1963
1267
  }
1964
1268
  async function updateSessionState(sessionId, event, options = {}) {
1965
1269
  const updateKey = `${sessionId}:state`;
1966
1270
  performanceTracker.startStateUpdate(updateKey);
1967
1271
  if (!getPanel(sessionId) && options.channel) {
1968
- const session2 = getSession(sessionId);
1272
+ const session2 = getSessionView(sessionId);
1969
1273
  await initializeSessionPanel(sessionId, options.channel, {
1970
1274
  statusCardMessageId: session2?.statusCardMessageId,
1971
1275
  initialTurn: session2?.currentTurn || 1
@@ -1980,15 +1284,20 @@ async function updateSessionState(sessionId, event, options = {}) {
1980
1284
  performanceTracker.endStateUpdate(updateKey, { skipped: true });
1981
1285
  return null;
1982
1286
  }
1983
- const session = getSession(sessionId);
1287
+ const session = getSessionView(sessionId);
1984
1288
  if (platformEvent.source === "codex" && platformEvent.type === "completed" && session?.isGenerating) {
1985
1289
  performanceTracker.endStateUpdate(updateKey, { skipped: true });
1986
1290
  return getSessionProjection(sessionId);
1987
1291
  }
1292
+ const previousProjection = getSessionProjection(sessionId);
1988
1293
  const projection = stateMachine.applyPlatformEvent(platformEvent);
1989
1294
  cacheProjection(sessionId, projection);
1990
- await scheduleProjectionRender(sessionId, projection, updateKey);
1991
- persistTurnState(sessionId, projection);
1295
+ const stateChanged = projection.state !== previousProjection.state || projection.turn !== previousProjection.turn || projection.phase !== previousProjection.phase;
1296
+ if (stateChanged) {
1297
+ await scheduleProjectionRender(sessionId, projection, updateKey);
1298
+ } else {
1299
+ performanceTracker.endStateUpdate(updateKey, { skipped: true });
1300
+ }
1992
1301
  return projection;
1993
1302
  }
1994
1303
  async function handleResultEvent(sessionId, event, textContent, attachments = []) {
@@ -2010,7 +1319,7 @@ async function handleResultEvent(sessionId, event, textContent, attachments = []
2010
1319
  metadata: { from: "result" }
2011
1320
  });
2012
1321
  } else if (!event.success) {
2013
- const session = getSession(sessionId);
1322
+ const session = getSessionView(sessionId);
2014
1323
  const failureText = textContent.trim() || event.errors.join("\n").trim() || "\u4EFB\u52A1\u5931\u8D25";
2015
1324
  await panel.summaryHandler.sendTurnFailure(
2016
1325
  failureText,
@@ -2028,7 +1337,7 @@ async function handleResultEvent(sessionId, event, textContent, attachments = []
2028
1337
  });
2029
1338
  } else {
2030
1339
  const beforeProjection = getSessionProjection(sessionId);
2031
- const session = getSession(sessionId);
1340
+ const session = getSessionView(sessionId);
2032
1341
  await panel.summaryHandler.sendTurnSummary(
2033
1342
  textContent,
2034
1343
  beforeProjection.turn,
@@ -2036,7 +1345,6 @@ async function handleResultEvent(sessionId, event, textContent, attachments = []
2036
1345
  attachments
2037
1346
  );
2038
1347
  const projectionAfterTurn = stateMachine.advanceTurnToIdle(sessionId);
2039
- persistTurnState(sessionId, projectionAfterTurn);
2040
1348
  await renderProjectionToStatusCard(sessionId, projectionAfterTurn);
2041
1349
  cacheProjection(sessionId, projectionAfterTurn);
2042
1350
  }
@@ -2044,15 +1352,17 @@ async function handleResultEvent(sessionId, event, textContent, attachments = []
2044
1352
  async function handleAwaitingHuman(sessionId, detail, options = {}) {
2045
1353
  const panel = getPanel(sessionId);
2046
1354
  if (!panel) return null;
2047
- const session = getSession(sessionId);
1355
+ const session = getSessionView(sessionId);
2048
1356
  if (!panel.checkInteractionCooldown()) {
2049
- console.warn(`\u4EA4\u4E92\u5361\u521B\u5EFA\u9650\u6D41 (${sessionId}): \u8DDD\u4E0A\u6B21\u521B\u5EFA\u4EC5 ${panel.getTimeSinceLastInteraction()}ms`);
1357
+ console.warn(
1358
+ `\u4EA4\u4E92\u5361\u521B\u5EFA\u9650\u6D41 (${sessionId}): \u8DDD\u4E0A\u6B21\u521B\u5EFA\u4EC5 ${panel.getTimeSinceLastInteraction()}ms`
1359
+ );
2050
1360
  return null;
2051
1361
  }
2052
1362
  const projection = ensureProjectionTurn(sessionId, 1, "turn_bootstrap");
2053
1363
  const provider = session?.provider ?? resolveProviderSource(sessionId);
2054
1364
  const remoteHumanControl = session?.remoteHumanControl !== false;
2055
- const gate = gateCoordinator.createGate({
1365
+ const gate = gateService.createGate({
2056
1366
  sessionId,
2057
1367
  provider,
2058
1368
  type: "binary_approval",
@@ -2074,7 +1384,7 @@ async function handleAwaitingHuman(sessionId, detail, options = {}) {
2074
1384
  remoteHumanControl,
2075
1385
  provider
2076
1386
  });
2077
- gateCoordinator.bindDiscordMessage(gate.id, messageId);
1387
+ gateService.bindDiscordMessage(gate.id, messageId);
2078
1388
  panel.recordInteractionCardTime();
2079
1389
  cacheProjection(sessionId, getSessionProjection(sessionId));
2080
1390
  updateSession(sessionId, {
@@ -2086,63 +1396,21 @@ async function handleAwaitingHuman(sessionId, detail, options = {}) {
2086
1396
  setCurrentInteractionMessage(sessionId, messageId);
2087
1397
  return messageId;
2088
1398
  }
2089
- async function relocateSessionPanelToBottom(sessionId, channel) {
2090
- let panel = getPanel(sessionId);
2091
- if (!panel && channel) {
2092
- const session = getSession(sessionId);
2093
- await initializeSessionPanel(sessionId, channel, {
2094
- statusCardMessageId: session?.statusCardMessageId,
2095
- initialTurn: session?.currentTurn || 1
2096
- });
2097
- panel = getPanel(sessionId);
2098
- }
2099
- if (!panel) return;
2100
- let statusRelocation = null;
2101
- try {
2102
- statusRelocation = await panel.statusCard.recreateAtBottom();
2103
- } catch (error) {
2104
- console.warn(`\u72B6\u6001\u6D88\u606F\u8FC1\u79FB\u5931\u8D25 (${sessionId})\uFF1A`, error);
2105
- return;
2106
- }
2107
- let digestRelocation = { oldMessageIds: [], newMessageIds: [] };
2108
- try {
2109
- digestRelocation = await panel.summaryHandler.relocateDigestToBottom();
2110
- } catch (error) {
2111
- console.warn(`\u6458\u8981\u6D88\u606F\u8FC1\u79FB\u5931\u8D25 (${sessionId})\uFF1A`, error);
2112
- if (statusRelocation?.oldMessageId && statusRelocation.newMessageId) {
2113
- panel.statusCard.adopt(statusRelocation.oldMessageId);
2114
- await panel.channel.messages.delete(statusRelocation.newMessageId).catch(() => {
2115
- });
2116
- }
2117
- return;
2118
- }
2119
- if (statusRelocation?.newMessageId) {
2120
- setStatusCardBinding(sessionId, {
2121
- messageId: statusRelocation.newMessageId
2122
- });
2123
- }
2124
- if (statusRelocation?.oldMessageId) {
2125
- await panel.channel.messages.delete(statusRelocation.oldMessageId).catch(() => {
2126
- });
2127
- }
2128
- for (const messageId of digestRelocation.oldMessageIds) {
2129
- await panel.channel.messages.delete(messageId).catch(() => {
2130
- });
2131
- }
1399
+ async function relocateSessionPanelToBottom2(sessionId, channel) {
1400
+ await relocateSessionPanelToBottom(
1401
+ sessionId,
1402
+ channel,
1403
+ (sid, ch, options) => initializeSessionPanel(sid, ch, options)
1404
+ );
2132
1405
  }
2133
1406
  function queueDigest(sessionId, item) {
2134
- const panel = getPanel(sessionId);
2135
- if (!panel) return;
2136
- panel.queueDigest(item);
1407
+ getPanel(sessionId)?.queueDigest(item);
2137
1408
  }
2138
1409
  function getDigestQueue(sessionId) {
2139
- const panel = getPanel(sessionId);
2140
- if (!panel) return [];
2141
- return panel.getDigestQueue();
1410
+ return getPanel(sessionId)?.getDigestQueue() ?? [];
2142
1411
  }
2143
1412
  function clearDigestQueue(sessionId) {
2144
- const panel = getPanel(sessionId);
2145
- if (panel) panel.clearDigestQueue();
1413
+ getPanel(sessionId)?.clearDigestQueue();
2146
1414
  }
2147
1415
  async function flushDigest(sessionId) {
2148
1416
  const panel = getPanel(sessionId);
@@ -2158,66 +1426,16 @@ function mapPlatformEventTypeToUnifiedState(type) {
2158
1426
  function getStateMachine() {
2159
1427
  return stateMachine;
2160
1428
  }
2161
- function cleanupSessionPanel(sessionId) {
2162
- const panel = getPanel(sessionId);
2163
- if (panel) panel.cleanup();
2164
- sessionPanels.delete(sessionId);
2165
- statusCardProjectionRenderer.clear(sessionId);
2166
- stateMachine.clearSession(sessionId);
2167
- clearPendingAnswers(sessionId);
2168
- cleanupSessionDeliveryState(sessionId);
2169
- clearCodexHint(sessionId);
2170
- const activeGate = gateCoordinator.getActiveGateForSession(sessionId);
2171
- if (activeGate) {
2172
- gateCoordinator.resolveFromDiscord(activeGate.id, "reject").catch(() => {
2173
- });
2174
- }
2175
- }
2176
- function cleanupInactiveSessions() {
2177
- const now = Date.now();
2178
- for (const [sessionId, panel] of sessionPanels) {
2179
- if (now - panel.getLastActivity() > SESSION_INACTIVE_TIMEOUT_MS) {
2180
- panel.cleanup();
2181
- sessionPanels.delete(sessionId);
2182
- statusCardProjectionRenderer.clear(sessionId);
2183
- console.log(`\u6E05\u7406\u5931\u6D3B\u4F1A\u8BDD\u72B6\u6001\u6295\u5F71: ${sessionId}`);
2184
- }
2185
- }
2186
- }
2187
- function getPerformanceStats() {
2188
- let projectionCount = 0;
2189
- for (const panel of sessionPanels.values()) {
2190
- if (panel.getCachedProjection() !== null) projectionCount++;
2191
- }
2192
- return {
2193
- discoveryLatency: performanceTracker.getMetricStats("session_discovery_latency"),
2194
- updateLatency: performanceTracker.getMetricStats("state_update_latency"),
2195
- activeSessions: sessionPanels.size,
2196
- projectionCount
2197
- };
2198
- }
2199
- var cleanupInterval = null;
2200
- function startPerformanceMonitoring() {
2201
- if (cleanupInterval) return;
2202
- cleanupInterval = setInterval(() => {
2203
- cleanupInactiveSessions();
2204
- performanceTracker.takeSnapshot();
2205
- performanceTracker.cleanup();
2206
- }, 6e4);
2207
- }
2208
- function stopPerformanceMonitoring() {
2209
- if (cleanupInterval) {
2210
- clearInterval(cleanupInterval);
2211
- cleanupInterval = null;
2212
- }
2213
- }
2214
- function generatePerformanceReport() {
2215
- return performanceTracker.generateReport();
1429
+ var cleanupSessionPanel = createCleanupSessionPanel(statusCardProjectionRenderer);
1430
+ var cleanupInactiveSessions = createCleanupInactiveSessions(statusCardProjectionRenderer);
1431
+ var getPerformanceStats = createGetPerformanceStats();
1432
+ function startPerformanceMonitoring2() {
1433
+ startPerformanceMonitoring(() => cleanupInactiveSessions());
2216
1434
  }
2217
1435
 
2218
1436
  export {
2219
- normalizeCodexEvent,
2220
- gateCoordinator,
1437
+ getSessionContext,
1438
+ getSessionView,
2221
1439
  setPendingAnswer,
2222
1440
  getPendingAnswers,
2223
1441
  getQuestionCount,
@@ -2227,13 +1445,15 @@ export {
2227
1445
  deliver,
2228
1446
  cleanupOldMessages,
2229
1447
  notifyUnmanagedCodexHint,
1448
+ stopPerformanceMonitoring,
1449
+ generatePerformanceReport,
2230
1450
  getSessionProjection,
2231
1451
  initializeSessionPanel,
2232
1452
  registerExistingStatusCard,
2233
1453
  updateSessionState,
2234
1454
  handleResultEvent,
2235
1455
  handleAwaitingHuman,
2236
- relocateSessionPanelToBottom,
1456
+ relocateSessionPanelToBottom2 as relocateSessionPanelToBottom,
2237
1457
  queueDigest,
2238
1458
  getDigestQueue,
2239
1459
  clearDigestQueue,
@@ -2243,7 +1463,5 @@ export {
2243
1463
  cleanupSessionPanel,
2244
1464
  cleanupInactiveSessions,
2245
1465
  getPerformanceStats,
2246
- startPerformanceMonitoring,
2247
- stopPerformanceMonitoring,
2248
- generatePerformanceReport
1466
+ startPerformanceMonitoring2 as startPerformanceMonitoring
2249
1467
  };