chatccc 0.2.27 → 0.2.29
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/im-skills/feishu-skill/{send-file.md → receive-send-file.md} +10 -5
- package/im-skills/feishu-skill/{send-image.md → receive-send-image.md} +10 -5
- package/im-skills/feishu-skill/skill.md +3 -2
- package/package.json +1 -1
- package/src/__tests__/im-skills.test.ts +4 -8
- package/src/agent-grants-rpc.ts +71 -0
- package/src/index.ts +2 -1
- package/src/session.ts +5 -3
|
@@ -4,17 +4,22 @@
|
|
|
4
4
|
|
|
5
5
|
Videos are sent as regular files (not media), which looks cleaner in Feishu.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
First, query the current send token:
|
|
8
|
+
```bash
|
|
9
|
+
curl "{{session_grants_url}}?sid={{session_id}}"
|
|
10
|
+
```
|
|
8
11
|
|
|
12
|
+
Then send with the returned url and token:
|
|
13
|
+
|
|
14
|
+
### Script (recommended)
|
|
9
15
|
```bash
|
|
10
|
-
node "{{send_file_script}}" --url
|
|
16
|
+
node "{{send_file_script}}" --url <url_from_query> --token <token_from_query> --path "<absolute file path>" --caption "<optional caption>"
|
|
11
17
|
```
|
|
12
18
|
|
|
13
19
|
### Direct HTTP
|
|
14
|
-
|
|
15
20
|
```http
|
|
16
|
-
POST
|
|
17
|
-
Authorization: Bearer
|
|
21
|
+
POST <url_from_query>
|
|
22
|
+
Authorization: Bearer <token_from_query>
|
|
18
23
|
Content-Type: application/json; charset=utf-8
|
|
19
24
|
|
|
20
25
|
{"path":"<absolute file path>","caption":"<optional caption>"}
|
|
@@ -2,17 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## Send Images
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
First, query the current send token:
|
|
6
|
+
```bash
|
|
7
|
+
curl "{{session_grants_url}}?sid={{session_id}}"
|
|
8
|
+
```
|
|
6
9
|
|
|
10
|
+
Then send with the returned url and token:
|
|
11
|
+
|
|
12
|
+
### Script (recommended)
|
|
7
13
|
```bash
|
|
8
|
-
node "{{send_image_script}}" --url
|
|
14
|
+
node "{{send_image_script}}" --url <url_from_query> --token <token_from_query> --path "<absolute image path>" --caption "<optional caption>"
|
|
9
15
|
```
|
|
10
16
|
|
|
11
17
|
### Direct HTTP
|
|
12
|
-
|
|
13
18
|
```http
|
|
14
|
-
POST
|
|
15
|
-
Authorization: Bearer
|
|
19
|
+
POST <url_from_query>
|
|
20
|
+
Authorization: Bearer <token_from_query>
|
|
16
21
|
Content-Type: application/json; charset=utf-8
|
|
17
22
|
|
|
18
23
|
{"path":"<absolute image path>","caption":"<optional caption>"}
|
|
@@ -7,5 +7,6 @@ Current working directory: {{cwd}}
|
|
|
7
7
|
|
|
8
8
|
Use local endpoints instead of calling Feishu Open Platform directly.
|
|
9
9
|
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
10
|
+
- **Get send tokens**: `GET {{session_grants_url}}?sid={{session_id}}` — returns `{ image: {url,token}, file: {url,token} }`
|
|
11
|
+
- **Images** (send & receive): read `{{im_skills_cache_dir}}/feishu-skill/receive-send-image.md`
|
|
12
|
+
- **Files & Videos** (send & download): read `{{im_skills_cache_dir}}/feishu-skill/receive-send-file.md`
|
package/package.json
CHANGED
|
@@ -23,9 +23,7 @@ describe("IM skills prompt rendering", () => {
|
|
|
23
23
|
"---",
|
|
24
24
|
"name: feishu-skill",
|
|
25
25
|
"---",
|
|
26
|
-
"
|
|
27
|
-
"Authorization: Bearer {{send_image_token}}",
|
|
28
|
-
"Content-Type: application/json; charset=utf-8",
|
|
26
|
+
"GET {{session_grants_url}}?sid={{session_id}}",
|
|
29
27
|
"cwd={{cwd}}",
|
|
30
28
|
].join("\n"),
|
|
31
29
|
"utf-8",
|
|
@@ -35,15 +33,13 @@ describe("IM skills prompt rendering", () => {
|
|
|
35
33
|
skillsDir: tempRoot,
|
|
36
34
|
variables: {
|
|
37
35
|
cwd: "C:/work",
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
session_grants_url: "http://127.0.0.1:18080/api/agent/session-grants",
|
|
37
|
+
session_id: "sid_test",
|
|
40
38
|
},
|
|
41
39
|
});
|
|
42
40
|
|
|
43
41
|
expect(prompt).toContain("[ChatCCC IM skill: feishu-skill]");
|
|
44
|
-
expect(prompt).toContain("
|
|
45
|
-
expect(prompt).toContain("Authorization: Bearer tok_test");
|
|
46
|
-
expect(prompt).toContain("Content-Type: application/json; charset=utf-8");
|
|
42
|
+
expect(prompt).toContain("GET http://127.0.0.1:18080/api/agent/session-grants?sid=sid_test");
|
|
47
43
|
expect(prompt).toContain("cwd=C:/work");
|
|
48
44
|
expect(prompt).not.toContain("{{");
|
|
49
45
|
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
|
|
3
|
+
import type { AgentImageGrant } from "./agent-image-rpc.ts";
|
|
4
|
+
import type { AgentFileGrant } from "./agent-file-rpc.ts";
|
|
5
|
+
import { ts } from "./config.ts";
|
|
6
|
+
|
|
7
|
+
export const AGENT_SESSION_GRANTS_PATH = "/api/agent/session-grants";
|
|
8
|
+
|
|
9
|
+
interface SessionGrantsEntry {
|
|
10
|
+
image: AgentImageGrant;
|
|
11
|
+
file: AgentFileGrant;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const sessionGrants = new Map<string, SessionGrantsEntry>();
|
|
15
|
+
|
|
16
|
+
export function setSessionGrants(
|
|
17
|
+
sessionId: string,
|
|
18
|
+
imageGrant: AgentImageGrant,
|
|
19
|
+
fileGrant: AgentFileGrant,
|
|
20
|
+
): void {
|
|
21
|
+
sessionGrants.set(sessionId, { image: imageGrant, file: fileGrant });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function clearSessionGrants(sessionId: string): void {
|
|
25
|
+
sessionGrants.delete(sessionId);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function jsonReply(res: ServerResponse, status: number, data: unknown): void {
|
|
29
|
+
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
|
|
30
|
+
res.end(JSON.stringify(data));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function handleAgentGrantsRequest(
|
|
34
|
+
req: IncomingMessage,
|
|
35
|
+
res: ServerResponse,
|
|
36
|
+
): Promise<boolean> {
|
|
37
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
38
|
+
if (url.pathname !== AGENT_SESSION_GRANTS_PATH) return false;
|
|
39
|
+
|
|
40
|
+
if (req.method !== "GET") {
|
|
41
|
+
jsonReply(res, 405, { ok: false, error: "Method not allowed" });
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const sid = url.searchParams.get("sid");
|
|
46
|
+
if (!sid) {
|
|
47
|
+
jsonReply(res, 400, { ok: false, error: "Missing sid parameter" });
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const entry = sessionGrants.get(sid);
|
|
52
|
+
if (!entry) {
|
|
53
|
+
jsonReply(res, 404, { ok: false, error: "No active grants for this session" });
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const now = Date.now();
|
|
58
|
+
if (entry.image.expiresAt <= now || entry.file.expiresAt <= now) {
|
|
59
|
+
sessionGrants.delete(sid);
|
|
60
|
+
jsonReply(res, 410, { ok: false, error: "Session grants expired" });
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log(`[${ts()}] [AGENT-GRANTS] query session=${sid}`);
|
|
65
|
+
jsonReply(res, 200, {
|
|
66
|
+
ok: true,
|
|
67
|
+
image: { url: entry.image.url, token: entry.image.token },
|
|
68
|
+
file: { url: entry.file.url, token: entry.file.token },
|
|
69
|
+
});
|
|
70
|
+
return true;
|
|
71
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -84,6 +84,7 @@ import { buildHelpCard, buildStatusCard, buildProgressCard, buildCdContent, buil
|
|
|
84
84
|
import { updateCardKitCard } from "./cardkit.ts";
|
|
85
85
|
import { handleAgentImageRequest } from "./agent-image-rpc.ts";
|
|
86
86
|
import { handleAgentFileRequest } from "./agent-file-rpc.ts";
|
|
87
|
+
import { handleAgentGrantsRequest } from "./agent-grants-rpc.ts";
|
|
87
88
|
import { formatGitResult, gitResultHeaderTemplate, runGitCommand } from "./git-command.ts";
|
|
88
89
|
import {
|
|
89
90
|
MAX_PROCESSED,
|
|
@@ -1058,7 +1059,7 @@ async function main(): Promise<void> {
|
|
|
1058
1059
|
});
|
|
1059
1060
|
});
|
|
1060
1061
|
setExtraApiHandler(async (req, res) => {
|
|
1061
|
-
return (await handleAgentImageRequest(req, res)) || (await handleAgentFileRequest(req, res));
|
|
1062
|
+
return (await handleAgentGrantsRequest(req, res)) || (await handleAgentImageRequest(req, res)) || (await handleAgentFileRequest(req, res));
|
|
1062
1063
|
});
|
|
1063
1064
|
|
|
1064
1065
|
console.log(`[启动 2/7] 环境与凭证检查`);
|
package/src/session.ts
CHANGED
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
createAgentFileGrant,
|
|
41
41
|
revokeAgentFileGrant,
|
|
42
42
|
} from "./agent-file-rpc.ts";
|
|
43
|
+
import { setSessionGrants, clearSessionGrants, AGENT_SESSION_GRANTS_PATH } from "./agent-grants-rpc.ts";
|
|
43
44
|
import { buildImSkillsPrompt, exportSkillSubDocs } from "./im-skills.ts";
|
|
44
45
|
|
|
45
46
|
// ---------------------------------------------------------------------------
|
|
@@ -341,15 +342,15 @@ export async function resumeAndPrompt(
|
|
|
341
342
|
const imSkillsCacheDir = join(USER_DATA_DIR, "im-skills");
|
|
342
343
|
const skillVariables = {
|
|
343
344
|
cwd,
|
|
345
|
+
session_id: sessionId,
|
|
344
346
|
im_skills_cache_dir: imSkillsCacheDir,
|
|
347
|
+
session_grants_url: `http://127.0.0.1:${CHATCCC_PORT}${AGENT_SESSION_GRANTS_PATH}`,
|
|
345
348
|
send_image_url: imageGrant.url,
|
|
346
|
-
send_image_token: imageGrant.token,
|
|
347
|
-
send_file_url: fileGrant.url,
|
|
348
|
-
send_file_token: fileGrant.token,
|
|
349
349
|
send_image_script: join(feishuSkillDir, "send-image.mjs"),
|
|
350
350
|
send_file_script: join(feishuSkillDir, "send-file.mjs"),
|
|
351
351
|
download_video_script: join(feishuSkillDir, "download-video.mjs"),
|
|
352
352
|
};
|
|
353
|
+
setSessionGrants(sessionId, imageGrant, fileGrant);
|
|
353
354
|
const imSkillsPrompt = await buildImSkillsPrompt({ variables: skillVariables });
|
|
354
355
|
// 渲染子文档到缓存目录,供 Agent 按需读取
|
|
355
356
|
await exportSkillSubDocs({ variables: skillVariables }, imSkillsCacheDir);
|
|
@@ -516,6 +517,7 @@ export async function resumeAndPrompt(
|
|
|
516
517
|
if (sendInterval) clearInterval(sendInterval);
|
|
517
518
|
revokeAgentImageGrant(imageGrant.token);
|
|
518
519
|
revokeAgentFileGrant(fileGrant.token);
|
|
520
|
+
clearSessionGrants(sessionId);
|
|
519
521
|
}
|
|
520
522
|
|
|
521
523
|
const cEntry = chatSessionMap.get(chatId);
|