@andreprado/agentkit 0.1.0-alpha.19 → 0.1.0-alpha.20
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/docs/guides/add-channel.md +5 -3
- package/docs/guides/channel-security.md +13 -1
- package/docs/guides/connect-slack.md +126 -0
- package/docs/guides/debug-channel.md +1 -1
- package/docs/guides/security-rules.md +1 -0
- package/docs/guides/send-feedback.md +135 -0
- package/docs/llms-full.txt +11 -3
- package/docs/llms.txt +5 -0
- package/package.json +1 -1
- package/src/cli/cloud-client.ts +12 -0
- package/src/cli/commands/channels.ts +86 -9
- package/src/cli/commands/feedback.ts +438 -0
- package/src/cli/help.ts +10 -2
- package/src/cli/index.ts +7 -0
- package/src/index.ts +37 -3
- package/src/providers/pi.ts +7 -0
- package/src/providers/types.ts +7 -0
- package/src/runtime/channel-test-harness.ts +3 -1
- package/src/runtime/channels/discord.ts +9 -0
- package/src/runtime/channels/slack.ts +646 -0
- package/src/runtime/channels/whatsapp-meta.ts +9 -0
- package/src/runtime/channels.ts +13 -0
- package/src/runtime/config.ts +20 -3
- package/src/runtime/core/targets.ts +5 -5
- package/src/runtime/dev-server.ts +30 -7
- package/src/runtime/targets/cloudflare/build.ts +203 -10
- package/src/templates/skills/agentkit-capsule/SKILL.md +1 -1
- package/src/templates/skills/agentkit-capsule/references/docs-router.md +2 -1
- package/src/templates/skills/agentkit-channels/SKILL.md +3 -1
- package/src/templates/skills/agentkit-channels/references/slack.md +56 -0
- package/src/templates/skills/agentkit-troubleshooting/SKILL.md +14 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { chmod, mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname, extname, join, relative, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
import { AgentKitError } from "../../runtime/errors";
|
|
7
|
+
import { findAgentCapsuleRoot, loadAgentCapsule } from "../../runtime/config";
|
|
8
|
+
import { readLocalDeployStateIfExists } from "../../runtime/core/deploy-state";
|
|
9
|
+
import type { ParsedArgs } from "../args";
|
|
10
|
+
import {
|
|
11
|
+
cloudApiRequest,
|
|
12
|
+
readCloudAuthForApiUrl,
|
|
13
|
+
resolveCloudApiUrl,
|
|
14
|
+
type FeedbackSendResponse,
|
|
15
|
+
} from "../cloud-client";
|
|
16
|
+
import { parseOptionalStringFlag } from "../flags";
|
|
17
|
+
|
|
18
|
+
type FeedbackKind =
|
|
19
|
+
| "bug"
|
|
20
|
+
| "confusion"
|
|
21
|
+
| "missing_docs"
|
|
22
|
+
| "feature_request"
|
|
23
|
+
| "deploy_issue"
|
|
24
|
+
| "runtime_issue"
|
|
25
|
+
| "other";
|
|
26
|
+
|
|
27
|
+
type FeedbackAbout = "last-run" | "deploy" | "manual";
|
|
28
|
+
|
|
29
|
+
type FeedbackDraft = {
|
|
30
|
+
schema_version: "agentkit.feedback.v1";
|
|
31
|
+
client_report_id: string;
|
|
32
|
+
kind: FeedbackKind;
|
|
33
|
+
summary: string;
|
|
34
|
+
message?: string;
|
|
35
|
+
source: Record<string, unknown>;
|
|
36
|
+
context: Record<string, unknown>;
|
|
37
|
+
diagnostics: Record<string, unknown>;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const feedbackKinds = new Set<FeedbackKind>([
|
|
41
|
+
"bug",
|
|
42
|
+
"confusion",
|
|
43
|
+
"missing_docs",
|
|
44
|
+
"feature_request",
|
|
45
|
+
"deploy_issue",
|
|
46
|
+
"runtime_issue",
|
|
47
|
+
"other",
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
const feedbackAboutValues = new Set<FeedbackAbout>(["last-run", "deploy", "manual"]);
|
|
51
|
+
|
|
52
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
53
|
+
|
|
54
|
+
export async function handleFeedbackCommand(args: ParsedArgs): Promise<void> {
|
|
55
|
+
const [subcommand, draftPath] = args.positional;
|
|
56
|
+
|
|
57
|
+
if (subcommand === "create") {
|
|
58
|
+
const draft = await buildFeedbackDraft(process.cwd(), args);
|
|
59
|
+
const written = await writeFeedbackDraft(process.cwd(), draft, args.flags.out);
|
|
60
|
+
|
|
61
|
+
console.log("Feedback draft created");
|
|
62
|
+
console.log(`JSON: ${relativePath(process.cwd(), written.jsonPath)}`);
|
|
63
|
+
console.log(`Markdown: ${relativePath(process.cwd(), written.markdownPath)}`);
|
|
64
|
+
console.log(`Send: agentkit feedback send ${relativePath(process.cwd(), written.jsonPath)}`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (subcommand === "preview") {
|
|
69
|
+
const draft = draftPath
|
|
70
|
+
? await readFeedbackDraft(resolve(process.cwd(), draftPath))
|
|
71
|
+
: await buildFeedbackDraft(process.cwd(), args);
|
|
72
|
+
console.log(JSON.stringify(draft, null, 2));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (subcommand === "send") {
|
|
77
|
+
const apiUrl = await resolveCloudApiUrl(args.flags.api);
|
|
78
|
+
const auth = await readCloudAuthForApiUrl(apiUrl);
|
|
79
|
+
|
|
80
|
+
if (!auth) {
|
|
81
|
+
throw new AgentKitError(
|
|
82
|
+
"auth_required",
|
|
83
|
+
`Log in before sending feedback: agentkit login --token <token> --api ${apiUrl}`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const draft = draftPath
|
|
88
|
+
? await readFeedbackDraft(resolve(process.cwd(), draftPath))
|
|
89
|
+
: await buildFeedbackDraft(process.cwd(), args);
|
|
90
|
+
|
|
91
|
+
if (!draftPath && args.flags.save === true) {
|
|
92
|
+
const written = await writeFeedbackDraft(process.cwd(), draft, args.flags.out);
|
|
93
|
+
console.log(`Feedback draft saved: ${relativePath(process.cwd(), written.jsonPath)}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const payload = (await cloudApiRequest(apiUrl, "/v1/feedback", {
|
|
97
|
+
method: "POST",
|
|
98
|
+
headers: {
|
|
99
|
+
"Idempotency-Key": draft.client_report_id,
|
|
100
|
+
},
|
|
101
|
+
body: JSON.stringify(feedbackDraftToSubmission(draft)),
|
|
102
|
+
})) as FeedbackSendResponse;
|
|
103
|
+
|
|
104
|
+
const feedback = payload.feedback;
|
|
105
|
+
|
|
106
|
+
if (!feedback?.id) {
|
|
107
|
+
throw new AgentKitError("feedback_response_invalid", "AgentKit Cloud returned an invalid feedback response.");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
console.log("Feedback sent");
|
|
111
|
+
console.log(`ID: ${feedback.id}`);
|
|
112
|
+
console.log(`Kind: ${feedback.kind ?? draft.kind}`);
|
|
113
|
+
console.log(`Status: ${feedback.status ?? "new"}`);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
throw new Error(
|
|
118
|
+
"Usage: agentkit feedback create|preview|send [draft.json] [--about last-run|deploy|manual] [--kind bug|confusion|missing_docs|feature_request|deploy_issue|runtime_issue|other] [--summary <text>] [--message <text>] [--api <url>]",
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function buildFeedbackDraft(cwd: string, args: ParsedArgs): Promise<FeedbackDraft> {
|
|
123
|
+
const about = parseFeedbackAbout(args.flags.about);
|
|
124
|
+
const kind = parseFeedbackKind(args.flags.kind, about === "deploy" ? "deploy_issue" : "other");
|
|
125
|
+
const message = parseOptionalStringFlag(args.flags.message, "message", "agentkit feedback create --message <text>");
|
|
126
|
+
const summary =
|
|
127
|
+
parseOptionalStringFlag(args.flags.summary, "summary", "agentkit feedback create --summary <text>") ??
|
|
128
|
+
summarizeMessage(message);
|
|
129
|
+
|
|
130
|
+
if (!summary) {
|
|
131
|
+
throw new Error("Missing summary. Usage: agentkit feedback create --summary <text> [--message <text>]");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const now = new Date().toISOString();
|
|
135
|
+
const context = await collectFeedbackContext(cwd);
|
|
136
|
+
const draft: FeedbackDraft = {
|
|
137
|
+
schema_version: "agentkit.feedback.v1",
|
|
138
|
+
client_report_id: `fbdraft_${Date.now().toString(36)}_${randomBytes(6).toString("hex")}`,
|
|
139
|
+
kind,
|
|
140
|
+
summary: redactText(summary).slice(0, 200),
|
|
141
|
+
...(message ? { message: redactText(message).slice(0, 5_000) } : {}),
|
|
142
|
+
source: {
|
|
143
|
+
channel: "cli",
|
|
144
|
+
about,
|
|
145
|
+
created_at: now,
|
|
146
|
+
agentkit_version: await readAgentKitVersion(),
|
|
147
|
+
},
|
|
148
|
+
context,
|
|
149
|
+
diagnostics: {
|
|
150
|
+
redaction: "agentkit-cli-redaction-v1",
|
|
151
|
+
note:
|
|
152
|
+
about === "last-run"
|
|
153
|
+
? "AgentKit does not persist a last-command transcript yet; this draft includes current capsule and deploy context."
|
|
154
|
+
: "This draft includes current capsule and deploy context.",
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
return redactJson(draft) as FeedbackDraft;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function collectFeedbackContext(cwd: string): Promise<Record<string, unknown>> {
|
|
162
|
+
const context: Record<string, unknown> = {
|
|
163
|
+
platform: {
|
|
164
|
+
node: process.version,
|
|
165
|
+
bun: (globalThis as { Bun?: { version?: string } }).Bun?.version,
|
|
166
|
+
platform: process.platform,
|
|
167
|
+
arch: process.arch,
|
|
168
|
+
package_manager: await detectPackageManager(cwd),
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const root = await findAgentCapsuleRoot(cwd).catch(() => null);
|
|
173
|
+
|
|
174
|
+
if (!root) {
|
|
175
|
+
context.capsule = {
|
|
176
|
+
detected: false,
|
|
177
|
+
cwd_name: basename(cwd),
|
|
178
|
+
};
|
|
179
|
+
return redactJson(context) as Record<string, unknown>;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const capsule = await loadAgentCapsule(root).catch((error: unknown) => ({
|
|
183
|
+
error: error instanceof Error ? error.message : String(error),
|
|
184
|
+
}));
|
|
185
|
+
const deployState = await readLocalDeployStateIfExists(root).catch(() => null);
|
|
186
|
+
|
|
187
|
+
if ("config" in capsule) {
|
|
188
|
+
context.capsule = {
|
|
189
|
+
detected: true,
|
|
190
|
+
root_name: basename(root),
|
|
191
|
+
name: capsule.config.name,
|
|
192
|
+
runtime: capsule.config.runtime,
|
|
193
|
+
provider: capsule.config.provider,
|
|
194
|
+
access: capsule.config.access,
|
|
195
|
+
storage: capsule.config.storage,
|
|
196
|
+
secret_names: capsule.config.secrets,
|
|
197
|
+
tool_count: capsule.config.tools?.length ?? 0,
|
|
198
|
+
channel_count: capsule.config.channels?.length ?? 0,
|
|
199
|
+
};
|
|
200
|
+
} else {
|
|
201
|
+
context.capsule = {
|
|
202
|
+
detected: true,
|
|
203
|
+
root_name: basename(root),
|
|
204
|
+
load_error: capsule.error,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (deployState) {
|
|
209
|
+
context.deploy = {
|
|
210
|
+
project_id: deployState.project_id,
|
|
211
|
+
deploy_id: deployState.deploy_id,
|
|
212
|
+
target: deployState.target,
|
|
213
|
+
runtime: deployState.runtime,
|
|
214
|
+
owner_state: deployState.owner_state,
|
|
215
|
+
access: deployState.access,
|
|
216
|
+
url: deployState.url,
|
|
217
|
+
created_at: deployState.created_at,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return redactJson(context) as Record<string, unknown>;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function detectPackageManager(cwd: string): Promise<string> {
|
|
225
|
+
const candidates = [
|
|
226
|
+
["bun.lock", "bun"],
|
|
227
|
+
["bun.lockb", "bun"],
|
|
228
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
229
|
+
["yarn.lock", "yarn"],
|
|
230
|
+
["package-lock.json", "npm"],
|
|
231
|
+
] as const;
|
|
232
|
+
|
|
233
|
+
for (const [file, name] of candidates) {
|
|
234
|
+
if (await pathExists(join(cwd, file))) {
|
|
235
|
+
return name;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return "unknown";
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function writeFeedbackDraft(
|
|
243
|
+
cwd: string,
|
|
244
|
+
draft: FeedbackDraft,
|
|
245
|
+
outFlag: string | boolean | undefined,
|
|
246
|
+
): Promise<{ jsonPath: string; markdownPath: string }> {
|
|
247
|
+
const out = parseOptionalStringFlag(outFlag, "out", "agentkit feedback create --out <path>");
|
|
248
|
+
const defaultDir = join(cwd, ".agentkit", "feedback");
|
|
249
|
+
const slug = slugify(draft.summary);
|
|
250
|
+
const filename = `${new Date().toISOString().replace(/[:.]/g, "-")}-${slug}`;
|
|
251
|
+
const jsonPath = out && extname(out) === ".json" ? resolve(cwd, out) : join(out ? resolve(cwd, out) : defaultDir, `${filename}.json`);
|
|
252
|
+
const markdownPath = jsonPath.replace(/\.json$/, ".md");
|
|
253
|
+
|
|
254
|
+
await mkdir(dirname(jsonPath), { recursive: true });
|
|
255
|
+
await writeFile(jsonPath, `${JSON.stringify(draft, null, 2)}\n`, { mode: 0o600 });
|
|
256
|
+
await chmod(jsonPath, 0o600);
|
|
257
|
+
await writeFile(markdownPath, renderFeedbackMarkdown(draft), { mode: 0o600 });
|
|
258
|
+
await chmod(markdownPath, 0o600);
|
|
259
|
+
|
|
260
|
+
return { jsonPath, markdownPath };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function readFeedbackDraft(path: string): Promise<FeedbackDraft> {
|
|
264
|
+
const parsed = JSON.parse(await readFile(path, "utf8")) as unknown;
|
|
265
|
+
const draft = validateFeedbackDraft(parsed);
|
|
266
|
+
|
|
267
|
+
if (!draft) {
|
|
268
|
+
throw new AgentKitError("feedback_draft_invalid", "Feedback draft must be an agentkit.feedback.v1 JSON object.");
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return redactJson(draft) as FeedbackDraft;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function validateFeedbackDraft(value: unknown): FeedbackDraft | null {
|
|
275
|
+
if (!isRecord(value) || value.schema_version !== "agentkit.feedback.v1") {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (typeof value.client_report_id !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(value.client_report_id)) {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (!isFeedbackKind(value.kind) || typeof value.summary !== "string" || !isRecord(value.source)) {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
schema_version: "agentkit.feedback.v1",
|
|
289
|
+
client_report_id: value.client_report_id,
|
|
290
|
+
kind: value.kind,
|
|
291
|
+
summary: redactText(value.summary).slice(0, 200),
|
|
292
|
+
...(typeof value.message === "string" ? { message: redactText(value.message).slice(0, 5_000) } : {}),
|
|
293
|
+
source: redactJson(value.source) as Record<string, unknown>,
|
|
294
|
+
context: isRecord(value.context) ? redactJson(value.context) as Record<string, unknown> : {},
|
|
295
|
+
diagnostics: isRecord(value.diagnostics) ? redactJson(value.diagnostics) as Record<string, unknown> : {},
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function feedbackDraftToSubmission(draft: FeedbackDraft): Record<string, unknown> {
|
|
300
|
+
return {
|
|
301
|
+
kind: draft.kind,
|
|
302
|
+
summary: draft.summary,
|
|
303
|
+
...(draft.message ? { message: draft.message } : {}),
|
|
304
|
+
source: draft.source,
|
|
305
|
+
context: draft.context,
|
|
306
|
+
diagnostics: draft.diagnostics,
|
|
307
|
+
client_report_id: draft.client_report_id,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function renderFeedbackMarkdown(draft: FeedbackDraft): string {
|
|
312
|
+
const lines = [
|
|
313
|
+
"# AgentKit Feedback Draft",
|
|
314
|
+
"",
|
|
315
|
+
`Kind: ${draft.kind}`,
|
|
316
|
+
`Summary: ${draft.summary}`,
|
|
317
|
+
`Client report: ${draft.client_report_id}`,
|
|
318
|
+
"",
|
|
319
|
+
];
|
|
320
|
+
|
|
321
|
+
if (draft.message) {
|
|
322
|
+
lines.push("## Message", "", draft.message, "");
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
lines.push("## Context", "", "```json", JSON.stringify(draft.context, null, 2), "```", "");
|
|
326
|
+
lines.push("Send with the JSON path printed by `agentkit feedback create`:", "", "```sh\nagentkit feedback send <draft.json>\n```", "");
|
|
327
|
+
|
|
328
|
+
return `${lines.join("\n")}\n`;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function parseFeedbackKind(value: string | boolean | undefined, fallback: FeedbackKind): FeedbackKind {
|
|
332
|
+
if (value === undefined || value === false) {
|
|
333
|
+
return fallback;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (typeof value === "string" && isFeedbackKind(value)) {
|
|
337
|
+
return value;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
throw new Error("feedback --kind must be bug, confusion, missing_docs, feature_request, deploy_issue, runtime_issue, or other.");
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function parseFeedbackAbout(value: string | boolean | undefined): FeedbackAbout {
|
|
344
|
+
if (value === undefined || value === false) {
|
|
345
|
+
return "last-run";
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (typeof value === "string" && feedbackAboutValues.has(value as FeedbackAbout)) {
|
|
349
|
+
return value as FeedbackAbout;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
throw new Error("feedback --about must be last-run, deploy, or manual.");
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function summarizeMessage(message: string | undefined): string | undefined {
|
|
356
|
+
if (!message) {
|
|
357
|
+
return undefined;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return message.replace(/\s+/g, " ").trim().slice(0, 120);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function isFeedbackKind(value: unknown): value is FeedbackKind {
|
|
364
|
+
return typeof value === "string" && feedbackKinds.has(value as FeedbackKind);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function redactJson(value: unknown, key = ""): unknown {
|
|
368
|
+
if (Array.isArray(value)) {
|
|
369
|
+
return value.map((item) => redactJson(item, key));
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (!isRecord(value)) {
|
|
373
|
+
return typeof value === "string" ? redactText(value) : value;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const out: Record<string, unknown> = {};
|
|
377
|
+
|
|
378
|
+
for (const [nestedKey, nestedValue] of Object.entries(value)) {
|
|
379
|
+
if (shouldRedactKey(nestedKey)) {
|
|
380
|
+
out[nestedKey] = "[redacted]";
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
out[nestedKey] = redactJson(nestedValue, nestedKey);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return out;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function shouldRedactKey(key: string): boolean {
|
|
391
|
+
return /token|password|credential|api[_-]?key|private[_-]?key/i.test(key) && key !== "client_report_id";
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function redactText(value: string): string {
|
|
395
|
+
return value
|
|
396
|
+
.replace(/Authorization:\s*Bearer\s+[A-Za-z0-9._:-]+/gi, "Authorization: Bearer [redacted]")
|
|
397
|
+
.replace(/\b(?:agk_user|agk_deploy|ak_live|bsec|whsec|sk|rk|pk)[-_A-Za-z0-9]{12,}\b/g, "[redacted-token]")
|
|
398
|
+
.replace(/\b[A-Za-z0-9_-]{24,}\.[A-Za-z0-9_-]{24,}\.[A-Za-z0-9_-]{24,}\b/g, "[redacted-token]");
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function slugify(value: string): string {
|
|
402
|
+
const slug = value
|
|
403
|
+
.toLowerCase()
|
|
404
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
405
|
+
.replace(/^-+|-+$/g, "")
|
|
406
|
+
.slice(0, 48);
|
|
407
|
+
|
|
408
|
+
return slug || "feedback";
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async function readAgentKitVersion(): Promise<string> {
|
|
412
|
+
try {
|
|
413
|
+
const packageJson = JSON.parse(await readFile(join(moduleDir, "..", "..", "..", "package.json"), "utf8")) as {
|
|
414
|
+
version?: unknown;
|
|
415
|
+
};
|
|
416
|
+
return typeof packageJson.version === "string" ? packageJson.version : "unknown";
|
|
417
|
+
} catch {
|
|
418
|
+
return "unknown";
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async function pathExists(path: string): Promise<boolean> {
|
|
423
|
+
try {
|
|
424
|
+
await stat(path);
|
|
425
|
+
return true;
|
|
426
|
+
} catch {
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function relativePath(root: string, path: string): string {
|
|
432
|
+
const output = relative(root, path);
|
|
433
|
+
return output.startsWith("..") ? path : output || ".";
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
437
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
438
|
+
}
|
package/src/cli/help.ts
CHANGED
|
@@ -30,6 +30,7 @@ Build:
|
|
|
30
30
|
agentkit eval run
|
|
31
31
|
agentkit eval from-conversation <id>
|
|
32
32
|
agentkit improve collect --deploy --since 24h
|
|
33
|
+
agentkit feedback create --about last-run --kind bug --summary <text>
|
|
33
34
|
agentkit replay <bundle-dir> --against local
|
|
34
35
|
agentkit conversations list
|
|
35
36
|
|
|
@@ -48,6 +49,7 @@ More:
|
|
|
48
49
|
agentkit account token ...
|
|
49
50
|
agentkit access token ...
|
|
50
51
|
agentkit env ...
|
|
52
|
+
agentkit feedback create --summary <text>
|
|
51
53
|
agentkit docs full
|
|
52
54
|
agentkit help commands
|
|
53
55
|
`;
|
|
@@ -79,13 +81,16 @@ Usage:
|
|
|
79
81
|
agentkit eval from-conversation <conversation-id> [--out <path>] [--force]
|
|
80
82
|
agentkit improve collect [--deploy] [--since <duration|iso>] [--conversation-id <id>] [--out <directory>]
|
|
81
83
|
agentkit improve evals <bundle-dir-or-json> [--force]
|
|
84
|
+
agentkit feedback create [--about last-run|deploy|manual] [--kind bug|confusion|missing_docs|feature_request|deploy_issue|runtime_issue|other] --summary <text> [--message <text>] [--out <path>]
|
|
85
|
+
agentkit feedback preview [draft.json]
|
|
86
|
+
agentkit feedback send [draft.json] [--about last-run|deploy|manual] [--kind <kind>] --summary <text> [--message <text>] [--api <url>] [--save]
|
|
82
87
|
agentkit replay <bundle-dir-or-json> --against local
|
|
83
88
|
agentkit conversations list [--deploy]
|
|
84
89
|
agentkit conversations show <conversation-id> [--deploy]
|
|
85
90
|
agentkit conversations trace <conversation-id> [--deploy]
|
|
86
91
|
agentkit channels list
|
|
87
|
-
agentkit channels add <website|telegram|whatsapp|discord> <name> [--provider zapster|meta] [--mode interactions|bot] [--api <url>]
|
|
88
|
-
agentkit channels connect <website|telegram|whatsapp|discord> <name> [--provider zapster|meta] [--mode interactions|bot] [--api <url>]
|
|
92
|
+
agentkit channels add <website|telegram|whatsapp|discord|slack> <name> [--provider zapster|meta] [--mode interactions|bot] [--api <url>]
|
|
93
|
+
agentkit channels connect <website|telegram|whatsapp|discord|slack> <name> [--provider zapster|meta] [--mode interactions|bot] [--api <url>]
|
|
89
94
|
agentkit channels setup <name> [--apply] [--api <url>]
|
|
90
95
|
agentkit channels status <name> [--api <url>]
|
|
91
96
|
agentkit channels doctor <name> [--api <url>]
|
|
@@ -155,6 +160,7 @@ Commands:
|
|
|
155
160
|
db Manage the local SQLite database
|
|
156
161
|
eval Run local eval files or create one from a conversation
|
|
157
162
|
improve Collect production or local traces and generate regression evals
|
|
163
|
+
feedback Create local redacted feedback drafts and send them to AgentKit Cloud
|
|
158
164
|
replay Replay an improve bundle against the local Agent Capsule
|
|
159
165
|
conversations List, inspect, and trace stored local conversations
|
|
160
166
|
channels Manage hosted channel resources
|
|
@@ -201,6 +207,8 @@ export function renderCommandHelp(command: string): string {
|
|
|
201
207
|
eval: "agentkit eval run | agentkit eval from-conversation <conversation-id> [--out <path>] [--force]",
|
|
202
208
|
improve:
|
|
203
209
|
"agentkit improve collect [--deploy] [--since <duration|iso>] [--conversation-id <id>] [--out <directory>] | agentkit improve evals <bundle-dir-or-json> [--force]",
|
|
210
|
+
feedback:
|
|
211
|
+
"agentkit feedback create|preview|send [draft.json] [--about last-run|deploy|manual] [--kind <kind>] [--summary <text>] [--message <text>]",
|
|
204
212
|
replay: "agentkit replay <bundle-dir-or-json> --against local",
|
|
205
213
|
conversations:
|
|
206
214
|
"agentkit conversations list [--deploy] | agentkit conversations show <conversation-id> [--deploy] | agentkit conversations trace <conversation-id> [--deploy]",
|
package/src/cli/index.ts
CHANGED
|
@@ -74,6 +74,7 @@ import {
|
|
|
74
74
|
import { renderCommandHelp, renderCommandReference, renderHelp } from "./help";
|
|
75
75
|
import { runInheritedCommand, waitForShutdown } from "./process";
|
|
76
76
|
import { handleChannelsCommand } from "./commands/channels";
|
|
77
|
+
import { handleFeedbackCommand } from "./commands/feedback";
|
|
77
78
|
import { handleKnowledgeCommand } from "./commands/knowledge";
|
|
78
79
|
import { handleTranscribeCommand } from "./commands/transcribe";
|
|
79
80
|
|
|
@@ -549,6 +550,11 @@ async function main() {
|
|
|
549
550
|
return;
|
|
550
551
|
}
|
|
551
552
|
|
|
553
|
+
if (args.command === "feedback") {
|
|
554
|
+
await handleFeedbackCommand(args);
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
|
|
552
558
|
if (args.command === "sync") {
|
|
553
559
|
const [subcommand] = args.positional;
|
|
554
560
|
|
|
@@ -1846,5 +1852,6 @@ main().catch((error: unknown) => {
|
|
|
1846
1852
|
const message = error instanceof Error ? error.message : String(error);
|
|
1847
1853
|
const prefix = isAgentKitError(error) ? `agentkit ${error.code}` : "agentkit";
|
|
1848
1854
|
console.error(`${prefix}: ${message}`);
|
|
1855
|
+
console.error("Feedback draft: agentkit feedback create --about last-run --kind bug --summary <text>");
|
|
1849
1856
|
process.exitCode = 1;
|
|
1850
1857
|
});
|
package/src/index.ts
CHANGED
|
@@ -2,8 +2,8 @@ export type AgentRuntime = "local" | "edge";
|
|
|
2
2
|
export type AgentProviderName = "test" | "openai" | "anthropic" | "openrouter" | "custom";
|
|
3
3
|
export type AccessMode = "private" | "token" | "public";
|
|
4
4
|
export type ToolVisibility = "user" | "internal";
|
|
5
|
-
export type ChannelType = "website" | "telegram" | "whatsapp" | "discord";
|
|
6
|
-
export type ChannelProvider = "agentkit" | "telegram" | "zapster" | "meta" | "discord";
|
|
5
|
+
export type ChannelType = "website" | "telegram" | "whatsapp" | "discord" | "slack";
|
|
6
|
+
export type ChannelProvider = "agentkit" | "telegram" | "zapster" | "meta" | "discord" | "slack";
|
|
7
7
|
export type WhatsappChannelProvider = "zapster" | "meta";
|
|
8
8
|
export type DiscordChannelMode = "interactions" | "bot";
|
|
9
9
|
export type TelegramAllowedUpdate = "message";
|
|
@@ -163,7 +163,29 @@ export type DiscordChannelConfig = {
|
|
|
163
163
|
audio?: never;
|
|
164
164
|
};
|
|
165
165
|
|
|
166
|
-
export type
|
|
166
|
+
export type SlackChannelInput = {
|
|
167
|
+
name: string;
|
|
168
|
+
secrets?: string[];
|
|
169
|
+
limits?: ChannelLimits;
|
|
170
|
+
buffer?: ChannelBufferInput;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export type SlackChannelConfig = {
|
|
174
|
+
type: "slack";
|
|
175
|
+
provider: "slack";
|
|
176
|
+
name: string;
|
|
177
|
+
secrets: string[];
|
|
178
|
+
limits?: ChannelLimits;
|
|
179
|
+
buffer?: ChannelBufferConfig;
|
|
180
|
+
audio?: never;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
export type AgentChannel =
|
|
184
|
+
| WebsiteChannelConfig
|
|
185
|
+
| TelegramChannelConfig
|
|
186
|
+
| WhatsappChannelConfig
|
|
187
|
+
| DiscordChannelConfig
|
|
188
|
+
| SlackChannelConfig;
|
|
167
189
|
|
|
168
190
|
export type IntegrationTagsConfig = {
|
|
169
191
|
enable?: string[];
|
|
@@ -462,6 +484,17 @@ export function discordChannel(input: DiscordChannelInput): DiscordChannelConfig
|
|
|
462
484
|
};
|
|
463
485
|
}
|
|
464
486
|
|
|
487
|
+
export function slackChannel(input: SlackChannelInput): SlackChannelConfig {
|
|
488
|
+
return {
|
|
489
|
+
type: "slack",
|
|
490
|
+
provider: "slack",
|
|
491
|
+
name: input.name,
|
|
492
|
+
secrets: normalizeChannelSecrets(input.secrets ?? ["SLACK_BOT_TOKEN", "SLACK_SIGNING_SECRET"], "slack channel"),
|
|
493
|
+
...(input.limits ? { limits: input.limits } : {}),
|
|
494
|
+
...(input.buffer ? { buffer: normalizeChannelBuffer(input.buffer, "slack channel") } : {}),
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
|
|
465
498
|
function defaultDiscordChannelSecrets(mode: DiscordChannelMode): string[] {
|
|
466
499
|
return mode === "bot" ? ["DISCORD_BOT_TOKEN"] : ["DISCORD_PUBLIC_KEY"];
|
|
467
500
|
}
|
|
@@ -692,6 +725,7 @@ export type {
|
|
|
692
725
|
ChannelSendResult,
|
|
693
726
|
ChannelStatusInput,
|
|
694
727
|
ChannelStatusResult,
|
|
728
|
+
ChannelWebhookResponse,
|
|
695
729
|
NormalizedChannelEvent,
|
|
696
730
|
NormalizedChannelMessage,
|
|
697
731
|
RawWebhookEvent,
|
package/src/providers/pi.ts
CHANGED
|
@@ -291,6 +291,13 @@ function normalizeUsage(usage: Usage | undefined): ProviderRunResult["usage"] |
|
|
|
291
291
|
inputTokens: usage.input,
|
|
292
292
|
outputTokens: usage.output,
|
|
293
293
|
totalTokens: usage.totalTokens,
|
|
294
|
+
cost: {
|
|
295
|
+
input: usage.cost.input,
|
|
296
|
+
output: usage.cost.output,
|
|
297
|
+
cacheRead: usage.cost.cacheRead,
|
|
298
|
+
cacheWrite: usage.cost.cacheWrite,
|
|
299
|
+
total: usage.cost.total,
|
|
300
|
+
},
|
|
294
301
|
};
|
|
295
302
|
}
|
|
296
303
|
|
package/src/providers/types.ts
CHANGED
|
@@ -24,6 +24,13 @@ export type ProviderRunResult = {
|
|
|
24
24
|
inputTokens?: number;
|
|
25
25
|
outputTokens?: number;
|
|
26
26
|
totalTokens?: number;
|
|
27
|
+
cost?: {
|
|
28
|
+
input?: number;
|
|
29
|
+
output?: number;
|
|
30
|
+
cacheRead?: number;
|
|
31
|
+
cacheWrite?: number;
|
|
32
|
+
total?: number;
|
|
33
|
+
};
|
|
27
34
|
};
|
|
28
35
|
raw?: unknown;
|
|
29
36
|
};
|
|
@@ -17,7 +17,9 @@ export type ChannelFixtureName =
|
|
|
17
17
|
| "zapster-unsupported-media"
|
|
18
18
|
| "discord-command"
|
|
19
19
|
| "discord-message-create"
|
|
20
|
-
| "discord-ping"
|
|
20
|
+
| "discord-ping"
|
|
21
|
+
| "slack-app-mention"
|
|
22
|
+
| "slack-direct-message";
|
|
21
23
|
|
|
22
24
|
export type ChannelFixture = {
|
|
23
25
|
name: ChannelFixtureName;
|
|
@@ -20,6 +20,15 @@ export const discordChannelAdapter: ChannelAdapter = {
|
|
|
20
20
|
async verifyWebhook(input): Promise<WebhookVerificationResult> {
|
|
21
21
|
return verifyDiscordWebhook(input);
|
|
22
22
|
},
|
|
23
|
+
getWebhookResponse(input) {
|
|
24
|
+
if (!isDiscordPingInteraction(input.rawBody)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
body: { type: 1 },
|
|
30
|
+
};
|
|
31
|
+
},
|
|
23
32
|
normalizeInbound(input) {
|
|
24
33
|
const body = parseJsonObject(input);
|
|
25
34
|
const interactionId = readString(body, "id");
|