chatccc 0.2.226 → 0.2.228
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/.agents/skills/create-chatccc-feishu-app/SKILL.md +85 -85
- package/.claude/skills/create-chatccc-feishu-app/SKILL.md +85 -85
- package/.cursor/skills/create-chatccc-feishu-app/SKILL.md +85 -85
- package/README.md +90 -90
- package/package.json +1 -1
- package/src/__tests__/agent-activity.test.ts +76 -76
- package/src/__tests__/builtin-chat-session.test.ts +350 -350
- package/src/__tests__/builtin-config.test.ts +26 -26
- package/src/__tests__/builtin-context.test.ts +163 -163
- package/src/__tests__/builtin-file-tools.test.ts +275 -275
- package/src/__tests__/builtin-permissions.test.ts +211 -211
- package/src/__tests__/builtin-session-select.test.ts +116 -116
- package/src/__tests__/builtin-skills.test.ts +252 -141
- package/src/__tests__/builtin-web-tools.test.ts +220 -0
- package/src/__tests__/card-action-routing.test.ts +18 -18
- package/src/__tests__/ccc-adapter.test.ts +136 -136
- package/src/__tests__/claude-adapter.test.ts +614 -614
- package/src/__tests__/codex-adapter.test.ts +58 -58
- package/src/__tests__/codex-raw-stream-log.test.ts +170 -170
- package/src/__tests__/cursor-adapter.test.ts +268 -268
- package/src/__tests__/feishu-avatar.test.ts +164 -164
- package/src/__tests__/feishu-message-ingress.test.ts +138 -138
- package/src/__tests__/package-files.test.ts +24 -24
- package/src/__tests__/progress-reducer.test.ts +110 -110
- package/src/__tests__/response-stall.test.ts +49 -49
- package/src/__tests__/sim-platform.test.ts +16 -16
- package/src/__tests__/startup-lifecycle.test.ts +231 -231
- package/src/__tests__/stop-session.test.ts +34 -34
- package/src/__tests__/terminal-renderer.test.ts +247 -247
- package/src/__tests__/update-command-guard.test.ts +144 -144
- package/src/__tests__/web-ui.test.ts +326 -326
- package/src/adapters/adapter-interface.ts +18 -18
- package/src/adapters/ccc-adapter.ts +131 -131
- package/src/adapters/claude-adapter.ts +620 -620
- package/src/adapters/codex-adapter.ts +426 -426
- package/src/adapters/cursor-adapter.ts +681 -681
- package/src/agent-activity.ts +170 -170
- package/src/agent-delegate-task.ts +91 -91
- package/src/builtin/cli.ts +61 -2
- package/src/builtin/config.ts +84 -84
- package/src/builtin/context.ts +323 -323
- package/src/builtin/file-log.ts +38 -38
- package/src/builtin/file-tools.ts +37 -0
- package/src/builtin/index.ts +44 -24
- package/src/builtin/proc-tree-kill.ts +61 -61
- package/src/builtin/progress/cards-helpers.ts +76 -76
- package/src/builtin/progress/reducer.ts +108 -108
- package/src/builtin/progress/terminal-renderer.ts +294 -294
- package/src/builtin/progress/view.ts +77 -77
- package/src/builtin/raw-stream-log.ts +124 -124
- package/src/builtin/session-select.ts +48 -48
- package/src/builtin/skills.ts +190 -108
- package/src/builtin/web-tools.ts +313 -0
- package/src/card-action-routing.ts +14 -14
- package/src/feishu-api.ts +193 -193
- package/src/feishu-message-ingress.ts +195 -195
- package/src/index.ts +306 -306
- package/src/orchestrator.ts +2388 -2388
- package/src/platform-adapter.ts +6 -6
- package/src/progress/reducer.ts +108 -108
- package/src/progress/terminal-renderer.ts +294 -294
- package/src/progress/view.ts +77 -77
- package/src/response-stall.ts +28 -28
- package/src/session-chat-binding.ts +82 -82
- package/src/startup-lifecycle.ts +250 -250
- package/src/stream-state.ts +18 -18
- package/src/update-command-guard.ts +165 -165
package/src/agent-activity.ts
CHANGED
|
@@ -1,170 +1,170 @@
|
|
|
1
|
-
import type { UnifiedBlock } from "./adapters/adapter-interface.ts";
|
|
2
|
-
|
|
3
|
-
export type AgentActivityKind =
|
|
4
|
-
| "starting"
|
|
5
|
-
| "thinking"
|
|
6
|
-
| "tool"
|
|
7
|
-
| "processing"
|
|
8
|
-
| "responding"
|
|
9
|
-
| "searching"
|
|
10
|
-
| "compacting";
|
|
11
|
-
|
|
12
|
-
/** The user-visible activity of a running Agent turn. */
|
|
13
|
-
export interface AgentActivity {
|
|
14
|
-
kind: AgentActivityKind;
|
|
15
|
-
/** Time when the current activity began, used for truthful elapsed time. */
|
|
16
|
-
startedAt: number;
|
|
17
|
-
toolName?: string;
|
|
18
|
-
toolCount?: number;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface ActiveTool {
|
|
22
|
-
id: string;
|
|
23
|
-
name: string;
|
|
24
|
-
startedAt: number;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface AgentActivityTracker {
|
|
28
|
-
activity: AgentActivity;
|
|
29
|
-
activeTools: Map<string, ActiveTool>;
|
|
30
|
-
nextAnonymousToolId: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function createAgentActivityTracker(now = Date.now()): AgentActivityTracker {
|
|
34
|
-
return {
|
|
35
|
-
activity: { kind: "starting", startedAt: now },
|
|
36
|
-
activeTools: new Map(),
|
|
37
|
-
nextAnonymousToolId: 1,
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function sameVisibleActivity(left: AgentActivity, right: AgentActivity): boolean {
|
|
42
|
-
return left.kind === right.kind
|
|
43
|
-
&& left.toolName === right.toolName
|
|
44
|
-
&& left.toolCount === right.toolCount;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function setActivity(tracker: AgentActivityTracker, next: AgentActivity): boolean {
|
|
48
|
-
if (sameVisibleActivity(tracker.activity, next)) return false;
|
|
49
|
-
tracker.activity = next;
|
|
50
|
-
return true;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function refreshToolActivity(tracker: AgentActivityTracker): boolean {
|
|
54
|
-
const tools = [...tracker.activeTools.values()];
|
|
55
|
-
const first = tools[0];
|
|
56
|
-
if (!first) return false;
|
|
57
|
-
return setActivity(tracker, {
|
|
58
|
-
kind: "tool",
|
|
59
|
-
startedAt: first.startedAt,
|
|
60
|
-
toolName: first.name,
|
|
61
|
-
toolCount: tools.length,
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function removeCompletedTool(tracker: AgentActivityTracker, toolUseId: string): void {
|
|
66
|
-
if (toolUseId && tracker.activeTools.delete(toolUseId)) return;
|
|
67
|
-
|
|
68
|
-
// Older adapters did not always include a tool ID. Prefer an anonymous entry;
|
|
69
|
-
// if there is only one active call, it is still safe to match that result.
|
|
70
|
-
const anonymousId = [...tracker.activeTools.keys()].find((id) => id.startsWith("anonymous:"));
|
|
71
|
-
if (anonymousId) {
|
|
72
|
-
tracker.activeTools.delete(anonymousId);
|
|
73
|
-
} else if (tracker.activeTools.size === 1) {
|
|
74
|
-
const onlyId = tracker.activeTools.keys().next().value as string | undefined;
|
|
75
|
-
if (onlyId) tracker.activeTools.delete(onlyId);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Applies one normalized Agent event and returns whether the persisted activity
|
|
81
|
-
* changed. Tool activity takes precedence while a tool call is still active.
|
|
82
|
-
*/
|
|
83
|
-
export function updateAgentActivity(
|
|
84
|
-
tracker: AgentActivityTracker,
|
|
85
|
-
block: UnifiedBlock,
|
|
86
|
-
now = Date.now(),
|
|
87
|
-
): boolean {
|
|
88
|
-
if (block.type === "tool_use") {
|
|
89
|
-
const id = block.id || `anonymous:${tracker.nextAnonymousToolId++}`;
|
|
90
|
-
const existing = tracker.activeTools.get(id);
|
|
91
|
-
tracker.activeTools.set(id, {
|
|
92
|
-
id,
|
|
93
|
-
name: block.name || "未知工具",
|
|
94
|
-
startedAt: existing?.startedAt ?? now,
|
|
95
|
-
});
|
|
96
|
-
return refreshToolActivity(tracker);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (block.type === "tool_result") {
|
|
100
|
-
removeCompletedTool(tracker, block.tool_use_id);
|
|
101
|
-
if (tracker.activeTools.size > 0) return refreshToolActivity(tracker);
|
|
102
|
-
return setActivity(tracker, { kind: "processing", startedAt: now });
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (tracker.activeTools.size > 0) return false;
|
|
106
|
-
|
|
107
|
-
switch (block.type) {
|
|
108
|
-
case "thinking":
|
|
109
|
-
case "redacted_thinking":
|
|
110
|
-
return setActivity(tracker, { kind: "thinking", startedAt: now });
|
|
111
|
-
case "text":
|
|
112
|
-
case "text_final":
|
|
113
|
-
return setActivity(tracker, { kind: "responding", startedAt: now });
|
|
114
|
-
case "search_result":
|
|
115
|
-
return setActivity(tracker, { kind: "searching", startedAt: now });
|
|
116
|
-
case "compact_boundary":
|
|
117
|
-
return setActivity(tracker, { kind: "compacting", startedAt: now });
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function formatElapsed(startedAt: number, now: number): string {
|
|
122
|
-
const totalSeconds = Math.max(0, Math.floor((now - startedAt) / 1000));
|
|
123
|
-
if (totalSeconds < 60) return `${totalSeconds}秒`;
|
|
124
|
-
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
125
|
-
const seconds = totalSeconds % 60;
|
|
126
|
-
if (totalMinutes < 60) return `${totalMinutes}分${seconds}秒`;
|
|
127
|
-
const hours = Math.floor(totalMinutes / 60);
|
|
128
|
-
const minutes = totalMinutes % 60;
|
|
129
|
-
return `${hours}小时${minutes}分`;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function displayToolName(name: string | undefined): string {
|
|
133
|
-
const normalized = (name || "未知工具").replace(/\s+/g, " ").trim();
|
|
134
|
-
return normalized.length > 24 ? `${normalized.slice(0, 23)}…` : normalized;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export function formatAgentActivityTitle(
|
|
138
|
-
activity: AgentActivity | undefined,
|
|
139
|
-
now = Date.now(),
|
|
140
|
-
): string {
|
|
141
|
-
if (!activity) return "正在处理";
|
|
142
|
-
|
|
143
|
-
let label: string;
|
|
144
|
-
switch (activity.kind) {
|
|
145
|
-
case "starting":
|
|
146
|
-
label = "正在启动 Agent";
|
|
147
|
-
break;
|
|
148
|
-
case "thinking":
|
|
149
|
-
label = "思考中";
|
|
150
|
-
break;
|
|
151
|
-
case "tool": {
|
|
152
|
-
const count = Math.max(1, activity.toolCount ?? 1);
|
|
153
|
-
label = `正在执行 ${displayToolName(activity.toolName)}${count > 1 ? ` 等 ${count} 项` : ""}`;
|
|
154
|
-
break;
|
|
155
|
-
}
|
|
156
|
-
case "processing":
|
|
157
|
-
label = "正在处理工具结果";
|
|
158
|
-
break;
|
|
159
|
-
case "responding":
|
|
160
|
-
label = "正在生成回复";
|
|
161
|
-
break;
|
|
162
|
-
case "searching":
|
|
163
|
-
label = "正在处理搜索结果";
|
|
164
|
-
break;
|
|
165
|
-
case "compacting":
|
|
166
|
-
label = "正在整理上下文";
|
|
167
|
-
break;
|
|
168
|
-
}
|
|
169
|
-
return `${label} · ${formatElapsed(activity.startedAt, now)}`;
|
|
170
|
-
}
|
|
1
|
+
import type { UnifiedBlock } from "./adapters/adapter-interface.ts";
|
|
2
|
+
|
|
3
|
+
export type AgentActivityKind =
|
|
4
|
+
| "starting"
|
|
5
|
+
| "thinking"
|
|
6
|
+
| "tool"
|
|
7
|
+
| "processing"
|
|
8
|
+
| "responding"
|
|
9
|
+
| "searching"
|
|
10
|
+
| "compacting";
|
|
11
|
+
|
|
12
|
+
/** The user-visible activity of a running Agent turn. */
|
|
13
|
+
export interface AgentActivity {
|
|
14
|
+
kind: AgentActivityKind;
|
|
15
|
+
/** Time when the current activity began, used for truthful elapsed time. */
|
|
16
|
+
startedAt: number;
|
|
17
|
+
toolName?: string;
|
|
18
|
+
toolCount?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface ActiveTool {
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
startedAt: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface AgentActivityTracker {
|
|
28
|
+
activity: AgentActivity;
|
|
29
|
+
activeTools: Map<string, ActiveTool>;
|
|
30
|
+
nextAnonymousToolId: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createAgentActivityTracker(now = Date.now()): AgentActivityTracker {
|
|
34
|
+
return {
|
|
35
|
+
activity: { kind: "starting", startedAt: now },
|
|
36
|
+
activeTools: new Map(),
|
|
37
|
+
nextAnonymousToolId: 1,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function sameVisibleActivity(left: AgentActivity, right: AgentActivity): boolean {
|
|
42
|
+
return left.kind === right.kind
|
|
43
|
+
&& left.toolName === right.toolName
|
|
44
|
+
&& left.toolCount === right.toolCount;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function setActivity(tracker: AgentActivityTracker, next: AgentActivity): boolean {
|
|
48
|
+
if (sameVisibleActivity(tracker.activity, next)) return false;
|
|
49
|
+
tracker.activity = next;
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function refreshToolActivity(tracker: AgentActivityTracker): boolean {
|
|
54
|
+
const tools = [...tracker.activeTools.values()];
|
|
55
|
+
const first = tools[0];
|
|
56
|
+
if (!first) return false;
|
|
57
|
+
return setActivity(tracker, {
|
|
58
|
+
kind: "tool",
|
|
59
|
+
startedAt: first.startedAt,
|
|
60
|
+
toolName: first.name,
|
|
61
|
+
toolCount: tools.length,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function removeCompletedTool(tracker: AgentActivityTracker, toolUseId: string): void {
|
|
66
|
+
if (toolUseId && tracker.activeTools.delete(toolUseId)) return;
|
|
67
|
+
|
|
68
|
+
// Older adapters did not always include a tool ID. Prefer an anonymous entry;
|
|
69
|
+
// if there is only one active call, it is still safe to match that result.
|
|
70
|
+
const anonymousId = [...tracker.activeTools.keys()].find((id) => id.startsWith("anonymous:"));
|
|
71
|
+
if (anonymousId) {
|
|
72
|
+
tracker.activeTools.delete(anonymousId);
|
|
73
|
+
} else if (tracker.activeTools.size === 1) {
|
|
74
|
+
const onlyId = tracker.activeTools.keys().next().value as string | undefined;
|
|
75
|
+
if (onlyId) tracker.activeTools.delete(onlyId);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Applies one normalized Agent event and returns whether the persisted activity
|
|
81
|
+
* changed. Tool activity takes precedence while a tool call is still active.
|
|
82
|
+
*/
|
|
83
|
+
export function updateAgentActivity(
|
|
84
|
+
tracker: AgentActivityTracker,
|
|
85
|
+
block: UnifiedBlock,
|
|
86
|
+
now = Date.now(),
|
|
87
|
+
): boolean {
|
|
88
|
+
if (block.type === "tool_use") {
|
|
89
|
+
const id = block.id || `anonymous:${tracker.nextAnonymousToolId++}`;
|
|
90
|
+
const existing = tracker.activeTools.get(id);
|
|
91
|
+
tracker.activeTools.set(id, {
|
|
92
|
+
id,
|
|
93
|
+
name: block.name || "未知工具",
|
|
94
|
+
startedAt: existing?.startedAt ?? now,
|
|
95
|
+
});
|
|
96
|
+
return refreshToolActivity(tracker);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (block.type === "tool_result") {
|
|
100
|
+
removeCompletedTool(tracker, block.tool_use_id);
|
|
101
|
+
if (tracker.activeTools.size > 0) return refreshToolActivity(tracker);
|
|
102
|
+
return setActivity(tracker, { kind: "processing", startedAt: now });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (tracker.activeTools.size > 0) return false;
|
|
106
|
+
|
|
107
|
+
switch (block.type) {
|
|
108
|
+
case "thinking":
|
|
109
|
+
case "redacted_thinking":
|
|
110
|
+
return setActivity(tracker, { kind: "thinking", startedAt: now });
|
|
111
|
+
case "text":
|
|
112
|
+
case "text_final":
|
|
113
|
+
return setActivity(tracker, { kind: "responding", startedAt: now });
|
|
114
|
+
case "search_result":
|
|
115
|
+
return setActivity(tracker, { kind: "searching", startedAt: now });
|
|
116
|
+
case "compact_boundary":
|
|
117
|
+
return setActivity(tracker, { kind: "compacting", startedAt: now });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function formatElapsed(startedAt: number, now: number): string {
|
|
122
|
+
const totalSeconds = Math.max(0, Math.floor((now - startedAt) / 1000));
|
|
123
|
+
if (totalSeconds < 60) return `${totalSeconds}秒`;
|
|
124
|
+
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
125
|
+
const seconds = totalSeconds % 60;
|
|
126
|
+
if (totalMinutes < 60) return `${totalMinutes}分${seconds}秒`;
|
|
127
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
128
|
+
const minutes = totalMinutes % 60;
|
|
129
|
+
return `${hours}小时${minutes}分`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function displayToolName(name: string | undefined): string {
|
|
133
|
+
const normalized = (name || "未知工具").replace(/\s+/g, " ").trim();
|
|
134
|
+
return normalized.length > 24 ? `${normalized.slice(0, 23)}…` : normalized;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function formatAgentActivityTitle(
|
|
138
|
+
activity: AgentActivity | undefined,
|
|
139
|
+
now = Date.now(),
|
|
140
|
+
): string {
|
|
141
|
+
if (!activity) return "正在处理";
|
|
142
|
+
|
|
143
|
+
let label: string;
|
|
144
|
+
switch (activity.kind) {
|
|
145
|
+
case "starting":
|
|
146
|
+
label = "正在启动 Agent";
|
|
147
|
+
break;
|
|
148
|
+
case "thinking":
|
|
149
|
+
label = "思考中";
|
|
150
|
+
break;
|
|
151
|
+
case "tool": {
|
|
152
|
+
const count = Math.max(1, activity.toolCount ?? 1);
|
|
153
|
+
label = `正在执行 ${displayToolName(activity.toolName)}${count > 1 ? ` 等 ${count} 项` : ""}`;
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
case "processing":
|
|
157
|
+
label = "正在处理工具结果";
|
|
158
|
+
break;
|
|
159
|
+
case "responding":
|
|
160
|
+
label = "正在生成回复";
|
|
161
|
+
break;
|
|
162
|
+
case "searching":
|
|
163
|
+
label = "正在处理搜索结果";
|
|
164
|
+
break;
|
|
165
|
+
case "compacting":
|
|
166
|
+
label = "正在整理上下文";
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
return `${label} · ${formatElapsed(activity.startedAt, now)}`;
|
|
170
|
+
}
|
|
@@ -1,91 +1,91 @@
|
|
|
1
|
-
import { resolve } from "node:path";
|
|
2
|
-
|
|
3
|
-
import { sessionPrefixForTool, toolDisplayName, ts } from "./config.ts";
|
|
4
|
-
import { setDefaultCwd } from "./config.ts";
|
|
5
|
-
import type { PlatformAdapter } from "./platform-adapter.ts";
|
|
6
|
-
import {
|
|
7
|
-
getEffectiveFastModeForTool,
|
|
8
|
-
initClaudeSession,
|
|
9
|
-
recordSessionRegistry,
|
|
10
|
-
resumeAndPrompt,
|
|
11
|
-
saveSessionTool,
|
|
12
|
-
} from "./session.ts";
|
|
13
|
-
import { bindChatToSession } from "./session-chat-binding.ts";
|
|
14
|
-
import { sessionChatName } from "./session-name.ts";
|
|
15
|
-
|
|
16
|
-
export interface DelegateAgentTaskInput {
|
|
17
|
-
platform: PlatformAdapter;
|
|
18
|
-
tool: string;
|
|
19
|
-
cwd: string;
|
|
20
|
-
promptText: string;
|
|
21
|
-
openIds: string[];
|
|
22
|
-
chatNamePrefix?: string;
|
|
23
|
-
msgTimestamp?: number;
|
|
24
|
-
traceId?: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface DelegateAgentTaskResult {
|
|
28
|
-
chatId: string;
|
|
29
|
-
sessionId: string;
|
|
30
|
-
tool: string;
|
|
31
|
-
cwd: string;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export async function delegateAgentTask(input: DelegateAgentTaskInput): Promise<DelegateAgentTaskResult> {
|
|
35
|
-
const cwd = resolve(input.cwd);
|
|
36
|
-
const toolLabel = toolDisplayName(input.tool);
|
|
37
|
-
const init = await initClaudeSession(input.tool, cwd);
|
|
38
|
-
const sessionId = init.sessionId;
|
|
39
|
-
const chatNamePrefix = input.chatNamePrefix?.trim() || input.promptText.slice(0, 10) || "新会话";
|
|
40
|
-
const chatName = sessionChatName(chatNamePrefix, cwd);
|
|
41
|
-
|
|
42
|
-
let chatId: string;
|
|
43
|
-
try {
|
|
44
|
-
chatId = await input.platform.createGroup(chatName, input.openIds);
|
|
45
|
-
await input.platform.updateChatInfo(chatId, chatName, `${sessionPrefixForTool(input.tool)} ${sessionId}`);
|
|
46
|
-
await setDefaultCwd(cwd, chatId);
|
|
47
|
-
bindChatToSession(sessionId, chatId);
|
|
48
|
-
await recordSessionRegistry({
|
|
49
|
-
chatId,
|
|
50
|
-
sessionId,
|
|
51
|
-
tool: input.tool,
|
|
52
|
-
chatType: "group",
|
|
53
|
-
chatName,
|
|
54
|
-
turnCount: 0,
|
|
55
|
-
startTime: Date.now(),
|
|
56
|
-
running: false,
|
|
57
|
-
});
|
|
58
|
-
await saveSessionTool(sessionId, input.tool, chatName);
|
|
59
|
-
} catch (err) {
|
|
60
|
-
console.error(`[${ts()}] [AGENT-DELEGATE-TASK] create group failed: ${(err as Error).message}`);
|
|
61
|
-
throw err;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
await input.platform.sendCard(
|
|
65
|
-
chatId,
|
|
66
|
-
`${toolLabel} Session Ready`,
|
|
67
|
-
`已创建 **${toolLabel}** 会话群。\n\n` +
|
|
68
|
-
`**Session ID:** ${sessionId}\n` +
|
|
69
|
-
`**工作目录:** \`${cwd}\`\n\n` +
|
|
70
|
-
`下面会自动把任务作为第一句话发送给 ${toolLabel}。`,
|
|
71
|
-
"green",
|
|
72
|
-
).catch(() => {});
|
|
73
|
-
const fastMode = getEffectiveFastModeForTool(input.tool, sessionId);
|
|
74
|
-
const avatarUpdate = fastMode
|
|
75
|
-
? input.platform.setChatAvatar(chatId, input.tool, "new", { fastMode: true })
|
|
76
|
-
: input.platform.setChatAvatar(chatId, input.tool, "new");
|
|
77
|
-
avatarUpdate.catch(() => {});
|
|
78
|
-
|
|
79
|
-
await resumeAndPrompt(
|
|
80
|
-
sessionId,
|
|
81
|
-
input.promptText,
|
|
82
|
-
input.platform,
|
|
83
|
-
chatId,
|
|
84
|
-
input.msgTimestamp ?? Date.now(),
|
|
85
|
-
input.tool,
|
|
86
|
-
input.traceId,
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
console.log(`[${ts()}] [AGENT-DELEGATE-TASK] created ${toolLabel} session=${sessionId} chat=${chatId} cwd=${cwd}`);
|
|
90
|
-
return { chatId, sessionId, tool: input.tool, cwd };
|
|
91
|
-
}
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
|
|
3
|
+
import { sessionPrefixForTool, toolDisplayName, ts } from "./config.ts";
|
|
4
|
+
import { setDefaultCwd } from "./config.ts";
|
|
5
|
+
import type { PlatformAdapter } from "./platform-adapter.ts";
|
|
6
|
+
import {
|
|
7
|
+
getEffectiveFastModeForTool,
|
|
8
|
+
initClaudeSession,
|
|
9
|
+
recordSessionRegistry,
|
|
10
|
+
resumeAndPrompt,
|
|
11
|
+
saveSessionTool,
|
|
12
|
+
} from "./session.ts";
|
|
13
|
+
import { bindChatToSession } from "./session-chat-binding.ts";
|
|
14
|
+
import { sessionChatName } from "./session-name.ts";
|
|
15
|
+
|
|
16
|
+
export interface DelegateAgentTaskInput {
|
|
17
|
+
platform: PlatformAdapter;
|
|
18
|
+
tool: string;
|
|
19
|
+
cwd: string;
|
|
20
|
+
promptText: string;
|
|
21
|
+
openIds: string[];
|
|
22
|
+
chatNamePrefix?: string;
|
|
23
|
+
msgTimestamp?: number;
|
|
24
|
+
traceId?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface DelegateAgentTaskResult {
|
|
28
|
+
chatId: string;
|
|
29
|
+
sessionId: string;
|
|
30
|
+
tool: string;
|
|
31
|
+
cwd: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function delegateAgentTask(input: DelegateAgentTaskInput): Promise<DelegateAgentTaskResult> {
|
|
35
|
+
const cwd = resolve(input.cwd);
|
|
36
|
+
const toolLabel = toolDisplayName(input.tool);
|
|
37
|
+
const init = await initClaudeSession(input.tool, cwd);
|
|
38
|
+
const sessionId = init.sessionId;
|
|
39
|
+
const chatNamePrefix = input.chatNamePrefix?.trim() || input.promptText.slice(0, 10) || "新会话";
|
|
40
|
+
const chatName = sessionChatName(chatNamePrefix, cwd);
|
|
41
|
+
|
|
42
|
+
let chatId: string;
|
|
43
|
+
try {
|
|
44
|
+
chatId = await input.platform.createGroup(chatName, input.openIds);
|
|
45
|
+
await input.platform.updateChatInfo(chatId, chatName, `${sessionPrefixForTool(input.tool)} ${sessionId}`);
|
|
46
|
+
await setDefaultCwd(cwd, chatId);
|
|
47
|
+
bindChatToSession(sessionId, chatId);
|
|
48
|
+
await recordSessionRegistry({
|
|
49
|
+
chatId,
|
|
50
|
+
sessionId,
|
|
51
|
+
tool: input.tool,
|
|
52
|
+
chatType: "group",
|
|
53
|
+
chatName,
|
|
54
|
+
turnCount: 0,
|
|
55
|
+
startTime: Date.now(),
|
|
56
|
+
running: false,
|
|
57
|
+
});
|
|
58
|
+
await saveSessionTool(sessionId, input.tool, chatName);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
console.error(`[${ts()}] [AGENT-DELEGATE-TASK] create group failed: ${(err as Error).message}`);
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
await input.platform.sendCard(
|
|
65
|
+
chatId,
|
|
66
|
+
`${toolLabel} Session Ready`,
|
|
67
|
+
`已创建 **${toolLabel}** 会话群。\n\n` +
|
|
68
|
+
`**Session ID:** ${sessionId}\n` +
|
|
69
|
+
`**工作目录:** \`${cwd}\`\n\n` +
|
|
70
|
+
`下面会自动把任务作为第一句话发送给 ${toolLabel}。`,
|
|
71
|
+
"green",
|
|
72
|
+
).catch(() => {});
|
|
73
|
+
const fastMode = getEffectiveFastModeForTool(input.tool, sessionId);
|
|
74
|
+
const avatarUpdate = fastMode
|
|
75
|
+
? input.platform.setChatAvatar(chatId, input.tool, "new", { fastMode: true })
|
|
76
|
+
: input.platform.setChatAvatar(chatId, input.tool, "new");
|
|
77
|
+
avatarUpdate.catch(() => {});
|
|
78
|
+
|
|
79
|
+
await resumeAndPrompt(
|
|
80
|
+
sessionId,
|
|
81
|
+
input.promptText,
|
|
82
|
+
input.platform,
|
|
83
|
+
chatId,
|
|
84
|
+
input.msgTimestamp ?? Date.now(),
|
|
85
|
+
input.tool,
|
|
86
|
+
input.traceId,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
console.log(`[${ts()}] [AGENT-DELEGATE-TASK] created ${toolLabel} session=${sessionId} chat=${chatId} cwd=${cwd}`);
|
|
90
|
+
return { chatId, sessionId, tool: input.tool, cwd };
|
|
91
|
+
}
|
package/src/builtin/cli.ts
CHANGED
|
@@ -13,13 +13,15 @@
|
|
|
13
13
|
|
|
14
14
|
import * as readline from "node:readline";
|
|
15
15
|
import * as process from "node:process";
|
|
16
|
-
import { appendFileSync } from "node:fs";
|
|
17
|
-
import {
|
|
16
|
+
import { appendFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
17
|
+
import { homedir } from "node:os";
|
|
18
|
+
import { join, resolve as resolvePath } from "node:path";
|
|
18
19
|
import { fileURLToPath } from "node:url";
|
|
19
20
|
|
|
20
21
|
import { listBuiltinContextSessions } from "./context.js";
|
|
21
22
|
import { resolveBuiltinSession, type BuiltinResumeRequest } from "./session-select.js";
|
|
22
23
|
import { createCtrlCState } from "./sigint.js";
|
|
24
|
+
import { buildSkillTemplate } from "./skills.js";
|
|
23
25
|
import { reduceProgress } from "./progress/reducer.js";
|
|
24
26
|
import { TerminalProgressRenderer } from "./progress/terminal-renderer.js";
|
|
25
27
|
import { progressView, type ProgressView } from "./progress/view.js";
|
|
@@ -562,7 +564,64 @@ async function runRepl(args: ParsedArgs): Promise<void> {
|
|
|
562
564
|
});
|
|
563
565
|
}
|
|
564
566
|
|
|
567
|
+
/**
|
|
568
|
+
* skill create 子命令:deepccc skill create <name> [--scope global|project] [--description "..."]
|
|
569
|
+
* 默认创建为全局技能(~/.deepccc/skills/<name>/SKILL.md,Codex 结构);
|
|
570
|
+
* --scope project 创建为项目技能(<cwd>/.deepccc/skills/<name>/SKILL.md)。
|
|
571
|
+
* 新技能在下一次对话自动生效(技能索引每次 chat() 前重扫)。
|
|
572
|
+
*/
|
|
573
|
+
function runSkillCreate(argv: string[]): void {
|
|
574
|
+
const positional = argv.filter((a) => !a.startsWith("--"));
|
|
575
|
+
const name = positional[0];
|
|
576
|
+
if (!name) {
|
|
577
|
+
console.error(
|
|
578
|
+
"usage: deepccc skill create <name> [--scope global|project] [--description \"...\"]",
|
|
579
|
+
);
|
|
580
|
+
process.exit(1);
|
|
581
|
+
}
|
|
582
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(name)) {
|
|
583
|
+
console.error(`invalid skill name: ${name}(允许字母/数字/._-,不能以 . 或 - 开头)`);
|
|
584
|
+
process.exit(1);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
const scopeIdx = argv.indexOf("--scope");
|
|
588
|
+
const scope = scopeIdx !== -1 && argv[scopeIdx + 1] === "project" ? "project" : "global";
|
|
589
|
+
const descIdx = argv.indexOf("--description");
|
|
590
|
+
const description = descIdx !== -1 ? (argv[descIdx + 1] ?? "") : "";
|
|
591
|
+
|
|
592
|
+
const base =
|
|
593
|
+
scope === "project"
|
|
594
|
+
? join(process.cwd(), ".deepccc", "skills")
|
|
595
|
+
: join(homedir(), ".deepccc", "skills");
|
|
596
|
+
const skillPath = join(base, name, "SKILL.md");
|
|
597
|
+
|
|
598
|
+
if (existsSync(skillPath)) {
|
|
599
|
+
console.error(`skill already exists: ${skillPath}`);
|
|
600
|
+
process.exit(1);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
try {
|
|
604
|
+
mkdirSync(join(base, name), { recursive: true });
|
|
605
|
+
writeFileSync(skillPath, buildSkillTemplate(name, description), "utf8");
|
|
606
|
+
} catch (err) {
|
|
607
|
+
console.error(`failed to create skill: ${(err as Error).message}`);
|
|
608
|
+
process.exit(1);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
console.log(`created skill: ${skillPath}`);
|
|
612
|
+
console.log(`scope: ${scope === "project" ? "project(仅当前项目生效)" : "global(所有项目生效)"}`);
|
|
613
|
+
if (description) console.log(`description: ${description}`);
|
|
614
|
+
console.log("hot reload: 下一次对话自动生效,无需重启");
|
|
615
|
+
}
|
|
616
|
+
|
|
565
617
|
async function main(): Promise<void> {
|
|
618
|
+
// skill create 子命令:deepccc skill create <name> [--scope global|project] [--description "..."]
|
|
619
|
+
// 默认创建在全局 ~/.deepccc/skills(Codex 目录结构),--scope project 创建到 <cwd>/.deepccc/skills。
|
|
620
|
+
if (process.argv[2] === "skill" && process.argv[3] === "create") {
|
|
621
|
+
runSkillCreate(process.argv.slice(4));
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
|
|
566
625
|
const args = parseArgs();
|
|
567
626
|
|
|
568
627
|
if (args.streamJson) {
|