ccgateway 0.1.0
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 +252 -0
- package/bin/ccg-dev.sh +3 -0
- package/dist/agents.d.ts +17 -0
- package/dist/agents.d.ts.map +1 -0
- package/dist/agents.js +45 -0
- package/dist/agents.js.map +1 -0
- package/dist/chat.d.ts +14 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +104 -0
- package/dist/chat.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +501 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +53 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +70 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +45 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +201 -0
- package/dist/context.js.map +1 -0
- package/dist/daemon.d.ts +27 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +207 -0
- package/dist/daemon.js.map +1 -0
- package/dist/heartbeat.d.ts +42 -0
- package/dist/heartbeat.d.ts.map +1 -0
- package/dist/heartbeat.js +153 -0
- package/dist/heartbeat.js.map +1 -0
- package/dist/logger.d.ts +15 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +70 -0
- package/dist/logger.js.map +1 -0
- package/dist/messaging.d.ts +43 -0
- package/dist/messaging.d.ts.map +1 -0
- package/dist/messaging.js +132 -0
- package/dist/messaging.js.map +1 -0
- package/dist/migrate.d.ts +24 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/migrate.js +356 -0
- package/dist/migrate.js.map +1 -0
- package/dist/plugin.d.ts +63 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +93 -0
- package/dist/plugin.js.map +1 -0
- package/dist/plugins/discord-gateway.d.ts +32 -0
- package/dist/plugins/discord-gateway.d.ts.map +1 -0
- package/dist/plugins/discord-gateway.js +208 -0
- package/dist/plugins/discord-gateway.js.map +1 -0
- package/dist/plugins/slack-gateway.d.ts +35 -0
- package/dist/plugins/slack-gateway.d.ts.map +1 -0
- package/dist/plugins/slack-gateway.js +291 -0
- package/dist/plugins/slack-gateway.js.map +1 -0
- package/dist/router.d.ts +44 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +103 -0
- package/dist/router.js.map +1 -0
- package/dist/sessions.d.ts +55 -0
- package/dist/sessions.d.ts.map +1 -0
- package/dist/sessions.js +160 -0
- package/dist/sessions.js.map +1 -0
- package/dist/skills.d.ts +58 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +194 -0
- package/dist/skills.js.map +1 -0
- package/dist/spawner.d.ts +29 -0
- package/dist/spawner.d.ts.map +1 -0
- package/dist/spawner.js +54 -0
- package/dist/spawner.js.map +1 -0
- package/dist/types.d.ts +22 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/package.json +48 -0
package/dist/router.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// ── MessageRouter ─────────────────────────────────────────────────────────
|
|
2
|
+
export class MessageRouter {
|
|
3
|
+
agents;
|
|
4
|
+
sessions;
|
|
5
|
+
context;
|
|
6
|
+
spawner;
|
|
7
|
+
bindings;
|
|
8
|
+
constructor(agents, sessions, context, spawner, bindings) {
|
|
9
|
+
this.agents = agents;
|
|
10
|
+
this.sessions = sessions;
|
|
11
|
+
this.context = context;
|
|
12
|
+
this.spawner = spawner;
|
|
13
|
+
this.bindings = bindings;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Resolve which agent handles a message based on bindings.
|
|
17
|
+
* Given (gateway, channelId), find the matching binding and return binding.agent.
|
|
18
|
+
*/
|
|
19
|
+
resolveAgent(gateway, channelId) {
|
|
20
|
+
const binding = this.bindings.find((b) => b.gateway === gateway && b.channel === channelId);
|
|
21
|
+
return binding?.agent;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Full message dispatch pipeline.
|
|
25
|
+
*
|
|
26
|
+
* 1. Look up agent config from registry
|
|
27
|
+
* 2. Derive session key: {agentId}:{gateway}:{channel}
|
|
28
|
+
* 3. Get or create session
|
|
29
|
+
* 4. Append user message to session
|
|
30
|
+
* 5. Build context
|
|
31
|
+
* 6. Spawn claude --print
|
|
32
|
+
* 7. Append assistant response to session (or error on failure)
|
|
33
|
+
* 8. Return response text
|
|
34
|
+
*/
|
|
35
|
+
async route(message) {
|
|
36
|
+
const agentId = message.to.agent;
|
|
37
|
+
// 1. Get agent config
|
|
38
|
+
const agent = this.agents.getAgent(agentId);
|
|
39
|
+
if (!agent) {
|
|
40
|
+
throw new Error(`Agent "${agentId}" not found in registry`);
|
|
41
|
+
}
|
|
42
|
+
// 2. Derive session key
|
|
43
|
+
const sessionKey = this.sessions.getOrCreateSession(agentId, message.from.gateway, message.from.channel);
|
|
44
|
+
// 3. Append user message to session
|
|
45
|
+
await this.sessions.appendMessage(agentId, sessionKey, {
|
|
46
|
+
role: "user",
|
|
47
|
+
content: message.content,
|
|
48
|
+
ts: Date.now(),
|
|
49
|
+
source: message.from.gateway,
|
|
50
|
+
sourceUser: message.from.user,
|
|
51
|
+
sourceMessageId: message.from.messageId,
|
|
52
|
+
});
|
|
53
|
+
// 4. Build context
|
|
54
|
+
const systemPrompt = await this.context.build(agentId, sessionKey);
|
|
55
|
+
// 5. Spawn claude --print
|
|
56
|
+
const result = await this.spawner.spawn({
|
|
57
|
+
workspace: agent.workspace,
|
|
58
|
+
message: message.content,
|
|
59
|
+
systemPrompt,
|
|
60
|
+
model: agent.model,
|
|
61
|
+
allowedTools: agent.allowedTools,
|
|
62
|
+
});
|
|
63
|
+
// 6. Handle failure: append error to session and throw
|
|
64
|
+
if (result.exitCode !== 0) {
|
|
65
|
+
const errorContent = result.response || `Spawner failed with exit code ${result.exitCode}`;
|
|
66
|
+
await this.sessions.appendMessage(agentId, sessionKey, {
|
|
67
|
+
role: "assistant",
|
|
68
|
+
content: `[error] ${errorContent}`,
|
|
69
|
+
ts: Date.now(),
|
|
70
|
+
tokens: result.tokensEstimate,
|
|
71
|
+
});
|
|
72
|
+
throw new Error(`Spawner exited with code ${result.exitCode}: ${errorContent}`);
|
|
73
|
+
}
|
|
74
|
+
// 7. Append assistant response to session
|
|
75
|
+
await this.sessions.appendMessage(agentId, sessionKey, {
|
|
76
|
+
role: "assistant",
|
|
77
|
+
content: result.response,
|
|
78
|
+
ts: Date.now(),
|
|
79
|
+
tokens: result.tokensEstimate,
|
|
80
|
+
});
|
|
81
|
+
// 8. Return response text
|
|
82
|
+
return result.response;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Add a binding at runtime.
|
|
86
|
+
*/
|
|
87
|
+
addBinding(binding) {
|
|
88
|
+
this.bindings.push(binding);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get all bindings for a given agent.
|
|
92
|
+
*/
|
|
93
|
+
getBindingsForAgent(agentId) {
|
|
94
|
+
return this.bindings.filter((b) => b.agent === agentId);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Get the primary (first) binding for an agent.
|
|
98
|
+
*/
|
|
99
|
+
getPrimaryBinding(agentId) {
|
|
100
|
+
return this.bindings.find((b) => b.agent === agentId);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export interface SessionMessage {
|
|
2
|
+
role: "user" | "assistant" | "system";
|
|
3
|
+
content: string;
|
|
4
|
+
ts: number;
|
|
5
|
+
source?: string;
|
|
6
|
+
sourceUser?: string;
|
|
7
|
+
sourceMessageId?: string;
|
|
8
|
+
tokens?: {
|
|
9
|
+
in: number;
|
|
10
|
+
out: number;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export interface SessionInfo {
|
|
14
|
+
agentId: string;
|
|
15
|
+
sessionKey: string;
|
|
16
|
+
messageCount: number;
|
|
17
|
+
lastActivity: number;
|
|
18
|
+
filePath: string;
|
|
19
|
+
}
|
|
20
|
+
export declare class SessionManager {
|
|
21
|
+
private ccgHome;
|
|
22
|
+
constructor(ccgHome: string);
|
|
23
|
+
/**
|
|
24
|
+
* Get or create a session. Returns the session key.
|
|
25
|
+
*/
|
|
26
|
+
getOrCreateSession(agentId: string, source: string, sourceId: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Get the JSONL file path for a session.
|
|
29
|
+
*/
|
|
30
|
+
getSessionPath(agentId: string, sessionKey: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* Append a message to the session JSONL.
|
|
33
|
+
*/
|
|
34
|
+
appendMessage(agentId: string, sessionKey: string, message: SessionMessage): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Read all messages from a session.
|
|
37
|
+
* Returns an empty array for non-existent sessions.
|
|
38
|
+
*/
|
|
39
|
+
readHistory(agentId: string, sessionKey: string): Promise<SessionMessage[]>;
|
|
40
|
+
/**
|
|
41
|
+
* Build windowed history that fits within token budget.
|
|
42
|
+
* Estimates tokens as chars/4. Returns newest messages that fit.
|
|
43
|
+
* tokenBudget defaults to 200000.
|
|
44
|
+
*/
|
|
45
|
+
getWindowedHistory(agentId: string, sessionKey: string, tokenBudget?: number): Promise<SessionMessage[]>;
|
|
46
|
+
/**
|
|
47
|
+
* Reset session: archive current JSONL (rename with timestamp suffix).
|
|
48
|
+
*/
|
|
49
|
+
resetSession(agentId: string, sessionKey: string): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* List all sessions, optionally filtered by agent.
|
|
52
|
+
*/
|
|
53
|
+
listSessions(agentId?: string): Promise<SessionInfo[]>;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=sessions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../src/sessions.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;CACtC;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAqCD,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,MAAM;IAI3B;;OAEG;IACH,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;IAI7E;;OAEG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM;IAU3D;;OAEG;IACG,aAAa,CACjB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,IAAI,CAAC;IAYhB;;;OAGG;IACG,WAAW,CACf,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,cAAc,EAAE,CAAC;IAY5B;;;;OAIG;IACG,kBAAkB,CACtB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,WAAW,GAAE,MAAe,GAC3B,OAAO,CAAC,cAAc,EAAE,CAAC;IAiC5B;;OAEG;IACG,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYtE;;OAEG;IACG,YAAY,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;CA8C7D"}
|
package/dist/sessions.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { appendFile, readFile, rename, readdir, mkdir, stat } from "node:fs/promises";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
5
|
+
/**
|
|
6
|
+
* Convert a session key (with colons) to a filesystem-safe filename stem.
|
|
7
|
+
* e.g. "salt:discord:1465736400014938230" → "salt-discord-1465736400014938230"
|
|
8
|
+
*/
|
|
9
|
+
function keyToFilename(sessionKey) {
|
|
10
|
+
return sessionKey.replace(/:/g, "-") + ".jsonl";
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Convert a filesystem filename back to a session key.
|
|
14
|
+
* e.g. "salt-discord-1465736400014938230.jsonl" → "salt:discord:1465736400014938230"
|
|
15
|
+
* The key format is {agentId}:{source}:{sourceId}. We know the agentId from context,
|
|
16
|
+
* so we can reconstruct by replacing the first two dashes with colons.
|
|
17
|
+
*/
|
|
18
|
+
function filenameToKey(filename) {
|
|
19
|
+
const stem = filename.replace(/\.jsonl$/, "");
|
|
20
|
+
// Replace first dash with colon, then second dash with colon.
|
|
21
|
+
// The sourceId portion may itself contain dashes, so only replace first two.
|
|
22
|
+
const first = stem.indexOf("-");
|
|
23
|
+
if (first === -1)
|
|
24
|
+
return stem;
|
|
25
|
+
const second = stem.indexOf("-", first + 1);
|
|
26
|
+
if (second === -1)
|
|
27
|
+
return stem;
|
|
28
|
+
return (stem.slice(0, first) +
|
|
29
|
+
":" +
|
|
30
|
+
stem.slice(first + 1, second) +
|
|
31
|
+
":" +
|
|
32
|
+
stem.slice(second + 1));
|
|
33
|
+
}
|
|
34
|
+
// ── SessionManager ─────────────────────────────────────────────────────────
|
|
35
|
+
export class SessionManager {
|
|
36
|
+
ccgHome;
|
|
37
|
+
constructor(ccgHome) {
|
|
38
|
+
this.ccgHome = ccgHome;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get or create a session. Returns the session key.
|
|
42
|
+
*/
|
|
43
|
+
getOrCreateSession(agentId, source, sourceId) {
|
|
44
|
+
return `${agentId}:${source}:${sourceId}`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get the JSONL file path for a session.
|
|
48
|
+
*/
|
|
49
|
+
getSessionPath(agentId, sessionKey) {
|
|
50
|
+
return join(this.ccgHome, "agents", agentId, "sessions", keyToFilename(sessionKey));
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Append a message to the session JSONL.
|
|
54
|
+
*/
|
|
55
|
+
async appendMessage(agentId, sessionKey, message) {
|
|
56
|
+
const filePath = this.getSessionPath(agentId, sessionKey);
|
|
57
|
+
const dir = join(this.ccgHome, "agents", agentId, "sessions");
|
|
58
|
+
if (!existsSync(dir)) {
|
|
59
|
+
await mkdir(dir, { recursive: true });
|
|
60
|
+
}
|
|
61
|
+
const line = JSON.stringify(message) + "\n";
|
|
62
|
+
await appendFile(filePath, line, "utf-8");
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Read all messages from a session.
|
|
66
|
+
* Returns an empty array for non-existent sessions.
|
|
67
|
+
*/
|
|
68
|
+
async readHistory(agentId, sessionKey) {
|
|
69
|
+
const filePath = this.getSessionPath(agentId, sessionKey);
|
|
70
|
+
if (!existsSync(filePath)) {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
const raw = await readFile(filePath, "utf-8");
|
|
74
|
+
const lines = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
75
|
+
return lines.map((line) => JSON.parse(line));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Build windowed history that fits within token budget.
|
|
79
|
+
* Estimates tokens as chars/4. Returns newest messages that fit.
|
|
80
|
+
* tokenBudget defaults to 200000.
|
|
81
|
+
*/
|
|
82
|
+
async getWindowedHistory(agentId, sessionKey, tokenBudget = 200000) {
|
|
83
|
+
const messages = await this.readHistory(agentId, sessionKey);
|
|
84
|
+
if (messages.length === 0) {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
// Always keep at least the most recent message
|
|
88
|
+
const result = [];
|
|
89
|
+
let totalTokens = 0;
|
|
90
|
+
// Walk backwards from newest to oldest
|
|
91
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
92
|
+
const msgTokens = Math.ceil(messages[i].content.length / 4);
|
|
93
|
+
if (result.length === 0) {
|
|
94
|
+
// Always include the most recent message
|
|
95
|
+
result.unshift(messages[i]);
|
|
96
|
+
totalTokens += msgTokens;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (totalTokens + msgTokens <= tokenBudget) {
|
|
100
|
+
result.unshift(messages[i]);
|
|
101
|
+
totalTokens += msgTokens;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Reset session: archive current JSONL (rename with timestamp suffix).
|
|
111
|
+
*/
|
|
112
|
+
async resetSession(agentId, sessionKey) {
|
|
113
|
+
const filePath = this.getSessionPath(agentId, sessionKey);
|
|
114
|
+
if (!existsSync(filePath)) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const timestamp = Date.now();
|
|
118
|
+
const archivePath = filePath.replace(/\.jsonl$/, `.${timestamp}.jsonl`);
|
|
119
|
+
await rename(filePath, archivePath);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* List all sessions, optionally filtered by agent.
|
|
123
|
+
*/
|
|
124
|
+
async listSessions(agentId) {
|
|
125
|
+
const agentsDir = join(this.ccgHome, "agents");
|
|
126
|
+
const results = [];
|
|
127
|
+
if (!existsSync(agentsDir)) {
|
|
128
|
+
return results;
|
|
129
|
+
}
|
|
130
|
+
const agentDirs = agentId ? [agentId] : await readdir(agentsDir);
|
|
131
|
+
for (const agent of agentDirs) {
|
|
132
|
+
const sessionsDir = join(agentsDir, agent, "sessions");
|
|
133
|
+
if (!existsSync(sessionsDir)) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const files = await readdir(sessionsDir);
|
|
137
|
+
for (const file of files) {
|
|
138
|
+
// Only match active session files (not archived ones with timestamp suffix)
|
|
139
|
+
if (!file.endsWith(".jsonl") || /\.\d+\.jsonl$/.test(file)) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const filePath = join(sessionsDir, file);
|
|
143
|
+
const sessionKey = filenameToKey(file);
|
|
144
|
+
const messages = await this.readHistory(agent, sessionKey);
|
|
145
|
+
const lastActivity = messages.length > 0
|
|
146
|
+
? messages[messages.length - 1].ts
|
|
147
|
+
: (await stat(filePath)).mtimeMs;
|
|
148
|
+
results.push({
|
|
149
|
+
agentId: agent,
|
|
150
|
+
sessionKey,
|
|
151
|
+
messageCount: messages.length,
|
|
152
|
+
lastActivity,
|
|
153
|
+
filePath,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return results;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=sessions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.js","sourceRoot":"","sources":["../src/sessions.ts"],"names":[],"mappings":"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"}
|
package/dist/skills.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export interface SkillDefinition {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
type: "markdown" | "native";
|
|
5
|
+
filePath?: string;
|
|
6
|
+
agentId?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Parse YAML frontmatter from a markdown skill file.
|
|
10
|
+
* Expects `---` delimiters at the top with `name:` and `description:` fields.
|
|
11
|
+
* Simple parser — no yaml dependency.
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseFrontmatter(content: string): {
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
} | null;
|
|
17
|
+
export declare class SkillManager {
|
|
18
|
+
private ccgHome;
|
|
19
|
+
constructor(ccgHome: string);
|
|
20
|
+
private sharedSkillsDir;
|
|
21
|
+
private agentSkillsDir;
|
|
22
|
+
/**
|
|
23
|
+
* Discover all skills: shared + agent-specific.
|
|
24
|
+
* Agent-specific skills override shared skills with the same name.
|
|
25
|
+
*/
|
|
26
|
+
discoverSkills(agentId?: string): Promise<SkillDefinition[]>;
|
|
27
|
+
/**
|
|
28
|
+
* Read a markdown skill's full content.
|
|
29
|
+
* Checks agent-specific dir first (if agentId provided), then shared.
|
|
30
|
+
*/
|
|
31
|
+
readSkill(name: string, agentId?: string): Promise<string | null>;
|
|
32
|
+
/**
|
|
33
|
+
* Build skill index for context injection (name + description per skill).
|
|
34
|
+
*/
|
|
35
|
+
buildSkillIndex(agentId?: string): Promise<string>;
|
|
36
|
+
/**
|
|
37
|
+
* Add a skill (copy .md file to shared or agent-specific directory).
|
|
38
|
+
*/
|
|
39
|
+
addSkill(filePath: string, agentId?: string): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Remove a skill.
|
|
42
|
+
*/
|
|
43
|
+
removeSkill(name: string, agentId?: string): Promise<boolean>;
|
|
44
|
+
/**
|
|
45
|
+
* List skills.
|
|
46
|
+
*/
|
|
47
|
+
listSkills(agentId?: string): Promise<SkillDefinition[]>;
|
|
48
|
+
/**
|
|
49
|
+
* Load all markdown skills from a directory.
|
|
50
|
+
*/
|
|
51
|
+
private loadSkillsFromDir;
|
|
52
|
+
/**
|
|
53
|
+
* Find a skill file in a directory by skill name.
|
|
54
|
+
* Reads frontmatter of each .md file to match by name.
|
|
55
|
+
*/
|
|
56
|
+
private findSkillFile;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=skills.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,UAAU,GAAG,QAAQ,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAID;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,GACd;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAoC9C;AAID,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,MAAM;IAM3B,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,cAAc;IAMtB;;;OAGG;IACG,cAAc,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAuBlE;;;OAGG;IACG,SAAS,CACb,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAqBzB;;OAEG;IACG,eAAe,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAexD;;OAEG;IACG,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcjE;;OAEG;IACG,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAYnE;;OAEG;IACG,UAAU,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAM9D;;OAEG;YACW,iBAAiB;IAiC/B;;;OAGG;YACW,aAAa;CAsB5B"}
|
package/dist/skills.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { readFile, readdir, mkdir, copyFile, unlink } from "node:fs/promises";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { join, basename } from "node:path";
|
|
4
|
+
// ── Frontmatter parser ──────────────────────────────────────────────────────
|
|
5
|
+
/**
|
|
6
|
+
* Parse YAML frontmatter from a markdown skill file.
|
|
7
|
+
* Expects `---` delimiters at the top with `name:` and `description:` fields.
|
|
8
|
+
* Simple parser — no yaml dependency.
|
|
9
|
+
*/
|
|
10
|
+
export function parseFrontmatter(content) {
|
|
11
|
+
const lines = content.split("\n");
|
|
12
|
+
// Must start with ---
|
|
13
|
+
if (lines[0]?.trim() !== "---")
|
|
14
|
+
return null;
|
|
15
|
+
let endIndex = -1;
|
|
16
|
+
for (let i = 1; i < lines.length; i++) {
|
|
17
|
+
if (lines[i].trim() === "---") {
|
|
18
|
+
endIndex = i;
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (endIndex === -1)
|
|
23
|
+
return null;
|
|
24
|
+
let name = "";
|
|
25
|
+
let description = "";
|
|
26
|
+
for (let i = 1; i < endIndex; i++) {
|
|
27
|
+
const line = lines[i];
|
|
28
|
+
const nameMatch = line.match(/^name:\s*(.+)/);
|
|
29
|
+
if (nameMatch) {
|
|
30
|
+
name = nameMatch[1].trim();
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const descMatch = line.match(/^description:\s*(.+)/);
|
|
34
|
+
if (descMatch) {
|
|
35
|
+
description = descMatch[1].trim();
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (!name)
|
|
40
|
+
return null;
|
|
41
|
+
return { name, description };
|
|
42
|
+
}
|
|
43
|
+
// ── SkillManager ────────────────────────────────────────────────────────────
|
|
44
|
+
export class SkillManager {
|
|
45
|
+
ccgHome;
|
|
46
|
+
constructor(ccgHome) {
|
|
47
|
+
this.ccgHome = ccgHome;
|
|
48
|
+
}
|
|
49
|
+
// ── Directory helpers ───────────────────────────────────────────────────
|
|
50
|
+
sharedSkillsDir() {
|
|
51
|
+
return join(this.ccgHome, "skills");
|
|
52
|
+
}
|
|
53
|
+
agentSkillsDir(agentId) {
|
|
54
|
+
return join(this.ccgHome, "agents", agentId, "skills");
|
|
55
|
+
}
|
|
56
|
+
// ── Core methods ────────────────────────────────────────────────────────
|
|
57
|
+
/**
|
|
58
|
+
* Discover all skills: shared + agent-specific.
|
|
59
|
+
* Agent-specific skills override shared skills with the same name.
|
|
60
|
+
*/
|
|
61
|
+
async discoverSkills(agentId) {
|
|
62
|
+
const skillMap = new Map();
|
|
63
|
+
// Load shared skills first
|
|
64
|
+
const sharedSkills = await this.loadSkillsFromDir(this.sharedSkillsDir());
|
|
65
|
+
for (const skill of sharedSkills) {
|
|
66
|
+
skillMap.set(skill.name, skill);
|
|
67
|
+
}
|
|
68
|
+
// Load agent-specific skills (override shared)
|
|
69
|
+
if (agentId) {
|
|
70
|
+
const agentSkills = await this.loadSkillsFromDir(this.agentSkillsDir(agentId), agentId);
|
|
71
|
+
for (const skill of agentSkills) {
|
|
72
|
+
skillMap.set(skill.name, skill);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return Array.from(skillMap.values());
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Read a markdown skill's full content.
|
|
79
|
+
* Checks agent-specific dir first (if agentId provided), then shared.
|
|
80
|
+
*/
|
|
81
|
+
async readSkill(name, agentId) {
|
|
82
|
+
// Check agent-specific first
|
|
83
|
+
if (agentId) {
|
|
84
|
+
const agentPath = await this.findSkillFile(this.agentSkillsDir(agentId), name);
|
|
85
|
+
if (agentPath) {
|
|
86
|
+
return readFile(agentPath, "utf-8");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Fall back to shared
|
|
90
|
+
const sharedPath = await this.findSkillFile(this.sharedSkillsDir(), name);
|
|
91
|
+
if (sharedPath) {
|
|
92
|
+
return readFile(sharedPath, "utf-8");
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Build skill index for context injection (name + description per skill).
|
|
98
|
+
*/
|
|
99
|
+
async buildSkillIndex(agentId) {
|
|
100
|
+
const skills = await this.discoverSkills(agentId);
|
|
101
|
+
if (skills.length === 0) {
|
|
102
|
+
return "--- Available Skills ---\n(none)";
|
|
103
|
+
}
|
|
104
|
+
const lines = skills.map((s) => {
|
|
105
|
+
const tag = s.type === "native" ? " [native]" : "";
|
|
106
|
+
return `- ${s.name}: ${s.description}${tag}`;
|
|
107
|
+
});
|
|
108
|
+
return `--- Available Skills ---\n${lines.join("\n")}`;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Add a skill (copy .md file to shared or agent-specific directory).
|
|
112
|
+
*/
|
|
113
|
+
async addSkill(filePath, agentId) {
|
|
114
|
+
const targetDir = agentId
|
|
115
|
+
? this.agentSkillsDir(agentId)
|
|
116
|
+
: this.sharedSkillsDir();
|
|
117
|
+
if (!existsSync(targetDir)) {
|
|
118
|
+
await mkdir(targetDir, { recursive: true });
|
|
119
|
+
}
|
|
120
|
+
const fileName = basename(filePath);
|
|
121
|
+
const targetPath = join(targetDir, fileName);
|
|
122
|
+
await copyFile(filePath, targetPath);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Remove a skill.
|
|
126
|
+
*/
|
|
127
|
+
async removeSkill(name, agentId) {
|
|
128
|
+
const targetDir = agentId
|
|
129
|
+
? this.agentSkillsDir(agentId)
|
|
130
|
+
: this.sharedSkillsDir();
|
|
131
|
+
const filePath = await this.findSkillFile(targetDir, name);
|
|
132
|
+
if (!filePath)
|
|
133
|
+
return false;
|
|
134
|
+
await unlink(filePath);
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* List skills.
|
|
139
|
+
*/
|
|
140
|
+
async listSkills(agentId) {
|
|
141
|
+
return this.discoverSkills(agentId);
|
|
142
|
+
}
|
|
143
|
+
// ── Private helpers ─────────────────────────────────────────────────────
|
|
144
|
+
/**
|
|
145
|
+
* Load all markdown skills from a directory.
|
|
146
|
+
*/
|
|
147
|
+
async loadSkillsFromDir(dir, agentId) {
|
|
148
|
+
if (!existsSync(dir))
|
|
149
|
+
return [];
|
|
150
|
+
const entries = await readdir(dir);
|
|
151
|
+
const skills = [];
|
|
152
|
+
for (const entry of entries) {
|
|
153
|
+
if (!entry.endsWith(".md"))
|
|
154
|
+
continue;
|
|
155
|
+
const filePath = join(dir, entry);
|
|
156
|
+
const content = await readFile(filePath, "utf-8");
|
|
157
|
+
const meta = parseFrontmatter(content);
|
|
158
|
+
if (meta) {
|
|
159
|
+
const skill = {
|
|
160
|
+
name: meta.name,
|
|
161
|
+
description: meta.description,
|
|
162
|
+
type: "markdown",
|
|
163
|
+
filePath,
|
|
164
|
+
};
|
|
165
|
+
if (agentId) {
|
|
166
|
+
skill.agentId = agentId;
|
|
167
|
+
}
|
|
168
|
+
skills.push(skill);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return skills;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Find a skill file in a directory by skill name.
|
|
175
|
+
* Reads frontmatter of each .md file to match by name.
|
|
176
|
+
*/
|
|
177
|
+
async findSkillFile(dir, name) {
|
|
178
|
+
if (!existsSync(dir))
|
|
179
|
+
return null;
|
|
180
|
+
const entries = await readdir(dir);
|
|
181
|
+
for (const entry of entries) {
|
|
182
|
+
if (!entry.endsWith(".md"))
|
|
183
|
+
continue;
|
|
184
|
+
const filePath = join(dir, entry);
|
|
185
|
+
const content = await readFile(filePath, "utf-8");
|
|
186
|
+
const meta = parseFrontmatter(content);
|
|
187
|
+
if (meta && meta.name === name) {
|
|
188
|
+
return filePath;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=skills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.js","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface SpawnResult {
|
|
2
|
+
response: string;
|
|
3
|
+
exitCode: number;
|
|
4
|
+
tokensEstimate: {
|
|
5
|
+
in: number;
|
|
6
|
+
out: number;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export interface SpawnOptions {
|
|
10
|
+
workspace: string;
|
|
11
|
+
message: string;
|
|
12
|
+
systemPrompt: string;
|
|
13
|
+
model: string;
|
|
14
|
+
allowedTools: string[];
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
}
|
|
17
|
+
export declare class CCSpawner {
|
|
18
|
+
/**
|
|
19
|
+
* Spawn a `claude --print` invocation.
|
|
20
|
+
*
|
|
21
|
+
* Executes:
|
|
22
|
+
* claude --print -p "<message>" --append-system-prompt "<context>"
|
|
23
|
+
* --model <model> --allowedTools Tool1,Tool2,...
|
|
24
|
+
*
|
|
25
|
+
* Captures stdout as the response. Estimates tokens as chars / 4.
|
|
26
|
+
*/
|
|
27
|
+
spawn(options: SpawnOptions): Promise<SpawnResult>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=spawner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawner.d.ts","sourceRoot":"","sources":["../src/spawner.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7C;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAMD,qBAAa,SAAS;IACpB;;;;;;;;OAQG;IACG,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC;CAwDzD"}
|