chatccc 0.2.267 → 0.2.269
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/README.md +11 -5
- package/dist/src/agent-delegate-task-rpc.js +40 -27
- package/dist/src/agent-delegate-task.js +8 -3
- package/dist/src/agent-set-cwd-rpc.js +73 -0
- package/dist/src/agent-team/application/main-agent-service.js +11 -2
- package/dist/src/agent-team/application/task-execution-service.js +34 -5
- package/dist/src/agent-team/domain/task-run.js +4 -0
- package/dist/src/agent-team/http/board-routes.js +6 -0
- package/dist/src/agent-team/infrastructure/task-execution-runtime.js +4 -0
- package/dist/src/agent-team/web/agent-team-page.js +243 -229
- package/dist/src/cards.js +3 -0
- package/dist/src/engines/engine-manager.js +7 -0
- package/dist/src/execution-transcript.js +110 -0
- package/dist/src/im-skills.js +2 -2
- package/dist/src/index.js +9 -2
- package/dist/src/orchestrator.js +194 -6
- package/dist/src/safe-maintenance.js +271 -0
- package/dist/src/session-chat-binding.js +12 -0
- package/dist/src/session.js +16 -2
- package/dist/src/stream-state.js +1 -0
- package/dist/src/web-ui.js +5 -0
- package/im-skills/feishu-skill/skill.md +18 -4
- package/package.json +76 -76
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { USER_DATA_DIR } from "./config.js";
|
|
5
|
+
export const SAFE_MAINTENANCE_FILE = join(USER_DATA_DIR, "state", "safe-maintenance.json");
|
|
6
|
+
export const SAFE_MAINTENANCE_STABLE_IDLE_MS = 1_000;
|
|
7
|
+
const EMPTY_SNAPSHOT = {
|
|
8
|
+
activeSessionIds: [],
|
|
9
|
+
queuedSessionIds: [],
|
|
10
|
+
activeEngineIds: [],
|
|
11
|
+
activeWorkLabels: [],
|
|
12
|
+
};
|
|
13
|
+
export class SafeMaintenanceCoordinator {
|
|
14
|
+
filePath;
|
|
15
|
+
now;
|
|
16
|
+
idFactory;
|
|
17
|
+
stableIdleMs;
|
|
18
|
+
pollMs;
|
|
19
|
+
autoPoll;
|
|
20
|
+
trackedWork = new Map();
|
|
21
|
+
runtime = null;
|
|
22
|
+
job;
|
|
23
|
+
snapshot = EMPTY_SNAPSHOT;
|
|
24
|
+
stableSince = null;
|
|
25
|
+
timer = null;
|
|
26
|
+
tickRunning = false;
|
|
27
|
+
constructor(options = {}) {
|
|
28
|
+
this.filePath = options.filePath ?? SAFE_MAINTENANCE_FILE;
|
|
29
|
+
this.now = options.now ?? (() => new Date());
|
|
30
|
+
this.idFactory = options.idFactory ?? randomUUID;
|
|
31
|
+
this.stableIdleMs = options.stableIdleMs ?? SAFE_MAINTENANCE_STABLE_IDLE_MS;
|
|
32
|
+
this.pollMs = options.pollMs ?? 500;
|
|
33
|
+
this.autoPoll = options.autoPoll ?? true;
|
|
34
|
+
this.job = readJob(this.filePath);
|
|
35
|
+
}
|
|
36
|
+
configure(runtime) {
|
|
37
|
+
this.runtime = runtime;
|
|
38
|
+
}
|
|
39
|
+
isAdmissionClosed() {
|
|
40
|
+
return this.job?.phase === "draining" || this.job?.phase === "executing";
|
|
41
|
+
}
|
|
42
|
+
beginTrackedWork(label) {
|
|
43
|
+
const id = this.idFactory();
|
|
44
|
+
this.trackedWork.set(id, label);
|
|
45
|
+
let released = false;
|
|
46
|
+
return () => {
|
|
47
|
+
if (released)
|
|
48
|
+
return;
|
|
49
|
+
released = true;
|
|
50
|
+
this.trackedWork.delete(id);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
async schedule(kind, requester) {
|
|
54
|
+
const now = this.now().toISOString();
|
|
55
|
+
if (this.job?.phase === "executing")
|
|
56
|
+
return structuredClone(this.job);
|
|
57
|
+
const previous = this.job;
|
|
58
|
+
if (this.job?.phase === "draining") {
|
|
59
|
+
const requesters = addRequester(this.job.requesters, requester);
|
|
60
|
+
this.job = {
|
|
61
|
+
...this.job,
|
|
62
|
+
kind: this.job.kind === "update" || kind === "update" ? "update" : "restart",
|
|
63
|
+
requesters,
|
|
64
|
+
updatedAt: now,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
this.job = {
|
|
69
|
+
schemaVersion: 1,
|
|
70
|
+
jobId: this.idFactory(),
|
|
71
|
+
kind,
|
|
72
|
+
phase: "draining",
|
|
73
|
+
requestedAt: now,
|
|
74
|
+
updatedAt: now,
|
|
75
|
+
requesters: [requester],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
this.stableSince = null;
|
|
79
|
+
try {
|
|
80
|
+
writeJob(this.filePath, this.job);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
this.job = previous;
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
this.startPolling();
|
|
87
|
+
return structuredClone(this.job);
|
|
88
|
+
}
|
|
89
|
+
async cancel(notify = true) {
|
|
90
|
+
if (this.job?.phase !== "draining")
|
|
91
|
+
return false;
|
|
92
|
+
const requesters = this.job.requesters;
|
|
93
|
+
removeJob(this.filePath);
|
|
94
|
+
this.job = null;
|
|
95
|
+
this.snapshot = EMPTY_SNAPSHOT;
|
|
96
|
+
this.stableSince = null;
|
|
97
|
+
this.stopPolling();
|
|
98
|
+
if (notify)
|
|
99
|
+
await this.notifyAll(requesters, "已取消安全维护预约,ChatCCC 恢复接受新任务。");
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
async status() {
|
|
103
|
+
if (this.runtime && this.isAdmissionClosed())
|
|
104
|
+
this.snapshot = await this.collectSnapshot();
|
|
105
|
+
return {
|
|
106
|
+
job: this.job ? structuredClone(this.job) : null,
|
|
107
|
+
snapshot: structuredClone(this.snapshot),
|
|
108
|
+
waitingCount: snapshotCount(this.snapshot),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
async tick() {
|
|
112
|
+
if (this.tickRunning || this.job?.phase !== "draining" || !this.runtime)
|
|
113
|
+
return;
|
|
114
|
+
this.tickRunning = true;
|
|
115
|
+
try {
|
|
116
|
+
this.snapshot = await this.collectSnapshot();
|
|
117
|
+
if (snapshotCount(this.snapshot) > 0) {
|
|
118
|
+
this.stableSince = null;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const nowMs = this.now().getTime();
|
|
122
|
+
if (this.stableSince === null) {
|
|
123
|
+
this.stableSince = nowMs;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (nowMs - this.stableSince < this.stableIdleMs)
|
|
127
|
+
return;
|
|
128
|
+
await this.executeCurrentJob();
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
this.tickRunning = false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async recoverAfterStartup(internalRestart) {
|
|
135
|
+
if (!this.job || !this.runtime)
|
|
136
|
+
return;
|
|
137
|
+
if (this.job.phase === "draining") {
|
|
138
|
+
this.startPolling();
|
|
139
|
+
await this.notifyAll(this.job.requesters, "ChatCCC 已恢复未完成的安全维护预约,继续等待现有任务结束。");
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (this.job.phase !== "executing")
|
|
143
|
+
return;
|
|
144
|
+
if (internalRestart) {
|
|
145
|
+
const completed = { ...this.job, phase: "completed", updatedAt: this.now().toISOString() };
|
|
146
|
+
writeJob(this.filePath, completed);
|
|
147
|
+
this.job = completed;
|
|
148
|
+
await this.notifyAll(this.job.requesters, this.job.kind === "update" ? "ChatCCC 已安全更新并重新启动。" : "ChatCCC 已安全重新启动。");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const message = "安全维护执行期间进程意外退出;为避免重启循环,未自动重试。";
|
|
152
|
+
const failed = {
|
|
153
|
+
...this.job,
|
|
154
|
+
phase: "failed",
|
|
155
|
+
updatedAt: this.now().toISOString(),
|
|
156
|
+
lastError: message,
|
|
157
|
+
};
|
|
158
|
+
writeJob(this.filePath, failed);
|
|
159
|
+
this.job = failed;
|
|
160
|
+
await this.notifyAll(this.job.requesters, message);
|
|
161
|
+
}
|
|
162
|
+
async collectSnapshot() {
|
|
163
|
+
const external = await this.runtime.getSnapshot();
|
|
164
|
+
return {
|
|
165
|
+
activeSessionIds: [...external.activeSessionIds],
|
|
166
|
+
queuedSessionIds: [...external.queuedSessionIds],
|
|
167
|
+
activeEngineIds: [...external.activeEngineIds],
|
|
168
|
+
activeWorkLabels: [...external.activeWorkLabels, ...this.trackedWork.values()],
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
async executeCurrentJob() {
|
|
172
|
+
if (!this.job || !this.runtime)
|
|
173
|
+
return;
|
|
174
|
+
const executing = { ...this.job, phase: "executing", updatedAt: this.now().toISOString() };
|
|
175
|
+
writeJob(this.filePath, executing);
|
|
176
|
+
this.job = executing;
|
|
177
|
+
this.stopPolling();
|
|
178
|
+
const label = this.job.kind === "update" ? "更新并重启" : "重启";
|
|
179
|
+
await this.notifyAll(this.job.requesters, `现有任务已全部完成,开始安全${label}。`);
|
|
180
|
+
const started = await this.runtime.execute(this.job.kind).catch(async (error) => {
|
|
181
|
+
await this.markFailed(error instanceof Error ? error.message : String(error));
|
|
182
|
+
return false;
|
|
183
|
+
});
|
|
184
|
+
if (!started && this.job?.phase === "executing") {
|
|
185
|
+
await this.markFailed(`安全${label}未能启动,当前进程将继续提供服务。`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
async markFailed(message) {
|
|
189
|
+
if (!this.job)
|
|
190
|
+
return;
|
|
191
|
+
const failed = {
|
|
192
|
+
...this.job,
|
|
193
|
+
phase: "failed",
|
|
194
|
+
updatedAt: this.now().toISOString(),
|
|
195
|
+
lastError: message,
|
|
196
|
+
};
|
|
197
|
+
writeJob(this.filePath, failed);
|
|
198
|
+
this.job = failed;
|
|
199
|
+
await this.notifyAll(this.job.requesters, message);
|
|
200
|
+
}
|
|
201
|
+
startPolling() {
|
|
202
|
+
if (!this.autoPoll || this.timer || this.job?.phase !== "draining")
|
|
203
|
+
return;
|
|
204
|
+
this.timer = setInterval(() => { void this.tick(); }, this.pollMs);
|
|
205
|
+
this.timer.unref?.();
|
|
206
|
+
}
|
|
207
|
+
stopPolling() {
|
|
208
|
+
if (!this.timer)
|
|
209
|
+
return;
|
|
210
|
+
clearInterval(this.timer);
|
|
211
|
+
this.timer = null;
|
|
212
|
+
}
|
|
213
|
+
async notifyAll(requesters, message) {
|
|
214
|
+
if (!this.runtime)
|
|
215
|
+
return;
|
|
216
|
+
await Promise.all(requesters.map((requester) => this.runtime.notify(requester, message).catch(() => { })));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function snapshotCount(snapshot) {
|
|
220
|
+
return snapshot.activeSessionIds.length
|
|
221
|
+
+ snapshot.queuedSessionIds.length
|
|
222
|
+
+ snapshot.activeEngineIds.length
|
|
223
|
+
+ snapshot.activeWorkLabels.length;
|
|
224
|
+
}
|
|
225
|
+
function addRequester(requesters, requester) {
|
|
226
|
+
if (requesters.some((item) => item.platform === requester.platform && item.chatId === requester.chatId))
|
|
227
|
+
return requesters;
|
|
228
|
+
return [...requesters, requester];
|
|
229
|
+
}
|
|
230
|
+
function readJob(filePath) {
|
|
231
|
+
if (!existsSync(filePath))
|
|
232
|
+
return null;
|
|
233
|
+
try {
|
|
234
|
+
const value = JSON.parse(readFileSync(filePath, "utf8"));
|
|
235
|
+
if (value.schemaVersion !== 1 || !value.jobId || !Array.isArray(value.requesters))
|
|
236
|
+
return null;
|
|
237
|
+
if (!["restart", "update"].includes(value.kind) || !["draining", "executing", "completed", "failed"].includes(value.phase))
|
|
238
|
+
return null;
|
|
239
|
+
return value;
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
function writeJob(filePath, job) {
|
|
246
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
247
|
+
const temporary = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
248
|
+
try {
|
|
249
|
+
writeFileSync(temporary, `${JSON.stringify(job, null, 2)}\n`, "utf8");
|
|
250
|
+
renameSync(temporary, filePath);
|
|
251
|
+
}
|
|
252
|
+
finally {
|
|
253
|
+
rmSync(temporary, { force: true });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
function removeJob(filePath) {
|
|
257
|
+
rmSync(filePath, { force: true });
|
|
258
|
+
}
|
|
259
|
+
export let safeMaintenanceCoordinator = new SafeMaintenanceCoordinator();
|
|
260
|
+
export function _setSafeMaintenanceCoordinatorForTest(coordinator) {
|
|
261
|
+
safeMaintenanceCoordinator = coordinator;
|
|
262
|
+
}
|
|
263
|
+
export function _resetSafeMaintenanceCoordinatorForTest() {
|
|
264
|
+
safeMaintenanceCoordinator = new SafeMaintenanceCoordinator();
|
|
265
|
+
}
|
|
266
|
+
export function isSafeMaintenanceAdmissionClosed() {
|
|
267
|
+
return safeMaintenanceCoordinator.isAdmissionClosed();
|
|
268
|
+
}
|
|
269
|
+
export function beginSafeMaintenanceTrackedWork(label) {
|
|
270
|
+
return safeMaintenanceCoordinator.beginTrackedWork(label);
|
|
271
|
+
}
|
|
@@ -133,6 +133,18 @@ export function setUnifiedDisplayLoopHandle(h) {
|
|
|
133
133
|
unifiedDisplayLoopHandle = h;
|
|
134
134
|
}
|
|
135
135
|
export const queuedMessages = new Map();
|
|
136
|
+
/** Includes prompt execution, finalization, auto-recovery reservations, and accepted queues. */
|
|
137
|
+
export function getSessionDrainSnapshot() {
|
|
138
|
+
const active = new Set([
|
|
139
|
+
...activePrompts.keys(),
|
|
140
|
+
...finalizingSessions,
|
|
141
|
+
...autoRecoveryReservations,
|
|
142
|
+
]);
|
|
143
|
+
return {
|
|
144
|
+
activeSessionIds: [...active].sort(),
|
|
145
|
+
queuedSessionIds: [...queuedMessages.keys()].sort(),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
136
148
|
export function enqueueMessage(sessionId, msg) {
|
|
137
149
|
if (queuedMessages.has(sessionId))
|
|
138
150
|
return false;
|
package/dist/src/session.js
CHANGED
|
@@ -6,6 +6,7 @@ import { progressView } from "./progress/view.js";
|
|
|
6
6
|
import { createAgentActivityTracker, formatAgentActivityTitle, updateAgentActivity, } from "./agent-activity.js";
|
|
7
7
|
import { simplifyToolUse, simplifyToolResult } from "./simplify.js";
|
|
8
8
|
import { logTrace } from "./trace.js";
|
|
9
|
+
import { appendExecutionTranscriptBlock, } from "./execution-transcript.js";
|
|
9
10
|
import { createClaudeAdapter } from "./adapters/claude-adapter.js";
|
|
10
11
|
import { createCursorAdapter } from "./adapters/cursor-adapter.js";
|
|
11
12
|
import { createCodexAdapter } from "./adapters/codex-adapter.js";
|
|
@@ -698,6 +699,11 @@ export function pickFinalReply(state) {
|
|
|
698
699
|
return state.finalCompleteText || state.finalText;
|
|
699
700
|
}
|
|
700
701
|
export function accumulateBlockContent(block, state, toolCallMap) {
|
|
702
|
+
if (state.transcript !== undefined || (block.type !== "agent_progress" && block.type !== "text_reset")) {
|
|
703
|
+
const transcriptState = { transcript: state.transcript ?? [] };
|
|
704
|
+
appendExecutionTranscriptBlock(block, transcriptState);
|
|
705
|
+
state.transcript = transcriptState.transcript;
|
|
706
|
+
}
|
|
701
707
|
switch (block.type) {
|
|
702
708
|
case "thinking":
|
|
703
709
|
state.chunkCount++;
|
|
@@ -907,8 +913,10 @@ export async function initClaudeSession(tool, overrideCwd, chatId) {
|
|
|
907
913
|
await addRecentDir(cwd);
|
|
908
914
|
return { sessionId, cwd };
|
|
909
915
|
}
|
|
910
|
-
export async function resumeAndPrompt(sessionId, userText, platform, chatId, msgTimestamp, tool, traceId) {
|
|
911
|
-
return runAgentSession(sessionId, userText, platform, chatId, msgTimestamp, tool, traceId
|
|
916
|
+
export async function resumeAndPrompt(sessionId, userText, platform, chatId, msgTimestamp, tool, traceId, initiatorOpenId) {
|
|
917
|
+
return runAgentSession(sessionId, userText, platform, chatId, msgTimestamp, tool, traceId, {
|
|
918
|
+
initiatorOpenId,
|
|
919
|
+
});
|
|
912
920
|
}
|
|
913
921
|
export async function runAgentSession(sessionId, userText, platform, _chatId, msgTimestamp, tool, traceId, options = {}) {
|
|
914
922
|
const tid = traceId ?? "";
|
|
@@ -984,8 +992,10 @@ export async function runAgentSession(sessionId, userText, platform, _chatId, ms
|
|
|
984
992
|
const skillVariables = {
|
|
985
993
|
cwd,
|
|
986
994
|
session_id: sessionId,
|
|
995
|
+
open_id: options.initiatorOpenId,
|
|
987
996
|
im_skills_cache_dir: imSkillsCacheDir,
|
|
988
997
|
delegate_task_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/delegate-task`,
|
|
998
|
+
set_cwd_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/set-cwd`,
|
|
989
999
|
send_image_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/send-image`,
|
|
990
1000
|
send_file_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/send-file`,
|
|
991
1001
|
send_image_script: join(feishuSkillDir, "send-image.mjs"),
|
|
@@ -1145,6 +1155,7 @@ export async function runAgentSession(sessionId, userText, platform, _chatId, ms
|
|
|
1145
1155
|
refreshBusySessionAvatar(sessionId, tool, platform).catch(() => { });
|
|
1146
1156
|
const state = {
|
|
1147
1157
|
accumulatedContent: "",
|
|
1158
|
+
transcript: [],
|
|
1148
1159
|
finalText: "",
|
|
1149
1160
|
finalCompleteText: "",
|
|
1150
1161
|
chunkCount: 0,
|
|
@@ -1196,6 +1207,7 @@ export async function runAgentSession(sessionId, userText, platform, _chatId, ms
|
|
|
1196
1207
|
status: "auto_ended",
|
|
1197
1208
|
accumulatedContent: state.accumulatedContent,
|
|
1198
1209
|
finalReply: pickFinalReply(state).trim(),
|
|
1210
|
+
transcript: state.transcript,
|
|
1199
1211
|
activity: activityTracker.activity,
|
|
1200
1212
|
chunkCount: state.chunkCount,
|
|
1201
1213
|
turnCount: nextTurnCount,
|
|
@@ -1308,6 +1320,7 @@ export async function runAgentSession(sessionId, userText, platform, _chatId, ms
|
|
|
1308
1320
|
status: "running",
|
|
1309
1321
|
accumulatedContent: state.accumulatedContent,
|
|
1310
1322
|
finalReply: pickFinalReply(state),
|
|
1323
|
+
transcript: state.transcript,
|
|
1311
1324
|
activity: activityTracker.activity,
|
|
1312
1325
|
chunkCount: state.chunkCount,
|
|
1313
1326
|
turnCount: nextTurnCount,
|
|
@@ -1406,6 +1419,7 @@ export async function runAgentSession(sessionId, userText, platform, _chatId, ms
|
|
|
1406
1419
|
status: finalStatus,
|
|
1407
1420
|
accumulatedContent: state.accumulatedContent,
|
|
1408
1421
|
finalReply: finalReplyToWrite,
|
|
1422
|
+
transcript: state.transcript,
|
|
1409
1423
|
activity: activityTracker.activity,
|
|
1410
1424
|
chunkCount: state.chunkCount,
|
|
1411
1425
|
turnCount: nextTurnCount,
|
package/dist/src/stream-state.js
CHANGED
|
@@ -100,6 +100,7 @@ export function createEmptyStreamState(sessionId, cwd, tool, turnCount) {
|
|
|
100
100
|
status: "running",
|
|
101
101
|
accumulatedContent: "",
|
|
102
102
|
finalReply: "",
|
|
103
|
+
transcript: [],
|
|
103
104
|
activity: createAgentActivityTracker(now).activity,
|
|
104
105
|
chunkCount: 0,
|
|
105
106
|
turnCount,
|
package/dist/src/web-ui.js
CHANGED
|
@@ -17,6 +17,7 @@ import { AGENT_TEAM_PAGE_HTML } from "./agent-team/web/agent-team-page.js";
|
|
|
17
17
|
export { AGENT_TEAM_PAGE_HTML } from "./agent-team/web/agent-team-page.js";
|
|
18
18
|
import { buildWebUiUrl, createInternalRestartEnv, openWebUiInDefaultBrowser, } from "./startup-lifecycle.js";
|
|
19
19
|
import { engineManager } from "./engines/engine-specs.js";
|
|
20
|
+
import { isSafeMaintenanceAdmissionClosed } from "./safe-maintenance.js";
|
|
20
21
|
const PROJECT_ROOT = CHATCCC_PACKAGE_ROOT;
|
|
21
22
|
const USER_DATA_DIR = join(homedir(), ".chatccc");
|
|
22
23
|
const CONFIG_FILE = join(USER_DATA_DIR, "config.json");
|
|
@@ -656,6 +657,10 @@ async function handleEngineStatus(engineId, res) {
|
|
|
656
657
|
}
|
|
657
658
|
}
|
|
658
659
|
async function handleEngineInstall(engineId, res) {
|
|
660
|
+
if (isSafeMaintenanceAdmissionClosed()) {
|
|
661
|
+
jsonReply(res, 409, { ok: false, error: "ChatCCC 正在等待安全维护,暂不接受新的依赖安装任务。" });
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
659
664
|
try {
|
|
660
665
|
jsonReply(res, 202, { ok: true, job: await engineManager.startInstall(engineId) });
|
|
661
666
|
}
|
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: feishu-skill
|
|
3
|
-
description: Feishu IM local skills for sending
|
|
3
|
+
description: Feishu IM local skills for sending images, files, videos, and for creating new sessions or switching working directories.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
Current working directory: {{cwd}}
|
|
7
|
+
Your session id: {{session_id}}
|
|
8
|
+
Your Feishu open_id: {{open_id}}
|
|
7
9
|
|
|
8
10
|
Use local endpoints instead of calling Feishu Open Platform directly.
|
|
9
11
|
|
|
10
|
-
- **Send images**: POST `{{send_image_url}}` with `{"session_id":"{{session_id}}","path":"<absolute path>","caption":"<optional>"}` — read `{{im_skills_cache_dir}}/feishu-skill/receive-send-image.md`
|
|
11
|
-
- **Send files/videos**: POST `{{send_file_url}}` with `{"session_id":"{{session_id}}","path":"<absolute path>","caption":"<optional>"}` — read `{{im_skills_cache_dir}}/feishu-skill/receive-send-file.md`
|
|
12
|
-
- **
|
|
12
|
+
- **Send images**: POST `{{send_image_url}}` with `{"session_id":"{{session_id}}","path":"<absolute path>","caption":"<optional>"}` — read `{{im_skills_cache_dir}}/feishu-skill/receive-send-image.md`
|
|
13
|
+
- **Send files/videos**: POST `{{send_file_url}}` with `{"session_id":"{{session_id}}","path":"<absolute path>","caption":"<optional>"}` — read `{{im_skills_cache_dir}}/feishu-skill/receive-send-file.md`
|
|
14
|
+
- **Create a new session (新建会话)**: POST `{{delegate_task_url}}` with `{"tool":"claude|cursor|codex|ccc|dsh","cwd":"<absolute path>","open_id":"{{open_id}}","prompt":"<optional first task>"}`. This creates a new Feishu group and session, and only adds you (the requester). `tool` and `prompt` are optional; omit `prompt` to just create the session without a first task.
|
|
15
|
+
- **Set default working directory (cd / 切换目录)**: POST `{{set_cwd_url}}` with `{"session_id":"{{session_id}}","dir":"<absolute path>"}`. This sets the default directory for future new sessions only; it does not change the current session.
|
|
16
|
+
|
|
17
|
+
How to map user requests to these endpoints:
|
|
18
|
+
|
|
19
|
+
- "新建会话 / 开个新会话 / 换个新会话" → create a new session (no prompt). Use `cwd` = your current working directory ({{cwd}}) unless the user names a directory.
|
|
20
|
+
- "在 <目录> 新建会话(做 <任务>)" → create a new session with `cwd` = that directory, and set `prompt` to the task if one was given.
|
|
21
|
+
- "cd 到 <目录> / 切换到 <目录> / 去 <目录> 干活" → judge intent:
|
|
22
|
+
- if the user wants to start working there now (a fresh conversation in that directory) → create a new session with `cwd` = that directory.
|
|
23
|
+
- if the user only wants to change the default directory for future sessions → call set-cwd.
|
|
24
|
+
- when ambiguous, prefer creating a new session (the more common intent for "切换到").
|
|
25
|
+
- Directory names may be fuzzy or relative; resolve them to an absolute local path (using your file tools) before calling either endpoint.
|
|
26
|
+
- `open_id` must always be passed as exactly {{open_id}}; do not invent it.
|
package/package.json
CHANGED
|
@@ -1,76 +1,76 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "chatccc",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Feishu bot bridge for Claude Code",
|
|
5
|
-
"license": "Apache-2.0",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"main": "./dist/src/index.js",
|
|
8
|
-
"bin": {
|
|
9
|
-
"chatccc": "bin/chatccc.mjs",
|
|
10
|
-
"cccagent": "bin/cccagent.mjs"
|
|
11
|
-
},
|
|
12
|
-
"files": [
|
|
13
|
-
"dist/",
|
|
14
|
-
"deepccc-agent/os-prompts/",
|
|
15
|
-
"bin/",
|
|
16
|
-
"scripts/postinstall-sharp-check.mjs",
|
|
17
|
-
"demo/ilink_echo_probe.ts",
|
|
18
|
-
"agent-prompts/",
|
|
19
|
-
"im-skills/",
|
|
20
|
-
".agents/skills/create-chatccc-feishu-app/",
|
|
21
|
-
".claude/skills/create-chatccc-feishu-app/",
|
|
22
|
-
".cursor/skills/create-chatccc-feishu-app/",
|
|
23
|
-
"images/img_readme_*.jpg",
|
|
24
|
-
"images/img_readme_*.png",
|
|
25
|
-
"images/avatars/status_*.png",
|
|
26
|
-
"images/avatars/badges/",
|
|
27
|
-
"images/avatars/combinations/",
|
|
28
|
-
"package.json",
|
|
29
|
-
"README.md",
|
|
30
|
-
"config.sample.json"
|
|
31
|
-
],
|
|
32
|
-
"scripts": {
|
|
33
|
-
"build": "node scripts/build.mjs",
|
|
34
|
-
"prepack": "npm run build",
|
|
35
|
-
"dev": "tsx src/index.ts",
|
|
36
|
-
"chatccc": "tsx src/index.ts",
|
|
37
|
-
"start": "tsx src/index.ts",
|
|
38
|
-
"demo:bot-test": "tsx demo/bot_test.ts",
|
|
39
|
-
"demo:bot-test:local": "tsx demo/bot_test.ts --local",
|
|
40
|
-
"demo:create-group": "tsx src/index.ts",
|
|
41
|
-
"demo:create-group:local": "tsx src/index.ts --local",
|
|
42
|
-
"demo:permission-check": "tsx demo/permission_check.ts",
|
|
43
|
-
"demo:claude-hi": "tsx demo/claude_say_hi.ts",
|
|
44
|
-
"demo:codex-hi": "tsx demo/codex_say_hi.ts",
|
|
45
|
-
"demo:codex-app-server-approval": "tsx demo/codex-app-server-approval/approval_demo.ts",
|
|
46
|
-
"demo:ilink-echo": "tsx demo/ilink_echo_probe.ts",
|
|
47
|
-
"claude-proxy": "tsx src/litellm-proxy.ts",
|
|
48
|
-
"test": "vitest run",
|
|
49
|
-
"test:deepccc": "vitest run --root deepccc-agent",
|
|
50
|
-
"test:watch": "vitest",
|
|
51
|
-
"postinstall": "node scripts/postinstall-sharp-check.mjs"
|
|
52
|
-
},
|
|
53
|
-
"dependencies": {
|
|
54
|
-
"@ai-sdk/anthropic": "^3.0.105",
|
|
55
|
-
"@ai-sdk/openai-compatible": "^2.0.47",
|
|
56
|
-
"@larksuiteoapi/node-sdk": "^1.59.0",
|
|
57
|
-
"@openilink/openilink-sdk-node": "^0.6.0",
|
|
58
|
-
"@vscode/ripgrep": "^1.18.0",
|
|
59
|
-
"ai": "^6.0.184",
|
|
60
|
-
"nodemailer": "^8.0.7",
|
|
61
|
-
"qrcode-terminal": "^0.12.0",
|
|
62
|
-
"sharp": "^0.34.5",
|
|
63
|
-
"ws": "^8.18.0"
|
|
64
|
-
},
|
|
65
|
-
"devDependencies": {
|
|
66
|
-
"@types/node": "^20.0.0",
|
|
67
|
-
"@types/qrcode-terminal": "^0.12.2",
|
|
68
|
-
"@types/ws": "^8.18.1",
|
|
69
|
-
"tsx": "^4.0.0",
|
|
70
|
-
"typescript": "^5.0.0",
|
|
71
|
-
"vitest": "^3.2.4"
|
|
72
|
-
},
|
|
73
|
-
"engines": {
|
|
74
|
-
"node": ">=20"
|
|
75
|
-
}
|
|
76
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "chatccc",
|
|
3
|
+
"version": "0.2.269",
|
|
4
|
+
"description": "Feishu bot bridge for Claude Code",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/src/index.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"chatccc": "bin/chatccc.mjs",
|
|
10
|
+
"cccagent": "bin/cccagent.mjs"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist/",
|
|
14
|
+
"deepccc-agent/os-prompts/",
|
|
15
|
+
"bin/",
|
|
16
|
+
"scripts/postinstall-sharp-check.mjs",
|
|
17
|
+
"demo/ilink_echo_probe.ts",
|
|
18
|
+
"agent-prompts/",
|
|
19
|
+
"im-skills/",
|
|
20
|
+
".agents/skills/create-chatccc-feishu-app/",
|
|
21
|
+
".claude/skills/create-chatccc-feishu-app/",
|
|
22
|
+
".cursor/skills/create-chatccc-feishu-app/",
|
|
23
|
+
"images/img_readme_*.jpg",
|
|
24
|
+
"images/img_readme_*.png",
|
|
25
|
+
"images/avatars/status_*.png",
|
|
26
|
+
"images/avatars/badges/",
|
|
27
|
+
"images/avatars/combinations/",
|
|
28
|
+
"package.json",
|
|
29
|
+
"README.md",
|
|
30
|
+
"config.sample.json"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "node scripts/build.mjs",
|
|
34
|
+
"prepack": "npm run build",
|
|
35
|
+
"dev": "tsx src/index.ts",
|
|
36
|
+
"chatccc": "tsx src/index.ts",
|
|
37
|
+
"start": "tsx src/index.ts",
|
|
38
|
+
"demo:bot-test": "tsx demo/bot_test.ts",
|
|
39
|
+
"demo:bot-test:local": "tsx demo/bot_test.ts --local",
|
|
40
|
+
"demo:create-group": "tsx src/index.ts",
|
|
41
|
+
"demo:create-group:local": "tsx src/index.ts --local",
|
|
42
|
+
"demo:permission-check": "tsx demo/permission_check.ts",
|
|
43
|
+
"demo:claude-hi": "tsx demo/claude_say_hi.ts",
|
|
44
|
+
"demo:codex-hi": "tsx demo/codex_say_hi.ts",
|
|
45
|
+
"demo:codex-app-server-approval": "tsx demo/codex-app-server-approval/approval_demo.ts",
|
|
46
|
+
"demo:ilink-echo": "tsx demo/ilink_echo_probe.ts",
|
|
47
|
+
"claude-proxy": "tsx src/litellm-proxy.ts",
|
|
48
|
+
"test": "vitest run",
|
|
49
|
+
"test:deepccc": "vitest run --root deepccc-agent",
|
|
50
|
+
"test:watch": "vitest",
|
|
51
|
+
"postinstall": "node scripts/postinstall-sharp-check.mjs"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@ai-sdk/anthropic": "^3.0.105",
|
|
55
|
+
"@ai-sdk/openai-compatible": "^2.0.47",
|
|
56
|
+
"@larksuiteoapi/node-sdk": "^1.59.0",
|
|
57
|
+
"@openilink/openilink-sdk-node": "^0.6.0",
|
|
58
|
+
"@vscode/ripgrep": "^1.18.0",
|
|
59
|
+
"ai": "^6.0.184",
|
|
60
|
+
"nodemailer": "^8.0.7",
|
|
61
|
+
"qrcode-terminal": "^0.12.0",
|
|
62
|
+
"sharp": "^0.34.5",
|
|
63
|
+
"ws": "^8.18.0"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@types/node": "^20.0.0",
|
|
67
|
+
"@types/qrcode-terminal": "^0.12.2",
|
|
68
|
+
"@types/ws": "^8.18.1",
|
|
69
|
+
"tsx": "^4.0.0",
|
|
70
|
+
"typescript": "^5.0.0",
|
|
71
|
+
"vitest": "^3.2.4"
|
|
72
|
+
},
|
|
73
|
+
"engines": {
|
|
74
|
+
"node": ">=20"
|
|
75
|
+
}
|
|
76
|
+
}
|