workspacecord 1.1.2 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{archive-manager-XM3UPOY2.js → archive-manager-FJU7YEEH.js} +4 -4
- package/dist/{attachment-cli-IGQBB6YJ.js → attachment-cli-AT4HXAGU.js} +2 -2
- package/dist/{chunk-54DP53ZK.js → chunk-5EMN2IL5.js} +29 -3
- package/dist/{chunk-L2ZJXV6H.js → chunk-66B64WL3.js} +1 -1
- package/dist/{chunk-QWPKAUSV.js → chunk-C4L34VJK.js} +8287 -7421
- package/dist/chunk-JR4B4L7I.js +5301 -0
- package/dist/{chunk-UEX7U2KW.js → chunk-MJ5JKFGS.js} +911 -232
- package/dist/{chunk-ZAQV2RZS.js → chunk-NNTMVOTM.js} +1054 -1310
- package/dist/{chunk-COXPTYH5.js → chunk-PWMEOBXG.js} +4 -4
- package/dist/{chunk-I6EOCCQV.js → chunk-SNPFYUQ3.js} +713 -628
- package/dist/{chunk-GMYN4SYT.js → chunk-SV7EHL3B.js} +3 -3
- package/dist/cli-framework-YF3LPLMT.js +18 -0
- package/dist/cli.js +10 -10
- package/dist/{codex-launcher-VDQ5VZPT.js → codex-launcher-CLGG4CVY.js} +1 -1
- package/dist/{codex-provider-NYI7KBGO.js → codex-provider-VLOS5QB6.js} +18 -4
- package/dist/{config-cli-RQR2ZRQ5.js → config-cli-7G5YWKJU.js} +2 -2
- package/dist/{panel-adapter-QTDL3S6O.js → panel-adapter-CLI4WDII.js} +4 -4
- package/dist/{project-cli-6P6ZWDR6.js → project-cli-ALKDLRMZ.js} +2 -2
- package/dist/{project-registry-OEVPECMS.js → project-registry-ZO3KSS25.js} +2 -2
- package/dist/sdk-C3D6X4EB.js +55 -0
- package/dist/{session-local-registration-RIO5EPZ5.js → session-local-registration-RL2A3QZB.js} +3 -3
- package/dist/{setup-KOS7SRSL.js → setup-GP3MML2R.js} +1 -1
- package/package.json +3 -3
- package/dist/chunk-RK6EIZOL.js +0 -589
- package/dist/cli-framework-SQM2465A.js +0 -18
- package/dist/sdk-V7A7IF7F.js +0 -43
|
@@ -1,36 +1,43 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
} from "./chunk-
|
|
3
|
+
E$$
|
|
4
|
+
} from "./chunk-C4L34VJK.js";
|
|
5
5
|
import {
|
|
6
6
|
buildDeliveryPlan,
|
|
7
7
|
cleanupOldMessages,
|
|
8
8
|
cleanupSessionPanel,
|
|
9
9
|
deliver,
|
|
10
|
+
dispatchEvent,
|
|
10
11
|
flushDigest,
|
|
11
|
-
gateCoordinator,
|
|
12
12
|
generatePerformanceReport,
|
|
13
13
|
getPendingAnswers,
|
|
14
14
|
getQuestionCount,
|
|
15
|
+
getSessionContext,
|
|
15
16
|
getSessionProjection,
|
|
17
|
+
getSessionView,
|
|
18
|
+
getSubagents,
|
|
16
19
|
handleAwaitingHuman,
|
|
17
20
|
handleResultEvent,
|
|
18
21
|
initializeSessionPanel,
|
|
19
|
-
|
|
22
|
+
makeModeButtons,
|
|
20
23
|
notifyUnmanagedCodexHint,
|
|
21
24
|
queueDigest,
|
|
22
25
|
registerExistingStatusCard,
|
|
23
26
|
relocateSessionPanelToBottom,
|
|
27
|
+
resolveEffectiveClaudePermissionMode as resolveEffectiveClaudePermissionMode2,
|
|
28
|
+
runSubagentWatchdog,
|
|
24
29
|
sendAckReaction,
|
|
30
|
+
sendSystemNotice,
|
|
25
31
|
sendTyping,
|
|
26
32
|
setPendingAnswer,
|
|
33
|
+
spawnSubagent,
|
|
27
34
|
startPerformanceMonitoring,
|
|
28
35
|
stopPerformanceMonitoring,
|
|
29
36
|
updateSessionState
|
|
30
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-NNTMVOTM.js";
|
|
31
38
|
import {
|
|
32
39
|
registerMessageAttachments
|
|
33
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-66B64WL3.js";
|
|
34
41
|
import {
|
|
35
42
|
addMcpServer,
|
|
36
43
|
addSkill,
|
|
@@ -51,14 +58,14 @@ import {
|
|
|
51
58
|
setControlChannelId,
|
|
52
59
|
setHistoryChannelId,
|
|
53
60
|
setPersonality
|
|
54
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-SV7EHL3B.js";
|
|
55
62
|
import {
|
|
56
63
|
getAllRegisteredProjects
|
|
57
|
-
} from "./chunk-
|
|
64
|
+
} from "./chunk-5EMN2IL5.js";
|
|
58
65
|
import {
|
|
59
66
|
buildClaudeSubagentProviderSessionId,
|
|
60
67
|
registerLocalSession
|
|
61
|
-
} from "./chunk-
|
|
68
|
+
} from "./chunk-PWMEOBXG.js";
|
|
62
69
|
import {
|
|
63
70
|
abortSession,
|
|
64
71
|
abortSessionWithReason,
|
|
@@ -66,10 +73,13 @@ import {
|
|
|
66
73
|
consumeAbortReason,
|
|
67
74
|
createSession,
|
|
68
75
|
debouncedSaveSession,
|
|
76
|
+
drainBatchApprovals,
|
|
69
77
|
endSession,
|
|
78
|
+
enqueueBatchApproval,
|
|
79
|
+
gateService,
|
|
70
80
|
getAllSessions,
|
|
81
|
+
getBatchApprovalCount,
|
|
71
82
|
getOutputPort,
|
|
72
|
-
getSession,
|
|
73
83
|
getSessionByChannel,
|
|
74
84
|
getSessionByProviderSession,
|
|
75
85
|
getSessionController,
|
|
@@ -78,7 +88,9 @@ import {
|
|
|
78
88
|
getSessionsByCategory,
|
|
79
89
|
loadSessions,
|
|
80
90
|
markSessionGenerating,
|
|
91
|
+
normalizeCodexEvent,
|
|
81
92
|
registerOutputPort,
|
|
93
|
+
removeBatchApproval,
|
|
82
94
|
resolveCodexSessionFromMonitor,
|
|
83
95
|
resolveEffectiveClaudePermissionMode,
|
|
84
96
|
resolveEffectiveCodexOptions,
|
|
@@ -91,21 +103,26 @@ import {
|
|
|
91
103
|
setSessionController,
|
|
92
104
|
setStatusCardBinding,
|
|
93
105
|
setVerbose,
|
|
106
|
+
stateMachine,
|
|
94
107
|
updateSession,
|
|
95
108
|
updateSessionPermissions,
|
|
96
109
|
updateWorkflowState
|
|
97
|
-
} from "./chunk-
|
|
110
|
+
} from "./chunk-JR4B4L7I.js";
|
|
98
111
|
import {
|
|
112
|
+
JsonFileRepository,
|
|
113
|
+
MonitorRunEnded,
|
|
114
|
+
MonitorRunStarted,
|
|
99
115
|
ServiceBus,
|
|
100
116
|
config,
|
|
101
117
|
formatDuration,
|
|
102
118
|
formatRelative,
|
|
103
119
|
formatUptime,
|
|
120
|
+
getDomainBus,
|
|
104
121
|
intervalService,
|
|
105
122
|
isAbortError,
|
|
106
123
|
isUserAllowed,
|
|
107
124
|
truncate
|
|
108
|
-
} from "./chunk-
|
|
125
|
+
} from "./chunk-MJ5JKFGS.js";
|
|
109
126
|
import {
|
|
110
127
|
__commonJS,
|
|
111
128
|
__require,
|
|
@@ -620,15 +637,43 @@ import cac from "cac";
|
|
|
620
637
|
import {
|
|
621
638
|
Client as Client2,
|
|
622
639
|
GatewayIntentBits,
|
|
623
|
-
ChannelType as
|
|
640
|
+
ChannelType as ChannelType7
|
|
624
641
|
} from "discord.js";
|
|
625
642
|
|
|
626
643
|
// ../bot/src/bot-services-orchestrator.ts
|
|
627
|
-
import { ChannelType as
|
|
644
|
+
import { ChannelType as ChannelType3 } from "discord.js";
|
|
628
645
|
import { homedir as homedir3 } from "os";
|
|
629
646
|
import { join as join4 } from "path";
|
|
630
647
|
|
|
631
648
|
// ../providers/src/claude-provider.ts
|
|
649
|
+
function mapClaudeTerminalReason(raw) {
|
|
650
|
+
if (!raw) return void 0;
|
|
651
|
+
switch (raw) {
|
|
652
|
+
case "completed":
|
|
653
|
+
return "completed";
|
|
654
|
+
case "max_turns":
|
|
655
|
+
return "max_turns";
|
|
656
|
+
case "blocking_limit":
|
|
657
|
+
case "rapid_refill_breaker":
|
|
658
|
+
return "rate_limited";
|
|
659
|
+
case "prompt_too_long":
|
|
660
|
+
return "context_too_long";
|
|
661
|
+
case "image_error":
|
|
662
|
+
return "image_error";
|
|
663
|
+
case "model_error":
|
|
664
|
+
return "model_error";
|
|
665
|
+
case "aborted_streaming":
|
|
666
|
+
case "aborted_tools":
|
|
667
|
+
return "aborted";
|
|
668
|
+
case "stop_hook_prevented":
|
|
669
|
+
case "hook_stopped":
|
|
670
|
+
return "hook_stopped";
|
|
671
|
+
case "tool_deferred":
|
|
672
|
+
return "tool_deferred";
|
|
673
|
+
default:
|
|
674
|
+
return "error";
|
|
675
|
+
}
|
|
676
|
+
}
|
|
632
677
|
var TASK_TOOLS = /* @__PURE__ */ new Set(["TaskCreate", "TaskUpdate", "TaskList", "TaskGet"]);
|
|
633
678
|
var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".bmp"]);
|
|
634
679
|
function extractImagePath(toolName, toolInput) {
|
|
@@ -681,7 +726,7 @@ var ClaudeProvider = class {
|
|
|
681
726
|
let resumeId = options.providerSessionId;
|
|
682
727
|
while (true) {
|
|
683
728
|
let failed = false;
|
|
684
|
-
const stream =
|
|
729
|
+
const stream = E$$({
|
|
685
730
|
prompt: buildQueryPrompt(),
|
|
686
731
|
options: {
|
|
687
732
|
cwd: options.directory,
|
|
@@ -716,7 +761,7 @@ var ClaudeProvider = class {
|
|
|
716
761
|
let resumeId = options.providerSessionId;
|
|
717
762
|
while (true) {
|
|
718
763
|
let failed = false;
|
|
719
|
-
const stream =
|
|
764
|
+
const stream = E$$({
|
|
720
765
|
prompt: "",
|
|
721
766
|
options: {
|
|
722
767
|
cwd: options.directory,
|
|
@@ -791,13 +836,14 @@ var ClaudeProvider = class {
|
|
|
791
836
|
let resultText = "";
|
|
792
837
|
const toolName = "";
|
|
793
838
|
if (Array.isArray(content)) {
|
|
794
|
-
for (const
|
|
839
|
+
for (const rawBlock of content) {
|
|
840
|
+
const block = rawBlock;
|
|
795
841
|
if (block.type === "tool_result" && block.content) {
|
|
796
842
|
if (typeof block.content === "string") {
|
|
797
843
|
resultText += block.content;
|
|
798
844
|
} else if (Array.isArray(block.content)) {
|
|
799
845
|
for (const sub of block.content) {
|
|
800
|
-
if (sub.type === "text") resultText += sub.text;
|
|
846
|
+
if (sub.type === "text" && sub.text) resultText += sub.text;
|
|
801
847
|
}
|
|
802
848
|
}
|
|
803
849
|
}
|
|
@@ -845,7 +891,8 @@ var ClaudeProvider = class {
|
|
|
845
891
|
costUsd: r.total_cost_usd ?? 0,
|
|
846
892
|
durationMs: r.duration_ms ?? 0,
|
|
847
893
|
numTurns: r.num_turns ?? 0,
|
|
848
|
-
errors: r.errors ?? []
|
|
894
|
+
errors: r.errors ?? [],
|
|
895
|
+
terminalReason: mapClaudeTerminalReason(r.terminal_reason) ?? (r.subtype === "success" ? "completed" : "error")
|
|
849
896
|
};
|
|
850
897
|
}
|
|
851
898
|
}
|
|
@@ -858,7 +905,7 @@ providers.set("claude", new ClaudeProvider());
|
|
|
858
905
|
var codexLoadAttempted = false;
|
|
859
906
|
async function loadCodexProvider() {
|
|
860
907
|
try {
|
|
861
|
-
const { CodexProvider } = await import("./codex-provider-
|
|
908
|
+
const { CodexProvider } = await import("./codex-provider-VLOS5QB6.js");
|
|
862
909
|
providers.set("codex", new CodexProvider());
|
|
863
910
|
codexLoadAttempted = true;
|
|
864
911
|
} catch (err) {
|
|
@@ -1017,15 +1064,18 @@ function buildMonitorSystemPromptParts(session) {
|
|
|
1017
1064
|
// ../engine/src/session/provider-runtime.ts
|
|
1018
1065
|
function buildProviderOptions(session, controller, isMonitor = false, runtimeOverrides = {}) {
|
|
1019
1066
|
const effectiveCodex = resolveEffectiveCodexOptions(session);
|
|
1067
|
+
const monitorEffort = isMonitor ? config.monitorReasoningEffort || void 0 : void 0;
|
|
1068
|
+
const useMonitorJudgeModel = isMonitor && session.provider === "claude" && (monitorEffort === "high" || monitorEffort === "xhigh");
|
|
1069
|
+
const model = useMonitorJudgeModel ? config.monitorClaudeModel || session.model : session.model;
|
|
1020
1070
|
return {
|
|
1021
1071
|
directory: session.directory,
|
|
1022
1072
|
providerSessionId: isMonitor ? session.monitorProviderSessionId : session.providerSessionId,
|
|
1023
|
-
model
|
|
1073
|
+
model,
|
|
1024
1074
|
sandboxMode: effectiveCodex.sandboxMode,
|
|
1025
1075
|
approvalPolicy: effectiveCodex.approvalPolicy,
|
|
1026
1076
|
networkAccessEnabled: effectiveCodex.networkAccessEnabled,
|
|
1027
1077
|
webSearchMode: effectiveCodex.webSearchMode,
|
|
1028
|
-
modelReasoningEffort: config.codexReasoningEffort || void 0,
|
|
1078
|
+
modelReasoningEffort: monitorEffort ?? (config.codexReasoningEffort || void 0),
|
|
1029
1079
|
claudePermissionMode: resolveEffectiveClaudePermissionMode(session),
|
|
1030
1080
|
systemPromptParts: isMonitor ? buildMonitorSystemPromptParts(session) : buildSystemPromptParts(session),
|
|
1031
1081
|
abortController: controller,
|
|
@@ -1033,34 +1083,29 @@ function buildProviderOptions(session, controller, isMonitor = false, runtimeOve
|
|
|
1033
1083
|
};
|
|
1034
1084
|
}
|
|
1035
1085
|
async function* sendPrompt(sessionId, prompt, runtimeOverrides = {}) {
|
|
1036
|
-
const
|
|
1037
|
-
if (!
|
|
1038
|
-
if (session.isGenerating) throw new Error("Session is already generating");
|
|
1086
|
+
const ctx = getSessionContext(sessionId);
|
|
1087
|
+
if (!ctx) throw new Error(`Session "${sessionId}" not found`);
|
|
1088
|
+
if (ctx.session.isGenerating) throw new Error("Session is already generating");
|
|
1039
1089
|
const controller = new AbortController();
|
|
1040
|
-
setSessionController(
|
|
1041
|
-
markSessionGenerating(
|
|
1042
|
-
const provider = await ensureProvider(session.provider);
|
|
1090
|
+
setSessionController(ctx.sessionId, controller);
|
|
1091
|
+
markSessionGenerating(ctx.sessionId, true);
|
|
1092
|
+
const provider = await ensureProvider(ctx.session.provider);
|
|
1043
1093
|
try {
|
|
1044
1094
|
const stream = provider.sendPrompt(
|
|
1045
1095
|
prompt,
|
|
1046
|
-
buildProviderOptions(session, controller, false, runtimeOverrides)
|
|
1096
|
+
buildProviderOptions(ctx.session, controller, false, runtimeOverrides)
|
|
1047
1097
|
);
|
|
1048
1098
|
for await (const event of stream) {
|
|
1049
1099
|
if (event.type === "session_init") {
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
current2.providerSessionId = event.providerSessionId || void 0;
|
|
1053
|
-
debouncedSaveSession();
|
|
1054
|
-
}
|
|
1100
|
+
ctx.session.providerSessionId = event.providerSessionId || void 0;
|
|
1101
|
+
ctx.save();
|
|
1055
1102
|
}
|
|
1056
1103
|
if (event.type === "result") {
|
|
1057
|
-
|
|
1058
|
-
if (current2) current2.totalCost += event.costUsd;
|
|
1104
|
+
ctx.session.totalCost += event.costUsd;
|
|
1059
1105
|
}
|
|
1060
1106
|
yield event;
|
|
1061
1107
|
}
|
|
1062
|
-
|
|
1063
|
-
if (current) current.messageCount++;
|
|
1108
|
+
ctx.session.messageCount++;
|
|
1064
1109
|
} catch (err) {
|
|
1065
1110
|
if (!isAbortError(err)) {
|
|
1066
1111
|
throw err;
|
|
@@ -1072,39 +1117,34 @@ async function* sendPrompt(sessionId, prompt, runtimeOverrides = {}) {
|
|
|
1072
1117
|
await saveSessionImmediate();
|
|
1073
1118
|
} catch (cleanupErr) {
|
|
1074
1119
|
console.error(`[ProviderRuntime] cleanup error for session ${sessionId}:`, cleanupErr);
|
|
1075
|
-
const
|
|
1076
|
-
if (
|
|
1120
|
+
const fallback = getSessionContext(sessionId);
|
|
1121
|
+
if (fallback) fallback.session.isGenerating = false;
|
|
1077
1122
|
}
|
|
1078
1123
|
}
|
|
1079
1124
|
}
|
|
1080
1125
|
async function* continueSessionWithOverrides(sessionId, runtimeOverrides = {}) {
|
|
1081
|
-
const
|
|
1082
|
-
if (!
|
|
1083
|
-
if (session.isGenerating) throw new Error("Session is already generating");
|
|
1126
|
+
const ctx = getSessionContext(sessionId);
|
|
1127
|
+
if (!ctx) throw new Error(`Session "${sessionId}" not found`);
|
|
1128
|
+
if (ctx.session.isGenerating) throw new Error("Session is already generating");
|
|
1084
1129
|
const controller = new AbortController();
|
|
1085
|
-
setSessionController(
|
|
1086
|
-
markSessionGenerating(
|
|
1087
|
-
const provider = await ensureProvider(session.provider);
|
|
1130
|
+
setSessionController(ctx.sessionId, controller);
|
|
1131
|
+
markSessionGenerating(ctx.sessionId, true);
|
|
1132
|
+
const provider = await ensureProvider(ctx.session.provider);
|
|
1088
1133
|
try {
|
|
1089
1134
|
const stream = provider.continueSession(
|
|
1090
|
-
buildProviderOptions(session, controller, false, runtimeOverrides)
|
|
1135
|
+
buildProviderOptions(ctx.session, controller, false, runtimeOverrides)
|
|
1091
1136
|
);
|
|
1092
1137
|
for await (const event of stream) {
|
|
1093
1138
|
if (event.type === "session_init") {
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
current2.providerSessionId = event.providerSessionId || void 0;
|
|
1097
|
-
debouncedSaveSession();
|
|
1098
|
-
}
|
|
1139
|
+
ctx.session.providerSessionId = event.providerSessionId || void 0;
|
|
1140
|
+
ctx.save();
|
|
1099
1141
|
}
|
|
1100
1142
|
if (event.type === "result") {
|
|
1101
|
-
|
|
1102
|
-
if (current2) current2.totalCost += event.costUsd;
|
|
1143
|
+
ctx.session.totalCost += event.costUsd;
|
|
1103
1144
|
}
|
|
1104
1145
|
yield event;
|
|
1105
1146
|
}
|
|
1106
|
-
|
|
1107
|
-
if (current) current.messageCount++;
|
|
1147
|
+
ctx.session.messageCount++;
|
|
1108
1148
|
} catch (err) {
|
|
1109
1149
|
if (!isAbortError(err)) {
|
|
1110
1150
|
throw err;
|
|
@@ -1116,39 +1156,33 @@ async function* continueSessionWithOverrides(sessionId, runtimeOverrides = {}) {
|
|
|
1116
1156
|
await saveSessionImmediate();
|
|
1117
1157
|
} catch (cleanupErr) {
|
|
1118
1158
|
console.error(`[ProviderRuntime] cleanup error for session ${sessionId}:`, cleanupErr);
|
|
1119
|
-
const
|
|
1120
|
-
if (
|
|
1159
|
+
const fallback = getSessionContext(sessionId);
|
|
1160
|
+
if (fallback) fallback.session.isGenerating = false;
|
|
1121
1161
|
}
|
|
1122
1162
|
}
|
|
1123
1163
|
}
|
|
1124
1164
|
async function* sendMonitorPrompt(sessionId, prompt) {
|
|
1125
|
-
const
|
|
1126
|
-
if (!
|
|
1127
|
-
const provider = await ensureProvider(session.provider);
|
|
1128
|
-
|
|
1129
|
-
if (current) current.lastActivity = Date.now();
|
|
1165
|
+
const ctx = getSessionContext(sessionId);
|
|
1166
|
+
if (!ctx) throw new Error(`Session "${sessionId}" not found`);
|
|
1167
|
+
const provider = await ensureProvider(ctx.session.provider);
|
|
1168
|
+
ctx.session.lastActivity = Date.now();
|
|
1130
1169
|
const mainController = getSessionController(sessionId);
|
|
1131
1170
|
const controller = new AbortController();
|
|
1132
1171
|
const onMainAbort = () => controller.abort();
|
|
1133
1172
|
mainController?.signal.addEventListener("abort", onMainAbort, { once: true });
|
|
1134
1173
|
try {
|
|
1135
|
-
const stream = provider.sendPrompt(prompt, buildProviderOptions(session, controller, true));
|
|
1174
|
+
const stream = provider.sendPrompt(prompt, buildProviderOptions(ctx.session, controller, true));
|
|
1136
1175
|
for await (const event of stream) {
|
|
1137
1176
|
if (event.type === "session_init") {
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
cur2.monitorProviderSessionId = event.providerSessionId || void 0;
|
|
1141
|
-
debouncedSaveSession();
|
|
1142
|
-
}
|
|
1177
|
+
ctx.session.monitorProviderSessionId = event.providerSessionId || void 0;
|
|
1178
|
+
ctx.save();
|
|
1143
1179
|
}
|
|
1144
1180
|
if (event.type === "result") {
|
|
1145
|
-
|
|
1146
|
-
if (cur2) cur2.totalCost += event.costUsd;
|
|
1181
|
+
ctx.session.totalCost += event.costUsd;
|
|
1147
1182
|
}
|
|
1148
1183
|
yield event;
|
|
1149
1184
|
}
|
|
1150
|
-
|
|
1151
|
-
if (cur) cur.lastActivity = Date.now();
|
|
1185
|
+
ctx.session.lastActivity = Date.now();
|
|
1152
1186
|
debouncedSaveSession();
|
|
1153
1187
|
} catch (err) {
|
|
1154
1188
|
if (!isAbortError(err)) {
|
|
@@ -1161,7 +1195,21 @@ async function* sendMonitorPrompt(sessionId, prompt) {
|
|
|
1161
1195
|
|
|
1162
1196
|
// ../engine/src/executor/permission-gate.ts
|
|
1163
1197
|
function refreshSession(session) {
|
|
1164
|
-
return
|
|
1198
|
+
return getSessionView(session.id) ?? session;
|
|
1199
|
+
}
|
|
1200
|
+
async function recordPermissionDenial(session, toolName, reason, source) {
|
|
1201
|
+
await getOutputPort().updateState(session.id, {
|
|
1202
|
+
type: "permission_denied",
|
|
1203
|
+
sessionId: session.id,
|
|
1204
|
+
source: session.provider === "codex" ? "codex" : "claude",
|
|
1205
|
+
confidence: "high",
|
|
1206
|
+
timestamp: Date.now(),
|
|
1207
|
+
metadata: { toolName, reason, source }
|
|
1208
|
+
});
|
|
1209
|
+
getOutputPort().queueDigest(session.id, {
|
|
1210
|
+
kind: "denied",
|
|
1211
|
+
text: `\u26D4 \u6743\u9650\u62D2\u7EDD\uFF1A${truncate(toolName, 40)} \u2014 ${truncate(reason, 80)}`
|
|
1212
|
+
});
|
|
1165
1213
|
}
|
|
1166
1214
|
function waitForGateResolution(session, gateId) {
|
|
1167
1215
|
console.log(`[SessionExecutor] gate:waiting sessionId=${session.id} gateId=${gateId}`);
|
|
@@ -1175,7 +1223,7 @@ function waitForGateResolution(session, gateId) {
|
|
|
1175
1223
|
);
|
|
1176
1224
|
resolve2(result);
|
|
1177
1225
|
};
|
|
1178
|
-
|
|
1226
|
+
gateService.registerReceiptHandle(gateId, {
|
|
1179
1227
|
type: session.provider === "codex" ? "codex" : "claude",
|
|
1180
1228
|
sessionId: session.id,
|
|
1181
1229
|
resolve: (action, source) => settle({ action, source }),
|
|
@@ -1202,6 +1250,80 @@ function createClaudePermissionHandler(session, _channel) {
|
|
|
1202
1250
|
console.log(
|
|
1203
1251
|
`[SessionExecutor] permission:request sessionId=${liveSession.id} tool=${toolName} action=${context.displayName || toolName}`
|
|
1204
1252
|
);
|
|
1253
|
+
const projection = stateMachine.getSnapshot(liveSession.id);
|
|
1254
|
+
if (projection.batchApprovalMode) {
|
|
1255
|
+
const gateId2 = `batch-${liveSession.id}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1256
|
+
const timestamp = Date.now();
|
|
1257
|
+
const displayName = context.displayName || toolName;
|
|
1258
|
+
const batchAction = await new Promise((resolve2) => {
|
|
1259
|
+
let settled = false;
|
|
1260
|
+
const settle = (action) => {
|
|
1261
|
+
if (settled) return;
|
|
1262
|
+
settled = true;
|
|
1263
|
+
resolve2(action);
|
|
1264
|
+
};
|
|
1265
|
+
const enqueueResult = enqueueBatchApproval(liveSession.id, {
|
|
1266
|
+
gateId: gateId2,
|
|
1267
|
+
toolUseID: context.toolUseID,
|
|
1268
|
+
toolName: displayName,
|
|
1269
|
+
detail,
|
|
1270
|
+
timestamp,
|
|
1271
|
+
resolve: settle
|
|
1272
|
+
});
|
|
1273
|
+
if (enqueueResult === "overflow") {
|
|
1274
|
+
console.warn(
|
|
1275
|
+
`[SessionExecutor] permission:batch-overflow sessionId=${liveSession.id} tool=${toolName} reason="queue at capacity"`
|
|
1276
|
+
);
|
|
1277
|
+
settle("reject");
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
stateMachine.enqueuePendingApproval(liveSession.id, {
|
|
1281
|
+
gateId: gateId2,
|
|
1282
|
+
toolName: displayName,
|
|
1283
|
+
detail,
|
|
1284
|
+
timestamp
|
|
1285
|
+
});
|
|
1286
|
+
void getOutputPort().updateState(liveSession.id, {
|
|
1287
|
+
type: "batch_approval_changed",
|
|
1288
|
+
sessionId: liveSession.id,
|
|
1289
|
+
source: liveSession.provider === "codex" ? "codex" : "claude",
|
|
1290
|
+
confidence: "high",
|
|
1291
|
+
timestamp,
|
|
1292
|
+
metadata: {
|
|
1293
|
+
enabled: true,
|
|
1294
|
+
pendingApprovals: stateMachine.getSnapshot(liveSession.id).pendingApprovals
|
|
1295
|
+
}
|
|
1296
|
+
}).catch(() => {
|
|
1297
|
+
});
|
|
1298
|
+
getOutputPort().queueDigest(liveSession.id, {
|
|
1299
|
+
kind: "batch",
|
|
1300
|
+
text: `\u5DF2\u5165\u6279\u91CF\u5BA1\u6279\u961F\u5217\uFF1A${truncate(toolName, 40)}`
|
|
1301
|
+
});
|
|
1302
|
+
const onAbort = () => {
|
|
1303
|
+
settle("reject");
|
|
1304
|
+
removeBatchApproval(liveSession.id, gateId2);
|
|
1305
|
+
stateMachine.removePendingApproval(liveSession.id, gateId2);
|
|
1306
|
+
};
|
|
1307
|
+
if (context.signal.aborted) onAbort();
|
|
1308
|
+
else context.signal.addEventListener("abort", onAbort, { once: true });
|
|
1309
|
+
});
|
|
1310
|
+
if (batchAction === "approve") {
|
|
1311
|
+
console.log(
|
|
1312
|
+
`[SessionExecutor] permission:batch-approved sessionId=${liveSession.id} tool=${toolName}`
|
|
1313
|
+
);
|
|
1314
|
+
return { behavior: "allow", toolUseID: context.toolUseID };
|
|
1315
|
+
}
|
|
1316
|
+
console.log(
|
|
1317
|
+
`[SessionExecutor] permission:batch-rejected sessionId=${liveSession.id} tool=${toolName}`
|
|
1318
|
+
);
|
|
1319
|
+
await recordPermissionDenial(liveSession, toolName, "\u6279\u91CF\u62D2\u7EDD", "user");
|
|
1320
|
+
return {
|
|
1321
|
+
behavior: "deny",
|
|
1322
|
+
message: "\u6279\u91CF\u62D2\u7EDD",
|
|
1323
|
+
interrupt: true,
|
|
1324
|
+
toolUseID: context.toolUseID
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1205
1327
|
await getOutputPort().handleAwaitingHuman(liveSession.id, detail, { source: "claude" });
|
|
1206
1328
|
const currentSession = refreshSession(liveSession);
|
|
1207
1329
|
const gateId = currentSession.activeHumanGateId;
|
|
@@ -1242,9 +1364,11 @@ function createClaudePermissionHandler(session, _channel) {
|
|
|
1242
1364
|
if (resolved.action === "approve") {
|
|
1243
1365
|
return { behavior: "allow", toolUseID: context.toolUseID };
|
|
1244
1366
|
}
|
|
1367
|
+
const denyReason = resolved.source === "timeout" ? "\u5BA1\u6279\u8D85\u65F6\uFF085 \u5206\u949F\uFF09" : resolved.source === "terminal" ? "\u5DF2\u5728\u7EC8\u7AEF\u62D2\u7EDD" : "\u5DF2\u5728 Discord \u62D2\u7EDD";
|
|
1368
|
+
await recordPermissionDenial(currentSession, toolName, denyReason, resolved.source);
|
|
1245
1369
|
return {
|
|
1246
1370
|
behavior: "deny",
|
|
1247
|
-
message:
|
|
1371
|
+
message: denyReason,
|
|
1248
1372
|
interrupt: true,
|
|
1249
1373
|
toolUseID: context.toolUseID
|
|
1250
1374
|
};
|
|
@@ -1259,7 +1383,7 @@ function shouldUseClaudePermissionHandler(session) {
|
|
|
1259
1383
|
|
|
1260
1384
|
// ../engine/src/executor/session-hooks.ts
|
|
1261
1385
|
function refreshSession2(session) {
|
|
1262
|
-
return
|
|
1386
|
+
return getSessionView(session.id) ?? session;
|
|
1263
1387
|
}
|
|
1264
1388
|
function applyWorkflowHook(session, hook, patch = {}) {
|
|
1265
1389
|
return updateWorkflowState(session.id, (current) => ({
|
|
@@ -1569,6 +1693,100 @@ function parseAskUserDecision(text) {
|
|
|
1569
1693
|
}
|
|
1570
1694
|
}
|
|
1571
1695
|
|
|
1696
|
+
// ../engine/src/executor/monitor-run-store.ts
|
|
1697
|
+
var monitorRepo = new JsonFileRepository({
|
|
1698
|
+
filename: "monitor-runs.json",
|
|
1699
|
+
idField: "id",
|
|
1700
|
+
debounceMs: 0
|
|
1701
|
+
});
|
|
1702
|
+
var initialized = false;
|
|
1703
|
+
async function ensureInit() {
|
|
1704
|
+
if (initialized) return;
|
|
1705
|
+
await monitorRepo.init();
|
|
1706
|
+
initialized = true;
|
|
1707
|
+
}
|
|
1708
|
+
function makeId(sessionId, startedAt) {
|
|
1709
|
+
const suffix = Math.random().toString(36).slice(2, 8);
|
|
1710
|
+
return `${sessionId}:${startedAt}:${suffix}`;
|
|
1711
|
+
}
|
|
1712
|
+
async function beginMonitorRun(params) {
|
|
1713
|
+
await ensureInit();
|
|
1714
|
+
const now = Date.now();
|
|
1715
|
+
const stale = monitorRepo.find({
|
|
1716
|
+
where: { sessionId: params.sessionId, status: "running" }
|
|
1717
|
+
});
|
|
1718
|
+
for (const old of stale) {
|
|
1719
|
+
await monitorRepo.update(old.id, { status: "abandoned", lastCheckpointAt: now });
|
|
1720
|
+
}
|
|
1721
|
+
const run = {
|
|
1722
|
+
id: makeId(params.sessionId, now),
|
|
1723
|
+
sessionId: params.sessionId,
|
|
1724
|
+
goal: params.goal,
|
|
1725
|
+
iteration: 0,
|
|
1726
|
+
maxIterations: params.maxIterations,
|
|
1727
|
+
status: "running",
|
|
1728
|
+
startedAt: now,
|
|
1729
|
+
lastCheckpointAt: now
|
|
1730
|
+
};
|
|
1731
|
+
await monitorRepo.save(run);
|
|
1732
|
+
getDomainBus().emit(
|
|
1733
|
+
MonitorRunStarted,
|
|
1734
|
+
{
|
|
1735
|
+
sessionId: run.sessionId,
|
|
1736
|
+
runId: run.id,
|
|
1737
|
+
goal: run.goal,
|
|
1738
|
+
maxIterations: run.maxIterations
|
|
1739
|
+
},
|
|
1740
|
+
"monitor-run-store"
|
|
1741
|
+
);
|
|
1742
|
+
return run;
|
|
1743
|
+
}
|
|
1744
|
+
async function checkpointMonitorRun(runId, patch) {
|
|
1745
|
+
await ensureInit();
|
|
1746
|
+
return monitorRepo.update(runId, { ...patch, lastCheckpointAt: Date.now() });
|
|
1747
|
+
}
|
|
1748
|
+
async function finishMonitorRun(runId, status, finalPatch = {}) {
|
|
1749
|
+
await ensureInit();
|
|
1750
|
+
const updated = await monitorRepo.update(runId, {
|
|
1751
|
+
...finalPatch,
|
|
1752
|
+
status,
|
|
1753
|
+
lastCheckpointAt: Date.now()
|
|
1754
|
+
});
|
|
1755
|
+
if (updated) {
|
|
1756
|
+
getDomainBus().emit(
|
|
1757
|
+
MonitorRunEnded,
|
|
1758
|
+
{
|
|
1759
|
+
sessionId: updated.sessionId,
|
|
1760
|
+
runId: updated.id,
|
|
1761
|
+
status,
|
|
1762
|
+
iteration: updated.iteration,
|
|
1763
|
+
rationale: updated.lastRationale
|
|
1764
|
+
},
|
|
1765
|
+
"monitor-run-store"
|
|
1766
|
+
);
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
async function reconcileMonitorRunsOnStartup() {
|
|
1770
|
+
await ensureInit();
|
|
1771
|
+
const running = monitorRepo.find({ where: { status: "running" } });
|
|
1772
|
+
const now = Date.now();
|
|
1773
|
+
for (const run of running) {
|
|
1774
|
+
await monitorRepo.update(run.id, { status: "abandoned", lastCheckpointAt: now });
|
|
1775
|
+
getDomainBus().emit(
|
|
1776
|
+
MonitorRunEnded,
|
|
1777
|
+
{
|
|
1778
|
+
sessionId: run.sessionId,
|
|
1779
|
+
runId: run.id,
|
|
1780
|
+
status: "abandoned",
|
|
1781
|
+
iteration: run.iteration,
|
|
1782
|
+
rationale: run.lastRationale
|
|
1783
|
+
},
|
|
1784
|
+
"monitor-run-store"
|
|
1785
|
+
);
|
|
1786
|
+
}
|
|
1787
|
+
return running;
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1572
1790
|
// ../engine/src/executor/monitor-loop.ts
|
|
1573
1791
|
var MAX_MONITOR_ITERATIONS = 6;
|
|
1574
1792
|
async function runMonitorDecision(session, goal, workerResult, iteration) {
|
|
@@ -1786,6 +2004,16 @@ async function runMonitorLoop(session, channel, goal, initialResult, runWorkerPa
|
|
|
1786
2004
|
console.log(
|
|
1787
2005
|
`[SessionExecutor] monitor:loop-start sessionId=${session.id} goal=${truncate(goal, 80)}`
|
|
1788
2006
|
);
|
|
2007
|
+
const monitorRun = await beginMonitorRun({
|
|
2008
|
+
sessionId: session.id,
|
|
2009
|
+
goal,
|
|
2010
|
+
maxIterations: MAX_MONITOR_ITERATIONS
|
|
2011
|
+
}).catch((err) => {
|
|
2012
|
+
console.warn(
|
|
2013
|
+
`[SessionExecutor] failed to persist MonitorRun for session ${session.id}: ${err.message}`
|
|
2014
|
+
);
|
|
2015
|
+
return null;
|
|
2016
|
+
});
|
|
1789
2017
|
let workerResult = initialResult;
|
|
1790
2018
|
let currentSession = refreshSession2(session);
|
|
1791
2019
|
for (let iteration = 1; iteration <= MAX_MONITOR_ITERATIONS; iteration++) {
|
|
@@ -1855,6 +2083,13 @@ async function runMonitorLoop(session, channel, goal, initialResult, runWorkerPa
|
|
|
1855
2083
|
});
|
|
1856
2084
|
const summary = decision.completionSummary || decision.rationale || "The monitor judged the request complete.";
|
|
1857
2085
|
await getOutputPort().handleResult(currentSession.id, createSyntheticResult(true, summary), summary);
|
|
2086
|
+
if (monitorRun) {
|
|
2087
|
+
await finishMonitorRun(monitorRun.id, "completed", {
|
|
2088
|
+
iteration,
|
|
2089
|
+
lastRationale: decision.rationale
|
|
2090
|
+
}).catch(() => {
|
|
2091
|
+
});
|
|
2092
|
+
}
|
|
1858
2093
|
console.log(
|
|
1859
2094
|
`[SessionExecutor] monitor:complete sessionId=${currentSession.id} iteration=${iteration} rationale=${truncate(decision.rationale, 80)}`
|
|
1860
2095
|
);
|
|
@@ -1875,11 +2110,26 @@ async function runMonitorLoop(session, channel, goal, initialResult, runWorkerPa
|
|
|
1875
2110
|
await getOutputPort().handleAwaitingHuman(currentSession.id, blocker, {
|
|
1876
2111
|
source: currentSession.provider === "codex" ? "codex" : "claude"
|
|
1877
2112
|
});
|
|
2113
|
+
if (monitorRun) {
|
|
2114
|
+
await finishMonitorRun(monitorRun.id, "blocked", {
|
|
2115
|
+
iteration,
|
|
2116
|
+
lastRationale: decision.rationale
|
|
2117
|
+
}).catch(() => {
|
|
2118
|
+
});
|
|
2119
|
+
}
|
|
1878
2120
|
console.warn(
|
|
1879
2121
|
`[SessionExecutor] monitor:blocked sessionId=${currentSession.id} iteration=${iteration} reason=${truncate(decision.rationale, 80)}`
|
|
1880
2122
|
);
|
|
1881
2123
|
return;
|
|
1882
2124
|
}
|
|
2125
|
+
if (monitorRun) {
|
|
2126
|
+
await checkpointMonitorRun(monitorRun.id, {
|
|
2127
|
+
iteration,
|
|
2128
|
+
lastRationale: decision.rationale,
|
|
2129
|
+
lastWorkerSummary: summarizeWorkerPass(buildWorkerProgressReport(goal, workerResult))
|
|
2130
|
+
}).catch(() => {
|
|
2131
|
+
});
|
|
2132
|
+
}
|
|
1883
2133
|
await updatePanelState(currentSession, "work_started", channel);
|
|
1884
2134
|
getOutputPort().queueDigest(currentSession.id, {
|
|
1885
2135
|
kind: "monitor",
|
|
@@ -1926,6 +2176,13 @@ async function runMonitorLoop(session, channel, goal, initialResult, runWorkerPa
|
|
|
1926
2176
|
await getOutputPort().handleAwaitingHuman(currentSession.id, limitSummary, {
|
|
1927
2177
|
source: currentSession.provider === "codex" ? "codex" : "claude"
|
|
1928
2178
|
});
|
|
2179
|
+
if (monitorRun) {
|
|
2180
|
+
await finishMonitorRun(monitorRun.id, "failed", {
|
|
2181
|
+
iteration: MAX_MONITOR_ITERATIONS,
|
|
2182
|
+
lastRationale: "Reached the continuation safety limit."
|
|
2183
|
+
}).catch(() => {
|
|
2184
|
+
});
|
|
2185
|
+
}
|
|
1929
2186
|
console.warn(
|
|
1930
2187
|
`[SessionExecutor] monitor:limit-reached sessionId=${currentSession.id} iterations=${MAX_MONITOR_ITERATIONS}`
|
|
1931
2188
|
);
|
|
@@ -2031,7 +2288,7 @@ async function executeSessionPrompt(session, channel, prompt, options = {}) {
|
|
|
2031
2288
|
}
|
|
2032
2289
|
if ((options.updateMonitorGoal ?? true) && goalText && !session.monitorGoal) {
|
|
2033
2290
|
setMonitorGoal(session.id, goalText);
|
|
2034
|
-
session =
|
|
2291
|
+
session = getSessionView(session.id) ?? session;
|
|
2035
2292
|
}
|
|
2036
2293
|
const goal = session.monitorGoal || goalText;
|
|
2037
2294
|
if (!goal) {
|
|
@@ -2141,166 +2398,31 @@ function getExpandableContent(id) {
|
|
|
2141
2398
|
return expandableStore.get(id)?.content;
|
|
2142
2399
|
}
|
|
2143
2400
|
|
|
2144
|
-
// ../
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
// ../bot/src/subagent-manager.ts
|
|
2151
|
-
import {
|
|
2152
|
-
ChannelType,
|
|
2153
|
-
ThreadAutoArchiveDuration
|
|
2154
|
-
} from "discord.js";
|
|
2155
|
-
|
|
2156
|
-
// ../bot/src/discord/delivery-notices.ts
|
|
2157
|
-
function getDeliveryPolicy() {
|
|
2158
|
-
return {
|
|
2159
|
-
textChunkLimit: config.textChunkLimit ?? 2e3,
|
|
2160
|
-
chunkMode: config.chunkMode ?? "length",
|
|
2161
|
-
replyToMode: config.replyToMode ?? "first",
|
|
2162
|
-
ackReaction: config.ackReaction ?? "\u{1F440}"
|
|
2163
|
-
};
|
|
2164
|
-
}
|
|
2165
|
-
async function sendSystemNotice(channel, sessionId, text, replyToMessageId) {
|
|
2166
|
-
if (!text.trim()) return;
|
|
2167
|
-
const plan = buildDeliveryPlan({
|
|
2168
|
-
sessionId,
|
|
2169
|
-
chatId: channel.id,
|
|
2170
|
-
text,
|
|
2171
|
-
files: [],
|
|
2172
|
-
mode: "system_notice",
|
|
2173
|
-
replyToMessageId,
|
|
2174
|
-
policy: getDeliveryPolicy()
|
|
2175
|
-
});
|
|
2176
|
-
try {
|
|
2177
|
-
await deliver(channel, plan);
|
|
2178
|
-
} catch {
|
|
2179
|
-
}
|
|
2180
|
-
}
|
|
2181
|
-
|
|
2182
|
-
// ../bot/src/subagent-manager.ts
|
|
2183
|
-
var SUBAGENT_IDLE_TIMEOUT_MS = 60 * 60 * 1e3;
|
|
2184
|
-
function canSpawnSubagent(parentSession) {
|
|
2185
|
-
return parentSession.subagentDepth < config.maxSubagentDepth;
|
|
2186
|
-
}
|
|
2187
|
-
async function spawnSubagent(parentSession, label, provider, sessionChannel) {
|
|
2188
|
-
if (!canSpawnSubagent(parentSession)) {
|
|
2189
|
-
console.warn(`[SubagentManager] Depth limit hit for session ${parentSession.id} (max depth ${config.maxSubagentDepth})`);
|
|
2190
|
-
throw new Error(
|
|
2191
|
-
`Max subagent depth (${config.maxSubagentDepth}) reached. Cannot spawn further subagents.`
|
|
2192
|
-
);
|
|
2401
|
+
// ../engine/src/executor/monitor-autoresume.ts
|
|
2402
|
+
async function reconcileAndCollectAutoResumeCandidates(policy) {
|
|
2403
|
+
const abandoned = await reconcileMonitorRunsOnStartup();
|
|
2404
|
+
if (policy === "abandon-only" || abandoned.length === 0) {
|
|
2405
|
+
return { abandoned, candidates: [] };
|
|
2193
2406
|
}
|
|
2194
|
-
const
|
|
2195
|
-
const
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
reason: `Subagent spawned by session ${parentSession.id}`
|
|
2200
|
-
});
|
|
2201
|
-
const session = await createSession({
|
|
2202
|
-
channelId: thread.id,
|
|
2203
|
-
// Subagent's primary ID is the Thread
|
|
2204
|
-
categoryId: parentSession.categoryId,
|
|
2205
|
-
projectName: parentSession.projectName,
|
|
2206
|
-
agentLabel: label,
|
|
2207
|
-
provider,
|
|
2208
|
-
directory: parentSession.directory,
|
|
2209
|
-
type: "subagent",
|
|
2210
|
-
parentChannelId: parentSession.channelId,
|
|
2211
|
-
// Parent session's TextChannel
|
|
2212
|
-
subagentDepth: parentSession.subagentDepth + 1,
|
|
2213
|
-
mode: parentSession.mode,
|
|
2214
|
-
claudePermissionMode: provider === "claude" ? parentSession.claudePermissionMode : void 0
|
|
2215
|
-
});
|
|
2216
|
-
console.log(`[SubagentManager] Spawned subagent "${label}" session ${session.id} thread ${thread.id} (depth ${session.subagentDepth}, parent ${parentSession.id})`);
|
|
2217
|
-
return session;
|
|
2218
|
-
}
|
|
2219
|
-
async function archiveSubagent(session, thread, summary) {
|
|
2220
|
-
if (summary) {
|
|
2221
|
-
await sendSystemNotice(thread, session.id, `*\u5B50\u4EFB\u52A1\u5B8C\u6210\uFF1A${summary}*`);
|
|
2222
|
-
}
|
|
2223
|
-
try {
|
|
2224
|
-
await thread.setArchived(true, "Subagent task completed");
|
|
2225
|
-
} catch (error) {
|
|
2226
|
-
console.warn(`[SubagentManager] Failed to archive thread ${thread.id} for subagent ${session.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2227
|
-
}
|
|
2228
|
-
try {
|
|
2229
|
-
await endSession(session.id);
|
|
2230
|
-
console.log(`[SubagentManager] Archived subagent ${session.id} thread ${thread.id}${summary ? ` \u2014 ${summary}` : ""}`);
|
|
2231
|
-
} catch (error) {
|
|
2232
|
-
console.warn(`[SubagentManager] Failed to end session for subagent ${session.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2233
|
-
}
|
|
2234
|
-
}
|
|
2235
|
-
function getSubagents(parentSession) {
|
|
2236
|
-
return getAllSessions().filter(
|
|
2237
|
-
(s) => s.type === "subagent" && s.parentChannelId === parentSession.channelId
|
|
2238
|
-
);
|
|
2239
|
-
}
|
|
2240
|
-
async function runSubagentWatchdog(getThread) {
|
|
2241
|
-
const now = Date.now();
|
|
2242
|
-
const checked = /* @__PURE__ */ new Set();
|
|
2243
|
-
let archived = 0;
|
|
2244
|
-
let errors = 0;
|
|
2245
|
-
for (const session of getSubagentSessions()) {
|
|
2246
|
-
if (checked.has(session.id)) continue;
|
|
2247
|
-
checked.add(session.id);
|
|
2248
|
-
const idle = now - session.lastActivity;
|
|
2249
|
-
if (idle < SUBAGENT_IDLE_TIMEOUT_MS) continue;
|
|
2407
|
+
const candidates = [];
|
|
2408
|
+
for (const run of abandoned) {
|
|
2409
|
+
const session = getSessionView(run.sessionId);
|
|
2410
|
+
if (!session) continue;
|
|
2411
|
+
if (session.mode !== "monitor") continue;
|
|
2250
2412
|
if (session.isGenerating) continue;
|
|
2251
|
-
const
|
|
2252
|
-
if (!
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
console.error(`[SubagentWatchdog] Failed to archive idle subagent ${session.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2262
|
-
errors += 1;
|
|
2263
|
-
}
|
|
2264
|
-
}
|
|
2265
|
-
console.log(`[SubagentWatchdog] Watchdog run: checked ${checked.size} subagents, archived ${archived}, errors ${errors}`);
|
|
2266
|
-
}
|
|
2267
|
-
async function autoSpawnSubagentThread(parentSession, taskId, description, sessionChannel) {
|
|
2268
|
-
if (parentSession.type === "subagent") return null;
|
|
2269
|
-
if (!canSpawnSubagent(parentSession)) return null;
|
|
2270
|
-
const threadName = `[task] ${description}`.slice(0, 100);
|
|
2271
|
-
const thread = await sessionChannel.threads.create({
|
|
2272
|
-
name: threadName,
|
|
2273
|
-
type: ChannelType.PublicThread,
|
|
2274
|
-
autoArchiveDuration: ThreadAutoArchiveDuration.OneHour,
|
|
2275
|
-
reason: `Auto-created for task ${taskId} in session ${parentSession.id}`
|
|
2276
|
-
});
|
|
2277
|
-
let session;
|
|
2278
|
-
try {
|
|
2279
|
-
session = await createSession({
|
|
2280
|
-
channelId: thread.id,
|
|
2281
|
-
categoryId: parentSession.categoryId,
|
|
2282
|
-
projectName: parentSession.projectName,
|
|
2283
|
-
agentLabel: description,
|
|
2284
|
-
provider: parentSession.provider,
|
|
2285
|
-
directory: parentSession.directory,
|
|
2286
|
-
type: "subagent",
|
|
2287
|
-
parentChannelId: parentSession.channelId,
|
|
2288
|
-
subagentDepth: (parentSession.subagentDepth || 0) + 1,
|
|
2289
|
-
mode: parentSession.mode,
|
|
2290
|
-
claudePermissionMode: parentSession.provider === "claude" ? parentSession.claudePermissionMode : void 0
|
|
2413
|
+
const goal = session.monitorGoal || run.goal;
|
|
2414
|
+
if (!goal) continue;
|
|
2415
|
+
candidates.push({
|
|
2416
|
+
sessionId: session.id,
|
|
2417
|
+
channelId: session.channelId,
|
|
2418
|
+
runId: run.id,
|
|
2419
|
+
goal,
|
|
2420
|
+
lastIteration: run.iteration,
|
|
2421
|
+
lastRationale: run.lastRationale,
|
|
2422
|
+
abandonedRun: run
|
|
2291
2423
|
});
|
|
2292
|
-
} catch (err) {
|
|
2293
|
-
console.warn(`[SubagentManager] createSession failed for auto-spawned task ${taskId}, deleting orphan thread ${thread.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2294
|
-
await thread.delete("Session creation failed").catch(
|
|
2295
|
-
(deleteErr) => console.warn(`[SubagentManager] Failed to delete orphan thread ${thread.id}: ${deleteErr instanceof Error ? deleteErr.message : String(deleteErr)}`)
|
|
2296
|
-
);
|
|
2297
|
-
throw err;
|
|
2298
2424
|
}
|
|
2299
|
-
|
|
2300
|
-
return { threadId: thread.id, session };
|
|
2301
|
-
}
|
|
2302
|
-
function getSubagentSessions() {
|
|
2303
|
-
return getAllSessions().filter((s) => s.type === "subagent");
|
|
2425
|
+
return { abandoned, candidates };
|
|
2304
2426
|
}
|
|
2305
2427
|
|
|
2306
2428
|
// ../bot/src/output/message-streamer.ts
|
|
@@ -2431,67 +2553,30 @@ var MessageStreamer = class {
|
|
|
2431
2553
|
}
|
|
2432
2554
|
};
|
|
2433
2555
|
|
|
2434
|
-
// ../bot/src/output/interaction-controls.ts
|
|
2435
|
-
import {
|
|
2436
|
-
ActionRowBuilder,
|
|
2437
|
-
ButtonBuilder,
|
|
2438
|
-
ButtonStyle,
|
|
2439
|
-
StringSelectMenuBuilder,
|
|
2440
|
-
EmbedBuilder as EmbedBuilder2
|
|
2441
|
-
} from "discord.js";
|
|
2442
|
-
function resolveEffectiveClaudePermissionMode2(currentMode, claudePermissionMode) {
|
|
2443
|
-
if (!claudePermissionMode) return void 0;
|
|
2444
|
-
return currentMode === "auto" ? "bypass" : claudePermissionMode;
|
|
2445
|
-
}
|
|
2446
|
-
function makeModeButtons(sessionId, currentMode, claudePermissionMode) {
|
|
2447
|
-
const modes = [
|
|
2448
|
-
{ id: "auto", label: "\u26A1 \u81EA\u52A8\u6A21\u5F0F" },
|
|
2449
|
-
{ id: "plan", label: "\u{1F4CB} \u8BA1\u5212\u6A21\u5F0F" },
|
|
2450
|
-
{ id: "normal", label: "\u{1F6E1}\uFE0F \u666E\u901A\u6A21\u5F0F" },
|
|
2451
|
-
{ id: "monitor", label: "\u{1F9E0} \u76D1\u63A7\u6A21\u5F0F" }
|
|
2452
|
-
];
|
|
2453
|
-
const row = new ActionRowBuilder();
|
|
2454
|
-
for (const m of modes) {
|
|
2455
|
-
row.addComponents(
|
|
2456
|
-
new ButtonBuilder().setCustomId(`mode:${sessionId}:${m.id}`).setLabel(m.id === currentMode ? `\u2713 ${m.label}` : m.label).setStyle(m.id === currentMode ? ButtonStyle.Success : ButtonStyle.Secondary).setDisabled(false)
|
|
2457
|
-
);
|
|
2458
|
-
}
|
|
2459
|
-
const effectiveClaudePermissionMode = resolveEffectiveClaudePermissionMode2(
|
|
2460
|
-
currentMode,
|
|
2461
|
-
claudePermissionMode
|
|
2462
|
-
);
|
|
2463
|
-
if (effectiveClaudePermissionMode) {
|
|
2464
|
-
const permLabel = effectiveClaudePermissionMode === "bypass" ? "\u26A1 \u7ED5\u8FC7\u6743\u9650" : "\u{1F6E1}\uFE0F \u9700\u8981\u786E\u8BA4";
|
|
2465
|
-
row.addComponents(
|
|
2466
|
-
new ButtonBuilder().setCustomId(`perm-info:${sessionId}`).setLabel(permLabel).setStyle(
|
|
2467
|
-
effectiveClaudePermissionMode === "bypass" ? ButtonStyle.Danger : ButtonStyle.Success
|
|
2468
|
-
).setDisabled(true)
|
|
2469
|
-
);
|
|
2470
|
-
}
|
|
2471
|
-
return row;
|
|
2472
|
-
}
|
|
2473
|
-
function shouldSuppressCommandExecution(command) {
|
|
2474
|
-
return command.toLowerCase().includes("total-recall");
|
|
2475
|
-
}
|
|
2476
|
-
|
|
2477
2556
|
// ../bot/src/output-handler.ts
|
|
2557
|
+
var DIGEST_FLUSH_INTERVAL_MS = 8e3;
|
|
2558
|
+
function createInitialState() {
|
|
2559
|
+
return {
|
|
2560
|
+
askedUser: false,
|
|
2561
|
+
hadError: false,
|
|
2562
|
+
success: null,
|
|
2563
|
+
commandCount: 0,
|
|
2564
|
+
fileChangeCount: 0,
|
|
2565
|
+
recentCommands: [],
|
|
2566
|
+
changedFiles: [],
|
|
2567
|
+
pendingAttachments: [],
|
|
2568
|
+
lastToolName: null,
|
|
2569
|
+
taskThreadMap: /* @__PURE__ */ new Map()
|
|
2570
|
+
};
|
|
2571
|
+
}
|
|
2478
2572
|
async function handleOutputStream(stream, channel, sessionId, verbose = false, mode = "auto", _provider = "claude", options = {}) {
|
|
2479
2573
|
const streamer = new MessageStreamer(channel, sessionId);
|
|
2480
|
-
console.log(
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
let hadError = false;
|
|
2485
|
-
let success = null;
|
|
2486
|
-
let commandCount = 0;
|
|
2487
|
-
let fileChangeCount = 0;
|
|
2488
|
-
const recentCommands = [];
|
|
2489
|
-
const changedFiles = [];
|
|
2490
|
-
const pendingAttachments = [];
|
|
2491
|
-
const taskThreadMap = /* @__PURE__ */ new Map();
|
|
2492
|
-
let deferredResult;
|
|
2574
|
+
console.log(
|
|
2575
|
+
`[OutputHandler] Stream started for session ${sessionId} (provider: ${_provider}, mode: ${mode}, verbose: ${verbose})`
|
|
2576
|
+
);
|
|
2577
|
+
const state = createInitialState();
|
|
2493
2578
|
let lastDigestFlushAt = Date.now();
|
|
2494
|
-
const session =
|
|
2579
|
+
const session = getSessionView(sessionId);
|
|
2495
2580
|
if (session) {
|
|
2496
2581
|
await initializeSessionPanel(sessionId, channel, {
|
|
2497
2582
|
statusCardMessageId: session.statusCardMessageId,
|
|
@@ -2506,238 +2591,34 @@ async function handleOutputStream(stream, channel, sessionId, verbose = false, m
|
|
|
2506
2591
|
timestamp: Date.now()
|
|
2507
2592
|
});
|
|
2508
2593
|
}
|
|
2594
|
+
const ctx = { sessionId, channel, streamer, verbose, mode, state };
|
|
2509
2595
|
try {
|
|
2510
2596
|
for await (const event of stream) {
|
|
2511
2597
|
options.onEvent?.(event);
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
streamer.append(event.text);
|
|
2515
|
-
break;
|
|
2516
|
-
}
|
|
2517
|
-
case "ask_user": {
|
|
2518
|
-
console.log(`[OutputHandler] Session ${sessionId}: ask_user event (human input requested)`);
|
|
2519
|
-
askedUser = true;
|
|
2520
|
-
askUserQuestionsJson = event.questionsJson;
|
|
2521
|
-
await streamer.discard();
|
|
2522
|
-
if (session) {
|
|
2523
|
-
await updateSessionState(sessionId, {
|
|
2524
|
-
type: "awaiting_human",
|
|
2525
|
-
sessionId,
|
|
2526
|
-
source: session.provider === "claude" ? "claude" : "codex",
|
|
2527
|
-
confidence: "high",
|
|
2528
|
-
timestamp: Date.now(),
|
|
2529
|
-
metadata: { detail: event.questionsJson }
|
|
2530
|
-
});
|
|
2531
|
-
await flushDigest(sessionId);
|
|
2532
|
-
await handleAwaitingHuman(sessionId, event.questionsJson, {
|
|
2533
|
-
source: session.provider === "claude" ? "claude" : "codex"
|
|
2534
|
-
});
|
|
2535
|
-
}
|
|
2536
|
-
break;
|
|
2537
|
-
}
|
|
2538
|
-
case "task": {
|
|
2539
|
-
await streamer.finalize();
|
|
2540
|
-
queueDigest(sessionId, { kind: "tool", text: `\u4EFB\u52A1\u5DE5\u5177\uFF1A${event.action}` });
|
|
2541
|
-
lastToolName = event.action;
|
|
2542
|
-
break;
|
|
2543
|
-
}
|
|
2544
|
-
case "task_started": {
|
|
2545
|
-
await streamer.finalize();
|
|
2546
|
-
queueDigest(sessionId, {
|
|
2547
|
-
kind: "subagent",
|
|
2548
|
-
text: `\u5B50\u4EE3\u7406\u542F\u52A8\uFF1A${truncate(event.description, 80)}`
|
|
2549
|
-
});
|
|
2550
|
-
if (session && channel.type !== void 0) {
|
|
2551
|
-
autoSpawnSubagentThread(session, event.taskId, event.description, channel).then((result) => {
|
|
2552
|
-
if (result) taskThreadMap.set(event.taskId, result.threadId);
|
|
2553
|
-
}).catch((err) => console.warn(`[OutputHandler] Failed to auto-spawn thread for task ${event.taskId}: ${err.message}`));
|
|
2554
|
-
}
|
|
2555
|
-
break;
|
|
2556
|
-
}
|
|
2557
|
-
case "task_progress": {
|
|
2558
|
-
if (event.summary) {
|
|
2559
|
-
queueDigest(sessionId, {
|
|
2560
|
-
kind: "subagent",
|
|
2561
|
-
text: `\u5B50\u4EE3\u7406\u8FDB\u5C55\uFF1A${truncate(event.summary, 100)}`
|
|
2562
|
-
});
|
|
2563
|
-
}
|
|
2564
|
-
const progressThreadId = taskThreadMap.get(event.taskId);
|
|
2565
|
-
if (progressThreadId && event.summary) {
|
|
2566
|
-
const thread = channel.threads?.cache.get(progressThreadId);
|
|
2567
|
-
if (thread) {
|
|
2568
|
-
thread.send(`\u{1F4DD} ${truncate(event.summary, 1900)}`).catch((e) => console.warn(`[OutputHandler] Failed to send progress to thread: ${e.message}`));
|
|
2569
|
-
}
|
|
2570
|
-
}
|
|
2571
|
-
break;
|
|
2572
|
-
}
|
|
2573
|
-
case "task_done": {
|
|
2574
|
-
await streamer.finalize();
|
|
2575
|
-
queueDigest(sessionId, {
|
|
2576
|
-
kind: "subagent",
|
|
2577
|
-
text: `\u5B50\u4EE3\u7406${event.status === "completed" ? "\u5B8C\u6210" : "\u7ED3\u675F"}\uFF1A${truncate(event.summary || "No summary.", 100)}`
|
|
2578
|
-
});
|
|
2579
|
-
const doneThreadId = taskThreadMap.get(event.taskId);
|
|
2580
|
-
if (doneThreadId) {
|
|
2581
|
-
const thread = channel.threads?.cache.get(doneThreadId);
|
|
2582
|
-
if (thread) {
|
|
2583
|
-
const statusEmoji = event.status === "completed" ? "\u2705" : "\u274C";
|
|
2584
|
-
thread.send(`${statusEmoji} \u5B50\u4EFB\u52A1${event.status === "completed" ? "\u5B8C\u6210" : "\u7ED3\u675F"}\uFF1A${truncate(event.summary || "", 1900)}`).catch((e) => console.warn(`[OutputHandler] Failed to send task done to thread: ${e.message}`));
|
|
2585
|
-
}
|
|
2586
|
-
taskThreadMap.delete(event.taskId);
|
|
2587
|
-
}
|
|
2588
|
-
break;
|
|
2589
|
-
}
|
|
2590
|
-
case "web_search": {
|
|
2591
|
-
if (verbose) {
|
|
2592
|
-
queueDigest(sessionId, { kind: "search", text: `\u68C0\u7D22\uFF1A${truncate(event.query, 80)}` });
|
|
2593
|
-
}
|
|
2594
|
-
break;
|
|
2595
|
-
}
|
|
2596
|
-
case "tool_start": {
|
|
2597
|
-
await streamer.finalize();
|
|
2598
|
-
queueDigest(sessionId, { kind: "tool", text: `\u5DE5\u5177\uFF1A${event.toolName}` });
|
|
2599
|
-
lastToolName = event.toolName;
|
|
2600
|
-
break;
|
|
2601
|
-
}
|
|
2602
|
-
case "tool_result": {
|
|
2603
|
-
await streamer.finalize();
|
|
2604
|
-
if (verbose && event.result) {
|
|
2605
|
-
queueDigest(sessionId, {
|
|
2606
|
-
kind: "tool",
|
|
2607
|
-
text: `\u5DE5\u5177\u7ED3\u679C\uFF1A${truncate(lastToolName || event.toolName || "tool", 60)}`
|
|
2608
|
-
});
|
|
2609
|
-
}
|
|
2610
|
-
break;
|
|
2611
|
-
}
|
|
2612
|
-
case "image_file": {
|
|
2613
|
-
if (existsSync(event.filePath)) {
|
|
2614
|
-
pendingAttachments.push(event.filePath);
|
|
2615
|
-
}
|
|
2616
|
-
break;
|
|
2617
|
-
}
|
|
2618
|
-
case "command_execution": {
|
|
2619
|
-
commandCount++;
|
|
2620
|
-
if (recentCommands.length < 8) recentCommands.push(event.command);
|
|
2621
|
-
if (!shouldSuppressCommandExecution(event.command)) {
|
|
2622
|
-
queueDigest(sessionId, {
|
|
2623
|
-
kind: "command",
|
|
2624
|
-
text: `\u547D\u4EE4\uFF1A${truncate(event.command, 80)}${event.exitCode !== null ? `\uFF08\u9000\u51FA\u7801 ${event.exitCode}\uFF09` : ""}`
|
|
2625
|
-
});
|
|
2626
|
-
}
|
|
2627
|
-
break;
|
|
2628
|
-
}
|
|
2629
|
-
case "file_change": {
|
|
2630
|
-
fileChangeCount += event.changes.length;
|
|
2631
|
-
for (const change of event.changes) {
|
|
2632
|
-
if (!change.filePath) continue;
|
|
2633
|
-
if (changedFiles.includes(change.filePath)) continue;
|
|
2634
|
-
if (changedFiles.length >= 12) break;
|
|
2635
|
-
changedFiles.push(change.filePath);
|
|
2636
|
-
}
|
|
2637
|
-
queueDigest(sessionId, {
|
|
2638
|
-
kind: "file",
|
|
2639
|
-
text: `\u6587\u4EF6\u53D8\u66F4\uFF1A${event.changes.length} \u4E2A\uFF08\u6700\u8FD1\uFF1A${truncate(changedFiles.slice(-3).join(", "), 120)}\uFF09`
|
|
2640
|
-
});
|
|
2641
|
-
break;
|
|
2642
|
-
}
|
|
2643
|
-
case "reasoning": {
|
|
2644
|
-
if (verbose) {
|
|
2645
|
-
queueDigest(sessionId, { kind: "reasoning", text: `\u63A8\u7406\uFF1A${truncate(event.text, 100)}` });
|
|
2646
|
-
}
|
|
2647
|
-
break;
|
|
2648
|
-
}
|
|
2649
|
-
case "todo_list": {
|
|
2650
|
-
queueDigest(sessionId, {
|
|
2651
|
-
kind: "todo",
|
|
2652
|
-
text: `\u5F85\u529E\u66F4\u65B0\uFF1A${event.items.filter((item) => item.completed).length}/${event.items.length} \u5DF2\u5B8C\u6210`
|
|
2653
|
-
});
|
|
2654
|
-
break;
|
|
2655
|
-
}
|
|
2656
|
-
case "result": {
|
|
2657
|
-
success = event.success;
|
|
2658
|
-
const lastText = streamer.getText();
|
|
2659
|
-
const cost = event.costUsd.toFixed(4);
|
|
2660
|
-
const duration = event.durationMs ? `${(event.durationMs / 1e3).toFixed(1)}s` : "unknown";
|
|
2661
|
-
const turns = event.numTurns || 0;
|
|
2662
|
-
const modeLabel = { auto: "\u81EA\u52A8", plan: "\u8BA1\u5212", normal: "\u666E\u901A", monitor: "\u76D1\u63A7" }[mode] || "\u81EA\u52A8";
|
|
2663
|
-
const statusLine = event.success ? `-# $${cost} | ${duration} | ${turns} turns | ${modeLabel}` : `-# Error | $${cost} | ${duration} | ${turns} turns`;
|
|
2664
|
-
streamer.append(`
|
|
2665
|
-
${statusLine}`, { persist: false });
|
|
2666
|
-
if (!event.success && event.errors.length) {
|
|
2667
|
-
streamer.append(`
|
|
2668
|
-
\`\`\`
|
|
2669
|
-
${event.errors.join("\n")}
|
|
2670
|
-
\`\`\``, { persist: false });
|
|
2671
|
-
}
|
|
2672
|
-
await streamer.finalize();
|
|
2673
|
-
if (session) {
|
|
2674
|
-
if (mode === "monitor") {
|
|
2675
|
-
await flushDigest(sessionId);
|
|
2676
|
-
await updateSessionState(sessionId, {
|
|
2677
|
-
type: "work_started",
|
|
2678
|
-
sessionId,
|
|
2679
|
-
source: session.provider === "claude" ? "claude" : "codex",
|
|
2680
|
-
confidence: "high",
|
|
2681
|
-
timestamp: Date.now(),
|
|
2682
|
-
metadata: {
|
|
2683
|
-
phase: "\u7B49\u5F85\u76D1\u7763\u5224\u65AD",
|
|
2684
|
-
summary: event.success ? "\u672C\u8F6E\u6267\u884C\u7ED3\u675F\uFF0C\u7B49\u5F85\u76D1\u7763\u5224\u65AD" : "\u672C\u8F6E\u6267\u884C\u5931\u8D25\uFF0C\u7B49\u5F85\u76D1\u7763\u5224\u65AD"
|
|
2685
|
-
}
|
|
2686
|
-
});
|
|
2687
|
-
} else {
|
|
2688
|
-
deferredResult = {
|
|
2689
|
-
event,
|
|
2690
|
-
text: lastText,
|
|
2691
|
-
attachments: [...pendingAttachments]
|
|
2692
|
-
};
|
|
2693
|
-
}
|
|
2694
|
-
}
|
|
2695
|
-
pendingAttachments.length = 0;
|
|
2696
|
-
break;
|
|
2697
|
-
}
|
|
2698
|
-
case "error": {
|
|
2699
|
-
console.warn(`[OutputHandler] Session ${sessionId}: error event \u2014 ${event.message}`);
|
|
2700
|
-
hadError = true;
|
|
2701
|
-
await streamer.finalize();
|
|
2702
|
-
queueDigest(sessionId, { kind: "error", text: `\u9519\u8BEF\uFF1A${truncate(event.message, 120)}` });
|
|
2703
|
-
if (session && mode !== "monitor") {
|
|
2704
|
-
await flushDigest(sessionId);
|
|
2705
|
-
await updateSessionState(sessionId, {
|
|
2706
|
-
type: "errored",
|
|
2707
|
-
sessionId,
|
|
2708
|
-
source: session.provider === "claude" ? "claude" : "codex",
|
|
2709
|
-
confidence: "high",
|
|
2710
|
-
timestamp: Date.now(),
|
|
2711
|
-
metadata: { errorMessage: event.message }
|
|
2712
|
-
});
|
|
2713
|
-
}
|
|
2714
|
-
break;
|
|
2715
|
-
}
|
|
2716
|
-
case "session_init": {
|
|
2717
|
-
break;
|
|
2718
|
-
}
|
|
2719
|
-
}
|
|
2720
|
-
if (session && Date.now() - lastDigestFlushAt >= 8e3) {
|
|
2598
|
+
await dispatchEvent(event, ctx);
|
|
2599
|
+
if (session && Date.now() - lastDigestFlushAt >= DIGEST_FLUSH_INTERVAL_MS) {
|
|
2721
2600
|
await flushDigest(sessionId);
|
|
2722
2601
|
lastDigestFlushAt = Date.now();
|
|
2723
2602
|
}
|
|
2724
2603
|
}
|
|
2725
|
-
if (session && mode !== "monitor" && deferredResult) {
|
|
2726
|
-
console.log(
|
|
2604
|
+
if (session && mode !== "monitor" && state.deferredResult) {
|
|
2605
|
+
console.log(
|
|
2606
|
+
`[OutputHandler] Session ${sessionId}: delivering deferred result (success: ${state.deferredResult.event.success})`
|
|
2607
|
+
);
|
|
2727
2608
|
await flushDigest(sessionId);
|
|
2728
2609
|
await handleResultEvent(
|
|
2729
2610
|
sessionId,
|
|
2730
|
-
deferredResult.event,
|
|
2731
|
-
deferredResult.text,
|
|
2732
|
-
deferredResult.attachments
|
|
2611
|
+
state.deferredResult.event,
|
|
2612
|
+
state.deferredResult.text,
|
|
2613
|
+
state.deferredResult.attachments
|
|
2733
2614
|
);
|
|
2734
2615
|
}
|
|
2735
2616
|
} catch (err) {
|
|
2736
|
-
hadError = true;
|
|
2617
|
+
state.hadError = true;
|
|
2737
2618
|
await streamer.finalize();
|
|
2738
2619
|
if (!isAbortError(err)) {
|
|
2739
|
-
console.error(`[OutputHandler] Session ${sessionId}: unhandled stream error \u2014 ${err.message || ""}`);
|
|
2740
2620
|
const errMsg = err.message || "";
|
|
2621
|
+
console.error(`[OutputHandler] Session ${sessionId}: unhandled stream error \u2014 ${errMsg}`);
|
|
2741
2622
|
queueDigest(sessionId, { kind: "error", text: `\u5F02\u5E38\uFF1A${truncate(errMsg, 120)}` });
|
|
2742
2623
|
if (session) {
|
|
2743
2624
|
await flushDigest(sessionId);
|
|
@@ -2755,37 +2636,56 @@ ${event.errors.join("\n")}
|
|
|
2755
2636
|
streamer.destroy();
|
|
2756
2637
|
}
|
|
2757
2638
|
const finalText = streamer.getText();
|
|
2758
|
-
console.log(
|
|
2639
|
+
console.log(
|
|
2640
|
+
`[OutputHandler] Stream ended for session ${sessionId} (text: ${finalText.length} chars, commands: ${state.commandCount}, files: ${state.fileChangeCount}, success: ${state.success}, hadError: ${state.hadError})`
|
|
2641
|
+
);
|
|
2759
2642
|
return {
|
|
2760
2643
|
text: finalText,
|
|
2761
|
-
askedUser,
|
|
2762
|
-
askUserQuestionsJson,
|
|
2763
|
-
hadError,
|
|
2764
|
-
success,
|
|
2765
|
-
commandCount,
|
|
2766
|
-
fileChangeCount,
|
|
2767
|
-
recentCommands,
|
|
2768
|
-
changedFiles
|
|
2644
|
+
askedUser: state.askedUser,
|
|
2645
|
+
askUserQuestionsJson: state.askUserQuestionsJson,
|
|
2646
|
+
hadError: state.hadError,
|
|
2647
|
+
success: state.success,
|
|
2648
|
+
commandCount: state.commandCount,
|
|
2649
|
+
fileChangeCount: state.fileChangeCount,
|
|
2650
|
+
recentCommands: state.recentCommands,
|
|
2651
|
+
changedFiles: state.changedFiles
|
|
2769
2652
|
};
|
|
2770
2653
|
}
|
|
2771
2654
|
|
|
2772
2655
|
// ../bot/src/discord-output-port.ts
|
|
2656
|
+
function asSessionChannel(ref) {
|
|
2657
|
+
return ref;
|
|
2658
|
+
}
|
|
2659
|
+
function toResultEvent(data) {
|
|
2660
|
+
return {
|
|
2661
|
+
type: "result",
|
|
2662
|
+
success: data.success,
|
|
2663
|
+
costUsd: data.costUsd,
|
|
2664
|
+
durationMs: data.durationMs,
|
|
2665
|
+
numTurns: data.numTurns,
|
|
2666
|
+
errors: data.errors,
|
|
2667
|
+
metadata: data.metadata
|
|
2668
|
+
};
|
|
2669
|
+
}
|
|
2773
2670
|
var DiscordOutputPort = class {
|
|
2774
2671
|
async initializePanel(session, channel) {
|
|
2775
|
-
await initializeSessionPanel(session.id, channel);
|
|
2672
|
+
await initializeSessionPanel(session.id, asSessionChannel(channel));
|
|
2776
2673
|
}
|
|
2777
2674
|
async updateState(sessionId, event, options) {
|
|
2778
|
-
await updateSessionState(
|
|
2675
|
+
await updateSessionState(
|
|
2676
|
+
sessionId,
|
|
2677
|
+
event,
|
|
2678
|
+
options?.channel ? { channel: asSessionChannel(options.channel) } : void 0
|
|
2679
|
+
);
|
|
2779
2680
|
}
|
|
2780
2681
|
async handleResult(sessionId, resultEvent, summary) {
|
|
2781
|
-
|
|
2782
|
-
await handleResultEvent(sessionId, event, summary ?? "");
|
|
2682
|
+
await handleResultEvent(sessionId, toResultEvent(resultEvent), summary ?? "");
|
|
2783
2683
|
}
|
|
2784
2684
|
async handleAwaitingHuman(sessionId, reason, options) {
|
|
2785
2685
|
await handleAwaitingHuman(sessionId, reason, options);
|
|
2786
2686
|
}
|
|
2787
2687
|
async relocatePanel(sessionId, channel) {
|
|
2788
|
-
await relocateSessionPanelToBottom(sessionId, channel);
|
|
2688
|
+
await relocateSessionPanelToBottom(sessionId, asSessionChannel(channel));
|
|
2789
2689
|
}
|
|
2790
2690
|
cleanupPanel(sessionId) {
|
|
2791
2691
|
cleanupSessionPanel(sessionId);
|
|
@@ -2796,7 +2696,7 @@ var DiscordOutputPort = class {
|
|
|
2796
2696
|
async handleOutputStream(stream, channel, sessionId, verbose = false, mode = "auto", provider = "claude", options = {}) {
|
|
2797
2697
|
return handleOutputStream(
|
|
2798
2698
|
stream,
|
|
2799
|
-
channel,
|
|
2699
|
+
asSessionChannel(channel),
|
|
2800
2700
|
sessionId,
|
|
2801
2701
|
verbose,
|
|
2802
2702
|
mode,
|
|
@@ -2934,7 +2834,7 @@ async function archiveSessionsById(guild, sessionIds, summary = "Bulk cleanup fr
|
|
|
2934
2834
|
failed: []
|
|
2935
2835
|
};
|
|
2936
2836
|
for (const sessionId of new Set(Array.from(sessionIds).filter(Boolean))) {
|
|
2937
|
-
const session =
|
|
2837
|
+
const session = getSessionView(sessionId);
|
|
2938
2838
|
if (!session || session.type !== "persistent") {
|
|
2939
2839
|
result.missingSessions += 1;
|
|
2940
2840
|
continue;
|
|
@@ -2976,10 +2876,10 @@ async function reconcileSessionRecordsWithGuild(guild) {
|
|
|
2976
2876
|
}
|
|
2977
2877
|
|
|
2978
2878
|
// ../bot/src/session-sync.ts
|
|
2979
|
-
import { ChannelType
|
|
2879
|
+
import { ChannelType } from "discord.js";
|
|
2980
2880
|
|
|
2981
2881
|
// ../bot/src/codex-session-discovery.ts
|
|
2982
|
-
import { existsSync
|
|
2882
|
+
import { existsSync } from "fs";
|
|
2983
2883
|
import { readFile, open } from "fs/promises";
|
|
2984
2884
|
import { glob } from "fs/promises";
|
|
2985
2885
|
import { join, resolve, sep } from "path";
|
|
@@ -3008,7 +2908,7 @@ function parseUpdatedAt(value) {
|
|
|
3008
2908
|
}
|
|
3009
2909
|
async function readSessionIndex(codexHome = join(homedir(), ".codex")) {
|
|
3010
2910
|
const indexPath = join(codexHome, "session_index.jsonl");
|
|
3011
|
-
if (!
|
|
2911
|
+
if (!existsSync(indexPath)) return [];
|
|
3012
2912
|
let content;
|
|
3013
2913
|
try {
|
|
3014
2914
|
content = await readFile(indexPath, "utf-8");
|
|
@@ -3078,7 +2978,7 @@ async function fileMatchesSessionId(file, id) {
|
|
|
3078
2978
|
}
|
|
3079
2979
|
async function findSessionFileById(id, codexHome = join(homedir(), ".codex")) {
|
|
3080
2980
|
const sessionsDir = join(codexHome, "sessions");
|
|
3081
|
-
if (!
|
|
2981
|
+
if (!existsSync(sessionsDir)) return null;
|
|
3082
2982
|
if (await isRipgrepAvailable()) {
|
|
3083
2983
|
try {
|
|
3084
2984
|
const { stdout } = await execFileAsync(
|
|
@@ -3252,12 +3152,12 @@ function stopSync() {
|
|
|
3252
3152
|
}
|
|
3253
3153
|
async function findOrCreateSessionChannel(guild, category, provider, providerSessionId, fallbackName) {
|
|
3254
3154
|
const existing = category.children.cache.find(
|
|
3255
|
-
(channel) => channel.type ===
|
|
3155
|
+
(channel) => channel.type === ChannelType.GuildText && typeof channel.topic === "string" && channel.topic.includes(`Provider Session: ${providerSessionId}`)
|
|
3256
3156
|
);
|
|
3257
3157
|
if (existing) return existing;
|
|
3258
3158
|
return guild.channels.create({
|
|
3259
3159
|
name: fallbackName,
|
|
3260
|
-
type:
|
|
3160
|
+
type: ChannelType.GuildText,
|
|
3261
3161
|
parent: category.id,
|
|
3262
3162
|
topic: `${provider} session (synced) | Provider Session: ${providerSessionId}`
|
|
3263
3163
|
}).then((channel) => {
|
|
@@ -3308,11 +3208,11 @@ async function runSync(client2) {
|
|
|
3308
3208
|
);
|
|
3309
3209
|
let createdThisRun = 0;
|
|
3310
3210
|
try {
|
|
3311
|
-
const claudeSdk = await import("./sdk-
|
|
3211
|
+
const claudeSdk = await import("./sdk-C3D6X4EB.js");
|
|
3312
3212
|
for (const project of projects) {
|
|
3313
3213
|
if (createdThisRun >= MAX_SESSIONS_PER_SYNC) break;
|
|
3314
3214
|
const category = guild.channels.cache.get(project.discordCategoryId);
|
|
3315
|
-
if (!category || category.type !==
|
|
3215
|
+
if (!category || category.type !== ChannelType.GuildCategory) {
|
|
3316
3216
|
recordSkip("categoryMissing");
|
|
3317
3217
|
continue;
|
|
3318
3218
|
}
|
|
@@ -3403,7 +3303,7 @@ async function runSync(client2) {
|
|
|
3403
3303
|
continue;
|
|
3404
3304
|
}
|
|
3405
3305
|
const category = guild.channels.cache.get(project.discordCategoryId);
|
|
3406
|
-
if (!category || category.type !==
|
|
3306
|
+
if (!category || category.type !== ChannelType.GuildCategory) {
|
|
3407
3307
|
recordSkip("categoryMissing");
|
|
3408
3308
|
continue;
|
|
3409
3309
|
}
|
|
@@ -3446,6 +3346,7 @@ var POLL_INTERVAL_IDLE_MS = 5e3;
|
|
|
3446
3346
|
var STALE_CLEANUP_MS = 12e4;
|
|
3447
3347
|
var MAX_LINES_PER_POLL = 100;
|
|
3448
3348
|
var MAX_BUFFER_SIZE = 10 * 1024 * 1024;
|
|
3349
|
+
var MAX_FILE_SIZE_BYTES = 500 * 1024 * 1024;
|
|
3449
3350
|
var CodexLogMonitor = class {
|
|
3450
3351
|
tracked = /* @__PURE__ */ new Map();
|
|
3451
3352
|
interval = null;
|
|
@@ -3539,6 +3440,14 @@ var CodexLogMonitor = class {
|
|
|
3539
3440
|
} catch {
|
|
3540
3441
|
return;
|
|
3541
3442
|
}
|
|
3443
|
+
if (stat.size > MAX_FILE_SIZE_BYTES) {
|
|
3444
|
+
if (!this.tracked.has(filePath)) {
|
|
3445
|
+
console.warn(
|
|
3446
|
+
`[CodexLogMonitor] Skipping oversized rollout file (${Math.round(stat.size / 1024 / 1024)}MB): ${filePath}`
|
|
3447
|
+
);
|
|
3448
|
+
}
|
|
3449
|
+
return;
|
|
3450
|
+
}
|
|
3542
3451
|
let tracked = this.tracked.get(filePath);
|
|
3543
3452
|
if (!tracked) {
|
|
3544
3453
|
const sessionId = this.extractSessionId(fileName);
|
|
@@ -3785,7 +3694,7 @@ async function handleCodexMonitorStateChange(resolveChannel2, monitorSessionId,
|
|
|
3785
3694
|
|
|
3786
3695
|
// ../bot/src/ipc-server.ts
|
|
3787
3696
|
import { createServer } from "net";
|
|
3788
|
-
import { unlinkSync, existsSync as
|
|
3697
|
+
import { unlinkSync, existsSync as existsSync2, chmodSync } from "fs";
|
|
3789
3698
|
|
|
3790
3699
|
// ../bot/src/session-discovery.ts
|
|
3791
3700
|
async function discoverAndRegisterSession(client2, params) {
|
|
@@ -3832,7 +3741,7 @@ function startIpcServer(client2) {
|
|
|
3832
3741
|
discordClient = client2;
|
|
3833
3742
|
const socketPath = config.socketPath;
|
|
3834
3743
|
activeSocketPath = socketPath;
|
|
3835
|
-
if (
|
|
3744
|
+
if (existsSync2(socketPath)) {
|
|
3836
3745
|
try {
|
|
3837
3746
|
unlinkSync(socketPath);
|
|
3838
3747
|
} catch {
|
|
@@ -3871,6 +3780,15 @@ function startIpcServer(client2) {
|
|
|
3871
3780
|
});
|
|
3872
3781
|
});
|
|
3873
3782
|
server.listen(socketPath, () => {
|
|
3783
|
+
if (process.platform !== "win32") {
|
|
3784
|
+
try {
|
|
3785
|
+
chmodSync(socketPath, 384);
|
|
3786
|
+
} catch (err) {
|
|
3787
|
+
console.warn(
|
|
3788
|
+
`[IpcServer] Failed to set 0600 on socket ${socketPath}: ${err.message}`
|
|
3789
|
+
);
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3874
3792
|
console.log(`[IpcServer] Listening on ${socketPath}`);
|
|
3875
3793
|
});
|
|
3876
3794
|
server.on("error", (err) => {
|
|
@@ -3886,7 +3804,7 @@ function stopIpcServer() {
|
|
|
3886
3804
|
server.close();
|
|
3887
3805
|
server = null;
|
|
3888
3806
|
}
|
|
3889
|
-
if (activeSocketPath &&
|
|
3807
|
+
if (activeSocketPath && existsSync2(activeSocketPath)) {
|
|
3890
3808
|
try {
|
|
3891
3809
|
unlinkSync(activeSocketPath);
|
|
3892
3810
|
} catch {
|
|
@@ -3941,7 +3859,7 @@ async function handleHookEvent(payload) {
|
|
|
3941
3859
|
} : void 0
|
|
3942
3860
|
});
|
|
3943
3861
|
if (registered) {
|
|
3944
|
-
session =
|
|
3862
|
+
session = getSessionView(registered.sessionId);
|
|
3945
3863
|
console.log(`[IpcServer] Auto-registered session: ${registered.sessionId}`);
|
|
3946
3864
|
}
|
|
3947
3865
|
}
|
|
@@ -3961,14 +3879,14 @@ async function handleHookEvent(payload) {
|
|
|
3961
3879
|
async function handleGateResolved(payload) {
|
|
3962
3880
|
const data = payload;
|
|
3963
3881
|
console.log(`[IpcServer] Terminal resolved gate ${data.gateId}: ${data.action}`);
|
|
3964
|
-
const result =
|
|
3882
|
+
const result = gateService.notifyTerminalResolved(data.gateId, data.action);
|
|
3965
3883
|
if (!result.success) {
|
|
3966
3884
|
console.warn(`[IpcServer] Gate resolution failed: ${result.message}`);
|
|
3967
3885
|
return;
|
|
3968
3886
|
}
|
|
3969
|
-
const gate =
|
|
3887
|
+
const gate = gateService.getGate(data.gateId);
|
|
3970
3888
|
if (gate?.discordMessageId) {
|
|
3971
|
-
const session =
|
|
3889
|
+
const session = getSessionView(data.sessionId);
|
|
3972
3890
|
if (session) {
|
|
3973
3891
|
const channel = discordClient?.channels.cache.get(session.channelId);
|
|
3974
3892
|
if (channel) {
|
|
@@ -4265,7 +4183,7 @@ function stopHealthMonitor() {
|
|
|
4265
4183
|
|
|
4266
4184
|
// ../bot/src/message-handler.ts
|
|
4267
4185
|
import {
|
|
4268
|
-
ChannelType as
|
|
4186
|
+
ChannelType as ChannelType2
|
|
4269
4187
|
} from "discord.js";
|
|
4270
4188
|
|
|
4271
4189
|
// ../bot/src/discord/inbound-envelope.ts
|
|
@@ -4332,7 +4250,7 @@ function stopMessageHandler() {
|
|
|
4332
4250
|
async function handleMessage(message) {
|
|
4333
4251
|
if (message.author.bot) return;
|
|
4334
4252
|
const channel = message.channel;
|
|
4335
|
-
const isSessionChannel3 = channel.type ===
|
|
4253
|
+
const isSessionChannel3 = channel.type === ChannelType2.GuildText;
|
|
4336
4254
|
const isSubagentThread = channel.isThread();
|
|
4337
4255
|
if (!isSessionChannel3 && !isSubagentThread) return;
|
|
4338
4256
|
const session = getSessionByChannel(channel.id);
|
|
@@ -4421,7 +4339,7 @@ async function handleMessage(message) {
|
|
|
4421
4339
|
// ../bot/src/commands.ts
|
|
4422
4340
|
import { REST, Routes } from "discord.js";
|
|
4423
4341
|
import { createHash } from "crypto";
|
|
4424
|
-
import { existsSync as
|
|
4342
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
4425
4343
|
import { join as join3 } from "path";
|
|
4426
4344
|
|
|
4427
4345
|
// ../bot/src/command-definitions/project-command.ts
|
|
@@ -4548,7 +4466,16 @@ function buildAgentCommand() {
|
|
|
4548
4466
|
{ name: "\u5F00\u542F", value: "on" }
|
|
4549
4467
|
)
|
|
4550
4468
|
)
|
|
4551
|
-
).addSubcommand((sub) => sub.setName("continue").setDescription("\u7EE7\u7EED\u5F53\u524D\u4F1A\u8BDD\u7684\u751F\u6210"))
|
|
4469
|
+
).addSubcommand((sub) => sub.setName("continue").setDescription("\u7EE7\u7EED\u5F53\u524D\u4F1A\u8BDD\u7684\u751F\u6210")).addSubcommand(
|
|
4470
|
+
(sub) => sub.setName("batch").setDescription("\u6279\u91CF\u5BA1\u6279\u6A21\u5F0F\uFF08\u5EF6\u540E\u6240\u6709\u5DE5\u5177\u5BA1\u6279\u81F3\u4E00\u6B21\u6027\u5904\u7406\uFF09").addStringOption(
|
|
4471
|
+
(opt) => opt.setName("action").setDescription("\u5F00\u5173 / \u4E00\u952E\u6279\u51C6 / \u4E00\u952E\u62D2\u7EDD").setRequired(true).addChoices(
|
|
4472
|
+
{ name: "\u5F00\u542F\u6279\u91CF\u5BA1\u6279\u6A21\u5F0F", value: "on" },
|
|
4473
|
+
{ name: "\u5173\u95ED\u6279\u91CF\u5BA1\u6279\u6A21\u5F0F", value: "off" },
|
|
4474
|
+
{ name: "\u6279\u51C6\u961F\u5217\u4E2D\u7684\u5168\u90E8\u5DE5\u5177\u8C03\u7528", value: "approve-all" },
|
|
4475
|
+
{ name: "\u62D2\u7EDD\u961F\u5217\u4E2D\u7684\u5168\u90E8\u5DE5\u5177\u8C03\u7528", value: "reject-all" }
|
|
4476
|
+
)
|
|
4477
|
+
)
|
|
4478
|
+
);
|
|
4552
4479
|
}
|
|
4553
4480
|
|
|
4554
4481
|
// ../bot/src/command-definitions/subagent-command.ts
|
|
@@ -4590,7 +4517,7 @@ function computeCommandsHash(body) {
|
|
|
4590
4517
|
}
|
|
4591
4518
|
function readStoredHash() {
|
|
4592
4519
|
try {
|
|
4593
|
-
if (
|
|
4520
|
+
if (existsSync3(HASH_FILE)) {
|
|
4594
4521
|
return readFileSync2(HASH_FILE, "utf-8").trim();
|
|
4595
4522
|
}
|
|
4596
4523
|
} catch {
|
|
@@ -4625,13 +4552,13 @@ async function registerCommands() {
|
|
|
4625
4552
|
// ../bot/src/bot-services-orchestrator.ts
|
|
4626
4553
|
async function findOrCreateLogChannel(guild) {
|
|
4627
4554
|
const existing = guild.channels.cache.find(
|
|
4628
|
-
(ch) => ch.name === "bot-logs" && ch.type ===
|
|
4555
|
+
(ch) => ch.name === "bot-logs" && ch.type === ChannelType3.GuildText && !ch.parentId
|
|
4629
4556
|
);
|
|
4630
4557
|
if (existing) return existing;
|
|
4631
4558
|
try {
|
|
4632
4559
|
return await guild.channels.create({
|
|
4633
4560
|
name: "bot-logs",
|
|
4634
|
-
type:
|
|
4561
|
+
type: ChannelType3.GuildText,
|
|
4635
4562
|
reason: "Auto-created by workspacecord for bot logs"
|
|
4636
4563
|
});
|
|
4637
4564
|
} catch {
|
|
@@ -4656,7 +4583,9 @@ var BotServicesOrchestrator = class {
|
|
|
4656
4583
|
logBuffer2.log(`Reconciled ${reconciled.endedMissingSessions} stale session record(s) on startup.`);
|
|
4657
4584
|
}
|
|
4658
4585
|
setBotStartTime(Date.now());
|
|
4659
|
-
await
|
|
4586
|
+
await gateService.init();
|
|
4587
|
+
await this.#reconcilePendingGates(client2, logBuffer2);
|
|
4588
|
+
await this.#resumeAbandonedMonitorRuns(client2, logBuffer2);
|
|
4660
4589
|
if (config.messageRetentionDays) {
|
|
4661
4590
|
await cleanupOldMessages(client2);
|
|
4662
4591
|
}
|
|
@@ -4667,15 +4596,55 @@ var BotServicesOrchestrator = class {
|
|
|
4667
4596
|
logBuffer2.log("Codex log monitor started");
|
|
4668
4597
|
return { serviceBus: this.#serviceBus, logBuffer: logBuffer2, presenceManager, logChannel, codexMonitor: null };
|
|
4669
4598
|
}
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4599
|
+
/**
|
|
4600
|
+
* 重启后对悬挂的 Monitor run 执行 reconcile,并按 config.monitorAutoResumePolicy 续跑符合条件的 session。
|
|
4601
|
+
* 续跑采用 fire-and-forget:每个 session 独立异步运行,不阻塞启动。
|
|
4602
|
+
*/
|
|
4603
|
+
async #resumeAbandonedMonitorRuns(client2, logBuffer2) {
|
|
4604
|
+
let result;
|
|
4605
|
+
try {
|
|
4606
|
+
result = await reconcileAndCollectAutoResumeCandidates(config.monitorAutoResumePolicy);
|
|
4607
|
+
} catch (err) {
|
|
4608
|
+
console.error("[MonitorAutoResume] reconcile failed:", err);
|
|
4609
|
+
return;
|
|
4610
|
+
}
|
|
4611
|
+
if (result.abandoned.length > 0) {
|
|
4612
|
+
logBuffer2.log(
|
|
4613
|
+
`Marked ${result.abandoned.length} orphan monitor run(s) as abandoned on startup.`
|
|
4614
|
+
);
|
|
4615
|
+
}
|
|
4616
|
+
if (result.candidates.length === 0) return;
|
|
4617
|
+
logBuffer2.log(
|
|
4618
|
+
`Auto-resuming ${result.candidates.length} monitor session(s) (policy=${config.monitorAutoResumePolicy}).`
|
|
4619
|
+
);
|
|
4620
|
+
for (const candidate of result.candidates) {
|
|
4621
|
+
const session = getSessionView(candidate.sessionId);
|
|
4622
|
+
if (!session) continue;
|
|
4623
|
+
const channel = client2.channels.cache.get(candidate.channelId);
|
|
4624
|
+
if (!channel) {
|
|
4625
|
+
console.warn(
|
|
4626
|
+
`[MonitorAutoResume] sessionId=${candidate.sessionId} channel ${candidate.channelId} not accessible, skipping`
|
|
4627
|
+
);
|
|
4628
|
+
continue;
|
|
4629
|
+
}
|
|
4630
|
+
console.log(
|
|
4631
|
+
`[MonitorAutoResume] resuming sessionId=${candidate.sessionId} runId=${candidate.runId} lastIteration=${candidate.lastIteration}`
|
|
4632
|
+
);
|
|
4633
|
+
void executeSessionContinue(session, channel).catch((err) => {
|
|
4634
|
+
console.error(
|
|
4635
|
+
`[MonitorAutoResume] sessionId=${candidate.sessionId} resume failed:`,
|
|
4636
|
+
err
|
|
4637
|
+
);
|
|
4638
|
+
});
|
|
4639
|
+
}
|
|
4640
|
+
}
|
|
4641
|
+
async #reconcilePendingGates(client2, logBuffer2) {
|
|
4642
|
+
const result = gateService.reconcileOnStartup(config.gateRestartPolicy);
|
|
4643
|
+
for (const { gateId, discordMessageId } of result.invalidated) {
|
|
4675
4644
|
if (!discordMessageId) continue;
|
|
4676
|
-
const gate =
|
|
4645
|
+
const gate = gateService.getGate(gateId);
|
|
4677
4646
|
if (!gate?.sessionId) continue;
|
|
4678
|
-
const session =
|
|
4647
|
+
const session = getSessionView(gate.sessionId);
|
|
4679
4648
|
if (!session) continue;
|
|
4680
4649
|
const channel = client2.channels.cache.get(session.channelId);
|
|
4681
4650
|
if (!channel?.messages) continue;
|
|
@@ -4692,10 +4661,17 @@ var BotServicesOrchestrator = class {
|
|
|
4692
4661
|
}))
|
|
4693
4662
|
});
|
|
4694
4663
|
} catch (err) {
|
|
4695
|
-
console.error(`[
|
|
4664
|
+
console.error(`[GateReconcile] Failed to update message for gate ${gateId}:`, err);
|
|
4696
4665
|
}
|
|
4697
4666
|
}
|
|
4698
|
-
|
|
4667
|
+
if (result.resumed.length > 0) {
|
|
4668
|
+
logBuffer2.log(
|
|
4669
|
+
`Resumed ${result.resumed.length} pending gate(s) across restart (policy=resume-pending).`
|
|
4670
|
+
);
|
|
4671
|
+
}
|
|
4672
|
+
if (result.invalidated.length > 0) {
|
|
4673
|
+
logBuffer2.log(`Closed ${result.invalidated.length} orphan/overdue gate(s) on startup.`);
|
|
4674
|
+
}
|
|
4699
4675
|
}
|
|
4700
4676
|
#registerServices(client2, logBuffer2, presence) {
|
|
4701
4677
|
this.#serviceBus.register({ name: "message-handler", start() {
|
|
@@ -4720,7 +4696,7 @@ var BotServicesOrchestrator = class {
|
|
|
4720
4696
|
);
|
|
4721
4697
|
},
|
|
4722
4698
|
async (providerSessionId, cwd, remoteHumanControl, subagent) => {
|
|
4723
|
-
const { registerLocalSession: registerLocalSession2 } = await import("./session-local-registration-
|
|
4699
|
+
const { registerLocalSession: registerLocalSession2 } = await import("./session-local-registration-RL2A3QZB.js");
|
|
4724
4700
|
const g = client2.guilds.cache.first();
|
|
4725
4701
|
if (!g) return false;
|
|
4726
4702
|
const result = await registerLocalSession2(
|
|
@@ -4768,8 +4744,8 @@ var BotServicesOrchestrator = class {
|
|
|
4768
4744
|
stopPerformanceMonitoring();
|
|
4769
4745
|
} });
|
|
4770
4746
|
this.#serviceBus.register(intervalService("gate-housekeeping", () => {
|
|
4771
|
-
const expired =
|
|
4772
|
-
const archived =
|
|
4747
|
+
const expired = gateService.cleanupExpired();
|
|
4748
|
+
const archived = gateService.archiveResolved(100);
|
|
4773
4749
|
if (expired || archived) console.log(`[GateHousekeeping] expired=${expired}, archived=${archived}`);
|
|
4774
4750
|
}, 6e4));
|
|
4775
4751
|
this.#serviceBus.register(intervalService("presence", () => presence.updatePresence(), 3e4));
|
|
@@ -4796,7 +4772,7 @@ var BotServicesOrchestrator = class {
|
|
|
4796
4772
|
};
|
|
4797
4773
|
|
|
4798
4774
|
// ../engine/src/bot-locks.ts
|
|
4799
|
-
import { existsSync as
|
|
4775
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
4800
4776
|
import { join as join5 } from "path";
|
|
4801
4777
|
var LOCK_FILE = join5(config.dataDir, "bot.lock");
|
|
4802
4778
|
var STALE_THRESHOLD_MS = 5 * 60 * 1e3;
|
|
@@ -4809,7 +4785,7 @@ function isProcessRunning(pid) {
|
|
|
4809
4785
|
}
|
|
4810
4786
|
}
|
|
4811
4787
|
function acquireLock() {
|
|
4812
|
-
if (!
|
|
4788
|
+
if (!existsSync4(LOCK_FILE)) {
|
|
4813
4789
|
const data2 = { pid: process.pid, startedAt: Date.now() };
|
|
4814
4790
|
writeFileSync2(LOCK_FILE, JSON.stringify(data2), "utf-8");
|
|
4815
4791
|
return true;
|
|
@@ -4839,7 +4815,7 @@ function acquireLock() {
|
|
|
4839
4815
|
}
|
|
4840
4816
|
function releaseLock() {
|
|
4841
4817
|
try {
|
|
4842
|
-
if (!
|
|
4818
|
+
if (!existsSync4(LOCK_FILE)) return;
|
|
4843
4819
|
const raw = readFileSync3(LOCK_FILE, "utf-8").trim();
|
|
4844
4820
|
const data = JSON.parse(raw);
|
|
4845
4821
|
if (data.pid === process.pid) {
|
|
@@ -4851,13 +4827,13 @@ function releaseLock() {
|
|
|
4851
4827
|
|
|
4852
4828
|
// ../bot/src/command-handlers-modules/project-handlers.ts
|
|
4853
4829
|
import {
|
|
4854
|
-
ChannelType as
|
|
4855
|
-
EmbedBuilder
|
|
4830
|
+
ChannelType as ChannelType5,
|
|
4831
|
+
EmbedBuilder
|
|
4856
4832
|
} from "discord.js";
|
|
4857
4833
|
|
|
4858
4834
|
// ../bot/src/command-handlers-shared.ts
|
|
4859
4835
|
import {
|
|
4860
|
-
ChannelType as
|
|
4836
|
+
ChannelType as ChannelType4
|
|
4861
4837
|
} from "discord.js";
|
|
4862
4838
|
var logFn = console.log;
|
|
4863
4839
|
function setLogger(fn) {
|
|
@@ -4884,7 +4860,7 @@ var CONTROL_CHANNEL_NAME = "control";
|
|
|
4884
4860
|
var CLEANUP_PREVIEW_LIST_LIMIT = 10;
|
|
4885
4861
|
var CLEANUP_PREVIEW_LABEL_LIMIT = 120;
|
|
4886
4862
|
async function registerStatusCardWithPanelAdapter(sessionId, channel, statusCardMessageId) {
|
|
4887
|
-
const { registerExistingStatusCard: registerExistingStatusCard2 } = await import("./panel-adapter-
|
|
4863
|
+
const { registerExistingStatusCard: registerExistingStatusCard2 } = await import("./panel-adapter-CLI4WDII.js");
|
|
4888
4864
|
if (typeof registerExistingStatusCard2 !== "function") {
|
|
4889
4865
|
log(`[panel-adapter] registerExistingStatusCard \u672A\u66B4\u9732\uFF0Csession=${sessionId}`);
|
|
4890
4866
|
return false;
|
|
@@ -5004,7 +4980,7 @@ function resolveCleanupCurrentChannelId(interaction) {
|
|
|
5004
4980
|
}
|
|
5005
4981
|
function findControlChannel(guild, categoryId) {
|
|
5006
4982
|
const existing = guild.channels.cache.find(
|
|
5007
|
-
(channel) => channel.type ===
|
|
4983
|
+
(channel) => channel.type === ChannelType4.GuildText && channel.parentId === categoryId && channel.name === CONTROL_CHANNEL_NAME
|
|
5008
4984
|
);
|
|
5009
4985
|
return existing ?? null;
|
|
5010
4986
|
}
|
|
@@ -5012,7 +4988,7 @@ async function resolveOrCreateControlChannel(interaction, guild, categoryId, sto
|
|
|
5012
4988
|
const currentChannel = interaction.channel;
|
|
5013
4989
|
if (storedControlChannelId) {
|
|
5014
4990
|
const existing = guild.channels.cache.get(storedControlChannelId);
|
|
5015
|
-
if (existing?.type ===
|
|
4991
|
+
if (existing?.type === ChannelType4.GuildText) {
|
|
5016
4992
|
return existing;
|
|
5017
4993
|
}
|
|
5018
4994
|
}
|
|
@@ -5028,7 +5004,7 @@ async function resolveOrCreateControlChannel(interaction, guild, categoryId, sto
|
|
|
5028
5004
|
}
|
|
5029
5005
|
const created = await guild.channels.create({
|
|
5030
5006
|
name: CONTROL_CHANNEL_NAME,
|
|
5031
|
-
type:
|
|
5007
|
+
type: ChannelType4.GuildText,
|
|
5032
5008
|
parent: categoryId,
|
|
5033
5009
|
topic: "Use /agent spawn here to create new agent sessions",
|
|
5034
5010
|
reason: `Project control channel created for ${interaction.user.tag}`
|
|
@@ -5101,7 +5077,7 @@ async function handleProjectSetup(interaction) {
|
|
|
5101
5077
|
try {
|
|
5102
5078
|
const historyForum = await guild.channels.create({
|
|
5103
5079
|
name: "history",
|
|
5104
|
-
type:
|
|
5080
|
+
type: ChannelType5.GuildForum,
|
|
5105
5081
|
parent: categoryId,
|
|
5106
5082
|
topic: "Archived agent sessions for this project",
|
|
5107
5083
|
reason: "Created by workspacecord for session archiving"
|
|
@@ -5119,7 +5095,7 @@ async function handleProjectSetup(interaction) {
|
|
|
5119
5095
|
setControlChannelId(categoryId, interaction.channelId);
|
|
5120
5096
|
const controlInfo = `
|
|
5121
5097
|
\u2022 \u63A7\u5236\u9891\u9053\uFF1A<#${interaction.channelId}>`;
|
|
5122
|
-
const embed = new
|
|
5098
|
+
const embed = new EmbedBuilder().setColor(3066993).setTitle(`\u2705 \u9879\u76EE\u5C31\u7EEA\uFF1A${project.name}`).addFields(
|
|
5123
5099
|
{ name: "\u5206\u7C7B", value: `**${categoryName}**`, inline: true },
|
|
5124
5100
|
{ name: "\u76EE\u5F55", value: `\`${project.directory}\``, inline: true }
|
|
5125
5101
|
).setDescription(
|
|
@@ -5147,7 +5123,7 @@ async function handleProjectInfo(interaction) {
|
|
|
5147
5123
|
}
|
|
5148
5124
|
const sessions = getSessionsByCategory(categoryId);
|
|
5149
5125
|
const activeSessions = sessions.filter((s) => s.type === "persistent");
|
|
5150
|
-
const embed = new
|
|
5126
|
+
const embed = new EmbedBuilder().setColor(3447003).setTitle(`\u{1F4C1} \u9879\u76EE\uFF1A${project.name}`).addFields(
|
|
5151
5127
|
{ name: "\u76EE\u5F55", value: `\`${project.directory}\``, inline: false },
|
|
5152
5128
|
{ name: "\u6D3B\u8DC3\u4F1A\u8BDD", value: `${activeSessions.length}`, inline: true },
|
|
5153
5129
|
{ name: "\u6280\u80FD", value: `${project.skills.length}`, inline: true },
|
|
@@ -5324,11 +5300,11 @@ ${lines.join("\n")}`, ephemeral: true });
|
|
|
5324
5300
|
|
|
5325
5301
|
// ../bot/src/command-handlers-modules/agent-handlers.ts
|
|
5326
5302
|
import {
|
|
5327
|
-
ActionRowBuilder
|
|
5328
|
-
ButtonBuilder
|
|
5329
|
-
ButtonStyle
|
|
5330
|
-
ChannelType as
|
|
5331
|
-
EmbedBuilder as
|
|
5303
|
+
ActionRowBuilder,
|
|
5304
|
+
ButtonBuilder,
|
|
5305
|
+
ButtonStyle,
|
|
5306
|
+
ChannelType as ChannelType6,
|
|
5307
|
+
EmbedBuilder as EmbedBuilder2
|
|
5332
5308
|
} from "discord.js";
|
|
5333
5309
|
|
|
5334
5310
|
// ../engine/src/agent-cleanup-request-store.ts
|
|
@@ -5406,6 +5382,8 @@ async function handleAgent(interaction) {
|
|
|
5406
5382
|
return handleAgentPermissions(interaction);
|
|
5407
5383
|
case "continue":
|
|
5408
5384
|
return handleAgentContinue(interaction);
|
|
5385
|
+
case "batch":
|
|
5386
|
+
return handleAgentBatch(interaction);
|
|
5409
5387
|
default:
|
|
5410
5388
|
await interaction.reply({ content: `\u672A\u77E5\u5B50\u547D\u4EE4\uFF1A${sub}`, ephemeral: true });
|
|
5411
5389
|
}
|
|
@@ -5458,7 +5436,7 @@ async function handleAgentSpawn(interaction) {
|
|
|
5458
5436
|
try {
|
|
5459
5437
|
sessionChannel = await guild.channels.create({
|
|
5460
5438
|
name: channelName,
|
|
5461
|
-
type:
|
|
5439
|
+
type: ChannelType6.GuildText,
|
|
5462
5440
|
parent: categoryId,
|
|
5463
5441
|
topic: `[${PROVIDER_LABELS[provider]}] ${label}`,
|
|
5464
5442
|
reason: `Agent session spawned by ${interaction.user.tag}`
|
|
@@ -5489,7 +5467,7 @@ async function handleAgentSpawn(interaction) {
|
|
|
5489
5467
|
if (mode !== "auto") {
|
|
5490
5468
|
setMode(session.id, mode);
|
|
5491
5469
|
}
|
|
5492
|
-
const statusEmbed = new
|
|
5470
|
+
const statusEmbed = new EmbedBuilder2().setColor(PROVIDER_COLORS[provider]).setTitle("\u{1F4A4} \u5F85\u547D").setDescription("\u7B49\u5F85\u9996\u6761\u6D88\u606F").addFields({
|
|
5493
5471
|
name: "\u6743\u9650",
|
|
5494
5472
|
value: getSessionPermissionSummary(session),
|
|
5495
5473
|
inline: false
|
|
@@ -5511,7 +5489,7 @@ async function handleAgentSpawn(interaction) {
|
|
|
5511
5489
|
}
|
|
5512
5490
|
setStatusCardBinding(session.id, { messageId: statusMessage.id });
|
|
5513
5491
|
log(`[panel-adapter] \u72B6\u6001\u5361\u5DF2\u6CE8\u518C\uFF0Csession=${session.id}`);
|
|
5514
|
-
const embed = new
|
|
5492
|
+
const embed = new EmbedBuilder2().setColor(3066993).setTitle(`\u2705 Agent \u5DF2\u521B\u5EFA\uFF1A${label}`).addFields(
|
|
5515
5493
|
{ name: "\u9891\u9053", value: `<#${sessionChannel.id}>`, inline: true },
|
|
5516
5494
|
{ name: "\u63D0\u4F9B\u5546", value: PROVIDER_LABELS[provider], inline: true },
|
|
5517
5495
|
{ name: "\u6A21\u5F0F", value: MODE_LABELS?.[mode] ?? mode, inline: false },
|
|
@@ -5552,7 +5530,7 @@ async function handleAgentList(interaction) {
|
|
|
5552
5530
|
const status = s.isGenerating ? "\u{1F504} \u6267\u884C\u4E2D" : "\u{1F4A4} \u7A7A\u95F2";
|
|
5553
5531
|
return `${status} | \`${s.agentLabel}\` | ${s.provider} | <#${s.channelId}> | ${formatRelative(s.lastActivity)}`;
|
|
5554
5532
|
});
|
|
5555
|
-
const embed = new
|
|
5533
|
+
const embed = new EmbedBuilder2().setColor(3447003).setTitle(`Agent \u4F1A\u8BDD\uFF08${sessions.length}\uFF09`).setDescription(lines.join("\n"));
|
|
5556
5534
|
await interaction.reply({ embeds: [embed], ephemeral: true });
|
|
5557
5535
|
}
|
|
5558
5536
|
async function handleAgentCleanup(interaction) {
|
|
@@ -5597,9 +5575,9 @@ async function handleAgentCleanup(interaction) {
|
|
|
5597
5575
|
candidateSessionIds: preview.archiveCandidates.map((session) => session.id)
|
|
5598
5576
|
});
|
|
5599
5577
|
const components = [
|
|
5600
|
-
new
|
|
5601
|
-
new
|
|
5602
|
-
new
|
|
5578
|
+
new ActionRowBuilder().addComponents(
|
|
5579
|
+
new ButtonBuilder().setCustomId(`cleanup:confirm:${request.id}`).setLabel("\u786E\u8BA4\u5F52\u6863").setStyle(ButtonStyle.Danger),
|
|
5580
|
+
new ButtonBuilder().setCustomId(`cleanup:cancel:${request.id}`).setLabel("\u53D6\u6D88").setStyle(ButtonStyle.Secondary)
|
|
5603
5581
|
)
|
|
5604
5582
|
];
|
|
5605
5583
|
await interaction.reply({
|
|
@@ -5736,7 +5714,7 @@ async function handleAgentPermissions(interaction) {
|
|
|
5736
5714
|
return;
|
|
5737
5715
|
}
|
|
5738
5716
|
await updateSessionPermissions(session.id, patch);
|
|
5739
|
-
const refreshed =
|
|
5717
|
+
const refreshed = getSessionView(session.id) ?? { ...session, ...patch };
|
|
5740
5718
|
const timing = session.isGenerating ? "\u5DF2\u4FDD\u5B58\uFF0C\u5C06\u5728\u4E0B\u4E00\u8F6E\u751F\u6548\u3002" : "\u5DF2\u66F4\u65B0\u5E76\u7ACB\u5373\u751F\u6548\u3002";
|
|
5741
5719
|
await interaction.reply({
|
|
5742
5720
|
content: `${timing}
|
|
@@ -5763,9 +5741,93 @@ async function handleAgentContinue(interaction) {
|
|
|
5763
5741
|
await interaction.editReply("\u7EE7\u7EED\u4E2D...");
|
|
5764
5742
|
await executeSessionContinue(session, channel);
|
|
5765
5743
|
}
|
|
5744
|
+
async function handleAgentBatch(interaction) {
|
|
5745
|
+
const channel = interaction.channel;
|
|
5746
|
+
if (!channel) {
|
|
5747
|
+
await interaction.reply({ content: "\u65E0\u9891\u9053\u4E0A\u4E0B\u6587\u3002", ephemeral: true });
|
|
5748
|
+
return;
|
|
5749
|
+
}
|
|
5750
|
+
const session = getSessionByChannel(channel.id);
|
|
5751
|
+
if (!session) {
|
|
5752
|
+
await interaction.reply({ content: "\u6B64\u9891\u9053\u6CA1\u6709\u6D3B\u8DC3\u7684\u4F1A\u8BDD\u3002", ephemeral: true });
|
|
5753
|
+
return;
|
|
5754
|
+
}
|
|
5755
|
+
if (session.provider !== "claude") {
|
|
5756
|
+
await interaction.reply({
|
|
5757
|
+
content: "\u6279\u91CF\u5BA1\u6279\u6A21\u5F0F\u76EE\u524D\u4EC5\u652F\u6301 Claude \u4F1A\u8BDD\u3002Codex \u8BF7\u4F7F\u7528 `/agent permissions codex-approval` \u914D\u7F6E\u3002",
|
|
5758
|
+
ephemeral: true
|
|
5759
|
+
});
|
|
5760
|
+
return;
|
|
5761
|
+
}
|
|
5762
|
+
const action = interaction.options.getString("action", true);
|
|
5763
|
+
const source = "claude";
|
|
5764
|
+
if (action === "on") {
|
|
5765
|
+
if (!shouldUseClaudePermissionHandler(session)) {
|
|
5766
|
+
const hint = session.mode === "auto" ? "\u5F53\u524D\u4F1A\u8BDD\u662F `auto` \u6A21\u5F0F\uFF0C\u5DE5\u5177\u4E0D\u8D70\u5BA1\u6279 hook\u3002\u8BF7\u5148 `/agent mode mode:normal` \u518D\u5F00\u542F\u6279\u91CF\u5BA1\u6279\u3002" : "\u5F53\u524D\u4F1A\u8BDD\u7684 Claude \u6743\u9650\u6A21\u5F0F\u4E3A `bypass`\uFF0C\u5DE5\u5177\u4E0D\u8D70\u5BA1\u6279 hook\u3002\u8BF7\u5148 `/agent permissions claude-permissions:normal`\u3002";
|
|
5767
|
+
await interaction.reply({ content: `\u26A0\uFE0F \u65E0\u6CD5\u5F00\u542F\u6279\u91CF\u5BA1\u6279\uFF1A${hint}`, ephemeral: true });
|
|
5768
|
+
return;
|
|
5769
|
+
}
|
|
5770
|
+
await updateSessionState(session.id, {
|
|
5771
|
+
type: "batch_approval_changed",
|
|
5772
|
+
sessionId: session.id,
|
|
5773
|
+
source,
|
|
5774
|
+
confidence: "high",
|
|
5775
|
+
timestamp: Date.now(),
|
|
5776
|
+
metadata: { enabled: true }
|
|
5777
|
+
});
|
|
5778
|
+
await interaction.reply({
|
|
5779
|
+
content: "\u2705 \u5DF2\u5F00\u542F\u6279\u91CF\u5BA1\u6279\u6A21\u5F0F\u3002\u540E\u7EED\u6743\u9650\u8BF7\u6C42\u5C06\u6392\u961F\uFF0C\u4F7F\u7528 `/agent batch action:approve-all` \u6216 `reject-all` \u7EDF\u4E00\u5904\u7406\u3002",
|
|
5780
|
+
ephemeral: true
|
|
5781
|
+
});
|
|
5782
|
+
return;
|
|
5783
|
+
}
|
|
5784
|
+
if (action === "off") {
|
|
5785
|
+
const pending = getBatchApprovalCount(session.id);
|
|
5786
|
+
drainBatchApprovals(session.id, "reject");
|
|
5787
|
+
await updateSessionState(session.id, {
|
|
5788
|
+
type: "batch_approval_changed",
|
|
5789
|
+
sessionId: session.id,
|
|
5790
|
+
source,
|
|
5791
|
+
confidence: "high",
|
|
5792
|
+
timestamp: Date.now(),
|
|
5793
|
+
metadata: { enabled: false }
|
|
5794
|
+
});
|
|
5795
|
+
await interaction.reply({
|
|
5796
|
+
content: `\u2705 \u5DF2\u5173\u95ED\u6279\u91CF\u5BA1\u6279\u6A21\u5F0F${pending > 0 ? `\uFF0C\u961F\u5217\u4E2D ${pending} \u6761\u5F85\u6279\u8BF7\u6C42\u5DF2\u6309\u62D2\u7EDD\u5904\u7406\u3002` : "\u3002"}`,
|
|
5797
|
+
ephemeral: true
|
|
5798
|
+
});
|
|
5799
|
+
return;
|
|
5800
|
+
}
|
|
5801
|
+
if (action === "approve-all" || action === "reject-all") {
|
|
5802
|
+
const projection = stateMachine.getSnapshot(session.id);
|
|
5803
|
+
if (!projection.batchApprovalMode) {
|
|
5804
|
+
await interaction.reply({
|
|
5805
|
+
content: "\u6279\u91CF\u5BA1\u6279\u6A21\u5F0F\u672A\u542F\u7528\u3002\u5148\u7528 `/agent batch action:on` \u5F00\u542F\u3002",
|
|
5806
|
+
ephemeral: true
|
|
5807
|
+
});
|
|
5808
|
+
return;
|
|
5809
|
+
}
|
|
5810
|
+
const batchAction = action === "approve-all" ? "approve" : "reject";
|
|
5811
|
+
const count2 = drainBatchApprovals(session.id, batchAction);
|
|
5812
|
+
await updateSessionState(session.id, {
|
|
5813
|
+
type: "batch_approval_changed",
|
|
5814
|
+
sessionId: session.id,
|
|
5815
|
+
source,
|
|
5816
|
+
confidence: "high",
|
|
5817
|
+
timestamp: Date.now(),
|
|
5818
|
+
metadata: { enabled: true, pendingApprovals: [] }
|
|
5819
|
+
});
|
|
5820
|
+
await interaction.reply({
|
|
5821
|
+
content: count2 === 0 ? "\u961F\u5217\u4E2D\u6CA1\u6709\u5F85\u6279\u51C6\u7684\u8BF7\u6C42\u3002" : `\u2705 \u5DF2${batchAction === "approve" ? "\u6279\u51C6" : "\u62D2\u7EDD"} ${count2} \u6761\u5F85\u6279\u8BF7\u6C42\u3002`,
|
|
5822
|
+
ephemeral: true
|
|
5823
|
+
});
|
|
5824
|
+
return;
|
|
5825
|
+
}
|
|
5826
|
+
await interaction.reply({ content: `\u672A\u77E5\u52A8\u4F5C\uFF1A${action}`, ephemeral: true });
|
|
5827
|
+
}
|
|
5766
5828
|
|
|
5767
5829
|
// ../bot/src/command-handlers-modules/subagent-handlers.ts
|
|
5768
|
-
import { EmbedBuilder as
|
|
5830
|
+
import { EmbedBuilder as EmbedBuilder3 } from "discord.js";
|
|
5769
5831
|
async function handleSubagent(interaction) {
|
|
5770
5832
|
if (!assertUserAllowed(interaction)) return;
|
|
5771
5833
|
const sub = interaction.options.getSubcommand();
|
|
@@ -5805,7 +5867,7 @@ async function handleSubagentRun(interaction) {
|
|
|
5805
5867
|
}
|
|
5806
5868
|
try {
|
|
5807
5869
|
const subSession = await spawnSubagent(session, label, provider, sessionChannel);
|
|
5808
|
-
const embed = new
|
|
5870
|
+
const embed = new EmbedBuilder3().setColor(15965202).setTitle(`\u{1F916} \u5B50\u4EFB\u52A1\u5DF2\u521B\u5EFA\uFF1A${label}`).addFields(
|
|
5809
5871
|
{ name: "\u5B50\u533A", value: `<#${subSession.channelId}>`, inline: true },
|
|
5810
5872
|
{ name: "\u63D0\u4F9B\u5546", value: PROVIDER_LABELS[provider], inline: true },
|
|
5811
5873
|
{ name: "\u5D4C\u5957\u6DF1\u5EA6", value: `${subSession.subagentDepth}`, inline: true }
|
|
@@ -12774,24 +12836,20 @@ async function handleShellKill(interaction) {
|
|
|
12774
12836
|
});
|
|
12775
12837
|
}
|
|
12776
12838
|
|
|
12777
|
-
// ../bot/src/button-handler.ts
|
|
12778
|
-
import "discord.js";
|
|
12779
|
-
|
|
12780
12839
|
// ../bot/src/button-handler-actions.ts
|
|
12781
12840
|
import {
|
|
12782
|
-
ActionRowBuilder as
|
|
12783
|
-
StringSelectMenuBuilder
|
|
12841
|
+
ActionRowBuilder as ActionRowBuilder2,
|
|
12842
|
+
StringSelectMenuBuilder
|
|
12784
12843
|
} from "discord.js";
|
|
12785
12844
|
function asComponentLike(component) {
|
|
12786
12845
|
return component || {};
|
|
12787
12846
|
}
|
|
12788
12847
|
async function resolveAwaitingHumanIfNeeded(sessionId) {
|
|
12789
|
-
const session =
|
|
12848
|
+
const session = getSessionView(sessionId);
|
|
12790
12849
|
if (!session?.currentInteractionMessageId) {
|
|
12791
12850
|
return;
|
|
12792
12851
|
}
|
|
12793
12852
|
updateSession(sessionId, {
|
|
12794
|
-
humanResolved: true,
|
|
12795
12853
|
currentInteractionMessageId: void 0
|
|
12796
12854
|
});
|
|
12797
12855
|
await updateSessionState(sessionId, {
|
|
@@ -12838,7 +12896,7 @@ async function handleAwaitingHumanButton(interaction) {
|
|
|
12838
12896
|
const sessionId = parts[1];
|
|
12839
12897
|
const turn = parseInt(parts[2], 10);
|
|
12840
12898
|
const action = parts[3];
|
|
12841
|
-
const session =
|
|
12899
|
+
const session = getSessionView(sessionId);
|
|
12842
12900
|
if (!session) {
|
|
12843
12901
|
await interaction.reply({ content: "\u4F1A\u8BDD\u4E0D\u5B58\u5728", ephemeral: true });
|
|
12844
12902
|
return true;
|
|
@@ -12856,12 +12914,12 @@ async function handleAwaitingHumanButton(interaction) {
|
|
|
12856
12914
|
await interaction.reply({ content: "\u5DF2\u88AB\u5176\u4ED6\u4EBA\u5904\u7406", ephemeral: true });
|
|
12857
12915
|
return true;
|
|
12858
12916
|
}
|
|
12859
|
-
const activeGate = session.activeHumanGateId ?
|
|
12917
|
+
const activeGate = session.activeHumanGateId ? gateService.getGate(session.activeHumanGateId) : gateService.getActiveGateForSession(sessionId);
|
|
12860
12918
|
if (!activeGate) {
|
|
12861
12919
|
await interaction.reply({ content: "\u672A\u627E\u5230\u6D3B\u8DC3\u7684\u95E8\u63A7\u8BB0\u5F55", ephemeral: true });
|
|
12862
12920
|
return true;
|
|
12863
12921
|
}
|
|
12864
|
-
const result = await
|
|
12922
|
+
const result = await gateService.resolveFromDiscord(
|
|
12865
12923
|
activeGate.id,
|
|
12866
12924
|
action === "approve" ? "approve" : "reject"
|
|
12867
12925
|
);
|
|
@@ -12872,8 +12930,8 @@ async function handleAwaitingHumanButton(interaction) {
|
|
|
12872
12930
|
});
|
|
12873
12931
|
return true;
|
|
12874
12932
|
}
|
|
12933
|
+
stateMachine.setHumanResolved(sessionId, true);
|
|
12875
12934
|
updateSession(sessionId, {
|
|
12876
|
-
humanResolved: true,
|
|
12877
12935
|
currentInteractionMessageId: void 0,
|
|
12878
12936
|
activeHumanGateId: void 0
|
|
12879
12937
|
});
|
|
@@ -12929,7 +12987,7 @@ async function handleContinueButton(interaction) {
|
|
|
12929
12987
|
const customId = interaction.customId;
|
|
12930
12988
|
if (!customId.startsWith("continue:")) return false;
|
|
12931
12989
|
const sessionId = customId.slice(9);
|
|
12932
|
-
const session =
|
|
12990
|
+
const session = getSessionView(sessionId);
|
|
12933
12991
|
if (!session) {
|
|
12934
12992
|
await interaction.reply({ content: "\u4F1A\u8BDD\u4E0D\u5B58\u5728\u3002", ephemeral: true });
|
|
12935
12993
|
return true;
|
|
@@ -13036,7 +13094,7 @@ async function handleModeButton(interaction) {
|
|
|
13036
13094
|
const parts = customId.split(":");
|
|
13037
13095
|
const sessionId = parts[1];
|
|
13038
13096
|
const newMode = parts[2];
|
|
13039
|
-
const session =
|
|
13097
|
+
const session = getSessionView(sessionId);
|
|
13040
13098
|
if (!session) {
|
|
13041
13099
|
await interaction.reply({ content: "\u4F1A\u8BDD\u4E0D\u5B58\u5728\u3002", ephemeral: true });
|
|
13042
13100
|
return true;
|
|
@@ -13053,7 +13111,7 @@ async function handleModeButton(interaction) {
|
|
|
13053
13111
|
await interaction.reply({ content: `\u5DF2\u5207\u6362\u81F3 **${labels[newMode]}**`, ephemeral: true });
|
|
13054
13112
|
try {
|
|
13055
13113
|
const original = interaction.message;
|
|
13056
|
-
const liveSession =
|
|
13114
|
+
const liveSession = getSessionView(sessionId);
|
|
13057
13115
|
const updatedComponents = original.components.map((row) => {
|
|
13058
13116
|
if (!("components" in row)) return row;
|
|
13059
13117
|
const first = asComponentLike(row.components?.[0]);
|
|
@@ -13074,7 +13132,7 @@ async function handleSelectMenuAction(interaction) {
|
|
|
13074
13132
|
const sessionId = parts[1];
|
|
13075
13133
|
const questionIndex = parseInt(parts[2], 10);
|
|
13076
13134
|
const selected = interaction.values[0];
|
|
13077
|
-
const session =
|
|
13135
|
+
const session = getSessionView(sessionId);
|
|
13078
13136
|
if (!session) {
|
|
13079
13137
|
await interaction.reply({ content: "\u4F1A\u8BDD\u4E0D\u5B58\u5728\u3002", ephemeral: true });
|
|
13080
13138
|
return true;
|
|
@@ -13089,7 +13147,7 @@ async function handleSelectMenuAction(interaction) {
|
|
|
13089
13147
|
if (!("components" in row)) return row;
|
|
13090
13148
|
const comp = asComponentLike(row.components?.[0]);
|
|
13091
13149
|
if (comp?.customId !== customId) return row;
|
|
13092
|
-
const menu = new
|
|
13150
|
+
const menu = new StringSelectMenuBuilder().setCustomId(customId).setPlaceholder(`\u5DF2\u9009\u62E9\uFF1A${selected.slice(0, 80)}`);
|
|
13093
13151
|
for (const opt of comp.options || []) {
|
|
13094
13152
|
menu.addOptions({
|
|
13095
13153
|
label: opt.label,
|
|
@@ -13098,7 +13156,7 @@ async function handleSelectMenuAction(interaction) {
|
|
|
13098
13156
|
default: opt.value === selected
|
|
13099
13157
|
});
|
|
13100
13158
|
}
|
|
13101
|
-
return new
|
|
13159
|
+
return new ActionRowBuilder2().addComponents(menu);
|
|
13102
13160
|
});
|
|
13103
13161
|
await original.edit({ components: updatedComponents });
|
|
13104
13162
|
} catch {
|
|
@@ -13113,7 +13171,7 @@ async function handleSelectMenuAction(interaction) {
|
|
|
13113
13171
|
const afterPrefix = customId.slice(14);
|
|
13114
13172
|
const sessionId = afterPrefix.includes(":") ? afterPrefix.split(":")[0] : afterPrefix;
|
|
13115
13173
|
const selected = interaction.values[0];
|
|
13116
|
-
const session =
|
|
13174
|
+
const session = getSessionView(sessionId);
|
|
13117
13175
|
if (!session) {
|
|
13118
13176
|
await interaction.reply({ content: "\u4F1A\u8BDD\u4E0D\u5B58\u5728\u3002", ephemeral: true });
|
|
13119
13177
|
return true;
|
|
@@ -13132,7 +13190,7 @@ async function handleSelectMenuAction(interaction) {
|
|
|
13132
13190
|
if (customId.startsWith("select:")) {
|
|
13133
13191
|
const sessionId = customId.slice(7);
|
|
13134
13192
|
const selected = interaction.values[0];
|
|
13135
|
-
const session =
|
|
13193
|
+
const session = getSessionView(sessionId);
|
|
13136
13194
|
if (!session) {
|
|
13137
13195
|
await interaction.reply({ content: "\u4F1A\u8BDD\u4E0D\u5B58\u5728\u3002", ephemeral: true });
|
|
13138
13196
|
return true;
|
|
@@ -13151,31 +13209,58 @@ async function handleSelectMenuAction(interaction) {
|
|
|
13151
13209
|
return false;
|
|
13152
13210
|
}
|
|
13153
13211
|
|
|
13154
|
-
// ../bot/src/button-
|
|
13155
|
-
|
|
13156
|
-
|
|
13157
|
-
|
|
13158
|
-
|
|
13212
|
+
// ../bot/src/button-router.ts
|
|
13213
|
+
var BUTTON_ROUTES = [
|
|
13214
|
+
handleStopButton,
|
|
13215
|
+
handleAwaitingHumanButton,
|
|
13216
|
+
handleContinueButton,
|
|
13217
|
+
handleExpandButton,
|
|
13218
|
+
handleDeprecatedInteractionButton,
|
|
13219
|
+
handleCleanupButtons,
|
|
13220
|
+
handleModeButton
|
|
13221
|
+
];
|
|
13222
|
+
var SELECT_ROUTES = [handleSelectMenuAction];
|
|
13223
|
+
async function checkAuthorization(userId, reply) {
|
|
13224
|
+
if (isUserAllowed(userId, config.allowedUsers, config.allowAllUsers)) return true;
|
|
13225
|
+
await reply("\u672A\u6388\u6743\u3002");
|
|
13226
|
+
return false;
|
|
13227
|
+
}
|
|
13228
|
+
async function routeButton(interaction) {
|
|
13229
|
+
const ok = await checkAuthorization(
|
|
13230
|
+
interaction.user.id,
|
|
13231
|
+
(content) => interaction.reply({ content, ephemeral: true })
|
|
13232
|
+
);
|
|
13233
|
+
if (!ok) {
|
|
13234
|
+
console.warn(
|
|
13235
|
+
`[ButtonHandler] Unauthorized button press by user ${interaction.user.id}: ${interaction.customId}`
|
|
13236
|
+
);
|
|
13159
13237
|
return;
|
|
13160
13238
|
}
|
|
13161
|
-
|
|
13162
|
-
|
|
13163
|
-
|
|
13164
|
-
if (await handleExpandButton(interaction)) return;
|
|
13165
|
-
if (await handleDeprecatedInteractionButton(interaction)) return;
|
|
13166
|
-
if (await handleCleanupButtons(interaction)) return;
|
|
13167
|
-
if (await handleModeButton(interaction)) return;
|
|
13239
|
+
for (const route of BUTTON_ROUTES) {
|
|
13240
|
+
if (await route(interaction)) return;
|
|
13241
|
+
}
|
|
13168
13242
|
await interaction.reply({ content: "\u672A\u77E5\u6309\u94AE\u3002", ephemeral: true });
|
|
13169
13243
|
}
|
|
13170
|
-
async function
|
|
13171
|
-
|
|
13172
|
-
|
|
13173
|
-
|
|
13244
|
+
async function routeSelectMenu(interaction) {
|
|
13245
|
+
const ok = await checkAuthorization(
|
|
13246
|
+
interaction.user.id,
|
|
13247
|
+
(content) => interaction.reply({ content, ephemeral: true })
|
|
13248
|
+
);
|
|
13249
|
+
if (!ok) return;
|
|
13250
|
+
for (const route of SELECT_ROUTES) {
|
|
13251
|
+
if (await route(interaction)) return;
|
|
13174
13252
|
}
|
|
13175
|
-
if (await handleSelectMenuAction(interaction)) return;
|
|
13176
13253
|
await interaction.reply({ content: "\u672A\u77E5\u9009\u62E9\u3002", ephemeral: true });
|
|
13177
13254
|
}
|
|
13178
13255
|
|
|
13256
|
+
// ../bot/src/button-handler.ts
|
|
13257
|
+
async function handleButton(interaction) {
|
|
13258
|
+
await routeButton(interaction);
|
|
13259
|
+
}
|
|
13260
|
+
async function handleSelectMenu(interaction) {
|
|
13261
|
+
await routeSelectMenu(interaction);
|
|
13262
|
+
}
|
|
13263
|
+
|
|
13179
13264
|
// ../bot/src/bot-event-router.ts
|
|
13180
13265
|
import {
|
|
13181
13266
|
InteractionType
|
|
@@ -13290,7 +13375,7 @@ async function startBot() {
|
|
|
13290
13375
|
const guild = client.guilds.cache.first();
|
|
13291
13376
|
if (guild) {
|
|
13292
13377
|
serviceContainer.logChannel = guild.channels.cache.find(
|
|
13293
|
-
(ch) => ch.name === "bot-logs" && ch.type ===
|
|
13378
|
+
(ch) => ch.name === "bot-logs" && ch.type === ChannelType7.GuildText && !ch.parentId
|
|
13294
13379
|
) ?? serviceContainer.logChannel;
|
|
13295
13380
|
logBuffer.setChannel(serviceContainer.logChannel);
|
|
13296
13381
|
}
|
|
@@ -13356,7 +13441,7 @@ async function runCli() {
|
|
|
13356
13441
|
await startBot();
|
|
13357
13442
|
});
|
|
13358
13443
|
cli.command("config [action] [key] [value]", "Manage configuration").action(async (action, key, value) => {
|
|
13359
|
-
const { handleConfig } = await import("./config-cli-
|
|
13444
|
+
const { handleConfig } = await import("./config-cli-7G5YWKJU.js");
|
|
13360
13445
|
const args = [action || "list"].filter(Boolean);
|
|
13361
13446
|
if (key) args.push(key);
|
|
13362
13447
|
if (value) args.push(value);
|
|
@@ -13364,12 +13449,12 @@ async function runCli() {
|
|
|
13364
13449
|
await handleConfig(args);
|
|
13365
13450
|
});
|
|
13366
13451
|
cli.command("project [action] [...args]", "Manage mounted projects").action(async (action, args) => {
|
|
13367
|
-
const { handleProject: handleProject2 } = await import("./project-cli-
|
|
13452
|
+
const { handleProject: handleProject2 } = await import("./project-cli-ALKDLRMZ.js");
|
|
13368
13453
|
const cmdArgs = [action || "help"].filter(Boolean).concat(args || []);
|
|
13369
13454
|
await handleProject2(cmdArgs);
|
|
13370
13455
|
});
|
|
13371
13456
|
cli.command("attachment [action]", "Manage attachments").action(async (action) => {
|
|
13372
|
-
const { handleAttachment } = await import("./attachment-cli-
|
|
13457
|
+
const { handleAttachment } = await import("./attachment-cli-AT4HXAGU.js");
|
|
13373
13458
|
await handleAttachment([action || "fetch"]);
|
|
13374
13459
|
});
|
|
13375
13460
|
cli.command("daemon <action>", "Manage background service").action(async (action) => {
|
|
@@ -13377,7 +13462,7 @@ async function runCli() {
|
|
|
13377
13462
|
await handleDaemon(action);
|
|
13378
13463
|
});
|
|
13379
13464
|
cli.command("codex [...options]", "Launch managed Codex session with remote approval").allowUnknownOptions().action(async function(options) {
|
|
13380
|
-
const { handleCodexCommand } = await import("./codex-launcher-
|
|
13465
|
+
const { handleCodexCommand } = await import("./codex-launcher-CLGG4CVY.js");
|
|
13381
13466
|
const parsed = this.options;
|
|
13382
13467
|
const rawArgs = [];
|
|
13383
13468
|
for (const [key, value] of Object.entries(parsed)) {
|