workspacecord 1.1.2 → 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,20 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  cleanupSessionAttachments
4
- } from "./chunk-L2ZJXV6H.js";
4
+ } from "./chunk-BFINJJYL.js";
5
5
  import {
6
+ STATE_COLORS,
7
+ STATE_LABELS,
8
+ debouncedSaveSession,
9
+ gateService,
6
10
  getAllSessions,
7
11
  getSession,
12
+ getSessionController,
8
13
  getSessionPermissionSummary,
14
+ mapPlatformEventToState,
9
15
  setCurrentInteractionMessage,
10
16
  setStatusCardBinding,
17
+ stateMachine,
18
+ toPlatformEvent,
11
19
  updateSession
12
- } from "./chunk-RK6EIZOL.js";
20
+ } from "./chunk-WON3DPE4.js";
13
21
  import {
14
- Store,
15
22
  config,
16
23
  truncate
17
- } from "./chunk-UEX7U2KW.js";
24
+ } from "./chunk-IVXCJA5I.js";
18
25
 
19
26
  // ../bot/src/discord/status-card-projection-renderer.ts
20
27
  var StatusCardProjectionRenderer = class {
@@ -70,1001 +77,6 @@ var StatusCardProjectionRenderer = class {
70
77
  import {
71
78
  EmbedBuilder
72
79
  } 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
- }
170
-
171
- // ../state/src/state-event-mapper.ts
172
- function mapEventToTransition(event, currentState, context) {
173
- const mappedState = PLATFORM_EVENT_TO_STATE[event.type];
174
- if (!mappedState) return null;
175
- if (event.type === "session_idle" && !context.isSessionIdleTransitionAllowed(event, currentState)) {
176
- return null;
177
- }
178
- const allowTransition = event.type === "human_resolved" || event.type === "completed" || event.type === "session_ended" || context.shouldTransition(
179
- currentState.displayState,
180
- mappedState,
181
- currentState.stateSource,
182
- event.stateSource ?? "formal"
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;
300
- }
301
- }
302
- function resolveTurn(event, current) {
303
- if (event.type === "session_started" && current.turn <= 0) {
304
- return 1;
305
- }
306
- return current.turn;
307
- }
308
- function resolveHumanResolved(event, current) {
309
- if (event.type === "awaiting_human") return false;
310
- if (event.type === "human_resolved") return true;
311
- if (event.type === "session_idle" && event.metadata?.action === "reject") return true;
312
- return current.humanResolved;
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 };
399
- }
400
- this.sessions.set(sessionId, target);
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) || [];
421
- }
422
- resolveDisplayState() {
423
- return resolveDisplayState(this.sessions.values());
424
- }
425
- shouldTransition(from, to, fromSource = "formal", toSource = "formal") {
426
- const fromPri = STATE_PRIORITY[from] || 0;
427
- const toPri = STATE_PRIORITY[to] || 0;
428
- if (fromSource === "formal" && toSource === "inferred") {
429
- return toPri > fromPri;
430
- }
431
- return toPri >= fromPri;
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
- }
470
- );
471
- return toProjection(settled.success ? settled.state : baseState);
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
490
- );
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
- }
541
- isSameState(a, b) {
542
- 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;
543
- }
544
- clearCompletedTimer(sessionId) {
545
- const completedTimer = this.completedTimers.get(sessionId);
546
- if (completedTimer) {
547
- clearTimeout(completedTimer);
548
- this.completedTimers.delete(sessionId);
549
- }
550
- this.completedTimerTokens.delete(sessionId);
551
- }
552
- isSessionIdleTransitionAllowed(event, current) {
553
- if (current.displayState === "completed") {
554
- return true;
555
- }
556
- const idleTimerToken = this.readNumericMetadata(event.metadata, "idleTimerToken");
557
- if (idleTimerToken === void 0) {
558
- return false;
559
- }
560
- const activeTimerToken = this.completedTimerTokens.get(event.sessionId);
561
- if (activeTimerToken !== idleTimerToken) {
562
- return false;
563
- }
564
- const timerTurn = this.readNumericMetadata(event.metadata, "turn");
565
- if (timerTurn !== void 0 && timerTurn !== current.turn) {
566
- return false;
567
- }
568
- return true;
569
- }
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
- }
602
- function mapToPlatformType(eventType) {
603
- const mapping = {
604
- session_init: "session_started",
605
- text_delta: "thinking_started",
606
- tool_start: "work_started",
607
- ask_user: "awaiting_human",
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";
622
- return {
623
- type: platformType,
624
- sessionId,
625
- source: "codex",
626
- stateSource,
627
- confidence: isPermissionEvent ? "medium" : "high",
628
- metadata: { eventKey, ...extra },
629
- timestamp: Date.now()
630
- };
631
- }
632
- function mapCodexToPlatformType(eventKey) {
633
- const mapping = {
634
- "session_meta": "session_started",
635
- "event_msg:task_started": "thinking_started",
636
- "event_msg:user_message": "thinking_started",
637
- "response_item:function_call": "work_started",
638
- "response_item:custom_tool_call": "work_started",
639
- "response_item:web_search_call": "work_started",
640
- "event_msg:exec_command_start": "work_started",
641
- "codex-permission": "awaiting_human",
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;
664
- }
665
- function isPlatformEvent(input) {
666
- if (!input || typeof input !== "object") return false;
667
- const obj = input;
668
- if (typeof obj.type !== "string") return false;
669
- if (typeof obj.sessionId !== "string") return false;
670
- if (obj.source !== "claude" && obj.source !== "codex") return false;
671
- if (obj.confidence !== "high" && obj.confidence !== "medium" && obj.confidence !== "low") {
672
- return false;
673
- }
674
- return typeof obj.timestamp === "number";
675
- }
676
- function toPlatformEvent(event, sessionId, source = "claude") {
677
- if (isPlatformEvent(event)) {
678
- return {
679
- ...event,
680
- sessionId: event.sessionId || sessionId,
681
- source: event.source || source,
682
- stateSource: event.stateSource ?? "formal",
683
- timestamp: event.timestamp || Date.now()
684
- };
685
- }
686
- if (source === "codex") {
687
- return null;
688
- }
689
- return normalizeClaudeEvent(event, sessionId);
690
- }
691
- function mapPlatformEventToState(type) {
692
- return PLATFORM_EVENT_TO_STATE[type] ?? null;
693
- }
694
-
695
- // ../state/src/human-gate.ts
696
- var HumanGateRegistry = class {
697
- gates = /* @__PURE__ */ new Map();
698
- store;
699
- constructor() {
700
- this.store = new Store("gates.json");
701
- }
702
- /** Load persisted gates from disk. Call during startup before using the registry. */
703
- async init() {
704
- try {
705
- const data = await this.store.read();
706
- if (data && Array.isArray(data)) {
707
- for (const record of data) {
708
- this.gates.set(record.id, record);
709
- }
710
- }
711
- } catch (err) {
712
- console.error("[HumanGateRegistry] Failed to load persisted gates:", err);
713
- }
714
- }
715
- async _save() {
716
- return this.store.write(Array.from(this.gates.values())).catch((err) => {
717
- console.error("[HumanGateRegistry] Failed to persist gates:", err);
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
- };
781
- }
782
- // 批量失效门控(用于重启后清理)
783
- invalidateAll(reason = "restart") {
784
- let count = 0;
785
- for (const [id, gate] of this.gates.entries()) {
786
- if (gate.status === "pending") {
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
- }
797
- }
798
- this._save();
799
- return count;
800
- }
801
- // 删除门控记录
802
- delete(id) {
803
- const result = this.gates.delete(id);
804
- if (result) this._save();
805
- return result;
806
- }
807
- // 清理过期门控(超过指定时间未解决)
808
- cleanupExpired(maxAgeMs = 5 * 60 * 1e3) {
809
- const now = Date.now();
810
- let count = 0;
811
- for (const [id, gate] of this.gates.entries()) {
812
- if (gate.status === "pending" && now - gate.createdAt >= maxAgeMs) {
813
- const updated = {
814
- ...gate,
815
- status: "expired",
816
- resolvedAt: now,
817
- resolvedBy: "timeout",
818
- version: gate.version + 1
819
- };
820
- this.gates.set(id, updated);
821
- count++;
822
- }
823
- }
824
- this._save();
825
- return count;
826
- }
827
- // 归档已解决的门控(保留最近 N 条)
828
- archiveResolved(keepCount = 100) {
829
- const resolved = Array.from(this.gates.values()).filter((g) => g.status !== "pending").sort((a, b) => (b.resolvedAt || 0) - (a.resolvedAt || 0));
830
- if (resolved.length <= keepCount) {
831
- return 0;
832
- }
833
- const toArchive = resolved.slice(keepCount);
834
- for (const gate of toArchive) {
835
- this.gates.delete(gate.id);
836
- }
837
- this._save();
838
- return toArchive.length;
839
- }
840
- // 获取所有门控(用于调试)
841
- getAll() {
842
- return Array.from(this.gates.values());
843
- }
844
- // 获取统计信息
845
- getStats() {
846
- const all = this.getAll();
847
- return {
848
- total: all.length,
849
- pending: all.filter((g) => g.status === "pending").length,
850
- approved: all.filter((g) => g.status === "approved").length,
851
- rejected: all.filter((g) => g.status === "rejected").length,
852
- expired: all.filter((g) => g.status === "expired").length,
853
- invalidated: all.filter((g) => g.status === "invalidated").length
854
- };
855
- }
856
- // 验证状态转换合法性
857
- isValidTransition(from, to) {
858
- const validTransitions = {
859
- pending: ["approved", "rejected", "expired", "invalidated"],
860
- approved: [],
861
- // 终态
862
- rejected: [],
863
- // 终态
864
- expired: [],
865
- // 终态
866
- invalidated: []
867
- // 终态
868
- };
869
- return validTransitions[from]?.includes(to) ?? false;
870
- }
871
- // 生成唯一 ID
872
- generateId() {
873
- return `gate_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
874
- }
875
- };
876
- var humanGateRegistry = new HumanGateRegistry();
877
-
878
- // ../state/src/gate-coordinator.ts
879
- var GateCoordinator = class {
880
- registry;
881
- receiptHandles = /* @__PURE__ */ new Map();
882
- timeoutTimers = /* @__PURE__ */ new Map();
883
- constructor(registry) {
884
- this.registry = registry;
885
- }
886
- /**
887
- * 创建新的人工门控
888
- */
889
- createGate(params) {
890
- const record = this.registry.create(params);
891
- if (record.supportsRemoteDecision && record.isBlocking) {
892
- this.setupTimeout(record.id);
893
- }
894
- return record;
895
- }
896
- /**
897
- * 注册回执句柄(仅内存,不持久化)
898
- */
899
- registerReceiptHandle(gateId, handle) {
900
- const record = this.registry.get(gateId);
901
- if (!record || record.status !== "pending") {
902
- handle.reject("\u95E8\u63A7\u4E0D\u5B58\u5728\u6216\u5DF2\u5904\u7406");
903
- return;
904
- }
905
- this.receiptHandles.set(gateId, { ...handle, gateId });
906
- }
907
- /**
908
- * 绑定 Discord 交互卡消息 ID
909
- */
910
- bindDiscordMessage(gateId, discordMessageId) {
911
- const record = this.registry.get(gateId);
912
- if (!record) {
913
- return false;
914
- }
915
- const result = this.registry.update(gateId, record.version, { discordMessageId });
916
- return result.success;
917
- }
918
- /**
919
- * 尝试通过 Discord 解决门控(CAS 更新)
920
- */
921
- async resolveFromDiscord(gateId, action) {
922
- const record = this.registry.get(gateId);
923
- if (!record) {
924
- return { success: false, message: "\u95E8\u63A7\u4E0D\u5B58\u5728", handledByReceipt: false };
925
- }
926
- if (record.status !== "pending") {
927
- return { success: false, message: "\u95E8\u63A7\u5DF2\u88AB\u5904\u7406", handledByReceipt: false };
928
- }
929
- const result = this.registry.update(record.id, record.version, {
930
- status: action === "approve" ? "approved" : "rejected",
931
- resolvedAt: Date.now(),
932
- resolvedBy: "discord",
933
- resolvedAction: action
934
- });
935
- if (!result.success) {
936
- return { success: false, message: result.message, handledByReceipt: false };
937
- }
938
- this.clearTimeout(gateId);
939
- const handle = this.receiptHandles.get(gateId);
940
- let handledByReceipt = false;
941
- if (handle) {
942
- handle.resolve(action, "discord");
943
- this.receiptHandles.delete(gateId);
944
- handledByReceipt = true;
945
- }
946
- return { success: true, handledByReceipt };
947
- }
948
- /**
949
- * 通知终端已处理(由钩子调用)
950
- */
951
- notifyTerminalResolved(gateId, action) {
952
- const record = this.registry.get(gateId);
953
- if (!record) {
954
- return { success: false, message: "\u95E8\u63A7\u4E0D\u5B58\u5728", handledByReceipt: false };
955
- }
956
- if (record.status !== "pending") {
957
- return { success: false, message: "\u95E8\u63A7\u5DF2\u88AB\u5904\u7406", handledByReceipt: false };
958
- }
959
- const result = this.registry.update(record.id, record.version, {
960
- status: action === "approve" ? "approved" : "rejected",
961
- resolvedAt: Date.now(),
962
- resolvedBy: "terminal",
963
- resolvedAction: action
964
- });
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
- }
978
- /**
979
- * 设置超时定时器(5 分钟)
980
- */
981
- setupTimeout(gateId) {
982
- const timer = setTimeout(() => {
983
- this.handleTimeout(gateId);
984
- }, 5 * 60 * 1e3);
985
- this.timeoutTimers.set(gateId, timer);
986
- }
987
- /**
988
- * 清理超时定时器
989
- */
990
- clearTimeout(gateId) {
991
- const timer = this.timeoutTimers.get(gateId);
992
- if (timer) {
993
- clearTimeout(timer);
994
- this.timeoutTimers.delete(gateId);
995
- }
996
- }
997
- /**
998
- * 处理超时
999
- */
1000
- handleTimeout(gateId) {
1001
- const record = this.registry.get(gateId);
1002
- if (!record || record.status !== "pending") {
1003
- return;
1004
- }
1005
- this.registry.update(record.id, record.version, {
1006
- status: "expired",
1007
- resolvedAt: Date.now(),
1008
- resolvedBy: "timeout"
1009
- });
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
- }
1017
- /**
1018
- * 重启时失效所有待处理门控
1019
- */
1020
- invalidateAllOnRestart() {
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;
1038
- }
1039
- /**
1040
- * 获取门控记录
1041
- */
1042
- getGate(gateId) {
1043
- return this.registry.get(gateId);
1044
- }
1045
- /**
1046
- * 获取会话的活跃门控
1047
- */
1048
- getActiveGateForSession(sessionId) {
1049
- const active = this.registry.getActiveBySession(sessionId);
1050
- return active[0];
1051
- }
1052
- /**
1053
- * 清理过期门控
1054
- */
1055
- cleanupExpired() {
1056
- return this.registry.cleanupExpired();
1057
- }
1058
- /**
1059
- * 归档已解决门控,保留最近 N 条
1060
- */
1061
- archiveResolved(keepCount = 100) {
1062
- return this.registry.archiveResolved(keepCount);
1063
- }
1064
- };
1065
- var gateCoordinator = new GateCoordinator(humanGateRegistry);
1066
-
1067
- // ../bot/src/discord/status-card.ts
1068
80
  var StatusCard = class {
1069
81
  messageId = null;
1070
82
  channel;
@@ -1310,6 +322,34 @@ async function sendAckReaction(message, reaction) {
1310
322
  } catch {
1311
323
  }
1312
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
+ }
1313
353
  async function sendChunk(channel, chunk, options = {}) {
1314
354
  const payload = { content: chunk };
1315
355
  if (options.files?.length) payload.files = options.files;
@@ -1317,16 +357,14 @@ async function sendChunk(channel, chunk, options = {}) {
1317
357
  payload.reply = { messageReference: options.replyToMessageId };
1318
358
  }
1319
359
  try {
1320
- const message = await channel.send(payload);
1321
- return message.id;
360
+ return await sendWithBackoff(channel, payload);
1322
361
  } catch (error) {
1323
362
  if (!options.replyToMessageId) {
1324
363
  throw error;
1325
364
  }
1326
365
  const fallbackPayload = { content: chunk };
1327
366
  if (options.files?.length) fallbackPayload.files = options.files;
1328
- const fallbackMessage = await channel.send(fallbackPayload);
1329
- return fallbackMessage.id;
367
+ return sendWithBackoff(channel, fallbackPayload);
1330
368
  }
1331
369
  }
1332
370
  async function deliver(channel, plan) {
@@ -1651,6 +689,97 @@ var SessionPanelComponent = class {
1651
689
  }
1652
690
  };
1653
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
+
1654
783
  // ../bot/src/monitoring/performance-tracker.ts
1655
784
  import { performance } from "perf_hooks";
1656
785
  import { memoryUsage, cpuUsage } from "process";
@@ -1795,6 +924,100 @@ var PerformanceTracker = class {
1795
924
  };
1796
925
  var performanceTracker = new PerformanceTracker();
1797
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
+
1798
1021
  // ../engine/src/output/answer-store.ts
1799
1022
  var pendingAnswersStore = /* @__PURE__ */ new Map();
1800
1023
  var questionCountStore = /* @__PURE__ */ new Map();
@@ -1868,15 +1091,82 @@ async function notifyUnmanagedCodexHint(client, sessionId, channelId) {
1868
1091
  }
1869
1092
  }
1870
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
+
1871
1167
  // ../bot/src/panel-adapter.ts
1872
- var sessionPanels = /* @__PURE__ */ new Map();
1873
- var sessionInitializationPromises = /* @__PURE__ */ new Map();
1874
1168
  var BATCH_UPDATE_DELAY_MS = 500;
1875
1169
  var statusCardProjectionRenderer = new StatusCardProjectionRenderer();
1876
- var SESSION_INACTIVE_TIMEOUT_MS = 36e5;
1877
- function getPanel(sessionId) {
1878
- return sessionPanels.get(sessionId);
1879
- }
1880
1170
  function getSessionProjection(sessionId) {
1881
1171
  return stateMachine.getSnapshot(sessionId);
1882
1172
  }
@@ -1889,20 +1179,18 @@ function ensureProjectionTurn(sessionId, turn = 1, event = "turn_bootstrap") {
1889
1179
  }
1890
1180
  function cacheProjection(sessionId, projection) {
1891
1181
  const panel = getPanel(sessionId);
1892
- if (panel) {
1893
- panel.updateProjection(projection);
1894
- }
1182
+ if (panel) panel.updateProjection(projection);
1895
1183
  }
1896
- function persistTurnState(sessionId, projection) {
1184
+ stateMachine.registerTurnStatePersister((sessionId, projection) => {
1897
1185
  updateSession(sessionId, {
1898
1186
  currentTurn: projection.turn,
1899
1187
  humanResolved: projection.humanResolved
1900
1188
  });
1901
- }
1189
+ });
1902
1190
  function createStatusCardProjectionContext(sessionId) {
1903
1191
  const panel = getPanel(sessionId);
1904
1192
  if (!panel) return void 0;
1905
- const session = getSession(sessionId);
1193
+ const session = getSessionView(sessionId);
1906
1194
  return {
1907
1195
  statusCard: panel.statusCard,
1908
1196
  remoteHumanControl: session?.remoteHumanControl,
@@ -1929,17 +1217,16 @@ async function scheduleProjectionRender(sessionId, projection, updateKey) {
1929
1217
  );
1930
1218
  }
1931
1219
  function resolveProviderSource(sessionId, fallback = "claude") {
1932
- const session = getSession(sessionId);
1220
+ const session = getSessionView(sessionId);
1933
1221
  return session?.provider === "codex" ? "codex" : fallback;
1934
1222
  }
1935
1223
  async function initializeSessionPanel(sessionId, channel, options = {}) {
1936
1224
  performanceTracker.startSessionDiscovery(sessionId);
1937
- const existing = getPanel(sessionId);
1938
- if (existing) {
1225
+ if (getPanel(sessionId)) {
1939
1226
  performanceTracker.endSessionDiscovery(sessionId, { cached: true });
1940
1227
  return;
1941
1228
  }
1942
- const pendingInitialization = sessionInitializationPromises.get(sessionId);
1229
+ const pendingInitialization = getInitializationPromise(sessionId);
1943
1230
  if (pendingInitialization) {
1944
1231
  await pendingInitialization;
1945
1232
  performanceTracker.endSessionDiscovery(sessionId, { cached: true });
@@ -1947,7 +1234,7 @@ async function initializeSessionPanel(sessionId, channel, options = {}) {
1947
1234
  }
1948
1235
  const initialization = (async () => {
1949
1236
  const panel = new SessionPanelComponent(sessionId, channel);
1950
- const session = getSession(sessionId);
1237
+ const session = getSessionView(sessionId);
1951
1238
  await panel.initialize({
1952
1239
  statusCardMessageId: options.statusCardMessageId,
1953
1240
  initialTurn: options.initialTurn,
@@ -1956,7 +1243,7 @@ async function initializeSessionPanel(sessionId, channel, options = {}) {
1956
1243
  provider: session?.provider,
1957
1244
  permissionsSummary: session ? getSessionPermissionSummary(session) : void 0
1958
1245
  });
1959
- sessionPanels.set(sessionId, panel);
1246
+ setPanel(sessionId, panel);
1960
1247
  setStatusCardBinding(sessionId, {
1961
1248
  messageId: panel.getMessageId() ?? options.statusCardMessageId
1962
1249
  });
@@ -1967,24 +1254,22 @@ async function initializeSessionPanel(sessionId, channel, options = {}) {
1967
1254
  );
1968
1255
  cacheProjection(sessionId, projection);
1969
1256
  })();
1970
- sessionInitializationPromises.set(sessionId, initialization);
1257
+ setInitializationPromise(sessionId, initialization);
1971
1258
  try {
1972
1259
  await initialization;
1973
1260
  performanceTracker.endSessionDiscovery(sessionId, { cached: false });
1974
1261
  } finally {
1975
- sessionInitializationPromises.delete(sessionId);
1262
+ deleteInitializationPromise(sessionId);
1976
1263
  }
1977
1264
  }
1978
1265
  async function registerExistingStatusCard(sessionId, channel, statusCardMessageId) {
1979
- await initializeSessionPanel(sessionId, channel, {
1980
- statusCardMessageId
1981
- });
1266
+ await initializeSessionPanel(sessionId, channel, { statusCardMessageId });
1982
1267
  }
1983
1268
  async function updateSessionState(sessionId, event, options = {}) {
1984
1269
  const updateKey = `${sessionId}:state`;
1985
1270
  performanceTracker.startStateUpdate(updateKey);
1986
1271
  if (!getPanel(sessionId) && options.channel) {
1987
- const session2 = getSession(sessionId);
1272
+ const session2 = getSessionView(sessionId);
1988
1273
  await initializeSessionPanel(sessionId, options.channel, {
1989
1274
  statusCardMessageId: session2?.statusCardMessageId,
1990
1275
  initialTurn: session2?.currentTurn || 1
@@ -1999,7 +1284,7 @@ async function updateSessionState(sessionId, event, options = {}) {
1999
1284
  performanceTracker.endStateUpdate(updateKey, { skipped: true });
2000
1285
  return null;
2001
1286
  }
2002
- const session = getSession(sessionId);
1287
+ const session = getSessionView(sessionId);
2003
1288
  if (platformEvent.source === "codex" && platformEvent.type === "completed" && session?.isGenerating) {
2004
1289
  performanceTracker.endStateUpdate(updateKey, { skipped: true });
2005
1290
  return getSessionProjection(sessionId);
@@ -2013,7 +1298,6 @@ async function updateSessionState(sessionId, event, options = {}) {
2013
1298
  } else {
2014
1299
  performanceTracker.endStateUpdate(updateKey, { skipped: true });
2015
1300
  }
2016
- persistTurnState(sessionId, projection);
2017
1301
  return projection;
2018
1302
  }
2019
1303
  async function handleResultEvent(sessionId, event, textContent, attachments = []) {
@@ -2035,7 +1319,7 @@ async function handleResultEvent(sessionId, event, textContent, attachments = []
2035
1319
  metadata: { from: "result" }
2036
1320
  });
2037
1321
  } else if (!event.success) {
2038
- const session = getSession(sessionId);
1322
+ const session = getSessionView(sessionId);
2039
1323
  const failureText = textContent.trim() || event.errors.join("\n").trim() || "\u4EFB\u52A1\u5931\u8D25";
2040
1324
  await panel.summaryHandler.sendTurnFailure(
2041
1325
  failureText,
@@ -2053,7 +1337,7 @@ async function handleResultEvent(sessionId, event, textContent, attachments = []
2053
1337
  });
2054
1338
  } else {
2055
1339
  const beforeProjection = getSessionProjection(sessionId);
2056
- const session = getSession(sessionId);
1340
+ const session = getSessionView(sessionId);
2057
1341
  await panel.summaryHandler.sendTurnSummary(
2058
1342
  textContent,
2059
1343
  beforeProjection.turn,
@@ -2061,7 +1345,6 @@ async function handleResultEvent(sessionId, event, textContent, attachments = []
2061
1345
  attachments
2062
1346
  );
2063
1347
  const projectionAfterTurn = stateMachine.advanceTurnToIdle(sessionId);
2064
- persistTurnState(sessionId, projectionAfterTurn);
2065
1348
  await renderProjectionToStatusCard(sessionId, projectionAfterTurn);
2066
1349
  cacheProjection(sessionId, projectionAfterTurn);
2067
1350
  }
@@ -2069,15 +1352,17 @@ async function handleResultEvent(sessionId, event, textContent, attachments = []
2069
1352
  async function handleAwaitingHuman(sessionId, detail, options = {}) {
2070
1353
  const panel = getPanel(sessionId);
2071
1354
  if (!panel) return null;
2072
- const session = getSession(sessionId);
1355
+ const session = getSessionView(sessionId);
2073
1356
  if (!panel.checkInteractionCooldown()) {
2074
- 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
+ );
2075
1360
  return null;
2076
1361
  }
2077
1362
  const projection = ensureProjectionTurn(sessionId, 1, "turn_bootstrap");
2078
1363
  const provider = session?.provider ?? resolveProviderSource(sessionId);
2079
1364
  const remoteHumanControl = session?.remoteHumanControl !== false;
2080
- const gate = gateCoordinator.createGate({
1365
+ const gate = gateService.createGate({
2081
1366
  sessionId,
2082
1367
  provider,
2083
1368
  type: "binary_approval",
@@ -2099,7 +1384,7 @@ async function handleAwaitingHuman(sessionId, detail, options = {}) {
2099
1384
  remoteHumanControl,
2100
1385
  provider
2101
1386
  });
2102
- gateCoordinator.bindDiscordMessage(gate.id, messageId);
1387
+ gateService.bindDiscordMessage(gate.id, messageId);
2103
1388
  panel.recordInteractionCardTime();
2104
1389
  cacheProjection(sessionId, getSessionProjection(sessionId));
2105
1390
  updateSession(sessionId, {
@@ -2111,73 +1396,21 @@ async function handleAwaitingHuman(sessionId, detail, options = {}) {
2111
1396
  setCurrentInteractionMessage(sessionId, messageId);
2112
1397
  return messageId;
2113
1398
  }
2114
- var RELOCATION_TIMEOUT_MS = 15e3;
2115
- async function relocateSessionPanelToBottom(sessionId, channel) {
2116
- let panel = getPanel(sessionId);
2117
- if (!panel && channel) {
2118
- const session = getSession(sessionId);
2119
- const initPromise = initializeSessionPanel(sessionId, channel, {
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
- }
1399
+ async function relocateSessionPanelToBottom2(sessionId, channel) {
1400
+ await relocateSessionPanelToBottom(
1401
+ sessionId,
1402
+ channel,
1403
+ (sid, ch, options) => initializeSessionPanel(sid, ch, options)
1404
+ );
2167
1405
  }
2168
1406
  function queueDigest(sessionId, item) {
2169
- const panel = getPanel(sessionId);
2170
- if (!panel) return;
2171
- panel.queueDigest(item);
1407
+ getPanel(sessionId)?.queueDigest(item);
2172
1408
  }
2173
1409
  function getDigestQueue(sessionId) {
2174
- const panel = getPanel(sessionId);
2175
- if (!panel) return [];
2176
- return panel.getDigestQueue();
1410
+ return getPanel(sessionId)?.getDigestQueue() ?? [];
2177
1411
  }
2178
1412
  function clearDigestQueue(sessionId) {
2179
- const panel = getPanel(sessionId);
2180
- if (panel) panel.clearDigestQueue();
1413
+ getPanel(sessionId)?.clearDigestQueue();
2181
1414
  }
2182
1415
  async function flushDigest(sessionId) {
2183
1416
  const panel = getPanel(sessionId);
@@ -2193,66 +1426,16 @@ function mapPlatformEventTypeToUnifiedState(type) {
2193
1426
  function getStateMachine() {
2194
1427
  return stateMachine;
2195
1428
  }
2196
- function cleanupSessionPanel(sessionId) {
2197
- const panel = getPanel(sessionId);
2198
- if (panel) panel.cleanup();
2199
- sessionPanels.delete(sessionId);
2200
- statusCardProjectionRenderer.clear(sessionId);
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();
1429
+ var cleanupSessionPanel = createCleanupSessionPanel(statusCardProjectionRenderer);
1430
+ var cleanupInactiveSessions = createCleanupInactiveSessions(statusCardProjectionRenderer);
1431
+ var getPerformanceStats = createGetPerformanceStats();
1432
+ function startPerformanceMonitoring2() {
1433
+ startPerformanceMonitoring(() => cleanupInactiveSessions());
2251
1434
  }
2252
1435
 
2253
1436
  export {
2254
- normalizeCodexEvent,
2255
- gateCoordinator,
1437
+ getSessionContext,
1438
+ getSessionView,
2256
1439
  setPendingAnswer,
2257
1440
  getPendingAnswers,
2258
1441
  getQuestionCount,
@@ -2262,13 +1445,15 @@ export {
2262
1445
  deliver,
2263
1446
  cleanupOldMessages,
2264
1447
  notifyUnmanagedCodexHint,
1448
+ stopPerformanceMonitoring,
1449
+ generatePerformanceReport,
2265
1450
  getSessionProjection,
2266
1451
  initializeSessionPanel,
2267
1452
  registerExistingStatusCard,
2268
1453
  updateSessionState,
2269
1454
  handleResultEvent,
2270
1455
  handleAwaitingHuman,
2271
- relocateSessionPanelToBottom,
1456
+ relocateSessionPanelToBottom2 as relocateSessionPanelToBottom,
2272
1457
  queueDigest,
2273
1458
  getDigestQueue,
2274
1459
  clearDigestQueue,
@@ -2278,7 +1463,5 @@ export {
2278
1463
  cleanupSessionPanel,
2279
1464
  cleanupInactiveSessions,
2280
1465
  getPerformanceStats,
2281
- startPerformanceMonitoring,
2282
- stopPerformanceMonitoring,
2283
- generatePerformanceReport
1466
+ startPerformanceMonitoring2 as startPerformanceMonitoring
2284
1467
  };