@ct-agents/prompts 0.1.1 → 0.1.3
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/package.json +3 -3
- package/src/load-skill.ts +0 -59
- package/src/postgres-skill-store.ts +33 -15
- package/src/testing.ts +36 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ct-agents/prompts",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"src"
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"zod": "4.4.3",
|
|
14
|
-
"@ct-agents/protocol": "0.1.
|
|
15
|
-
"@ct-agents/store": "0.1.
|
|
14
|
+
"@ct-agents/protocol": "0.1.3",
|
|
15
|
+
"@ct-agents/store": "0.1.3"
|
|
16
16
|
},
|
|
17
17
|
"publishConfig": {
|
|
18
18
|
"access": "public"
|
package/src/load-skill.ts
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type {
|
|
3
3
|
HarnessConfig,
|
|
4
|
-
HarnessConfigStore,
|
|
5
4
|
RuntimeSkillView,
|
|
6
5
|
SkillResource,
|
|
7
|
-
SkillStore,
|
|
8
|
-
SkillView,
|
|
9
6
|
ToolDescriptor,
|
|
10
7
|
ToolDescriptorContext,
|
|
11
8
|
ToolHandler,
|
|
@@ -92,62 +89,6 @@ export function resolveHarnessSkillNames(harness: HarnessConfig) {
|
|
|
92
89
|
return skillNames;
|
|
93
90
|
}
|
|
94
91
|
|
|
95
|
-
async function readHarnessConfig(input: {
|
|
96
|
-
harnessStore: HarnessConfigStore;
|
|
97
|
-
appId?: string;
|
|
98
|
-
harnessId: string;
|
|
99
|
-
harnessVersion: number;
|
|
100
|
-
}) {
|
|
101
|
-
return input.harnessStore.get({
|
|
102
|
-
appId: input.appId,
|
|
103
|
-
id: input.harnessId,
|
|
104
|
-
version: input.harnessVersion,
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/** 读取 harness 关联且当前启用的 Skill 目录(仅元数据,不含 instructions)。 */
|
|
109
|
-
export async function readAvailableSkills(input: {
|
|
110
|
-
skillStore: SkillStore;
|
|
111
|
-
skillNames: string[];
|
|
112
|
-
}) {
|
|
113
|
-
const enabledSkills = await input.skillStore.list();
|
|
114
|
-
const enabledByName = new Map(enabledSkills.map((skill) => [skill.name, skill]));
|
|
115
|
-
return input.skillNames
|
|
116
|
-
.map((skillName) => enabledByName.get(skillName))
|
|
117
|
-
.filter((skill): skill is SkillView => Boolean(skill))
|
|
118
|
-
.map(toCatalogItem);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* 构造 harness 关联 Skill 的 markdown 目录,供 {@link createLoadSkillToolHandler} 注入 description。
|
|
123
|
-
*
|
|
124
|
-
* 只列关联且启用的 Skill;读取失败或无可用 Skill 时返回空串,让调用方回退静态 description。
|
|
125
|
-
*/
|
|
126
|
-
export async function buildHarnessSkillCatalogMarkdown(input: {
|
|
127
|
-
harnessStore: HarnessConfigStore;
|
|
128
|
-
skillStore: SkillStore;
|
|
129
|
-
appId?: string;
|
|
130
|
-
harnessId: string;
|
|
131
|
-
harnessVersion: number;
|
|
132
|
-
}): Promise<string> {
|
|
133
|
-
const harnessConfig = await readHarnessConfig(input);
|
|
134
|
-
if (!harnessConfig) {
|
|
135
|
-
return '';
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const items = await readAvailableSkills({
|
|
139
|
-
skillStore: input.skillStore,
|
|
140
|
-
skillNames: resolveHarnessSkillNames(harnessConfig),
|
|
141
|
-
});
|
|
142
|
-
if (items.length === 0) {
|
|
143
|
-
return '';
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return items
|
|
147
|
-
.map((item) => `- ${item.name}:${item.description}`)
|
|
148
|
-
.join('\n');
|
|
149
|
-
}
|
|
150
|
-
|
|
151
92
|
function warning(message: string): LoadSkillToolResult {
|
|
152
93
|
return { status: 'warning', message };
|
|
153
94
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SqlPool } from '@ct-agents/store';
|
|
2
2
|
import type {
|
|
3
3
|
CreateSkillInput,
|
|
4
|
+
SkillKey,
|
|
4
5
|
SkillListQuery,
|
|
5
6
|
SkillStore,
|
|
6
7
|
SkillView,
|
|
@@ -18,6 +19,13 @@ type SkillRow = {
|
|
|
18
19
|
updated_at?: string | Date | null;
|
|
19
20
|
};
|
|
20
21
|
|
|
22
|
+
function isUniqueViolation(error: unknown) {
|
|
23
|
+
return error !== null
|
|
24
|
+
&& typeof error === 'object'
|
|
25
|
+
&& 'code' in error
|
|
26
|
+
&& error.code === '23505';
|
|
27
|
+
}
|
|
28
|
+
|
|
21
29
|
function toIsoString(value: string | Date | null | undefined) {
|
|
22
30
|
if (!value) return undefined;
|
|
23
31
|
return value instanceof Date ? value.toISOString() : value;
|
|
@@ -46,15 +54,19 @@ export class PostgresSkillStore implements SkillStore {
|
|
|
46
54
|
// 指定 appId 时按 app 精确过滤,includeGlobal 时额外纳入 global(app_id is null)。
|
|
47
55
|
const conditions = ['enabled = true'];
|
|
48
56
|
const params: unknown[] = [];
|
|
49
|
-
if (query?.appId) {
|
|
57
|
+
if (query?.appId === null) {
|
|
58
|
+
conditions.push('app_id is null');
|
|
59
|
+
} else if (query?.appId) {
|
|
50
60
|
params.push(query.appId);
|
|
51
61
|
conditions.push(query.includeGlobal ? '(app_id = $1 or app_id is null)' : 'app_id = $1');
|
|
52
62
|
}
|
|
63
|
+
const effective = query?.resolution === 'effective' && Boolean(query.appId);
|
|
53
64
|
const result = await client.query<SkillRow>(`
|
|
54
|
-
select
|
|
65
|
+
select ${effective ? 'distinct on (name)' : ''}
|
|
66
|
+
app_id, version, name, description, instructions, enabled, created_at, updated_at
|
|
55
67
|
from agent.agent_skills
|
|
56
68
|
where ${conditions.join(' and ')}
|
|
57
|
-
order by name asc
|
|
69
|
+
order by name asc${effective ? ', (app_id = $1) desc' : ', app_id asc nulls first'}
|
|
58
70
|
`, params);
|
|
59
71
|
return result.rows.map(mapSkillRow);
|
|
60
72
|
} finally {
|
|
@@ -62,15 +74,15 @@ export class PostgresSkillStore implements SkillStore {
|
|
|
62
74
|
}
|
|
63
75
|
}
|
|
64
76
|
|
|
65
|
-
async get(
|
|
77
|
+
async get(key: SkillKey): Promise<SkillView | null> {
|
|
66
78
|
const client = await this.pool.connect();
|
|
67
79
|
try {
|
|
68
80
|
const result = await client.query<SkillRow>(`
|
|
69
81
|
select app_id, version, name, description, instructions, enabled, created_at, updated_at
|
|
70
82
|
from agent.agent_skills
|
|
71
|
-
where name = $
|
|
83
|
+
where app_id is not distinct from $1 and name = $2
|
|
72
84
|
limit 1
|
|
73
|
-
`, [name]);
|
|
85
|
+
`, [key.appId, key.name]);
|
|
74
86
|
return result.rows[0] ? mapSkillRow(result.rows[0]) : null;
|
|
75
87
|
} finally {
|
|
76
88
|
client.release();
|
|
@@ -98,31 +110,37 @@ export class PostgresSkillStore implements SkillStore {
|
|
|
98
110
|
input.enabled ?? true,
|
|
99
111
|
]);
|
|
100
112
|
return mapSkillRow(result.rows[0]);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (isUniqueViolation(error)) {
|
|
115
|
+
throw new Error(`SKILL_ALREADY_EXISTS: ${input.name.trim()}`);
|
|
116
|
+
}
|
|
117
|
+
throw error;
|
|
101
118
|
} finally {
|
|
102
119
|
client.release();
|
|
103
120
|
}
|
|
104
121
|
}
|
|
105
122
|
|
|
106
123
|
async update(input: UpdateSkillInput): Promise<SkillView> {
|
|
107
|
-
const current = await this.get(input.name);
|
|
124
|
+
const current = await this.get({ appId: input.appId, name: input.name });
|
|
108
125
|
if (!current) {
|
|
109
126
|
throw new Error(`Skill not found: ${input.name}`);
|
|
110
127
|
}
|
|
111
128
|
|
|
112
129
|
const client = await this.pool.connect();
|
|
113
130
|
try {
|
|
114
|
-
// name
|
|
131
|
+
// app_id + name 是稳定管理身份,仅更新内容与启用状态。
|
|
115
132
|
const result = await client.query<SkillRow>(`
|
|
116
133
|
update agent.agent_skills
|
|
117
134
|
set
|
|
118
|
-
description = $
|
|
119
|
-
instructions = $
|
|
120
|
-
enabled = $
|
|
135
|
+
description = $3,
|
|
136
|
+
instructions = $4,
|
|
137
|
+
enabled = $5,
|
|
121
138
|
version = version + 1,
|
|
122
139
|
updated_at = now()
|
|
123
|
-
where name = $
|
|
140
|
+
where app_id is not distinct from $1 and name = $2 and version = $6
|
|
124
141
|
returning app_id, version, name, description, instructions, enabled, created_at, updated_at
|
|
125
142
|
`, [
|
|
143
|
+
input.appId,
|
|
126
144
|
input.name,
|
|
127
145
|
input.description === undefined ? current.description : input.description.trim(),
|
|
128
146
|
input.instructions === undefined ? current.instructions : input.instructions.trim(),
|
|
@@ -140,7 +158,7 @@ export class PostgresSkillStore implements SkillStore {
|
|
|
140
158
|
}
|
|
141
159
|
}
|
|
142
160
|
|
|
143
|
-
async delete(
|
|
161
|
+
async delete(key: SkillKey): Promise<void> {
|
|
144
162
|
const client = await this.pool.connect();
|
|
145
163
|
try {
|
|
146
164
|
await client.query(`
|
|
@@ -149,8 +167,8 @@ export class PostgresSkillStore implements SkillStore {
|
|
|
149
167
|
enabled = false,
|
|
150
168
|
version = version + 1,
|
|
151
169
|
updated_at = now()
|
|
152
|
-
where name = $
|
|
153
|
-
`, [name]);
|
|
170
|
+
where app_id is not distinct from $1 and name = $2
|
|
171
|
+
`, [key.appId, key.name]);
|
|
154
172
|
} finally {
|
|
155
173
|
client.release();
|
|
156
174
|
}
|
package/src/testing.ts
CHANGED
|
@@ -4,6 +4,8 @@ import type {
|
|
|
4
4
|
CreateSkillInput,
|
|
5
5
|
PromptFragmentStore,
|
|
6
6
|
PromptFragmentView,
|
|
7
|
+
SkillKey,
|
|
8
|
+
SkillListQuery,
|
|
7
9
|
SkillStore,
|
|
8
10
|
SkillView,
|
|
9
11
|
UpdateSkillInput,
|
|
@@ -101,22 +103,38 @@ export class InMemoryPromptFragmentStore implements PromptFragmentStore {
|
|
|
101
103
|
export class InMemorySkillStore implements SkillStore {
|
|
102
104
|
private readonly skills = new Map<string, SkillView>();
|
|
103
105
|
|
|
104
|
-
|
|
105
|
-
return
|
|
106
|
+
private key(appId: string | null | undefined, name: string) {
|
|
107
|
+
return JSON.stringify([appId ?? null, name]);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async list(query?: SkillListQuery) {
|
|
111
|
+
const filtered = Array.from(this.skills.values())
|
|
106
112
|
.filter((skill) => skill.enabled)
|
|
107
113
|
.filter((skill) => {
|
|
114
|
+
if (query?.appId === null) {
|
|
115
|
+
return skill.appId === null;
|
|
116
|
+
}
|
|
108
117
|
if (!query?.appId) {
|
|
109
118
|
return true;
|
|
110
119
|
}
|
|
111
120
|
return query.includeGlobal
|
|
112
121
|
? skill.appId === query.appId || skill.appId === null
|
|
113
122
|
: skill.appId === query.appId;
|
|
114
|
-
})
|
|
115
|
-
|
|
123
|
+
});
|
|
124
|
+
const resolved = query?.resolution === 'effective' && query.appId !== undefined
|
|
125
|
+
? Array.from(filtered.reduce((byName, skill) => {
|
|
126
|
+
const current = byName.get(skill.name);
|
|
127
|
+
if (!current || (current.appId === null && skill.appId !== null)) {
|
|
128
|
+
byName.set(skill.name, skill);
|
|
129
|
+
}
|
|
130
|
+
return byName;
|
|
131
|
+
}, new Map<string, SkillView>()).values())
|
|
132
|
+
: filtered;
|
|
133
|
+
return resolved.map((skill) => structuredClone(skill));
|
|
116
134
|
}
|
|
117
135
|
|
|
118
|
-
async get(
|
|
119
|
-
const skill = this.skills.get(name);
|
|
136
|
+
async get(input: SkillKey) {
|
|
137
|
+
const skill = this.skills.get(this.key(input.appId, input.name));
|
|
120
138
|
return skill ? structuredClone(skill) : null;
|
|
121
139
|
}
|
|
122
140
|
|
|
@@ -132,12 +150,17 @@ export class InMemorySkillStore implements SkillStore {
|
|
|
132
150
|
createdAt: now,
|
|
133
151
|
updatedAt: now,
|
|
134
152
|
};
|
|
135
|
-
this.
|
|
153
|
+
const key = this.key(skill.appId, skill.name);
|
|
154
|
+
if (this.skills.has(key)) {
|
|
155
|
+
throw new Error(`SKILL_ALREADY_EXISTS: ${skill.name}`);
|
|
156
|
+
}
|
|
157
|
+
this.skills.set(key, skill);
|
|
136
158
|
return structuredClone(skill);
|
|
137
159
|
}
|
|
138
160
|
|
|
139
161
|
async update(input: UpdateSkillInput) {
|
|
140
|
-
const
|
|
162
|
+
const key = this.key(input.appId, input.name);
|
|
163
|
+
const current = this.skills.get(key);
|
|
141
164
|
if (!current) {
|
|
142
165
|
throw new Error(`Skill not found: ${input.name}`);
|
|
143
166
|
}
|
|
@@ -153,16 +176,17 @@ export class InMemorySkillStore implements SkillStore {
|
|
|
153
176
|
enabled: input.enabled ?? current.enabled,
|
|
154
177
|
updatedAt: new Date().toISOString(),
|
|
155
178
|
};
|
|
156
|
-
this.skills.set(
|
|
179
|
+
this.skills.set(key, next);
|
|
157
180
|
return structuredClone(next);
|
|
158
181
|
}
|
|
159
182
|
|
|
160
|
-
async delete(
|
|
161
|
-
const
|
|
183
|
+
async delete(input: SkillKey) {
|
|
184
|
+
const key = this.key(input.appId, input.name);
|
|
185
|
+
const current = this.skills.get(key);
|
|
162
186
|
if (!current) {
|
|
163
187
|
return;
|
|
164
188
|
}
|
|
165
|
-
this.skills.set(
|
|
189
|
+
this.skills.set(key, {
|
|
166
190
|
...current,
|
|
167
191
|
version: current.version + 1,
|
|
168
192
|
enabled: false,
|