agenticros 0.3.5 → 0.4.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/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +34 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/skills.d.ts +3 -5
- package/dist/commands/skills.d.ts.map +1 -1
- package/dist/commands/skills.js +21 -7
- package/dist/commands/skills.js.map +1 -1
- package/dist/util/skills.d.ts +6 -0
- package/dist/util/skills.d.ts.map +1 -1
- package/dist/util/skills.js +69 -1
- package/dist/util/skills.js.map +1 -1
- package/package.json +2 -1
- package/runtime/BUNDLE.json +1 -1
- package/runtime/packages/agenticros/src/index.ts +8 -1
- package/runtime/packages/agenticros/src/tools/ros2-capabilities.ts +9 -12
- package/runtime/packages/agenticros-claude-code/dist/config.d.ts +5 -3
- package/runtime/packages/agenticros-claude-code/dist/config.d.ts.map +1 -1
- package/runtime/packages/agenticros-claude-code/dist/config.js +32 -10
- package/runtime/packages/agenticros-claude-code/dist/config.js.map +1 -1
- package/runtime/packages/agenticros-claude-code/dist/index.js +3 -3
- package/runtime/packages/agenticros-claude-code/dist/index.js.map +1 -1
- package/runtime/packages/agenticros-claude-code/dist/tools.d.ts.map +1 -1
- package/runtime/packages/agenticros-claude-code/dist/tools.js +8 -8
- package/runtime/packages/agenticros-claude-code/dist/tools.js.map +1 -1
- package/runtime/packages/agenticros-claude-code/src/config.ts +33 -10
- package/runtime/packages/agenticros-claude-code/src/index.ts +3 -3
- package/runtime/packages/agenticros-claude-code/src/tools.ts +11 -8
- package/runtime/packages/agenticros-gemini/src/config.ts +27 -18
- package/runtime/packages/agenticros-gemini/src/index.ts +2 -2
- package/runtime/packages/agenticros-gemini/src/tools.ts +5 -2
- package/runtime/packages/core/package.json +1 -1
- package/runtime/packages/core/src/__tests__/skill-refs.test.ts +77 -0
- package/runtime/packages/core/src/config.ts +8 -0
- package/runtime/packages/core/src/discoverable-capabilities.ts +178 -0
- package/runtime/packages/core/src/external-capability.ts +16 -3
- package/runtime/packages/core/src/index.ts +30 -0
- package/runtime/packages/core/src/skill-refs.ts +330 -0
- package/runtime/pnpm-lock.yaml +3 -0
|
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import type { AgenticROSConfig } from "@agenticros/core";
|
|
5
|
-
import { parseConfig } from "@agenticros/core";
|
|
5
|
+
import { parseConfig, withResolvedSkillRefs, applyCachedSkillRefs } from "@agenticros/core";
|
|
6
6
|
|
|
7
7
|
/** Same as Claude Code MCP: optional `robot.namespace` override from the environment. */
|
|
8
8
|
function applyMcpEnvOverrides(config: AgenticROSConfig): AgenticROSConfig {
|
|
@@ -17,11 +17,6 @@ function applyMcpEnvOverrides(config: AgenticROSConfig): AgenticROSConfig {
|
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
/**
|
|
21
|
-
* Resolve path to AgenticROS config file.
|
|
22
|
-
* Prefer AGENTICROS_CONFIG_PATH; else ~/.agenticros/config.json.
|
|
23
|
-
* Optional: fallback to OpenClaw config and read plugins.entries.agenticros.config.
|
|
24
|
-
*/
|
|
25
20
|
function getConfigPath(): string {
|
|
26
21
|
const env = process.env.AGENTICROS_CONFIG_PATH;
|
|
27
22
|
if (env && env.trim().length > 0) {
|
|
@@ -30,9 +25,6 @@ function getConfigPath(): string {
|
|
|
30
25
|
return path.join(os.homedir(), ".agenticros", "config.json");
|
|
31
26
|
}
|
|
32
27
|
|
|
33
|
-
/**
|
|
34
|
-
* Try to read config from OpenClaw file (plugins.entries.agenticros.config).
|
|
35
|
-
*/
|
|
36
28
|
function tryOpenClawConfig(): Record<string, unknown> | null {
|
|
37
29
|
const openclawEnv = process.env.OPENCLAW_CONFIG;
|
|
38
30
|
const openclawPath = openclawEnv && openclawEnv.trim().length > 0
|
|
@@ -57,12 +49,7 @@ function tryOpenClawConfig(): Record<string, unknown> | null {
|
|
|
57
49
|
return null;
|
|
58
50
|
}
|
|
59
51
|
|
|
60
|
-
|
|
61
|
-
* Load and parse AgenticROS config.
|
|
62
|
-
* 1) AGENTICROS_CONFIG_PATH or ~/.agenticros/config.json (full JSON object = config).
|
|
63
|
-
* 2) If that file does not exist, try OpenClaw config and read plugins.entries.agenticros.config.
|
|
64
|
-
*/
|
|
65
|
-
export function loadConfig(): AgenticROSConfig {
|
|
52
|
+
function loadConfigSync(): AgenticROSConfig {
|
|
66
53
|
const primaryPath = getConfigPath();
|
|
67
54
|
try {
|
|
68
55
|
const raw = fs.readFileSync(primaryPath, "utf8");
|
|
@@ -72,14 +59,14 @@ export function loadConfig(): AgenticROSConfig {
|
|
|
72
59
|
if (process.stderr && typeof process.stderr.write === "function") {
|
|
73
60
|
process.stderr.write(`[AgenticROS] Config from ${primaryPath}\n`);
|
|
74
61
|
}
|
|
75
|
-
return applyMcpEnvOverrides(cfg);
|
|
62
|
+
return applyMcpEnvOverrides(applyCachedSkillRefs(cfg));
|
|
76
63
|
}
|
|
77
64
|
} catch (err) {
|
|
78
65
|
const nodeErr = err as NodeJS.ErrnoException;
|
|
79
66
|
if (nodeErr.code === "ENOENT") {
|
|
80
67
|
const openclawConfig = tryOpenClawConfig();
|
|
81
68
|
if (openclawConfig) {
|
|
82
|
-
return applyMcpEnvOverrides(parseConfig(openclawConfig));
|
|
69
|
+
return applyMcpEnvOverrides(applyCachedSkillRefs(parseConfig(openclawConfig)));
|
|
83
70
|
}
|
|
84
71
|
throw new Error(
|
|
85
72
|
`AgenticROS config not found at ${primaryPath}. Create it or set AGENTICROS_CONFIG_PATH. ` +
|
|
@@ -88,5 +75,27 @@ export function loadConfig(): AgenticROSConfig {
|
|
|
88
75
|
}
|
|
89
76
|
throw err;
|
|
90
77
|
}
|
|
91
|
-
return applyMcpEnvOverrides(parseConfig({}));
|
|
78
|
+
return applyMcpEnvOverrides(applyCachedSkillRefs(parseConfig({})));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function loadConfig(): AgenticROSConfig {
|
|
82
|
+
return loadConfigSync();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function loadConfigAsync(): Promise<AgenticROSConfig> {
|
|
86
|
+
const base = loadConfigSync();
|
|
87
|
+
if (!(base.skillRefs?.length)) return base;
|
|
88
|
+
const { config, errors } = await withResolvedSkillRefs(base, {
|
|
89
|
+
onLog: (msg) => {
|
|
90
|
+
if (process.stderr && typeof process.stderr.write === "function") {
|
|
91
|
+
process.stderr.write(`[AgenticROS] ${msg}\n`);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
for (const e of errors) {
|
|
96
|
+
if (process.stderr && typeof process.stderr.write === "function") {
|
|
97
|
+
process.stderr.write(`[AgenticROS] skillRef ${e.ref}: ${e.error}\n`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return applyMcpEnvOverrides(config);
|
|
92
101
|
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Optional: GEMINI_MODEL (default gemini-2.5-flash) if you hit quota on a specific model.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { loadConfigAsync } from "./config.js";
|
|
14
14
|
import { connect, disconnect } from "./transport.js";
|
|
15
15
|
import { chatWithRobot } from "./chat.js";
|
|
16
16
|
import { renderAgenticROSBanner } from "@agenticros/core";
|
|
@@ -32,7 +32,7 @@ async function main(): Promise<void> {
|
|
|
32
32
|
process.exit(1);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
const config =
|
|
35
|
+
const config = await loadConfigAsync();
|
|
36
36
|
await connect(config);
|
|
37
37
|
|
|
38
38
|
try {
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
toNamespacedTopic,
|
|
15
15
|
toNamespacedTopicFull,
|
|
16
16
|
listAllCapabilities,
|
|
17
|
+
listCapabilitiesWithDiscoverable,
|
|
17
18
|
runMission,
|
|
18
19
|
listRobots,
|
|
19
20
|
getActiveRobotId,
|
|
@@ -500,15 +501,17 @@ export async function executeTool(
|
|
|
500
501
|
if (name === "ros2_list_capabilities") {
|
|
501
502
|
const resolved = resolveRobotForTool(config, args);
|
|
502
503
|
if ("error" in resolved) return resolved.error;
|
|
503
|
-
const caps
|
|
504
|
+
const caps = await listCapabilitiesWithDiscoverable(config);
|
|
504
505
|
const intrinsic = caps.filter((c) => c.source?.kind === "builtin").length;
|
|
505
|
-
const skill = caps.filter((c) => c.source?.kind === "skill").length;
|
|
506
|
+
const skill = caps.filter((c) => c.installed !== false && c.source?.kind === "skill").length;
|
|
507
|
+
const discoverable = caps.filter((c) => c.discoverable === true).length;
|
|
506
508
|
return {
|
|
507
509
|
output: JSON.stringify({
|
|
508
510
|
success: true,
|
|
509
511
|
total: caps.length,
|
|
510
512
|
intrinsic_count: intrinsic,
|
|
511
513
|
skill_count: skill,
|
|
514
|
+
discoverable_count: discoverable,
|
|
512
515
|
capabilities: caps,
|
|
513
516
|
}),
|
|
514
517
|
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for skillRefs parsing and discoverable capability merge.
|
|
3
|
+
*/
|
|
4
|
+
import { test } from "node:test";
|
|
5
|
+
import assert from "node:assert/strict";
|
|
6
|
+
import {
|
|
7
|
+
parseSkillRef,
|
|
8
|
+
parseConfig,
|
|
9
|
+
listCapabilitiesWithDiscoverable,
|
|
10
|
+
listAllCapabilities,
|
|
11
|
+
} from "../index.js";
|
|
12
|
+
|
|
13
|
+
test("parseSkillRef: owner/skill and @pin", () => {
|
|
14
|
+
assert.deepEqual(parseSkillRef("agenticros/navigate-to"), {
|
|
15
|
+
marketplaceRef: "agenticros/navigate-to",
|
|
16
|
+
owner: "agenticros",
|
|
17
|
+
skill: "navigate-to",
|
|
18
|
+
gitRef: "main",
|
|
19
|
+
});
|
|
20
|
+
assert.equal(parseSkillRef("agenticros/start-slam@v0.1.0")?.gitRef, "v0.1.0");
|
|
21
|
+
assert.equal(parseSkillRef("nope"), null);
|
|
22
|
+
assert.equal(parseSkillRef(""), null);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("parseConfig: skillRefs defaults to []", () => {
|
|
26
|
+
const cfg = parseConfig({});
|
|
27
|
+
assert.deepEqual(cfg.skillRefs, []);
|
|
28
|
+
const cfg2 = parseConfig({ skillRefs: ["agenticros/detect-humans"] });
|
|
29
|
+
assert.deepEqual(cfg2.skillRefs, ["agenticros/detect-humans"]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("listCapabilitiesWithDiscoverable: soft-fail offline keeps installed", async () => {
|
|
33
|
+
const cfg = parseConfig({});
|
|
34
|
+
const caps = await listCapabilitiesWithDiscoverable(cfg, {
|
|
35
|
+
apiBase: "http://127.0.0.1:9", // nothing listening
|
|
36
|
+
softFail: true,
|
|
37
|
+
});
|
|
38
|
+
const baseline = listAllCapabilities(cfg);
|
|
39
|
+
assert.equal(caps.length, baseline.length);
|
|
40
|
+
assert.ok(caps.every((c) => c.installed !== false || c.discoverable !== true));
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("listCapabilitiesWithDiscoverable: merges marketplace caps when fetch works", async () => {
|
|
44
|
+
const originalFetch = globalThis.fetch;
|
|
45
|
+
globalThis.fetch = (async () =>
|
|
46
|
+
new Response(
|
|
47
|
+
JSON.stringify({
|
|
48
|
+
skills: [
|
|
49
|
+
{
|
|
50
|
+
marketplaceRef: "agenticros/detect-humans",
|
|
51
|
+
visibility: "public",
|
|
52
|
+
capabilities: [
|
|
53
|
+
{
|
|
54
|
+
id: "detect_humans",
|
|
55
|
+
verb: "detect",
|
|
56
|
+
description: "Read detections",
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
}),
|
|
62
|
+
{ status: 200, headers: { "Content-Type": "application/json" } },
|
|
63
|
+
)) as typeof fetch;
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const cfg = parseConfig({});
|
|
67
|
+
const caps = await listCapabilitiesWithDiscoverable(cfg, { softFail: false });
|
|
68
|
+
const det = caps.find((c) => c.id === "detect_humans");
|
|
69
|
+
assert.ok(det);
|
|
70
|
+
assert.equal(det!.discoverable, true);
|
|
71
|
+
assert.equal(det!.installed, false);
|
|
72
|
+
assert.equal(det!.install_ref, "agenticros/detect-humans");
|
|
73
|
+
assert.ok(caps.some((c) => c.id === "drive_base" && c.installed === true));
|
|
74
|
+
} finally {
|
|
75
|
+
globalThis.fetch = originalFetch;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
@@ -418,6 +418,14 @@ export const AgenticROSConfigSchema = z.object({
|
|
|
418
418
|
|
|
419
419
|
/** Npm (or local) package names to load as skills. Resolved via require.resolve from plugin context. */
|
|
420
420
|
skillPackages: z.array(z.string()).default([]),
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Marketplace skill refs to auto-fetch into `~/.agenticros/skills-cache/`
|
|
424
|
+
* (e.g. `agenticros/navigate-to` or `owner/skill@main`). Resolved at
|
|
425
|
+
* adapter startup into effective skillPaths. Never auto-upgrades pins.
|
|
426
|
+
* Distinct from `skills` (per-skill runtime config object).
|
|
427
|
+
*/
|
|
428
|
+
skillRefs: z.array(z.string()).default([]),
|
|
421
429
|
});
|
|
422
430
|
|
|
423
431
|
export type AgenticROSConfig = z.infer<typeof AgenticROSConfigSchema>;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discoverable marketplace capabilities — list verbs from skills.agenticros.com
|
|
3
|
+
* that are not yet installed locally, so agents can propose installs.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { AgenticROSConfig } from "./config.js";
|
|
7
|
+
import {
|
|
8
|
+
listAllCapabilities,
|
|
9
|
+
type Capability,
|
|
10
|
+
type CapabilityField,
|
|
11
|
+
type CapabilitySource,
|
|
12
|
+
} from "./capabilities.js";
|
|
13
|
+
import { skillsApiBase } from "./skill-refs.js";
|
|
14
|
+
|
|
15
|
+
export interface DiscoverableCapability extends Capability {
|
|
16
|
+
/** Always true for marketplace-only entries. */
|
|
17
|
+
discoverable: true;
|
|
18
|
+
/** False when not present in the local registry. */
|
|
19
|
+
installed: false;
|
|
20
|
+
/** Marketplace install ref (owner/skill). */
|
|
21
|
+
install_ref: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ListedCapability extends Capability {
|
|
25
|
+
discoverable?: boolean;
|
|
26
|
+
installed?: boolean;
|
|
27
|
+
install_ref?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ListCapabilitiesOptions {
|
|
31
|
+
/** Include marketplace browse (network). Default true. */
|
|
32
|
+
includeDiscoverable?: boolean;
|
|
33
|
+
apiBase?: string;
|
|
34
|
+
/** Max marketplace skills to scan. Default 50. */
|
|
35
|
+
marketplaceLimit?: number;
|
|
36
|
+
/** Soft-fail: on network error return installed-only. Default true. */
|
|
37
|
+
softFail?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface MarketplaceSkillDoc {
|
|
41
|
+
marketplaceRef?: string;
|
|
42
|
+
ownerLogin?: string;
|
|
43
|
+
skillSlug?: string;
|
|
44
|
+
slug?: string;
|
|
45
|
+
capabilities?: Array<Record<string, unknown>>;
|
|
46
|
+
visibility?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function installRefOf(s: MarketplaceSkillDoc): string | null {
|
|
50
|
+
if (s.marketplaceRef && typeof s.marketplaceRef === "string") {
|
|
51
|
+
return s.marketplaceRef.toLowerCase();
|
|
52
|
+
}
|
|
53
|
+
if (s.ownerLogin && s.skillSlug) {
|
|
54
|
+
return `${s.ownerLogin}/${s.skillSlug}`.toLowerCase();
|
|
55
|
+
}
|
|
56
|
+
if (s.slug && s.slug.includes("/")) return s.slug.toLowerCase();
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function fieldMap(
|
|
61
|
+
raw: unknown,
|
|
62
|
+
): Record<string, CapabilityField> | undefined {
|
|
63
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
|
|
64
|
+
const out: Record<string, CapabilityField> = {};
|
|
65
|
+
for (const [k, v] of Object.entries(raw as Record<string, unknown>)) {
|
|
66
|
+
if (typeof v === "string") {
|
|
67
|
+
out[k] = { type: v };
|
|
68
|
+
} else if (v && typeof v === "object") {
|
|
69
|
+
const o = v as Record<string, unknown>;
|
|
70
|
+
out[k] = {
|
|
71
|
+
type: typeof o.type === "string" ? o.type : "unknown",
|
|
72
|
+
description: typeof o.description === "string" ? o.description : undefined,
|
|
73
|
+
optional: o.optional === true,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return Object.keys(out).length ? out : undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function normalizeMarketplaceCap(
|
|
81
|
+
raw: Record<string, unknown>,
|
|
82
|
+
installRef: string,
|
|
83
|
+
packageName: string,
|
|
84
|
+
): DiscoverableCapability | null {
|
|
85
|
+
const id = typeof raw.id === "string" ? raw.id : null;
|
|
86
|
+
const verb = typeof raw.verb === "string" ? raw.verb : null;
|
|
87
|
+
const description =
|
|
88
|
+
typeof raw.description === "string" ? raw.description : id ? `Marketplace capability ${id}` : null;
|
|
89
|
+
if (!id || !verb || !description) return null;
|
|
90
|
+
const source: CapabilitySource = {
|
|
91
|
+
kind: "skill",
|
|
92
|
+
skillId: installRef.split("/")[1] ?? installRef,
|
|
93
|
+
package: packageName,
|
|
94
|
+
};
|
|
95
|
+
return {
|
|
96
|
+
id,
|
|
97
|
+
verb,
|
|
98
|
+
description,
|
|
99
|
+
inputs: fieldMap(raw.inputs),
|
|
100
|
+
outputs: fieldMap(raw.outputs),
|
|
101
|
+
interruptible: raw.interruptible === true ? true : raw.interruptible === false ? false : undefined,
|
|
102
|
+
blocks_base: raw.blocks_base === true ? true : undefined,
|
|
103
|
+
source,
|
|
104
|
+
discoverable: true,
|
|
105
|
+
installed: false,
|
|
106
|
+
install_ref: installRef,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function fetchMarketplaceSkills(
|
|
111
|
+
opts: { apiBase?: string; limit?: number } = {},
|
|
112
|
+
): Promise<MarketplaceSkillDoc[]> {
|
|
113
|
+
const base = skillsApiBase(opts.apiBase);
|
|
114
|
+
const limit = opts.limit ?? 50;
|
|
115
|
+
const u = new URL(`${base}/skills`);
|
|
116
|
+
u.searchParams.set("limit", String(limit));
|
|
117
|
+
u.searchParams.set("sort", "popular");
|
|
118
|
+
const res = await fetch(u.toString(), { headers: { Accept: "application/json" } });
|
|
119
|
+
if (!res.ok) {
|
|
120
|
+
throw new Error(`Marketplace list ${res.status}`);
|
|
121
|
+
}
|
|
122
|
+
const body = (await res.json()) as { skills?: MarketplaceSkillDoc[] };
|
|
123
|
+
return body.skills ?? [];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Merge installed capabilities with discoverable marketplace capabilities
|
|
128
|
+
* (ids not already present locally). Soft-fails offline.
|
|
129
|
+
*/
|
|
130
|
+
export async function listCapabilitiesWithDiscoverable(
|
|
131
|
+
config: AgenticROSConfig,
|
|
132
|
+
opts: ListCapabilitiesOptions = {},
|
|
133
|
+
): Promise<ListedCapability[]> {
|
|
134
|
+
const installed = listAllCapabilities(config).map((c) => {
|
|
135
|
+
const listed: ListedCapability = {
|
|
136
|
+
...c,
|
|
137
|
+
installed: true,
|
|
138
|
+
discoverable: false,
|
|
139
|
+
};
|
|
140
|
+
return listed;
|
|
141
|
+
});
|
|
142
|
+
if (opts.includeDiscoverable === false) {
|
|
143
|
+
return installed;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const installedIds = new Set(installed.map((c) => c.id));
|
|
147
|
+
try {
|
|
148
|
+
const skills = await fetchMarketplaceSkills({
|
|
149
|
+
apiBase: opts.apiBase,
|
|
150
|
+
limit: opts.marketplaceLimit ?? 50,
|
|
151
|
+
});
|
|
152
|
+
const extra: DiscoverableCapability[] = [];
|
|
153
|
+
const seenDiscoverable = new Set<string>();
|
|
154
|
+
for (const s of skills) {
|
|
155
|
+
if (s.visibility && s.visibility !== "public") continue;
|
|
156
|
+
const ref = installRefOf(s);
|
|
157
|
+
if (!ref) continue;
|
|
158
|
+
const caps = Array.isArray(s.capabilities) ? s.capabilities : [];
|
|
159
|
+
for (const raw of caps) {
|
|
160
|
+
if (!raw || typeof raw !== "object") continue;
|
|
161
|
+
const id = typeof (raw as { id?: unknown }).id === "string" ? (raw as { id: string }).id : null;
|
|
162
|
+
if (!id || installedIds.has(id) || seenDiscoverable.has(id)) continue;
|
|
163
|
+
const norm = normalizeMarketplaceCap(
|
|
164
|
+
raw as Record<string, unknown>,
|
|
165
|
+
ref,
|
|
166
|
+
s.slug ?? ref,
|
|
167
|
+
);
|
|
168
|
+
if (!norm) continue;
|
|
169
|
+
seenDiscoverable.add(id);
|
|
170
|
+
extra.push(norm);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return [...installed, ...extra];
|
|
174
|
+
} catch (e) {
|
|
175
|
+
if (opts.softFail === false) throw e;
|
|
176
|
+
return installed;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -156,12 +156,25 @@ export async function executeExternalCapability(
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
if (impl.topic) {
|
|
159
|
-
const
|
|
159
|
+
const topicName =
|
|
160
|
+
typeof inputs.topic === "string" && inputs.topic.trim()
|
|
161
|
+
? inputs.topic.trim()
|
|
162
|
+
: impl.topic;
|
|
163
|
+
const topic = resolveName(topicName, ns);
|
|
160
164
|
const msgType = impl.msg_type ?? "";
|
|
161
|
-
|
|
165
|
+
// Detection / sensor skills default to subscribe_once; publishers must pass mode: "publish".
|
|
166
|
+
const mode =
|
|
167
|
+
typeof inputs.mode === "string"
|
|
168
|
+
? inputs.mode
|
|
169
|
+
: "subscribe";
|
|
162
170
|
if (mode === "subscribe" || mode === "subscribe_once") {
|
|
163
171
|
const timeout =
|
|
164
|
-
options.timeoutMs ??
|
|
172
|
+
options.timeoutMs ??
|
|
173
|
+
(typeof inputs.timeout_ms === "number"
|
|
174
|
+
? inputs.timeout_ms
|
|
175
|
+
: typeof inputs.timeout === "number"
|
|
176
|
+
? inputs.timeout
|
|
177
|
+
: 5000);
|
|
165
178
|
const msg = await new Promise<Record<string, unknown>>((resolve, reject) => {
|
|
166
179
|
let settled = false;
|
|
167
180
|
const timer = setTimeout(() => {
|
|
@@ -171,5 +171,35 @@ export type {
|
|
|
171
171
|
FindRobotsForResult,
|
|
172
172
|
} from "./find-robots-for.js";
|
|
173
173
|
|
|
174
|
+
export {
|
|
175
|
+
DEFAULT_SKILLS_API,
|
|
176
|
+
DEFAULT_SKILLS_CACHE_DIR,
|
|
177
|
+
ensureSkillRefCached,
|
|
178
|
+
fetchInstallDescriptor,
|
|
179
|
+
githubRepoBasename,
|
|
180
|
+
parseSkillRef,
|
|
181
|
+
resolveSkillRefs,
|
|
182
|
+
skillsApiBase,
|
|
183
|
+
skillsCacheDir,
|
|
184
|
+
withResolvedSkillRefs,
|
|
185
|
+
applyCachedSkillRefs,
|
|
186
|
+
} from "./skill-refs.js";
|
|
187
|
+
export type {
|
|
188
|
+
InstallDescriptor,
|
|
189
|
+
ParsedSkillRef,
|
|
190
|
+
ResolveSkillRefsOptions,
|
|
191
|
+
ResolveSkillRefsResult,
|
|
192
|
+
} from "./skill-refs.js";
|
|
193
|
+
|
|
194
|
+
export {
|
|
195
|
+
fetchMarketplaceSkills,
|
|
196
|
+
listCapabilitiesWithDiscoverable,
|
|
197
|
+
} from "./discoverable-capabilities.js";
|
|
198
|
+
export type {
|
|
199
|
+
DiscoverableCapability,
|
|
200
|
+
ListCapabilitiesOptions,
|
|
201
|
+
ListedCapability,
|
|
202
|
+
} from "./discoverable-capabilities.js";
|
|
203
|
+
|
|
174
204
|
export { TransportPool, TRANSPORT_POOL_GLOBAL_KEY } from "./transport-pool.js";
|
|
175
205
|
export type { TransportFactory } from "./transport-pool.js";
|