workspacecord 1.1.3 → 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-RW36JGUV.js → archive-manager-FJU7YEEH.js} +4 -4
- package/dist/{attachment-cli-MF7XZ4WT.js → attachment-cli-AT4HXAGU.js} +2 -2
- package/dist/{chunk-ZO62NAYX.js → chunk-5EMN2IL5.js} +1 -1
- package/dist/{chunk-BFINJJYL.js → chunk-66B64WL3.js} +1 -1
- package/dist/{chunk-QWPKAUSV.js → chunk-C4L34VJK.js} +8287 -7421
- package/dist/{chunk-WON3DPE4.js → chunk-JR4B4L7I.js} +213 -4
- package/dist/{chunk-IVXCJA5I.js → chunk-MJ5JKFGS.js} +9 -0
- package/dist/{chunk-7GTUWAQR.js → chunk-NNTMVOTM.js} +876 -315
- package/dist/{chunk-WP6YJVAE.js → chunk-PWMEOBXG.js} +4 -4
- package/dist/{chunk-AGB4GP4G.js → chunk-SNPFYUQ3.js} +441 -654
- package/dist/{chunk-4KQ7OSK7.js → chunk-SV7EHL3B.js} +3 -3
- package/dist/cli-framework-YF3LPLMT.js +18 -0
- package/dist/cli.js +10 -10
- package/dist/{codex-launcher-ZBQ5VL6L.js → codex-launcher-CLGG4CVY.js} +1 -1
- package/dist/{codex-provider-Q4Z6UKO6.js → codex-provider-VLOS5QB6.js} +18 -4
- package/dist/{config-cli-7JEV3WYY.js → config-cli-7G5YWKJU.js} +2 -2
- package/dist/{panel-adapter-U75WXDLB.js → panel-adapter-CLI4WDII.js} +4 -4
- package/dist/{project-cli-ZXMHOFUJ.js → project-cli-ALKDLRMZ.js} +2 -2
- package/dist/{project-registry-ED6P5ZTM.js → project-registry-ZO3KSS25.js} +2 -2
- package/dist/sdk-C3D6X4EB.js +55 -0
- package/dist/{session-local-registration-MISPPGXF.js → session-local-registration-RL2A3QZB.js} +3 -3
- package/dist/{setup-ZFVMMNT2.js → setup-GP3MML2R.js} +1 -1
- package/package.json +3 -3
- package/dist/cli-framework-7E5MKPMM.js +0 -18
- package/dist/sdk-V7A7IF7F.js +0 -43
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
cleanupSessionAttachments
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-66B64WL3.js";
|
|
5
5
|
import {
|
|
6
6
|
STATE_COLORS,
|
|
7
7
|
STATE_LABELS,
|
|
8
|
+
createSession,
|
|
8
9
|
debouncedSaveSession,
|
|
10
|
+
endSession,
|
|
9
11
|
gateService,
|
|
10
12
|
getAllSessions,
|
|
11
13
|
getSession,
|
|
@@ -17,17 +19,22 @@ import {
|
|
|
17
19
|
stateMachine,
|
|
18
20
|
toPlatformEvent,
|
|
19
21
|
updateSession
|
|
20
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-JR4B4L7I.js";
|
|
21
23
|
import {
|
|
22
24
|
config,
|
|
23
25
|
truncate
|
|
24
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-MJ5JKFGS.js";
|
|
25
27
|
|
|
26
28
|
// ../bot/src/discord/status-card-projection-renderer.ts
|
|
27
29
|
var StatusCardProjectionRenderer = class {
|
|
28
30
|
#pending = /* @__PURE__ */ new Map();
|
|
29
31
|
async renderNow(sessionId, projection, context) {
|
|
30
32
|
if (!context) return;
|
|
33
|
+
if (process.env.E2E_DEBUG_RENDER === "1") {
|
|
34
|
+
console.log(
|
|
35
|
+
`[renderer] ${sessionId} todoList=${projection.todoList?.length ?? 0} denials=${projection.recentPermissionDenials?.length ?? 0} batch=${projection.batchApprovalMode}`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
31
38
|
try {
|
|
32
39
|
await context.statusCard.update(projection.state, {
|
|
33
40
|
turn: projection.turn,
|
|
@@ -37,7 +44,11 @@ var StatusCardProjectionRenderer = class {
|
|
|
37
44
|
provider: context.provider,
|
|
38
45
|
permissionsSummary: context.permissionsSummary,
|
|
39
46
|
verbose: context.verbose,
|
|
40
|
-
monitorGoal: context.monitorGoal
|
|
47
|
+
monitorGoal: context.monitorGoal,
|
|
48
|
+
todoList: projection.todoList,
|
|
49
|
+
recentPermissionDenials: projection.recentPermissionDenials,
|
|
50
|
+
batchApprovalMode: projection.batchApprovalMode,
|
|
51
|
+
pendingApprovals: projection.pendingApprovals
|
|
41
52
|
});
|
|
42
53
|
} catch (error) {
|
|
43
54
|
console.error(`\u72B6\u6001\u5361\u66F4\u65B0\u5931\u8D25 (${sessionId}):`, error);
|
|
@@ -73,156 +84,105 @@ var StatusCardProjectionRenderer = class {
|
|
|
73
84
|
}
|
|
74
85
|
};
|
|
75
86
|
|
|
76
|
-
// ../bot/src/
|
|
77
|
-
import {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
var
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
// ../bot/src/output/event-handlers.ts
|
|
88
|
+
import { existsSync as existsSync2 } from "fs";
|
|
89
|
+
|
|
90
|
+
// ../engine/src/session-context.ts
|
|
91
|
+
var EMPTY_PROJECTION = Object.freeze({
|
|
92
|
+
turn: 0,
|
|
93
|
+
humanResolved: false,
|
|
94
|
+
updatedAt: 0
|
|
95
|
+
});
|
|
96
|
+
function safeGetSessionController(sessionId) {
|
|
97
|
+
try {
|
|
98
|
+
const fn = getSessionController;
|
|
99
|
+
return typeof fn === "function" ? fn(sessionId) : void 0;
|
|
100
|
+
} catch {
|
|
101
|
+
return void 0;
|
|
90
102
|
}
|
|
91
|
-
|
|
92
|
-
|
|
103
|
+
}
|
|
104
|
+
function safeDebouncedSaveSession() {
|
|
105
|
+
try {
|
|
106
|
+
const fn = debouncedSaveSession;
|
|
107
|
+
if (typeof fn === "function") fn();
|
|
108
|
+
} catch {
|
|
93
109
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
provider: data.provider,
|
|
101
|
-
permissionsSummary: data.permissionsSummary
|
|
102
|
-
};
|
|
103
|
-
this.lastState = "idle";
|
|
104
|
-
this.lastData = payload;
|
|
105
|
-
if (this.messageId) {
|
|
106
|
-
await this.update("idle", payload);
|
|
107
|
-
return;
|
|
110
|
+
}
|
|
111
|
+
function safeGetProjection(sessionId) {
|
|
112
|
+
try {
|
|
113
|
+
const sm = stateMachine;
|
|
114
|
+
if (sm && typeof sm.getSnapshot === "function") {
|
|
115
|
+
return sm.getSnapshot(sessionId);
|
|
108
116
|
}
|
|
109
|
-
|
|
110
|
-
await this.sendNewMessage(embed);
|
|
117
|
+
} catch {
|
|
111
118
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
+
return EMPTY_PROJECTION;
|
|
120
|
+
}
|
|
121
|
+
var SessionSupervisor = class {
|
|
122
|
+
contexts = /* @__PURE__ */ new Map();
|
|
123
|
+
get(sessionId) {
|
|
124
|
+
const live = getSession(sessionId);
|
|
125
|
+
const cached = this.contexts.get(sessionId);
|
|
126
|
+
if (!live) {
|
|
127
|
+
if (cached) this.contexts.delete(sessionId);
|
|
128
|
+
return void 0;
|
|
119
129
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (!this.messageId || !this.lastData) return null;
|
|
124
|
-
const oldMessageId = this.messageId;
|
|
125
|
-
const embed = this.buildEmbed(this.lastState, this.lastData);
|
|
126
|
-
const msg = await this.channel.send({ embeds: [embed] });
|
|
127
|
-
this.messageId = msg.id;
|
|
128
|
-
return { oldMessageId, newMessageId: msg.id };
|
|
129
|
-
}
|
|
130
|
-
async sendNewMessage(embed) {
|
|
131
|
-
try {
|
|
132
|
-
const msg = await this.channel.send({ embeds: [embed] });
|
|
133
|
-
this.messageId = msg.id;
|
|
134
|
-
} catch (error) {
|
|
135
|
-
console.error("\u72B6\u6001\u5361\u521B\u5EFA\u5931\u8D25:", error);
|
|
136
|
-
throw error;
|
|
130
|
+
if (cached) {
|
|
131
|
+
this.syncContext(cached);
|
|
132
|
+
return cached;
|
|
137
133
|
}
|
|
134
|
+
const ctx = this.build(live);
|
|
135
|
+
this.contexts.set(sessionId, ctx);
|
|
136
|
+
return ctx;
|
|
138
137
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
try {
|
|
145
|
-
const msg = await this.channel.messages.edit(this.messageId, {
|
|
146
|
-
embeds: [embed],
|
|
147
|
-
components: []
|
|
148
|
-
});
|
|
149
|
-
this.messageId = msg.id;
|
|
150
|
-
} catch (error) {
|
|
151
|
-
console.warn(`\u72B6\u6001\u5361\u7F16\u8F91\u5931\u8D25 (${this.messageId}), \u521B\u5EFA\u65B0\u6D88\u606F:`, error);
|
|
152
|
-
await this.sendNewMessage(embed);
|
|
153
|
-
}
|
|
138
|
+
/** 枚举现有 context。用于 supervisor 层面的扫描(健康检查、空闲回收)。 */
|
|
139
|
+
all() {
|
|
140
|
+
return Array.from(this.contexts.values());
|
|
154
141
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
{ name: "\u66F4\u65B0", value: `<t:${Math.floor(data.updatedAt / 1e3)}:R>`, inline: true }
|
|
159
|
-
).setTimestamp();
|
|
160
|
-
if (data.provider === "codex") {
|
|
161
|
-
const managedLabel = data.remoteHumanControl ? "\u2713 \u53D7\u7BA1\u4F1A\u8BDD" : "\u25CB \u975E\u53D7\u7BA1\u4F1A\u8BDD\uFF08\u4EC5\u72B6\u6001\u76D1\u63A7\uFF09";
|
|
162
|
-
embed.addFields({ name: "\u4F1A\u8BDD\u7C7B\u578B", value: managedLabel, inline: true });
|
|
163
|
-
}
|
|
164
|
-
if (data.phase) {
|
|
165
|
-
const sanitizedPhase = this.sanitizePhase(data.phase);
|
|
166
|
-
if (sanitizedPhase) {
|
|
167
|
-
embed.addFields({ name: "\u9636\u6BB5", value: sanitizedPhase, inline: true });
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
if (data.verbose !== void 0) {
|
|
171
|
-
embed.addFields({ name: "\u8F93\u51FA", value: data.verbose ? "\u{1F50A} \u8BE6\u7EC6" : "\u{1F507} \u7CBE\u7B80", inline: true });
|
|
172
|
-
}
|
|
173
|
-
if (data.monitorGoal) {
|
|
174
|
-
embed.addFields({ name: "\u76D1\u63A7\u76EE\u6807", value: truncate(data.monitorGoal, 150) });
|
|
175
|
-
}
|
|
176
|
-
if (data.monitorIteration !== void 0 && data.maxMonitorIterations !== void 0) {
|
|
177
|
-
embed.addFields({ name: "\u8FED\u4EE3", value: `${data.monitorIteration}/${data.maxMonitorIterations}`, inline: true });
|
|
178
|
-
}
|
|
179
|
-
if (data.permissionsSummary) {
|
|
180
|
-
embed.addFields({ name: "\u6743\u9650", value: data.permissionsSummary, inline: false });
|
|
181
|
-
}
|
|
182
|
-
return embed;
|
|
142
|
+
/** 释放某个 session 的 context(endSession 调用时触发)。 */
|
|
143
|
+
release(sessionId) {
|
|
144
|
+
this.contexts.delete(sessionId);
|
|
183
145
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
* Returns cleaned text or empty string if unsuitable.
|
|
187
|
-
*/
|
|
188
|
-
sanitizePhase(description) {
|
|
189
|
-
let normalized = description.trim();
|
|
190
|
-
if (!normalized) return "";
|
|
191
|
-
if (normalized.includes("```") || /diff --git/.test(normalized) || this.isLikelyFileList(normalized)) {
|
|
192
|
-
console.warn(`[StatusCard] Phase contains unsuitable content, stripping: ${normalized.slice(0, 60)}...`);
|
|
193
|
-
return "";
|
|
194
|
-
}
|
|
195
|
-
if (normalized.length > 200) {
|
|
196
|
-
normalized = normalized.slice(0, 197) + "...";
|
|
197
|
-
}
|
|
198
|
-
return normalized;
|
|
146
|
+
releaseAll() {
|
|
147
|
+
this.contexts.clear();
|
|
199
148
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (/diff --git/.test(normalized)) {
|
|
211
|
-
throw new Error("\u72B6\u6001\u5361\u4E0D\u5E94\u5305\u542B diff");
|
|
212
|
-
}
|
|
213
|
-
if (this.isLikelyFileList(normalized)) {
|
|
214
|
-
throw new Error("\u72B6\u6001\u5361\u4E0D\u5E94\u5305\u542B\u6587\u4EF6\u5217\u8868");
|
|
215
|
-
}
|
|
149
|
+
build(session) {
|
|
150
|
+
const ctx = {
|
|
151
|
+
sessionId: session.id,
|
|
152
|
+
session,
|
|
153
|
+
controller: safeGetSessionController(session.id),
|
|
154
|
+
projection: safeGetProjection(session.id),
|
|
155
|
+
save: () => safeDebouncedSaveSession(),
|
|
156
|
+
refresh: () => this.syncContext(ctx)
|
|
157
|
+
};
|
|
158
|
+
return ctx;
|
|
216
159
|
}
|
|
217
|
-
|
|
218
|
-
const
|
|
219
|
-
if (
|
|
220
|
-
|
|
160
|
+
syncContext(ctx) {
|
|
161
|
+
const live = getSession(ctx.sessionId);
|
|
162
|
+
if (live) {
|
|
163
|
+
ctx.session = live;
|
|
164
|
+
}
|
|
165
|
+
ctx.controller = safeGetSessionController(
|
|
166
|
+
ctx.sessionId
|
|
167
|
+
);
|
|
168
|
+
ctx.projection = safeGetProjection(
|
|
169
|
+
ctx.sessionId
|
|
170
|
+
);
|
|
221
171
|
}
|
|
222
172
|
};
|
|
173
|
+
var sessionSupervisor = new SessionSupervisor();
|
|
174
|
+
function getSessionContext(sessionId) {
|
|
175
|
+
return sessionSupervisor.get(sessionId);
|
|
176
|
+
}
|
|
177
|
+
function getSessionView(sessionId) {
|
|
178
|
+
return sessionSupervisor.get(sessionId)?.session;
|
|
179
|
+
}
|
|
223
180
|
|
|
224
|
-
// ../bot/src/
|
|
225
|
-
import
|
|
181
|
+
// ../bot/src/subagent-manager.ts
|
|
182
|
+
import {
|
|
183
|
+
ChannelType,
|
|
184
|
+
ThreadAutoArchiveDuration
|
|
185
|
+
} from "discord.js";
|
|
226
186
|
|
|
227
187
|
// ../bot/src/discord/delivery-policy.ts
|
|
228
188
|
import { existsSync, statSync } from "fs";
|
|
@@ -338,71 +298,772 @@ async function sendWithBackoff(channel, payload) {
|
|
|
338
298
|
const message = await channel.send(payload);
|
|
339
299
|
return message.id;
|
|
340
300
|
} catch (error) {
|
|
341
|
-
if (!isDiscordRateLimitError(error) || attempt >= SEND_MAX_RETRIES) {
|
|
342
|
-
throw error;
|
|
343
|
-
}
|
|
344
|
-
const delayMs = 250 * 2 ** attempt;
|
|
345
|
-
console.warn(
|
|
346
|
-
`[Delivery] Discord rate-limited (attempt ${attempt + 1}/${SEND_MAX_RETRIES}), retrying in ${delayMs}ms`
|
|
347
|
-
);
|
|
348
|
-
await sleep(delayMs);
|
|
349
|
-
attempt++;
|
|
301
|
+
if (!isDiscordRateLimitError(error) || attempt >= SEND_MAX_RETRIES) {
|
|
302
|
+
throw error;
|
|
303
|
+
}
|
|
304
|
+
const delayMs = 250 * 2 ** attempt;
|
|
305
|
+
console.warn(
|
|
306
|
+
`[Delivery] Discord rate-limited (attempt ${attempt + 1}/${SEND_MAX_RETRIES}), retrying in ${delayMs}ms`
|
|
307
|
+
);
|
|
308
|
+
await sleep(delayMs);
|
|
309
|
+
attempt++;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
async function sendChunk(channel, chunk, options = {}) {
|
|
314
|
+
const payload = { content: chunk };
|
|
315
|
+
if (options.files?.length) payload.files = options.files;
|
|
316
|
+
if (options.replyToMessageId) {
|
|
317
|
+
payload.reply = { messageReference: options.replyToMessageId };
|
|
318
|
+
}
|
|
319
|
+
try {
|
|
320
|
+
return await sendWithBackoff(channel, payload);
|
|
321
|
+
} catch (error) {
|
|
322
|
+
if (!options.replyToMessageId) {
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
325
|
+
const fallbackPayload = { content: chunk };
|
|
326
|
+
if (options.files?.length) fallbackPayload.files = options.files;
|
|
327
|
+
return sendWithBackoff(channel, fallbackPayload);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
async function deliver(channel, plan) {
|
|
331
|
+
pruneStaleSessionStates();
|
|
332
|
+
const state = getSessionState(plan.sessionId);
|
|
333
|
+
if (plan.mode === "progress_update") {
|
|
334
|
+
const targetMessageId = plan.editTargetMessageId ?? state.recentProgressMessageId;
|
|
335
|
+
if (targetMessageId && plan.chunks.length === 1 && plan.filesOnFirstChunk.length === 0) {
|
|
336
|
+
try {
|
|
337
|
+
await channel.messages?.edit(targetMessageId, { content: plan.chunks[0] });
|
|
338
|
+
state.recentProgressMessageId = targetMessageId;
|
|
339
|
+
return [targetMessageId];
|
|
340
|
+
} catch {
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
const ids = [];
|
|
345
|
+
const replyToMode = plan.replyToMode ?? "first";
|
|
346
|
+
for (let index = 0; index < plan.chunks.length; index++) {
|
|
347
|
+
const shouldReply = !!plan.replyToMessageId && replyToMode !== "off" && (replyToMode === "all" || index === 0);
|
|
348
|
+
const id = await sendChunk(channel, plan.chunks[index], {
|
|
349
|
+
replyToMessageId: shouldReply ? plan.replyToMessageId : void 0,
|
|
350
|
+
files: index === 0 ? plan.filesOnFirstChunk : void 0
|
|
351
|
+
});
|
|
352
|
+
ids.push(id);
|
|
353
|
+
}
|
|
354
|
+
if (ids.length > 0) {
|
|
355
|
+
if (plan.mode === "progress_update") {
|
|
356
|
+
state.recentProgressMessageId = ids[ids.length - 1];
|
|
357
|
+
} else {
|
|
358
|
+
state.recentFinalMessageId = ids[ids.length - 1];
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return ids;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ../bot/src/discord/delivery-notices.ts
|
|
365
|
+
function getDeliveryPolicy() {
|
|
366
|
+
return {
|
|
367
|
+
textChunkLimit: config.textChunkLimit ?? 2e3,
|
|
368
|
+
chunkMode: config.chunkMode ?? "length",
|
|
369
|
+
replyToMode: config.replyToMode ?? "first",
|
|
370
|
+
ackReaction: config.ackReaction ?? "\u{1F440}"
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
async function sendSystemNotice(channel, sessionId, text, replyToMessageId) {
|
|
374
|
+
if (!text.trim()) return;
|
|
375
|
+
const plan = buildDeliveryPlan({
|
|
376
|
+
sessionId,
|
|
377
|
+
chatId: channel.id,
|
|
378
|
+
text,
|
|
379
|
+
files: [],
|
|
380
|
+
mode: "system_notice",
|
|
381
|
+
replyToMessageId,
|
|
382
|
+
policy: getDeliveryPolicy()
|
|
383
|
+
});
|
|
384
|
+
try {
|
|
385
|
+
await deliver(channel, plan);
|
|
386
|
+
} catch {
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// ../bot/src/subagent-manager.ts
|
|
391
|
+
var SUBAGENT_IDLE_TIMEOUT_MS = 60 * 60 * 1e3;
|
|
392
|
+
function canSpawnSubagent(parentSession) {
|
|
393
|
+
return parentSession.subagentDepth < config.maxSubagentDepth;
|
|
394
|
+
}
|
|
395
|
+
async function spawnSubagent(parentSession, label, provider, sessionChannel) {
|
|
396
|
+
if (!canSpawnSubagent(parentSession)) {
|
|
397
|
+
console.warn(`[SubagentManager] Depth limit hit for session ${parentSession.id} (max depth ${config.maxSubagentDepth})`);
|
|
398
|
+
throw new Error(
|
|
399
|
+
`Max subagent depth (${config.maxSubagentDepth}) reached. Cannot spawn further subagents.`
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
const threadName = `[sub:${provider}] ${label}`.slice(0, 100);
|
|
403
|
+
const thread = await sessionChannel.threads.create({
|
|
404
|
+
name: threadName,
|
|
405
|
+
type: ChannelType.PublicThread,
|
|
406
|
+
autoArchiveDuration: ThreadAutoArchiveDuration.OneHour,
|
|
407
|
+
reason: `Subagent spawned by session ${parentSession.id}`
|
|
408
|
+
});
|
|
409
|
+
const session = await createSession({
|
|
410
|
+
channelId: thread.id,
|
|
411
|
+
// Subagent's primary ID is the Thread
|
|
412
|
+
categoryId: parentSession.categoryId,
|
|
413
|
+
projectName: parentSession.projectName,
|
|
414
|
+
agentLabel: label,
|
|
415
|
+
provider,
|
|
416
|
+
directory: parentSession.directory,
|
|
417
|
+
type: "subagent",
|
|
418
|
+
parentChannelId: parentSession.channelId,
|
|
419
|
+
// Parent session's TextChannel
|
|
420
|
+
subagentDepth: parentSession.subagentDepth + 1,
|
|
421
|
+
mode: parentSession.mode,
|
|
422
|
+
claudePermissionMode: provider === "claude" ? parentSession.claudePermissionMode : void 0
|
|
423
|
+
});
|
|
424
|
+
console.log(`[SubagentManager] Spawned subagent "${label}" session ${session.id} thread ${thread.id} (depth ${session.subagentDepth}, parent ${parentSession.id})`);
|
|
425
|
+
return session;
|
|
426
|
+
}
|
|
427
|
+
async function archiveSubagent(session, thread, summary) {
|
|
428
|
+
if (summary) {
|
|
429
|
+
await sendSystemNotice(thread, session.id, `*\u5B50\u4EFB\u52A1\u5B8C\u6210\uFF1A${summary}*`);
|
|
430
|
+
}
|
|
431
|
+
try {
|
|
432
|
+
await thread.setArchived(true, "Subagent task completed");
|
|
433
|
+
} catch (error) {
|
|
434
|
+
console.warn(`[SubagentManager] Failed to archive thread ${thread.id} for subagent ${session.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
435
|
+
}
|
|
436
|
+
try {
|
|
437
|
+
await endSession(session.id);
|
|
438
|
+
console.log(`[SubagentManager] Archived subagent ${session.id} thread ${thread.id}${summary ? ` \u2014 ${summary}` : ""}`);
|
|
439
|
+
} catch (error) {
|
|
440
|
+
console.warn(`[SubagentManager] Failed to end session for subagent ${session.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
function getSubagents(parentSession) {
|
|
444
|
+
return getAllSessions().filter(
|
|
445
|
+
(s) => s.type === "subagent" && s.parentChannelId === parentSession.channelId
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
async function runSubagentWatchdog(getThread) {
|
|
449
|
+
const now = Date.now();
|
|
450
|
+
const checked = /* @__PURE__ */ new Set();
|
|
451
|
+
let archived = 0;
|
|
452
|
+
let errors = 0;
|
|
453
|
+
for (const session of getSubagentSessions()) {
|
|
454
|
+
if (checked.has(session.id)) continue;
|
|
455
|
+
checked.add(session.id);
|
|
456
|
+
const idle = now - session.lastActivity;
|
|
457
|
+
if (idle < SUBAGENT_IDLE_TIMEOUT_MS) continue;
|
|
458
|
+
if (session.isGenerating) continue;
|
|
459
|
+
const thread = getThread(session.channelId);
|
|
460
|
+
if (!thread) {
|
|
461
|
+
await endSession(session.id).catch((e) => console.warn(`[SubagentWatchdog] Failed to end orphaned subagent ${session.id}: ${e.message}`));
|
|
462
|
+
console.log(`[SubagentWatchdog] Ended orphaned subagent ${session.id} \u2014 thread ${session.channelId} not found`);
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
try {
|
|
466
|
+
await archiveSubagent(session, thread, "Idle timeout reached.");
|
|
467
|
+
archived += 1;
|
|
468
|
+
} catch (error) {
|
|
469
|
+
console.error(`[SubagentWatchdog] Failed to archive idle subagent ${session.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
470
|
+
errors += 1;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
console.log(`[SubagentWatchdog] Watchdog run: checked ${checked.size} subagents, archived ${archived}, errors ${errors}`);
|
|
474
|
+
}
|
|
475
|
+
async function autoSpawnSubagentThread(parentSession, taskId, description, sessionChannel) {
|
|
476
|
+
if (parentSession.type === "subagent") return null;
|
|
477
|
+
if (!canSpawnSubagent(parentSession)) return null;
|
|
478
|
+
const threadName = `[task] ${description}`.slice(0, 100);
|
|
479
|
+
const thread = await sessionChannel.threads.create({
|
|
480
|
+
name: threadName,
|
|
481
|
+
type: ChannelType.PublicThread,
|
|
482
|
+
autoArchiveDuration: ThreadAutoArchiveDuration.OneHour,
|
|
483
|
+
reason: `Auto-created for task ${taskId} in session ${parentSession.id}`
|
|
484
|
+
});
|
|
485
|
+
let session;
|
|
486
|
+
try {
|
|
487
|
+
session = await createSession({
|
|
488
|
+
channelId: thread.id,
|
|
489
|
+
categoryId: parentSession.categoryId,
|
|
490
|
+
projectName: parentSession.projectName,
|
|
491
|
+
agentLabel: description,
|
|
492
|
+
provider: parentSession.provider,
|
|
493
|
+
directory: parentSession.directory,
|
|
494
|
+
type: "subagent",
|
|
495
|
+
parentChannelId: parentSession.channelId,
|
|
496
|
+
subagentDepth: (parentSession.subagentDepth || 0) + 1,
|
|
497
|
+
mode: parentSession.mode,
|
|
498
|
+
claudePermissionMode: parentSession.provider === "claude" ? parentSession.claudePermissionMode : void 0
|
|
499
|
+
});
|
|
500
|
+
} catch (err) {
|
|
501
|
+
console.warn(`[SubagentManager] createSession failed for auto-spawned task ${taskId}, deleting orphan thread ${thread.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
502
|
+
await thread.delete("Session creation failed").catch(
|
|
503
|
+
(deleteErr) => console.warn(`[SubagentManager] Failed to delete orphan thread ${thread.id}: ${deleteErr instanceof Error ? deleteErr.message : String(deleteErr)}`)
|
|
504
|
+
);
|
|
505
|
+
throw err;
|
|
506
|
+
}
|
|
507
|
+
console.log(`[SubagentManager] Auto-spawned thread for task ${taskId} session ${session.id} thread ${thread.id}`);
|
|
508
|
+
return { threadId: thread.id, session };
|
|
509
|
+
}
|
|
510
|
+
function getSubagentSessions() {
|
|
511
|
+
return getAllSessions().filter((s) => s.type === "subagent");
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// ../bot/src/codex-renderer.ts
|
|
515
|
+
import { EmbedBuilder } from "discord.js";
|
|
516
|
+
|
|
517
|
+
// ../bot/src/output/interaction-controls.ts
|
|
518
|
+
import {
|
|
519
|
+
ActionRowBuilder,
|
|
520
|
+
ButtonBuilder,
|
|
521
|
+
ButtonStyle,
|
|
522
|
+
StringSelectMenuBuilder,
|
|
523
|
+
EmbedBuilder as EmbedBuilder2
|
|
524
|
+
} from "discord.js";
|
|
525
|
+
|
|
526
|
+
// ../engine/src/output/answer-store.ts
|
|
527
|
+
var pendingAnswersStore = /* @__PURE__ */ new Map();
|
|
528
|
+
var questionCountStore = /* @__PURE__ */ new Map();
|
|
529
|
+
function setPendingAnswer(sessionId, questionIndex, answer) {
|
|
530
|
+
if (!pendingAnswersStore.has(sessionId)) {
|
|
531
|
+
pendingAnswersStore.set(sessionId, /* @__PURE__ */ new Map());
|
|
532
|
+
}
|
|
533
|
+
pendingAnswersStore.get(sessionId).set(questionIndex, answer);
|
|
534
|
+
}
|
|
535
|
+
function getPendingAnswers(sessionId) {
|
|
536
|
+
return pendingAnswersStore.get(sessionId);
|
|
537
|
+
}
|
|
538
|
+
function clearPendingAnswers(sessionId) {
|
|
539
|
+
pendingAnswersStore.delete(sessionId);
|
|
540
|
+
questionCountStore.delete(sessionId);
|
|
541
|
+
}
|
|
542
|
+
function getQuestionCount(sessionId) {
|
|
543
|
+
return questionCountStore.get(sessionId) || 0;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// ../bot/src/output/interaction-controls.ts
|
|
547
|
+
function resolveEffectiveClaudePermissionMode(currentMode, claudePermissionMode) {
|
|
548
|
+
if (!claudePermissionMode) return void 0;
|
|
549
|
+
return currentMode === "auto" ? "bypass" : claudePermissionMode;
|
|
550
|
+
}
|
|
551
|
+
function makeModeButtons(sessionId, currentMode, claudePermissionMode) {
|
|
552
|
+
const modes = [
|
|
553
|
+
{ id: "auto", label: "\u26A1 \u81EA\u52A8\u6A21\u5F0F" },
|
|
554
|
+
{ id: "plan", label: "\u{1F4CB} \u8BA1\u5212\u6A21\u5F0F" },
|
|
555
|
+
{ id: "normal", label: "\u{1F6E1}\uFE0F \u666E\u901A\u6A21\u5F0F" },
|
|
556
|
+
{ id: "monitor", label: "\u{1F9E0} \u76D1\u63A7\u6A21\u5F0F" }
|
|
557
|
+
];
|
|
558
|
+
const row = new ActionRowBuilder();
|
|
559
|
+
for (const m of modes) {
|
|
560
|
+
row.addComponents(
|
|
561
|
+
new ButtonBuilder().setCustomId(`mode:${sessionId}:${m.id}`).setLabel(m.id === currentMode ? `\u2713 ${m.label}` : m.label).setStyle(m.id === currentMode ? ButtonStyle.Success : ButtonStyle.Secondary).setDisabled(false)
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
const effectiveClaudePermissionMode = resolveEffectiveClaudePermissionMode(
|
|
565
|
+
currentMode,
|
|
566
|
+
claudePermissionMode
|
|
567
|
+
);
|
|
568
|
+
if (effectiveClaudePermissionMode) {
|
|
569
|
+
const permLabel = effectiveClaudePermissionMode === "bypass" ? "\u26A1 \u7ED5\u8FC7\u6743\u9650" : "\u{1F6E1}\uFE0F \u9700\u8981\u786E\u8BA4";
|
|
570
|
+
row.addComponents(
|
|
571
|
+
new ButtonBuilder().setCustomId(`perm-info:${sessionId}`).setLabel(permLabel).setStyle(
|
|
572
|
+
effectiveClaudePermissionMode === "bypass" ? ButtonStyle.Danger : ButtonStyle.Success
|
|
573
|
+
).setDisabled(true)
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
return row;
|
|
577
|
+
}
|
|
578
|
+
function shouldSuppressCommandExecution(command) {
|
|
579
|
+
return command.toLowerCase().includes("total-recall");
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// ../bot/src/output/event-handlers.ts
|
|
583
|
+
function providerSource(sessionId) {
|
|
584
|
+
const session = getSessionView(sessionId);
|
|
585
|
+
return session?.provider === "codex" ? "codex" : "claude";
|
|
586
|
+
}
|
|
587
|
+
var textDelta = (event, ctx) => {
|
|
588
|
+
ctx.streamer.append(event.text);
|
|
589
|
+
};
|
|
590
|
+
var askUser = async (event, ctx) => {
|
|
591
|
+
ctx.state.askedUser = true;
|
|
592
|
+
ctx.state.askUserQuestionsJson = event.questionsJson;
|
|
593
|
+
await ctx.streamer.discard();
|
|
594
|
+
const session = getSessionView(ctx.sessionId);
|
|
595
|
+
if (!session) return;
|
|
596
|
+
const source = providerSource(ctx.sessionId);
|
|
597
|
+
await updateSessionState(ctx.sessionId, {
|
|
598
|
+
type: "awaiting_human",
|
|
599
|
+
sessionId: ctx.sessionId,
|
|
600
|
+
source,
|
|
601
|
+
confidence: "high",
|
|
602
|
+
timestamp: Date.now(),
|
|
603
|
+
metadata: { detail: event.questionsJson }
|
|
604
|
+
});
|
|
605
|
+
await flushDigest(ctx.sessionId);
|
|
606
|
+
await handleAwaitingHuman(ctx.sessionId, event.questionsJson, { source });
|
|
607
|
+
};
|
|
608
|
+
var task = async (event, ctx) => {
|
|
609
|
+
await ctx.streamer.finalize();
|
|
610
|
+
queueDigest(ctx.sessionId, { kind: "tool", text: `\u4EFB\u52A1\u5DE5\u5177\uFF1A${event.action}` });
|
|
611
|
+
ctx.state.lastToolName = event.action;
|
|
612
|
+
};
|
|
613
|
+
var taskStarted = async (event, ctx) => {
|
|
614
|
+
await ctx.streamer.finalize();
|
|
615
|
+
queueDigest(ctx.sessionId, {
|
|
616
|
+
kind: "subagent",
|
|
617
|
+
text: `\u5B50\u4EE3\u7406\u542F\u52A8\uFF1A${truncate(event.description, 80)}`
|
|
618
|
+
});
|
|
619
|
+
const session = getSessionView(ctx.sessionId);
|
|
620
|
+
if (!session || ctx.channel.type === void 0) return;
|
|
621
|
+
autoSpawnSubagentThread(session, event.taskId, event.description, ctx.channel).then((result2) => {
|
|
622
|
+
if (result2) ctx.state.taskThreadMap.set(event.taskId, result2.threadId);
|
|
623
|
+
}).catch(
|
|
624
|
+
(err) => console.warn(
|
|
625
|
+
`[OutputHandler] Failed to auto-spawn thread for task ${event.taskId}: ${err.message}`
|
|
626
|
+
)
|
|
627
|
+
);
|
|
628
|
+
};
|
|
629
|
+
var taskProgress = (event, ctx) => {
|
|
630
|
+
if (event.summary) {
|
|
631
|
+
queueDigest(ctx.sessionId, {
|
|
632
|
+
kind: "subagent",
|
|
633
|
+
text: `\u5B50\u4EE3\u7406\u8FDB\u5C55\uFF1A${truncate(event.summary, 100)}`
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
const threadId = ctx.state.taskThreadMap.get(event.taskId);
|
|
637
|
+
if (!threadId || !event.summary) return;
|
|
638
|
+
const thread = ctx.channel.threads?.cache.get(threadId);
|
|
639
|
+
if (!thread) return;
|
|
640
|
+
thread.send(`\u{1F4DD} ${truncate(event.summary, 1900)}`).catch(
|
|
641
|
+
(e) => console.warn(
|
|
642
|
+
`[OutputHandler] Failed to send progress to thread: ${e.message}`
|
|
643
|
+
)
|
|
644
|
+
);
|
|
645
|
+
};
|
|
646
|
+
var taskDone = async (event, ctx) => {
|
|
647
|
+
await ctx.streamer.finalize();
|
|
648
|
+
queueDigest(ctx.sessionId, {
|
|
649
|
+
kind: "subagent",
|
|
650
|
+
text: `\u5B50\u4EE3\u7406${event.status === "completed" ? "\u5B8C\u6210" : "\u7ED3\u675F"}\uFF1A${truncate(event.summary || "No summary.", 100)}`
|
|
651
|
+
});
|
|
652
|
+
const threadId = ctx.state.taskThreadMap.get(event.taskId);
|
|
653
|
+
if (!threadId) return;
|
|
654
|
+
const thread = ctx.channel.threads?.cache.get(threadId);
|
|
655
|
+
if (thread) {
|
|
656
|
+
const emoji = event.status === "completed" ? "\u2705" : "\u274C";
|
|
657
|
+
thread.send(`${emoji} \u5B50\u4EFB\u52A1${event.status === "completed" ? "\u5B8C\u6210" : "\u7ED3\u675F"}\uFF1A${truncate(event.summary || "", 1900)}`).catch(
|
|
658
|
+
(e) => console.warn(
|
|
659
|
+
`[OutputHandler] Failed to send task done to thread: ${e.message}`
|
|
660
|
+
)
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
ctx.state.taskThreadMap.delete(event.taskId);
|
|
664
|
+
};
|
|
665
|
+
var webSearch = (event, ctx) => {
|
|
666
|
+
if (ctx.verbose) {
|
|
667
|
+
queueDigest(ctx.sessionId, { kind: "search", text: `\u68C0\u7D22\uFF1A${truncate(event.query, 80)}` });
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
var toolStart = async (event, ctx) => {
|
|
671
|
+
await ctx.streamer.finalize();
|
|
672
|
+
queueDigest(ctx.sessionId, { kind: "tool", text: `\u5DE5\u5177\uFF1A${event.toolName}` });
|
|
673
|
+
ctx.state.lastToolName = event.toolName;
|
|
674
|
+
};
|
|
675
|
+
var toolResult = async (event, ctx) => {
|
|
676
|
+
await ctx.streamer.finalize();
|
|
677
|
+
if (ctx.verbose && event.result) {
|
|
678
|
+
queueDigest(ctx.sessionId, {
|
|
679
|
+
kind: "tool",
|
|
680
|
+
text: `\u5DE5\u5177\u7ED3\u679C\uFF1A${truncate(ctx.state.lastToolName || event.toolName || "tool", 60)}`
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
};
|
|
684
|
+
var imageFile = (event, ctx) => {
|
|
685
|
+
if (existsSync2(event.filePath)) {
|
|
686
|
+
ctx.state.pendingAttachments.push(event.filePath);
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
var commandExecution = (event, ctx) => {
|
|
690
|
+
ctx.state.commandCount++;
|
|
691
|
+
if (ctx.state.recentCommands.length < 8) ctx.state.recentCommands.push(event.command);
|
|
692
|
+
if (!shouldSuppressCommandExecution(event.command)) {
|
|
693
|
+
queueDigest(ctx.sessionId, {
|
|
694
|
+
kind: "command",
|
|
695
|
+
text: `\u547D\u4EE4\uFF1A${truncate(event.command, 80)}${event.exitCode !== null ? `\uFF08\u9000\u51FA\u7801 ${event.exitCode}\uFF09` : ""}`
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
var fileChange = (event, ctx) => {
|
|
700
|
+
ctx.state.fileChangeCount += event.changes.length;
|
|
701
|
+
for (const change of event.changes) {
|
|
702
|
+
if (!change.filePath) continue;
|
|
703
|
+
if (ctx.state.changedFiles.includes(change.filePath)) continue;
|
|
704
|
+
if (ctx.state.changedFiles.length >= 12) break;
|
|
705
|
+
ctx.state.changedFiles.push(change.filePath);
|
|
706
|
+
}
|
|
707
|
+
queueDigest(ctx.sessionId, {
|
|
708
|
+
kind: "file",
|
|
709
|
+
text: `\u6587\u4EF6\u53D8\u66F4\uFF1A${event.changes.length} \u4E2A\uFF08\u6700\u8FD1\uFF1A${truncate(ctx.state.changedFiles.slice(-3).join(", "), 120)}\uFF09`
|
|
710
|
+
});
|
|
711
|
+
};
|
|
712
|
+
var reasoning = (event, ctx) => {
|
|
713
|
+
if (ctx.verbose) {
|
|
714
|
+
queueDigest(ctx.sessionId, { kind: "reasoning", text: `\u63A8\u7406\uFF1A${truncate(event.text, 100)}` });
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
var todoList = async (event, ctx) => {
|
|
718
|
+
const completed = event.items.filter((item) => item.completed).length;
|
|
719
|
+
queueDigest(ctx.sessionId, {
|
|
720
|
+
kind: "todo",
|
|
721
|
+
text: `\u5F85\u529E\u66F4\u65B0\uFF1A${completed}/${event.items.length} \u5DF2\u5B8C\u6210`
|
|
722
|
+
});
|
|
723
|
+
await updateSessionState(ctx.sessionId, {
|
|
724
|
+
type: "todo_updated",
|
|
725
|
+
sessionId: ctx.sessionId,
|
|
726
|
+
source: providerSource(ctx.sessionId),
|
|
727
|
+
confidence: "high",
|
|
728
|
+
timestamp: Date.now(),
|
|
729
|
+
metadata: { items: event.items }
|
|
730
|
+
});
|
|
731
|
+
};
|
|
732
|
+
var MODE_LABELS = {
|
|
733
|
+
auto: "\u81EA\u52A8",
|
|
734
|
+
plan: "\u8BA1\u5212",
|
|
735
|
+
normal: "\u666E\u901A",
|
|
736
|
+
monitor: "\u76D1\u63A7"
|
|
737
|
+
};
|
|
738
|
+
var TERMINAL_REASON_LABELS = {
|
|
739
|
+
completed: "\u5DF2\u5B8C\u6210",
|
|
740
|
+
max_turns: "\u5DF2\u8FBE\u6700\u5927\u8F6E\u6B21",
|
|
741
|
+
aborted: "\u5DF2\u4E2D\u6B62",
|
|
742
|
+
rate_limited: "\u89E6\u53D1\u901F\u7387\u9650\u5236",
|
|
743
|
+
context_too_long: "\u4E0A\u4E0B\u6587\u8D85\u957F",
|
|
744
|
+
model_error: "\u6A21\u578B\u9519\u8BEF",
|
|
745
|
+
tool_deferred: "\u5DE5\u5177\u5BA1\u6279\u5EF6\u540E",
|
|
746
|
+
hook_stopped: "\u88AB\u94A9\u5B50\u963B\u65AD",
|
|
747
|
+
image_error: "\u56FE\u50CF\u9519\u8BEF",
|
|
748
|
+
error: "\u5F02\u5E38"
|
|
749
|
+
};
|
|
750
|
+
function formatTerminalReason(reason, fallbackSuccess) {
|
|
751
|
+
if (!reason) return fallbackSuccess ? "\u5DF2\u5B8C\u6210" : "\u5F02\u5E38";
|
|
752
|
+
return TERMINAL_REASON_LABELS[reason] ?? reason;
|
|
753
|
+
}
|
|
754
|
+
var result = async (event, ctx) => {
|
|
755
|
+
ctx.state.success = event.success;
|
|
756
|
+
const lastText = ctx.streamer.getText();
|
|
757
|
+
const cost = event.costUsd.toFixed(4);
|
|
758
|
+
const duration = event.durationMs ? `${(event.durationMs / 1e3).toFixed(1)}s` : "unknown";
|
|
759
|
+
const turns = event.numTurns || 0;
|
|
760
|
+
const modeLabel = MODE_LABELS[ctx.mode] || "\u81EA\u52A8";
|
|
761
|
+
const reasonLabel = formatTerminalReason(event.terminalReason, event.success);
|
|
762
|
+
const showReason = event.terminalReason && event.terminalReason !== "completed";
|
|
763
|
+
const reasonSuffix = showReason ? ` | ${reasonLabel}` : "";
|
|
764
|
+
const statusLine = event.success ? `-# $${cost} | ${duration} | ${turns} turns | ${modeLabel}${reasonSuffix}` : `-# ${reasonLabel} | $${cost} | ${duration} | ${turns} turns`;
|
|
765
|
+
ctx.streamer.append(`
|
|
766
|
+
${statusLine}`, { persist: false });
|
|
767
|
+
if (!event.success && event.errors.length) {
|
|
768
|
+
ctx.streamer.append(`
|
|
769
|
+
\`\`\`
|
|
770
|
+
${event.errors.join("\n")}
|
|
771
|
+
\`\`\``, { persist: false });
|
|
772
|
+
}
|
|
773
|
+
await ctx.streamer.finalize();
|
|
774
|
+
const session = getSessionView(ctx.sessionId);
|
|
775
|
+
if (!session) {
|
|
776
|
+
ctx.state.pendingAttachments = [];
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
if (ctx.mode === "monitor") {
|
|
780
|
+
await flushDigest(ctx.sessionId);
|
|
781
|
+
await updateSessionState(ctx.sessionId, {
|
|
782
|
+
type: "work_started",
|
|
783
|
+
sessionId: ctx.sessionId,
|
|
784
|
+
source: providerSource(ctx.sessionId),
|
|
785
|
+
confidence: "high",
|
|
786
|
+
timestamp: Date.now(),
|
|
787
|
+
metadata: {
|
|
788
|
+
phase: "\u7B49\u5F85\u76D1\u7763\u5224\u65AD",
|
|
789
|
+
summary: event.success ? "\u672C\u8F6E\u6267\u884C\u7ED3\u675F\uFF0C\u7B49\u5F85\u76D1\u7763\u5224\u65AD" : "\u672C\u8F6E\u6267\u884C\u5931\u8D25\uFF0C\u7B49\u5F85\u76D1\u7763\u5224\u65AD"
|
|
790
|
+
}
|
|
791
|
+
});
|
|
792
|
+
} else {
|
|
793
|
+
ctx.state.deferredResult = {
|
|
794
|
+
event,
|
|
795
|
+
text: lastText,
|
|
796
|
+
attachments: [...ctx.state.pendingAttachments]
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
ctx.state.pendingAttachments = [];
|
|
800
|
+
};
|
|
801
|
+
var permissionDenied = async (event, ctx) => {
|
|
802
|
+
queueDigest(ctx.sessionId, {
|
|
803
|
+
kind: "denied",
|
|
804
|
+
text: `\u26D4 \u6743\u9650\u62D2\u7EDD\uFF1A${truncate(event.toolName, 40)} \u2014 ${truncate(event.reason, 80)}`
|
|
805
|
+
});
|
|
806
|
+
await updateSessionState(ctx.sessionId, {
|
|
807
|
+
type: "permission_denied",
|
|
808
|
+
sessionId: ctx.sessionId,
|
|
809
|
+
source: providerSource(ctx.sessionId),
|
|
810
|
+
confidence: "high",
|
|
811
|
+
timestamp: Date.now(),
|
|
812
|
+
metadata: {
|
|
813
|
+
toolName: event.toolName,
|
|
814
|
+
reason: event.reason,
|
|
815
|
+
source: event.source
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
};
|
|
819
|
+
var errorEvent = async (event, ctx) => {
|
|
820
|
+
console.warn(`[OutputHandler] Session ${ctx.sessionId}: error event \u2014 ${event.message}`);
|
|
821
|
+
ctx.state.hadError = true;
|
|
822
|
+
await ctx.streamer.finalize();
|
|
823
|
+
queueDigest(ctx.sessionId, { kind: "error", text: `\u9519\u8BEF\uFF1A${truncate(event.message, 120)}` });
|
|
824
|
+
if (ctx.mode === "monitor") return;
|
|
825
|
+
const session = getSessionView(ctx.sessionId);
|
|
826
|
+
if (!session) return;
|
|
827
|
+
await flushDigest(ctx.sessionId);
|
|
828
|
+
await updateSessionState(ctx.sessionId, {
|
|
829
|
+
type: "errored",
|
|
830
|
+
sessionId: ctx.sessionId,
|
|
831
|
+
source: providerSource(ctx.sessionId),
|
|
832
|
+
confidence: "high",
|
|
833
|
+
timestamp: Date.now(),
|
|
834
|
+
metadata: { errorMessage: event.message }
|
|
835
|
+
});
|
|
836
|
+
};
|
|
837
|
+
var sessionInit = () => {
|
|
838
|
+
};
|
|
839
|
+
var EVENT_HANDLERS = {
|
|
840
|
+
text_delta: textDelta,
|
|
841
|
+
ask_user: askUser,
|
|
842
|
+
task,
|
|
843
|
+
task_started: taskStarted,
|
|
844
|
+
task_progress: taskProgress,
|
|
845
|
+
task_done: taskDone,
|
|
846
|
+
web_search: webSearch,
|
|
847
|
+
tool_start: toolStart,
|
|
848
|
+
tool_result: toolResult,
|
|
849
|
+
image_file: imageFile,
|
|
850
|
+
command_execution: commandExecution,
|
|
851
|
+
file_change: fileChange,
|
|
852
|
+
reasoning,
|
|
853
|
+
todo_list: todoList,
|
|
854
|
+
permission_denied: permissionDenied,
|
|
855
|
+
result,
|
|
856
|
+
error: errorEvent,
|
|
857
|
+
session_init: sessionInit
|
|
858
|
+
};
|
|
859
|
+
async function dispatchEvent(event, ctx) {
|
|
860
|
+
const handler = EVENT_HANDLERS[event.type];
|
|
861
|
+
if (!handler) return;
|
|
862
|
+
await handler(event, ctx);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// ../bot/src/discord/status-card.ts
|
|
866
|
+
import {
|
|
867
|
+
EmbedBuilder as EmbedBuilder3
|
|
868
|
+
} from "discord.js";
|
|
869
|
+
var StatusCard = class {
|
|
870
|
+
messageId = null;
|
|
871
|
+
channel;
|
|
872
|
+
lastState = "idle";
|
|
873
|
+
lastData = null;
|
|
874
|
+
constructor(channel) {
|
|
875
|
+
this.channel = channel;
|
|
876
|
+
}
|
|
877
|
+
adopt(messageId) {
|
|
878
|
+
this.messageId = messageId;
|
|
879
|
+
}
|
|
880
|
+
getMessageId() {
|
|
881
|
+
return this.messageId;
|
|
882
|
+
}
|
|
883
|
+
async initialize(data = {}) {
|
|
884
|
+
const payload = {
|
|
885
|
+
turn: data.turn ?? 1,
|
|
886
|
+
updatedAt: data.updatedAt ?? Date.now(),
|
|
887
|
+
phase: data.phase,
|
|
888
|
+
remoteHumanControl: data.remoteHumanControl,
|
|
889
|
+
provider: data.provider,
|
|
890
|
+
permissionsSummary: data.permissionsSummary
|
|
891
|
+
};
|
|
892
|
+
this.lastState = "idle";
|
|
893
|
+
this.lastData = payload;
|
|
894
|
+
if (this.messageId) {
|
|
895
|
+
await this.update("idle", payload);
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
const embed = this.buildEmbed("idle", payload);
|
|
899
|
+
await this.sendNewMessage(embed);
|
|
900
|
+
}
|
|
901
|
+
async update(state, data) {
|
|
902
|
+
this.lastState = state;
|
|
903
|
+
this.lastData = { ...data };
|
|
904
|
+
const embed = this.buildEmbed(state, data);
|
|
905
|
+
if (!this.messageId) {
|
|
906
|
+
await this.sendNewMessage(embed);
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
await this.editExistingMessage(embed);
|
|
910
|
+
}
|
|
911
|
+
async recreateAtBottom() {
|
|
912
|
+
if (!this.messageId || !this.lastData) return null;
|
|
913
|
+
const oldMessageId = this.messageId;
|
|
914
|
+
const embed = this.buildEmbed(this.lastState, this.lastData);
|
|
915
|
+
const msg = await this.channel.send({ embeds: [embed] });
|
|
916
|
+
this.messageId = msg.id;
|
|
917
|
+
return { oldMessageId, newMessageId: msg.id };
|
|
918
|
+
}
|
|
919
|
+
async sendNewMessage(embed) {
|
|
920
|
+
try {
|
|
921
|
+
const msg = await this.channel.send({ embeds: [embed] });
|
|
922
|
+
this.messageId = msg.id;
|
|
923
|
+
} catch (error) {
|
|
924
|
+
console.error("\u72B6\u6001\u5361\u521B\u5EFA\u5931\u8D25:", error);
|
|
925
|
+
throw error;
|
|
350
926
|
}
|
|
351
927
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
928
|
+
async editExistingMessage(embed) {
|
|
929
|
+
if (!this.messageId) {
|
|
930
|
+
await this.sendNewMessage(embed);
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
try {
|
|
934
|
+
const msg = await this.channel.messages.edit(this.messageId, {
|
|
935
|
+
embeds: [embed],
|
|
936
|
+
components: []
|
|
937
|
+
});
|
|
938
|
+
this.messageId = msg.id;
|
|
939
|
+
} catch (error) {
|
|
940
|
+
console.warn(`\u72B6\u6001\u5361\u7F16\u8F91\u5931\u8D25 (${this.messageId}), \u521B\u5EFA\u65B0\u6D88\u606F:`, error);
|
|
941
|
+
await this.sendNewMessage(embed);
|
|
364
942
|
}
|
|
365
|
-
const fallbackPayload = { content: chunk };
|
|
366
|
-
if (options.files?.length) fallbackPayload.files = options.files;
|
|
367
|
-
return sendWithBackoff(channel, fallbackPayload);
|
|
368
943
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
944
|
+
buildEmbed(state, data) {
|
|
945
|
+
const embed = new EmbedBuilder3().setColor(STATE_COLORS[state]).setTitle(`\u{1F916} ${STATE_LABELS[state]}`).addFields(
|
|
946
|
+
{ name: "\u8F6E\u6B21", value: `#${data.turn}`, inline: true },
|
|
947
|
+
{ name: "\u66F4\u65B0", value: `<t:${Math.floor(data.updatedAt / 1e3)}:R>`, inline: true }
|
|
948
|
+
).setTimestamp();
|
|
949
|
+
if (data.provider === "codex") {
|
|
950
|
+
const managedLabel = data.remoteHumanControl ? "\u2713 \u53D7\u7BA1\u4F1A\u8BDD" : "\u25CB \u975E\u53D7\u7BA1\u4F1A\u8BDD\uFF08\u4EC5\u72B6\u6001\u76D1\u63A7\uFF09";
|
|
951
|
+
embed.addFields({ name: "\u4F1A\u8BDD\u7C7B\u578B", value: managedLabel, inline: true });
|
|
952
|
+
}
|
|
953
|
+
if (data.phase) {
|
|
954
|
+
const sanitizedPhase = this.sanitizePhase(data.phase);
|
|
955
|
+
if (sanitizedPhase) {
|
|
956
|
+
embed.addFields({ name: "\u9636\u6BB5", value: sanitizedPhase, inline: true });
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
if (data.verbose !== void 0) {
|
|
960
|
+
embed.addFields({ name: "\u8F93\u51FA", value: data.verbose ? "\u{1F50A} \u8BE6\u7EC6" : "\u{1F507} \u7CBE\u7B80", inline: true });
|
|
961
|
+
}
|
|
962
|
+
if (data.monitorGoal) {
|
|
963
|
+
embed.addFields({ name: "\u76D1\u63A7\u76EE\u6807", value: truncate(data.monitorGoal, 150) });
|
|
964
|
+
}
|
|
965
|
+
if (data.monitorIteration !== void 0 && data.maxMonitorIterations !== void 0) {
|
|
966
|
+
embed.addFields({ name: "\u8FED\u4EE3", value: `${data.monitorIteration}/${data.maxMonitorIterations}`, inline: true });
|
|
967
|
+
}
|
|
968
|
+
if (data.permissionsSummary) {
|
|
969
|
+
embed.addFields({ name: "\u6743\u9650", value: data.permissionsSummary, inline: false });
|
|
970
|
+
}
|
|
971
|
+
if (data.todoList && data.todoList.length > 0) {
|
|
972
|
+
const rendered = this.renderTodoList(data.todoList);
|
|
973
|
+
if (rendered) {
|
|
974
|
+
const completed = data.todoList.filter((t) => t.completed).length;
|
|
975
|
+
embed.addFields({
|
|
976
|
+
name: `\u5F85\u529E\uFF08${completed}/${data.todoList.length}\uFF09`,
|
|
977
|
+
value: rendered,
|
|
978
|
+
inline: false
|
|
979
|
+
});
|
|
381
980
|
}
|
|
382
981
|
}
|
|
982
|
+
if (data.batchApprovalMode) {
|
|
983
|
+
const pending = data.pendingApprovals ?? [];
|
|
984
|
+
const queueLine = pending.length === 0 ? "\u961F\u5217\u4E3A\u7A7A" : pending.slice(0, 5).map((a) => `\u2022 ${truncate(a.toolName, 32)} \u2014 ${truncate(a.detail, 80)}`).join("\n");
|
|
985
|
+
embed.addFields({
|
|
986
|
+
name: `\u6279\u91CF\u5BA1\u6279\uFF08${pending.length} \u5F85\u6279\uFF09`,
|
|
987
|
+
value: queueLine + (pending.length > 5 ? `
|
|
988
|
+
\u2026\u8FD8\u6709 ${pending.length - 5} \u6761` : ""),
|
|
989
|
+
inline: false
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
if (data.recentPermissionDenials && data.recentPermissionDenials.length > 0) {
|
|
993
|
+
const lines = data.recentPermissionDenials.slice(0, 3).map((d) => `\u26D4 ${truncate(d.toolName, 28)} \u2014 ${truncate(d.reason, 80)}`).join("\n");
|
|
994
|
+
embed.addFields({ name: "\u6700\u8FD1\u62D2\u7EDD", value: lines, inline: false });
|
|
995
|
+
}
|
|
996
|
+
return embed;
|
|
383
997
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
const
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
998
|
+
renderTodoList(items) {
|
|
999
|
+
const TOTAL_BUDGET = 950;
|
|
1000
|
+
const MAX_LINES = 8;
|
|
1001
|
+
const MAX_CHAR_PER_LINE = 180;
|
|
1002
|
+
const lines = [];
|
|
1003
|
+
let used = 0;
|
|
1004
|
+
let renderedCount = 0;
|
|
1005
|
+
for (const item of items.slice(0, MAX_LINES)) {
|
|
1006
|
+
const mark = item.completed ? "\u2611" : "\u2610";
|
|
1007
|
+
const line = `${mark} ${truncate(item.text, MAX_CHAR_PER_LINE)}`;
|
|
1008
|
+
const cost = (lines.length > 0 ? 1 : 0) + line.length;
|
|
1009
|
+
if (used + cost > TOTAL_BUDGET) break;
|
|
1010
|
+
lines.push(line);
|
|
1011
|
+
used += cost;
|
|
1012
|
+
renderedCount++;
|
|
1013
|
+
}
|
|
1014
|
+
const hidden = items.length - renderedCount;
|
|
1015
|
+
if (hidden > 0) {
|
|
1016
|
+
const more = `\u2026 \u8FD8\u6709 ${hidden} \u9879`;
|
|
1017
|
+
const cost = (lines.length > 0 ? 1 : 0) + more.length;
|
|
1018
|
+
if (used + cost <= TOTAL_BUDGET) lines.push(more);
|
|
1019
|
+
}
|
|
1020
|
+
return lines.join("\n");
|
|
393
1021
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
1022
|
+
/**
|
|
1023
|
+
* Sanitize phase text for status card display.
|
|
1024
|
+
* Returns cleaned text or empty string if unsuitable.
|
|
1025
|
+
*/
|
|
1026
|
+
sanitizePhase(description) {
|
|
1027
|
+
let normalized = description.trim();
|
|
1028
|
+
if (!normalized) return "";
|
|
1029
|
+
if (normalized.includes("```") || /diff --git/.test(normalized) || this.isLikelyFileList(normalized)) {
|
|
1030
|
+
console.warn(`[StatusCard] Phase contains unsuitable content, stripping: ${normalized.slice(0, 60)}...`);
|
|
1031
|
+
return "";
|
|
1032
|
+
}
|
|
1033
|
+
if (normalized.length > 200) {
|
|
1034
|
+
normalized = normalized.slice(0, 197) + "...";
|
|
399
1035
|
}
|
|
1036
|
+
return normalized;
|
|
400
1037
|
}
|
|
401
|
-
|
|
402
|
-
|
|
1038
|
+
validate(description) {
|
|
1039
|
+
if (!description) return;
|
|
1040
|
+
const normalized = description.trim();
|
|
1041
|
+
if (!normalized) return;
|
|
1042
|
+
if (normalized.length > 200) {
|
|
1043
|
+
throw new Error("\u72B6\u6001\u5361\u63CF\u8FF0\u8FC7\u957F\uFF0C\u5E94\u79FB\u81F3\u6458\u8981\u5361\u6216\u7ED3\u679C\u6D88\u606F");
|
|
1044
|
+
}
|
|
1045
|
+
if (normalized.includes("```")) {
|
|
1046
|
+
throw new Error("\u72B6\u6001\u5361\u4E0D\u5E94\u5305\u542B\u4EE3\u7801\u5757");
|
|
1047
|
+
}
|
|
1048
|
+
if (/diff --git/.test(normalized)) {
|
|
1049
|
+
throw new Error("\u72B6\u6001\u5361\u4E0D\u5E94\u5305\u542B diff");
|
|
1050
|
+
}
|
|
1051
|
+
if (this.isLikelyFileList(normalized)) {
|
|
1052
|
+
throw new Error("\u72B6\u6001\u5361\u4E0D\u5E94\u5305\u542B\u6587\u4EF6\u5217\u8868");
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
isLikelyFileList(text) {
|
|
1056
|
+
const lines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
1057
|
+
if (lines.length < 2) return false;
|
|
1058
|
+
return lines.every((line) => /^[-+*]\s+[\w./\\-]+$/.test(line));
|
|
1059
|
+
}
|
|
1060
|
+
};
|
|
1061
|
+
|
|
1062
|
+
// ../bot/src/discord/summary-handler.ts
|
|
1063
|
+
import "discord.js";
|
|
403
1064
|
|
|
404
1065
|
// ../bot/src/discord/digest-delivery.ts
|
|
405
|
-
import { EmbedBuilder as
|
|
1066
|
+
import { EmbedBuilder as EmbedBuilder4 } from "discord.js";
|
|
406
1067
|
var DigestDelivery = class {
|
|
407
1068
|
channel;
|
|
408
1069
|
messageIds = [];
|
|
@@ -461,7 +1122,7 @@ var DigestDelivery = class {
|
|
|
461
1122
|
return { oldMessageIds, newMessageIds };
|
|
462
1123
|
}
|
|
463
1124
|
buildDigestEmbed(content, index, total) {
|
|
464
|
-
const embed = new
|
|
1125
|
+
const embed = new EmbedBuilder4().setColor(3447003).setDescription(content).setTimestamp();
|
|
465
1126
|
if (index === 0) embed.setTitle("\u{1F4CC} \u6700\u8FD1\u6458\u8981");
|
|
466
1127
|
if (total > 1) {
|
|
467
1128
|
embed.setFooter({ text: `\u7B2C ${index + 1}/${total} \u90E8\u5206` });
|
|
@@ -543,10 +1204,10 @@ var SummaryHandler = class {
|
|
|
543
1204
|
|
|
544
1205
|
// ../bot/src/discord/interaction-card.ts
|
|
545
1206
|
import {
|
|
546
|
-
EmbedBuilder as
|
|
547
|
-
ActionRowBuilder,
|
|
548
|
-
ButtonBuilder,
|
|
549
|
-
ButtonStyle
|
|
1207
|
+
EmbedBuilder as EmbedBuilder5,
|
|
1208
|
+
ActionRowBuilder as ActionRowBuilder2,
|
|
1209
|
+
ButtonBuilder as ButtonBuilder2,
|
|
1210
|
+
ButtonStyle as ButtonStyle2
|
|
550
1211
|
} from "discord.js";
|
|
551
1212
|
var InteractionCard = class {
|
|
552
1213
|
channel;
|
|
@@ -556,16 +1217,16 @@ var InteractionCard = class {
|
|
|
556
1217
|
}
|
|
557
1218
|
async show(sessionId, turn, detail, options = {}) {
|
|
558
1219
|
const customIdBase = `awaiting_human:${sessionId}:${turn}`;
|
|
559
|
-
const embed = new
|
|
1220
|
+
const embed = new EmbedBuilder5().setTitle("\u23F8\uFE0F \u7B49\u5F85\u4EBA\u5DE5\u5904\u7406").setDescription(detail).setColor(16755200).setTimestamp();
|
|
560
1221
|
embed.addFields(
|
|
561
1222
|
{ name: "\u8F6E\u6B21", value: `#${turn}`, inline: true },
|
|
562
1223
|
{ name: "\u7B49\u5F85\u81EA", value: `<t:${Math.floor(Date.now() / 1e3)}:R>`, inline: true }
|
|
563
1224
|
);
|
|
564
1225
|
let components = [];
|
|
565
1226
|
if (options.remoteHumanControl !== false) {
|
|
566
|
-
const row = new
|
|
567
|
-
new
|
|
568
|
-
new
|
|
1227
|
+
const row = new ActionRowBuilder2().addComponents(
|
|
1228
|
+
new ButtonBuilder2().setCustomId(`${customIdBase}:approve`).setLabel("\u5141\u8BB8\u7EE7\u7EED").setStyle(ButtonStyle2.Success),
|
|
1229
|
+
new ButtonBuilder2().setCustomId(`${customIdBase}:deny`).setLabel("\u62D2\u7EDD").setStyle(ButtonStyle2.Danger)
|
|
569
1230
|
);
|
|
570
1231
|
components = [row];
|
|
571
1232
|
} else {
|
|
@@ -689,97 +1350,6 @@ var SessionPanelComponent = class {
|
|
|
689
1350
|
}
|
|
690
1351
|
};
|
|
691
1352
|
|
|
692
|
-
// ../engine/src/session-context.ts
|
|
693
|
-
var EMPTY_PROJECTION = Object.freeze({
|
|
694
|
-
turn: 0,
|
|
695
|
-
humanResolved: false,
|
|
696
|
-
updatedAt: 0
|
|
697
|
-
});
|
|
698
|
-
function safeGetSessionController(sessionId) {
|
|
699
|
-
try {
|
|
700
|
-
const fn = getSessionController;
|
|
701
|
-
return typeof fn === "function" ? fn(sessionId) : void 0;
|
|
702
|
-
} catch {
|
|
703
|
-
return void 0;
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
function safeDebouncedSaveSession() {
|
|
707
|
-
try {
|
|
708
|
-
const fn = debouncedSaveSession;
|
|
709
|
-
if (typeof fn === "function") fn();
|
|
710
|
-
} catch {
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
function safeGetProjection(sessionId) {
|
|
714
|
-
try {
|
|
715
|
-
const sm = stateMachine;
|
|
716
|
-
if (sm && typeof sm.getSnapshot === "function") {
|
|
717
|
-
return sm.getSnapshot(sessionId);
|
|
718
|
-
}
|
|
719
|
-
} catch {
|
|
720
|
-
}
|
|
721
|
-
return EMPTY_PROJECTION;
|
|
722
|
-
}
|
|
723
|
-
var SessionSupervisor = class {
|
|
724
|
-
contexts = /* @__PURE__ */ new Map();
|
|
725
|
-
get(sessionId) {
|
|
726
|
-
const live = getSession(sessionId);
|
|
727
|
-
const cached = this.contexts.get(sessionId);
|
|
728
|
-
if (!live) {
|
|
729
|
-
if (cached) this.contexts.delete(sessionId);
|
|
730
|
-
return void 0;
|
|
731
|
-
}
|
|
732
|
-
if (cached) {
|
|
733
|
-
this.syncContext(cached);
|
|
734
|
-
return cached;
|
|
735
|
-
}
|
|
736
|
-
const ctx = this.build(live);
|
|
737
|
-
this.contexts.set(sessionId, ctx);
|
|
738
|
-
return ctx;
|
|
739
|
-
}
|
|
740
|
-
/** 枚举现有 context。用于 supervisor 层面的扫描(健康检查、空闲回收)。 */
|
|
741
|
-
all() {
|
|
742
|
-
return Array.from(this.contexts.values());
|
|
743
|
-
}
|
|
744
|
-
/** 释放某个 session 的 context(endSession 调用时触发)。 */
|
|
745
|
-
release(sessionId) {
|
|
746
|
-
this.contexts.delete(sessionId);
|
|
747
|
-
}
|
|
748
|
-
releaseAll() {
|
|
749
|
-
this.contexts.clear();
|
|
750
|
-
}
|
|
751
|
-
build(session) {
|
|
752
|
-
const ctx = {
|
|
753
|
-
sessionId: session.id,
|
|
754
|
-
session,
|
|
755
|
-
controller: safeGetSessionController(session.id),
|
|
756
|
-
projection: safeGetProjection(session.id),
|
|
757
|
-
save: () => safeDebouncedSaveSession(),
|
|
758
|
-
refresh: () => this.syncContext(ctx)
|
|
759
|
-
};
|
|
760
|
-
return ctx;
|
|
761
|
-
}
|
|
762
|
-
syncContext(ctx) {
|
|
763
|
-
const live = getSession(ctx.sessionId);
|
|
764
|
-
if (live) {
|
|
765
|
-
ctx.session = live;
|
|
766
|
-
}
|
|
767
|
-
ctx.controller = safeGetSessionController(
|
|
768
|
-
ctx.sessionId
|
|
769
|
-
);
|
|
770
|
-
ctx.projection = safeGetProjection(
|
|
771
|
-
ctx.sessionId
|
|
772
|
-
);
|
|
773
|
-
}
|
|
774
|
-
};
|
|
775
|
-
var sessionSupervisor = new SessionSupervisor();
|
|
776
|
-
function getSessionContext(sessionId) {
|
|
777
|
-
return sessionSupervisor.get(sessionId);
|
|
778
|
-
}
|
|
779
|
-
function getSessionView(sessionId) {
|
|
780
|
-
return sessionSupervisor.get(sessionId)?.session;
|
|
781
|
-
}
|
|
782
|
-
|
|
783
1353
|
// ../bot/src/monitoring/performance-tracker.ts
|
|
784
1354
|
import { performance } from "perf_hooks";
|
|
785
1355
|
import { memoryUsage, cpuUsage } from "process";
|
|
@@ -1018,26 +1588,6 @@ async function relocateSessionPanelToBottom(sessionId, channel, initialize) {
|
|
|
1018
1588
|
}
|
|
1019
1589
|
}
|
|
1020
1590
|
|
|
1021
|
-
// ../engine/src/output/answer-store.ts
|
|
1022
|
-
var pendingAnswersStore = /* @__PURE__ */ new Map();
|
|
1023
|
-
var questionCountStore = /* @__PURE__ */ new Map();
|
|
1024
|
-
function setPendingAnswer(sessionId, questionIndex, answer) {
|
|
1025
|
-
if (!pendingAnswersStore.has(sessionId)) {
|
|
1026
|
-
pendingAnswersStore.set(sessionId, /* @__PURE__ */ new Map());
|
|
1027
|
-
}
|
|
1028
|
-
pendingAnswersStore.get(sessionId).set(questionIndex, answer);
|
|
1029
|
-
}
|
|
1030
|
-
function getPendingAnswers(sessionId) {
|
|
1031
|
-
return pendingAnswersStore.get(sessionId);
|
|
1032
|
-
}
|
|
1033
|
-
function clearPendingAnswers(sessionId) {
|
|
1034
|
-
pendingAnswersStore.delete(sessionId);
|
|
1035
|
-
questionCountStore.delete(sessionId);
|
|
1036
|
-
}
|
|
1037
|
-
function getQuestionCount(sessionId) {
|
|
1038
|
-
return questionCountStore.get(sessionId) || 0;
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
1591
|
// ../bot/src/bot-services-helpers.ts
|
|
1042
1592
|
var unmanagedCodexHintedSessions = /* @__PURE__ */ new Set();
|
|
1043
1593
|
function clearCodexHint(sessionId) {
|
|
@@ -1293,7 +1843,8 @@ async function updateSessionState(sessionId, event, options = {}) {
|
|
|
1293
1843
|
const projection = stateMachine.applyPlatformEvent(platformEvent);
|
|
1294
1844
|
cacheProjection(sessionId, projection);
|
|
1295
1845
|
const stateChanged = projection.state !== previousProjection.state || projection.turn !== previousProjection.turn || projection.phase !== previousProjection.phase;
|
|
1296
|
-
|
|
1846
|
+
const contextChanged = projection.todoList !== previousProjection.todoList || projection.recentPermissionDenials !== previousProjection.recentPermissionDenials || projection.batchApprovalMode !== previousProjection.batchApprovalMode || projection.pendingApprovals !== previousProjection.pendingApprovals;
|
|
1847
|
+
if (stateChanged || contextChanged) {
|
|
1297
1848
|
await scheduleProjectionRender(sessionId, projection, updateKey);
|
|
1298
1849
|
} else {
|
|
1299
1850
|
performanceTracker.endStateUpdate(updateKey, { skipped: true });
|
|
@@ -1321,8 +1872,11 @@ async function handleResultEvent(sessionId, event, textContent, attachments = []
|
|
|
1321
1872
|
} else if (!event.success) {
|
|
1322
1873
|
const session = getSessionView(sessionId);
|
|
1323
1874
|
const failureText = textContent.trim() || event.errors.join("\n").trim() || "\u4EFB\u52A1\u5931\u8D25";
|
|
1875
|
+
const reasonLabel = event.terminalReason && event.terminalReason !== "completed" ? formatTerminalReason(event.terminalReason, false) : null;
|
|
1876
|
+
const withReason = reasonLabel ? `\uFF08${reasonLabel}\uFF09
|
|
1877
|
+
${failureText}` : failureText;
|
|
1324
1878
|
await panel.summaryHandler.sendTurnFailure(
|
|
1325
|
-
|
|
1879
|
+
withReason,
|
|
1326
1880
|
projection.turn,
|
|
1327
1881
|
session?.lastInboundMessageId,
|
|
1328
1882
|
attachments
|
|
@@ -1443,6 +1997,13 @@ export {
|
|
|
1443
1997
|
sendTyping,
|
|
1444
1998
|
sendAckReaction,
|
|
1445
1999
|
deliver,
|
|
2000
|
+
sendSystemNotice,
|
|
2001
|
+
spawnSubagent,
|
|
2002
|
+
getSubagents,
|
|
2003
|
+
runSubagentWatchdog,
|
|
2004
|
+
resolveEffectiveClaudePermissionMode,
|
|
2005
|
+
makeModeButtons,
|
|
2006
|
+
dispatchEvent,
|
|
1446
2007
|
cleanupOldMessages,
|
|
1447
2008
|
notifyUnmanagedCodexHint,
|
|
1448
2009
|
stopPerformanceMonitoring,
|