@botcord/daemon 0.2.85 → 0.2.86
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/LICENSE +21 -0
- package/dist/cloud-daemon.js +18 -2
- package/dist/daemon-singleton.d.ts +12 -0
- package/dist/daemon-singleton.js +83 -7
- package/dist/daemon.d.ts +1 -0
- package/dist/daemon.js +24 -0
- package/dist/gateway/channels/botcord.js +72 -29
- package/dist/gateway/runtimes/deepseek-tui.js +5 -1
- package/dist/index.js +27 -7
- package/dist/provision.js +30 -3
- package/dist/skill-index.d.ts +12 -0
- package/dist/skill-index.js +15 -4
- package/package.json +10 -11
- package/src/__tests__/cloud-daemon.test.ts +79 -0
- package/src/__tests__/daemon-singleton.test.ts +59 -1
- package/src/__tests__/provision.test.ts +42 -0
- package/src/__tests__/runtime-discovery.test.ts +17 -1
- package/src/__tests__/skill-index.test.ts +91 -0
- package/src/cloud-daemon.ts +18 -2
- package/src/daemon-singleton.ts +98 -6
- package/src/daemon.ts +28 -0
- package/src/gateway/__tests__/botcord-channel.test.ts +79 -0
- package/src/gateway/__tests__/deepseek-tui-adapter.test.ts +49 -0
- package/src/gateway/channels/botcord.ts +81 -33
- package/src/gateway/runtimes/deepseek-tui.ts +7 -1
- package/src/index.ts +25 -6
- package/src/provision.ts +34 -1
- package/src/skill-index.ts +32 -4
|
@@ -793,6 +793,85 @@ describe("createBotCordChannel — streamBlock()", () => {
|
|
|
793
793
|
});
|
|
794
794
|
});
|
|
795
795
|
|
|
796
|
+
it("normalizes wrapped DeepSeek item.started tool input from the runtime event stream", () => {
|
|
797
|
+
expect(
|
|
798
|
+
__normalizeBlockForHubForTests(
|
|
799
|
+
{
|
|
800
|
+
kind: "tool_use",
|
|
801
|
+
seq: 5,
|
|
802
|
+
raw: {
|
|
803
|
+
event: "item.started",
|
|
804
|
+
payload: {
|
|
805
|
+
seq: 922,
|
|
806
|
+
thread_id: "thr_test",
|
|
807
|
+
turn_id: "turn_test",
|
|
808
|
+
item_id: "item_exec",
|
|
809
|
+
event: "item.started",
|
|
810
|
+
payload: {
|
|
811
|
+
item: {
|
|
812
|
+
id: "item_exec",
|
|
813
|
+
kind: "tool_call",
|
|
814
|
+
status: "in_progress",
|
|
815
|
+
summary: "exec_shell started",
|
|
816
|
+
detail: "{\"cmd\":\"botcord-daemon status\"}",
|
|
817
|
+
},
|
|
818
|
+
},
|
|
819
|
+
},
|
|
820
|
+
},
|
|
821
|
+
},
|
|
822
|
+
5,
|
|
823
|
+
),
|
|
824
|
+
).toMatchObject({
|
|
825
|
+
kind: "tool_call",
|
|
826
|
+
seq: 5,
|
|
827
|
+
payload: {
|
|
828
|
+
id: "item_exec",
|
|
829
|
+
name: "exec_shell",
|
|
830
|
+
params: { cmd: "botcord-daemon status" },
|
|
831
|
+
status: "in_progress",
|
|
832
|
+
},
|
|
833
|
+
});
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
it("normalizes wrapped DeepSeek item.completed output without showing the event envelope", () => {
|
|
837
|
+
expect(
|
|
838
|
+
__normalizeBlockForHubForTests(
|
|
839
|
+
{
|
|
840
|
+
kind: "tool_result",
|
|
841
|
+
seq: 6,
|
|
842
|
+
raw: {
|
|
843
|
+
event: "item.completed",
|
|
844
|
+
payload: {
|
|
845
|
+
seq: 955,
|
|
846
|
+
thread_id: "thr_test",
|
|
847
|
+
turn_id: "turn_test",
|
|
848
|
+
item_id: "item_exec",
|
|
849
|
+
event: "item.completed",
|
|
850
|
+
payload: {
|
|
851
|
+
item: {
|
|
852
|
+
id: "item_exec",
|
|
853
|
+
kind: "command_execution",
|
|
854
|
+
status: "completed",
|
|
855
|
+
summary: "exec_shell: daemon: pid 49616",
|
|
856
|
+
detail: "daemon: pid 49616 (alive)",
|
|
857
|
+
},
|
|
858
|
+
},
|
|
859
|
+
},
|
|
860
|
+
},
|
|
861
|
+
},
|
|
862
|
+
6,
|
|
863
|
+
),
|
|
864
|
+
).toMatchObject({
|
|
865
|
+
kind: "tool_result",
|
|
866
|
+
seq: 6,
|
|
867
|
+
payload: {
|
|
868
|
+
name: "exec_shell",
|
|
869
|
+
result: "daemon: pid 49616 (alive)",
|
|
870
|
+
tool_use_id: "item_exec",
|
|
871
|
+
},
|
|
872
|
+
});
|
|
873
|
+
});
|
|
874
|
+
|
|
796
875
|
it("POSTs to /hub/stream-block with the right trace_id + block", async () => {
|
|
797
876
|
const fetchSpy = vi.fn().mockResolvedValue(new Response(null, { status: 204 }));
|
|
798
877
|
const realFetch = globalThis.fetch;
|
|
@@ -353,6 +353,55 @@ describe("DeepseekTuiAdapter", () => {
|
|
|
353
353
|
}
|
|
354
354
|
});
|
|
355
355
|
|
|
356
|
+
it("treats DeepSeek command_execution item.started events as tool blocks", async () => {
|
|
357
|
+
const server = await startMockDeepseekServer({
|
|
358
|
+
events: [
|
|
359
|
+
{
|
|
360
|
+
event: "turn.started",
|
|
361
|
+
data: { thread_id: "thr_test", turn_id: "turn_test", event: "turn.started" },
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
event: "item.started",
|
|
365
|
+
data: {
|
|
366
|
+
thread_id: "thr_test",
|
|
367
|
+
turn_id: "turn_test",
|
|
368
|
+
event: "item.started",
|
|
369
|
+
payload: {
|
|
370
|
+
item: {
|
|
371
|
+
id: "item_exec",
|
|
372
|
+
kind: "command_execution",
|
|
373
|
+
status: "in_progress",
|
|
374
|
+
summary: "exec_shell started",
|
|
375
|
+
detail: "{\"cmd\":\"date\"}",
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
event: "item.delta",
|
|
382
|
+
data: {
|
|
383
|
+
thread_id: "thr_test",
|
|
384
|
+
turn_id: "turn_test",
|
|
385
|
+
event: "item.delta",
|
|
386
|
+
payload: { kind: "agent_message", delta: "done" },
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
event: "turn.completed",
|
|
391
|
+
data: { thread_id: "thr_test", turn_id: "turn_test", event: "turn.completed" },
|
|
392
|
+
},
|
|
393
|
+
],
|
|
394
|
+
});
|
|
395
|
+
try {
|
|
396
|
+
const { result, blocks, status } = runAdapter(server.baseUrl, server.token);
|
|
397
|
+
await expect(result).resolves.toMatchObject({ text: "done" });
|
|
398
|
+
expect(blocks).toEqual(expect.arrayContaining(["tool_use", "assistant_text"]));
|
|
399
|
+
expect(status).toContainEqual({ phase: "updated", label: "exec_shell" });
|
|
400
|
+
} finally {
|
|
401
|
+
await server.close();
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
|
|
356
405
|
it("emits current DeepSeek agent_reasoning completions as thinking blocks", async () => {
|
|
357
406
|
const server = await startMockDeepseekServer({
|
|
358
407
|
events: [
|
|
@@ -1196,27 +1196,46 @@ function extractToolResult(raw: any): { name?: string; result: string; id?: stri
|
|
|
1196
1196
|
function extractDeepseekToolCall(raw: any): { name: string; params?: unknown; id?: string; status?: string } | null {
|
|
1197
1197
|
const payload = raw?.payload;
|
|
1198
1198
|
if (!payload || typeof payload !== "object") return null;
|
|
1199
|
+
const innerPayload = unwrapDeepseekPayload(raw);
|
|
1200
|
+
const event = stringField(raw, "event") ?? stringField(payload, "event");
|
|
1199
1201
|
|
|
1200
|
-
if (
|
|
1201
|
-
const tool =
|
|
1202
|
+
if (event === "tool.started") {
|
|
1203
|
+
const tool = innerPayload?.tool && typeof innerPayload.tool === "object" ? innerPayload.tool : undefined;
|
|
1202
1204
|
return {
|
|
1203
|
-
name: stringField(
|
|
1204
|
-
params: parseMaybeJson(
|
|
1205
|
-
|
|
1206
|
-
|
|
1205
|
+
name: stringField(innerPayload, "name") ?? stringField(tool, "name") ?? "tool",
|
|
1206
|
+
params: parseMaybeJson(
|
|
1207
|
+
innerPayload?.input ??
|
|
1208
|
+
innerPayload?.arguments ??
|
|
1209
|
+
innerPayload?.params ??
|
|
1210
|
+
tool?.input ??
|
|
1211
|
+
tool?.rawInput ??
|
|
1212
|
+
tool?.arguments ??
|
|
1213
|
+
tool?.params,
|
|
1214
|
+
),
|
|
1215
|
+
id: stringField(innerPayload, "id") ?? stringField(tool, "id"),
|
|
1216
|
+
status: stringField(innerPayload, "status") ?? stringField(tool, "status"),
|
|
1207
1217
|
};
|
|
1208
1218
|
}
|
|
1209
1219
|
|
|
1210
|
-
if (
|
|
1211
|
-
const inner =
|
|
1212
|
-
raw?.event === "item.started"
|
|
1213
|
-
? payload
|
|
1214
|
-
: payload.payload && typeof payload.payload === "object"
|
|
1215
|
-
? payload.payload
|
|
1216
|
-
: {};
|
|
1220
|
+
if (event === "item.started") {
|
|
1221
|
+
const inner = innerPayload ?? {};
|
|
1217
1222
|
const item = inner.item && typeof inner.item === "object" ? inner.item : undefined;
|
|
1218
1223
|
const tool = inner.tool && typeof inner.tool === "object" ? inner.tool : item?.tool;
|
|
1219
|
-
const
|
|
1224
|
+
const metadata = item?.metadata && typeof item.metadata === "object" ? item.metadata : undefined;
|
|
1225
|
+
const metadataCommand =
|
|
1226
|
+
metadata && (metadata.command ?? metadata.cmd)
|
|
1227
|
+
? { [metadata.command ? "command" : "cmd"]: metadata.command ?? metadata.cmd }
|
|
1228
|
+
: undefined;
|
|
1229
|
+
const itemParams = parseMaybeJson(
|
|
1230
|
+
item?.input ??
|
|
1231
|
+
item?.arguments ??
|
|
1232
|
+
item?.params ??
|
|
1233
|
+
metadata?.input ??
|
|
1234
|
+
metadata?.arguments ??
|
|
1235
|
+
metadata?.params ??
|
|
1236
|
+
metadataCommand ??
|
|
1237
|
+
item?.detail,
|
|
1238
|
+
);
|
|
1220
1239
|
const detailParams =
|
|
1221
1240
|
itemParams !== undefined
|
|
1222
1241
|
? itemParams
|
|
@@ -1240,9 +1259,18 @@ function extractDeepseekToolCall(raw: any): { name: string; params?: unknown; id
|
|
|
1240
1259
|
inner.arguments ??
|
|
1241
1260
|
inner.params ??
|
|
1242
1261
|
item?.input ??
|
|
1243
|
-
item?.arguments
|
|
1262
|
+
item?.arguments ??
|
|
1263
|
+
item?.params ??
|
|
1264
|
+
metadata?.input ??
|
|
1265
|
+
metadata?.arguments ??
|
|
1266
|
+
metadata?.params ??
|
|
1267
|
+
metadataCommand,
|
|
1244
1268
|
) ?? detailParams ?? tool ?? item,
|
|
1245
|
-
id:
|
|
1269
|
+
id:
|
|
1270
|
+
stringField(tool, "id") ??
|
|
1271
|
+
stringField(inner, "id") ??
|
|
1272
|
+
stringField(item, "id") ??
|
|
1273
|
+
stringField(payload, "item_id"),
|
|
1246
1274
|
status: stringField(tool, "status") ?? stringField(inner, "status") ?? stringField(item, "status"),
|
|
1247
1275
|
};
|
|
1248
1276
|
}
|
|
@@ -1253,28 +1281,26 @@ function extractDeepseekToolCall(raw: any): { name: string; params?: unknown; id
|
|
|
1253
1281
|
function extractDeepseekToolResult(raw: any): { name?: string; result: string; id?: string } | null {
|
|
1254
1282
|
const payload = raw?.payload;
|
|
1255
1283
|
if (!payload || typeof payload !== "object") return null;
|
|
1284
|
+
const innerPayload = unwrapDeepseekPayload(raw);
|
|
1285
|
+
const event = stringField(raw, "event") ?? stringField(payload, "event");
|
|
1256
1286
|
|
|
1257
|
-
if (
|
|
1258
|
-
const result =
|
|
1287
|
+
if (event === "tool.completed") {
|
|
1288
|
+
const result =
|
|
1289
|
+
innerPayload?.output ??
|
|
1290
|
+
innerPayload?.result ??
|
|
1291
|
+
innerPayload?.content ??
|
|
1292
|
+
innerPayload?.error ??
|
|
1293
|
+
innerPayload ??
|
|
1294
|
+
payload;
|
|
1259
1295
|
return {
|
|
1260
|
-
name: stringField(
|
|
1296
|
+
name: stringField(innerPayload, "name"),
|
|
1261
1297
|
result: stringifyToolResult(result),
|
|
1262
|
-
id: stringField(
|
|
1298
|
+
id: stringField(innerPayload, "id"),
|
|
1263
1299
|
};
|
|
1264
1300
|
}
|
|
1265
1301
|
|
|
1266
|
-
if (
|
|
1267
|
-
|
|
1268
|
-
raw?.event === "item.failed" ||
|
|
1269
|
-
payload.event === "item.completed" ||
|
|
1270
|
-
payload.event === "item.failed"
|
|
1271
|
-
) {
|
|
1272
|
-
const inner =
|
|
1273
|
-
raw?.event === "item.completed" || raw?.event === "item.failed"
|
|
1274
|
-
? payload
|
|
1275
|
-
: payload.payload && typeof payload.payload === "object"
|
|
1276
|
-
? payload.payload
|
|
1277
|
-
: {};
|
|
1302
|
+
if (event === "item.completed" || event === "item.failed") {
|
|
1303
|
+
const inner = innerPayload ?? {};
|
|
1278
1304
|
const item = inner.item && typeof inner.item === "object" ? inner.item : undefined;
|
|
1279
1305
|
const result =
|
|
1280
1306
|
item?.output ??
|
|
@@ -1295,13 +1321,35 @@ function extractDeepseekToolResult(raw: any): { name?: string; result: string; i
|
|
|
1295
1321
|
stringField(inner, "name") ??
|
|
1296
1322
|
stringField(item, "type"),
|
|
1297
1323
|
result: stringifyToolResult(result),
|
|
1298
|
-
id: stringField(item, "id") ?? stringField(inner, "id"),
|
|
1324
|
+
id: stringField(item, "id") ?? stringField(inner, "id") ?? stringField(payload, "item_id"),
|
|
1299
1325
|
};
|
|
1300
1326
|
}
|
|
1301
1327
|
|
|
1302
1328
|
return null;
|
|
1303
1329
|
}
|
|
1304
1330
|
|
|
1331
|
+
function unwrapDeepseekPayload(raw: any): any {
|
|
1332
|
+
const payload = raw?.payload;
|
|
1333
|
+
if (!payload || typeof payload !== "object") return undefined;
|
|
1334
|
+
const nested = payload.payload;
|
|
1335
|
+
if (nested && typeof nested === "object") {
|
|
1336
|
+
const outerEvent = stringField(payload, "event");
|
|
1337
|
+
if (
|
|
1338
|
+
outerEvent ||
|
|
1339
|
+
nested.item ||
|
|
1340
|
+
nested.tool ||
|
|
1341
|
+
nested.turn ||
|
|
1342
|
+
nested.kind ||
|
|
1343
|
+
nested.output ||
|
|
1344
|
+
nested.result ||
|
|
1345
|
+
nested.error
|
|
1346
|
+
) {
|
|
1347
|
+
return nested;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
return payload;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1305
1353
|
function formatBlockDetails(raw: unknown): string {
|
|
1306
1354
|
if (!raw || typeof raw !== "object") return "";
|
|
1307
1355
|
const r = raw as any;
|
|
@@ -497,9 +497,15 @@ function isDeepseekTerminalEvent(eventName: string, payload: any): boolean {
|
|
|
497
497
|
}
|
|
498
498
|
|
|
499
499
|
function isToolStarted(eventName: string, payload: any): boolean {
|
|
500
|
+
const itemKind = payload?.payload?.item?.kind ?? payload?.item?.kind;
|
|
500
501
|
return (
|
|
501
502
|
(eventName === "item.started" &&
|
|
502
|
-
(
|
|
503
|
+
(
|
|
504
|
+
!!payload?.tool ||
|
|
505
|
+
itemKind === "tool_call" ||
|
|
506
|
+
itemKind === "command_execution" ||
|
|
507
|
+
itemKind === "file_change"
|
|
508
|
+
)) ||
|
|
503
509
|
(payload?.event === "item.started" && !!payload?.payload?.tool)
|
|
504
510
|
);
|
|
505
511
|
}
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
type RouteRuleMatch,
|
|
18
18
|
} from "./config.js";
|
|
19
19
|
import {
|
|
20
|
+
acquireDaemonSingletonLock,
|
|
20
21
|
ensureNoOtherDaemonFromPidFile,
|
|
21
22
|
findOtherDaemonProcesses,
|
|
22
23
|
pidAlive,
|
|
@@ -625,13 +626,22 @@ async function cmdStart(args: ParsedArgs): Promise<void> {
|
|
|
625
626
|
}
|
|
626
627
|
|
|
627
628
|
// Foreground: we ARE the daemon.
|
|
629
|
+
const singletonLock = await acquireDaemonSingletonLock({ logger: log });
|
|
628
630
|
writeCurrentPid();
|
|
629
|
-
|
|
631
|
+
let handle: Awaited<ReturnType<typeof startDaemon>>;
|
|
632
|
+
try {
|
|
633
|
+
handle = await startDaemon({ config: cfg, configPath: CONFIG_FILE_PATH });
|
|
634
|
+
} catch (err) {
|
|
635
|
+
removePidFile();
|
|
636
|
+
singletonLock.release();
|
|
637
|
+
throw err;
|
|
638
|
+
}
|
|
630
639
|
|
|
631
640
|
const shutdown = async (sig: string) => {
|
|
632
641
|
log.info("signal received", { sig });
|
|
633
642
|
await handle.stop(sig);
|
|
634
643
|
removePidFile();
|
|
644
|
+
singletonLock.release();
|
|
635
645
|
process.exit(0);
|
|
636
646
|
};
|
|
637
647
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
@@ -661,6 +671,7 @@ async function cmdStartCloud(_args: ParsedArgs): Promise<void> {
|
|
|
661
671
|
daemonInstanceId: cloudConfig.daemonInstanceId,
|
|
662
672
|
hubUrl: cloudConfig.hubUrl,
|
|
663
673
|
});
|
|
674
|
+
const singletonLock = await acquireDaemonSingletonLock({ logger: log });
|
|
664
675
|
await stopDaemonFromPidFileForRestart({ logger: log });
|
|
665
676
|
await stopOtherDaemonProcessesForRestart({ logger: log });
|
|
666
677
|
writeCurrentPid();
|
|
@@ -676,16 +687,24 @@ async function cmdStartCloud(_args: ParsedArgs): Promise<void> {
|
|
|
676
687
|
saveConfig(cfg);
|
|
677
688
|
log.info("cloud mode config initialized", { configPath: CONFIG_FILE_PATH });
|
|
678
689
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
690
|
+
let handle: Awaited<ReturnType<typeof startCloudDaemon>>;
|
|
691
|
+
try {
|
|
692
|
+
handle = await startCloudDaemon({
|
|
693
|
+
cloudConfig,
|
|
694
|
+
config: cfg,
|
|
695
|
+
configPath: CONFIG_FILE_PATH,
|
|
696
|
+
});
|
|
697
|
+
} catch (err) {
|
|
698
|
+
removePidFile();
|
|
699
|
+
singletonLock.release();
|
|
700
|
+
throw err;
|
|
701
|
+
}
|
|
684
702
|
|
|
685
703
|
const shutdown = async (sig: string): Promise<void> => {
|
|
686
704
|
log.info("signal received", { sig });
|
|
687
705
|
await handle.stop(sig);
|
|
688
706
|
removePidFile();
|
|
707
|
+
singletonLock.release();
|
|
689
708
|
process.exit(0);
|
|
690
709
|
};
|
|
691
710
|
process.on("SIGTERM", () => void shutdown("SIGTERM"));
|
package/src/provision.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
defaultCredentialsFile,
|
|
14
14
|
derivePublicKey,
|
|
15
15
|
loadStoredCredentials,
|
|
16
|
+
normalizeTokenExpiresAt,
|
|
16
17
|
writeCredentialsFile,
|
|
17
18
|
type AgentIdentitySnapshot,
|
|
18
19
|
type ControlAck,
|
|
@@ -74,6 +75,7 @@ import { log as daemonLog } from "./log.js";
|
|
|
74
75
|
import { discoverAgentCredentials } from "./agent-discovery.js";
|
|
75
76
|
import { resolveMemoryDir } from "./working-memory.js";
|
|
76
77
|
import { discoverRuntimeModelCatalog } from "./runtime-models.js";
|
|
78
|
+
import { collectAgentSkillSnapshot } from "./skill-index.js";
|
|
77
79
|
import {
|
|
78
80
|
buildRuntimeSelectionExtraArgs,
|
|
79
81
|
mergeRuntimeExtraArgs,
|
|
@@ -83,6 +85,10 @@ import {
|
|
|
83
85
|
type CloudGatewayTypingEmitter,
|
|
84
86
|
} from "./cloud-gateway-runtime.js";
|
|
85
87
|
|
|
88
|
+
interface ListAgentSkillsParams {
|
|
89
|
+
agentId: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
86
92
|
/**
|
|
87
93
|
* Information passed to {@link OnAgentInstalledHook} after a successful
|
|
88
94
|
* provision. Mirrors the credential fields the daemon's per-agent caches
|
|
@@ -486,6 +492,32 @@ export function createProvisioner(opts: ProvisionerOptions): (
|
|
|
486
492
|
return { ok: true, result };
|
|
487
493
|
}
|
|
488
494
|
|
|
495
|
+
case "list_agent_skills": {
|
|
496
|
+
const params = (frame.params ?? {}) as unknown as ListAgentSkillsParams;
|
|
497
|
+
if (!params.agentId) {
|
|
498
|
+
return {
|
|
499
|
+
ok: false,
|
|
500
|
+
error: { code: "bad_params", message: "list_agent_skills requires params.agentId" },
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
const channels = gateway.snapshot().channels;
|
|
504
|
+
if (!channels[params.agentId]) {
|
|
505
|
+
return {
|
|
506
|
+
ok: false,
|
|
507
|
+
error: {
|
|
508
|
+
code: "agent_not_loaded",
|
|
509
|
+
message: `agent ${params.agentId} is not loaded in daemon gateway`,
|
|
510
|
+
},
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
const result = collectAgentSkillSnapshot(params.agentId);
|
|
514
|
+
daemonLog.debug("list_agent_skills", {
|
|
515
|
+
agentId: params.agentId,
|
|
516
|
+
count: result.skills.length,
|
|
517
|
+
});
|
|
518
|
+
return { ok: true, result };
|
|
519
|
+
}
|
|
520
|
+
|
|
489
521
|
case "wake_agent": {
|
|
490
522
|
return handleWakeAgent(gateway, frame.params);
|
|
491
523
|
}
|
|
@@ -1227,7 +1259,8 @@ async function materializeCredentials(
|
|
|
1227
1259
|
};
|
|
1228
1260
|
if (c.displayName) record.displayName = c.displayName;
|
|
1229
1261
|
if (c.token) record.token = c.token;
|
|
1230
|
-
|
|
1262
|
+
const tokenExpiresAt = normalizeTokenExpiresAt(c.tokenExpiresAt);
|
|
1263
|
+
if (tokenExpiresAt !== undefined) record.tokenExpiresAt = tokenExpiresAt;
|
|
1231
1264
|
if (runtime) record.runtime = runtime;
|
|
1232
1265
|
const runtimeSelection = pickRuntimeSelection(params);
|
|
1233
1266
|
if (runtimeSelection.runtimeModel) record.runtimeModel = runtimeSelection.runtimeModel;
|
package/src/skill-index.ts
CHANGED
|
@@ -23,6 +23,19 @@ export interface SoftSkillEntry {
|
|
|
23
23
|
mtimeMs: number;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
export interface AgentSkillSnapshotEntry {
|
|
27
|
+
name: string;
|
|
28
|
+
source: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
mtimeMs: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface AgentSkillSnapshot {
|
|
34
|
+
agentId: string;
|
|
35
|
+
skills: AgentSkillSnapshotEntry[];
|
|
36
|
+
probedAt: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
26
39
|
export interface SkillIndexOptions {
|
|
27
40
|
extraDirs?: string[];
|
|
28
41
|
includeGlobal?: boolean;
|
|
@@ -70,7 +83,7 @@ export function scanSoftSkills(
|
|
|
70
83
|
if (!existsSync(root.dir)) continue;
|
|
71
84
|
let children: string[];
|
|
72
85
|
try {
|
|
73
|
-
children = readdirSync(root.dir);
|
|
86
|
+
children = readdirSync(root.dir).sort((a, b) => a.localeCompare(b));
|
|
74
87
|
} catch {
|
|
75
88
|
continue;
|
|
76
89
|
}
|
|
@@ -105,15 +118,30 @@ export function scanSoftSkills(
|
|
|
105
118
|
}
|
|
106
119
|
|
|
107
120
|
return Array.from(byName.values())
|
|
108
|
-
.sort((a, b) => a.name.localeCompare(b.name))
|
|
109
|
-
|
|
121
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function collectAgentSkillSnapshot(
|
|
125
|
+
agentId: string,
|
|
126
|
+
opts: SkillIndexOptions = {},
|
|
127
|
+
): AgentSkillSnapshot {
|
|
128
|
+
return {
|
|
129
|
+
agentId,
|
|
130
|
+
skills: scanSoftSkills(agentId, opts).map((skill) => ({
|
|
131
|
+
name: skill.name,
|
|
132
|
+
source: skill.source.startsWith("agent-") ? "workspace" : "runtime-global",
|
|
133
|
+
...(skill.description ? { description: skill.description } : {}),
|
|
134
|
+
mtimeMs: skill.mtimeMs,
|
|
135
|
+
})),
|
|
136
|
+
probedAt: Date.now(),
|
|
137
|
+
};
|
|
110
138
|
}
|
|
111
139
|
|
|
112
140
|
export function buildSoftSkillIndexPrompt(
|
|
113
141
|
agentId: string,
|
|
114
142
|
opts: SkillIndexOptions = {},
|
|
115
143
|
): string | null {
|
|
116
|
-
const skills = scanSoftSkills(agentId, opts);
|
|
144
|
+
const skills = scanSoftSkills(agentId, opts).slice(0, MAX_SKILLS);
|
|
117
145
|
if (skills.length === 0) return null;
|
|
118
146
|
|
|
119
147
|
const lines = [
|