@botbotgo/agent-harness 0.0.307 → 0.0.308
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 +43 -9
- package/README.zh.md +43 -9
- package/dist/config/runtime/workspace.yaml +16 -0
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/resource/isolation.js +6 -1
- package/dist/runtime/adapter/model/invocation-request.d.ts +1 -0
- package/dist/runtime/adapter/model/invocation-request.js +4 -0
- package/dist/workspace/compile.js +135 -5
- package/dist/workspace/object-loader.js +27 -17
- package/dist/workspace/resource-compilers.js +3 -3
- package/dist/workspace/support/source-protocols.d.ts +19 -0
- package/dist/workspace/support/source-protocols.js +192 -0
- package/dist/workspace/support/workspace-ref-utils.d.ts +4 -0
- package/dist/workspace/support/workspace-ref-utils.js +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -469,9 +469,9 @@ import { AgentHarnessRuntime, createAgentHarness } from "@botbotgo/agent-harness
|
|
|
469
469
|
const runtime: AgentHarnessRuntime = await createAgentHarness("/absolute/path/to/workspace");
|
|
470
470
|
```
|
|
471
471
|
|
|
472
|
-
`createAgentHarness(...)` loads the workspace, resolves
|
|
472
|
+
`createAgentHarness(...)` loads the workspace, resolves workspace sources, initializes persistence under `runtimeRoot`, and starts runtime maintenance.
|
|
473
473
|
|
|
474
|
-
`runtime.spec.
|
|
474
|
+
`runtime.spec.sources` is the primary public discovery surface for local tools, package tools, and skill packages.
|
|
475
475
|
|
|
476
476
|
```yaml
|
|
477
477
|
apiVersion: agent-harness/v1alpha1
|
|
@@ -479,11 +479,29 @@ kind: Runtime
|
|
|
479
479
|
metadata:
|
|
480
480
|
name: default
|
|
481
481
|
spec:
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
482
|
+
sources:
|
|
483
|
+
tools:
|
|
484
|
+
- file://./resources/tools
|
|
485
|
+
- file://../shared-tools
|
|
486
|
+
- npm://@acme/agent-tools
|
|
487
|
+
skills:
|
|
488
|
+
- file://./resources/skills
|
|
489
|
+
- https://example.com/skills/review/SKILL.md
|
|
485
490
|
```
|
|
486
491
|
|
|
492
|
+
Tool-source rules:
|
|
493
|
+
|
|
494
|
+
- `file://...` scans only the configured folder
|
|
495
|
+
- `npm://...` resolves one package, auto-installs it when missing, and discovers exported `tool({...})` definitions from the package entry
|
|
496
|
+
- tool discovery never traverses `node_modules/**`
|
|
497
|
+
|
|
498
|
+
Skill-source rules:
|
|
499
|
+
|
|
500
|
+
- `file://...` accepts a skill collection folder, a single skill root, or a direct `SKILL.md` path
|
|
501
|
+
- `http://...` and `https://...` currently accept a single remote `SKILL.md`
|
|
502
|
+
|
|
503
|
+
`runtime.spec.resources` remains supported as a compatibility path for attached resource packages.
|
|
504
|
+
|
|
487
505
|
`createAgentHarness(..., { load })` accepts workspace loading controls.
|
|
488
506
|
|
|
489
507
|
Merge order is deterministic:
|
|
@@ -739,8 +757,10 @@ Discovery rules:
|
|
|
739
757
|
|
|
740
758
|
- every YAML document under `config/**` is discovered recursively; filenames and subfolders are organizational only
|
|
741
759
|
- YAML object semantics come from `kind`, `metadata.name` or `id`, and object content rather than the file path
|
|
742
|
-
-
|
|
743
|
-
- skills
|
|
760
|
+
- `Runtime.spec.sources.tools` defaults to `file://./resources/tools`
|
|
761
|
+
- `Runtime.spec.sources.skills` defaults to `file://./resources/skills`
|
|
762
|
+
- local file-based tools are auto-discovered from each configured tool folder when modules export `tool({...})`
|
|
763
|
+
- file-based skills are auto-discovered from each configured skill source
|
|
744
764
|
- a minimal workspace can start with only `config/models.yaml`; the repository defaults provide the `Runtime`, the default `orchestra` host, and runtime-managed durable memory with `enabled: true`
|
|
745
765
|
- when you do not override runtime placement, harness-owned generated state is written under `./.botbotgo/`
|
|
746
766
|
|
|
@@ -768,7 +788,9 @@ For DeepAgents-backed agents, the runtime still keeps an internal compatibility
|
|
|
768
788
|
|
|
769
789
|
Default wiring guidance:
|
|
770
790
|
|
|
771
|
-
- let
|
|
791
|
+
- let `Runtime.spec.sources` declare the tool and skill roots the workspace owns
|
|
792
|
+
- let workspace startup scan only those declared sources into one registry
|
|
793
|
+
- let workspace startup scan local and attached `resources` packages into one registry when compatibility paths are still in use
|
|
772
794
|
- let agents whitelist tools and skills by name
|
|
773
795
|
- keep `config/catalogs/tools.yaml` for reusable shared tools
|
|
774
796
|
- keep `config/catalogs/mcp.yaml` for shared MCP server definitions
|
|
@@ -810,6 +832,8 @@ Important fields:
|
|
|
810
832
|
|
|
811
833
|
- `runtimeRoot`
|
|
812
834
|
- `concurrency.maxConcurrentRequests`
|
|
835
|
+
- `sources.tools`
|
|
836
|
+
- `sources.skills`
|
|
813
837
|
- `routing.defaultAgentId`
|
|
814
838
|
- `routing.rules`
|
|
815
839
|
- `toolModuleDiscovery.scope`
|
|
@@ -826,7 +850,17 @@ Important fields:
|
|
|
826
850
|
- `maintenance.checkpoints` trims backend checkpoint state used for resume/recovery
|
|
827
851
|
- `maintenance.records` trims harness-owned terminal session/request records stored in `runtime.sqlite`
|
|
828
852
|
|
|
829
|
-
`
|
|
853
|
+
`sources.tools` controls which tool roots or packages participate in workspace discovery:
|
|
854
|
+
|
|
855
|
+
- `file://...` for folder scanning
|
|
856
|
+
- `npm://...` for package-entry discovery and auto-install when missing
|
|
857
|
+
|
|
858
|
+
`sources.skills` controls which skill folders or skill documents participate in workspace discovery:
|
|
859
|
+
|
|
860
|
+
- `file://...` for local folders, skill roots, or direct `SKILL.md`
|
|
861
|
+
- `http://...` / `https://...` for one remote `SKILL.md`
|
|
862
|
+
|
|
863
|
+
`toolModuleDiscovery.scope` controls how local `resources/tools/`-style file discovery walks tool directories:
|
|
830
864
|
|
|
831
865
|
- `recursive` is the default and keeps scanning nested folders
|
|
832
866
|
- `top-level` limits module discovery to files directly under each tool root while leaving YAML catalogs recursive
|
package/README.zh.md
CHANGED
|
@@ -464,9 +464,9 @@ import { AgentHarnessRuntime, createAgentHarness } from "@botbotgo/agent-harness
|
|
|
464
464
|
const runtime: AgentHarnessRuntime = await createAgentHarness("/absolute/path/to/workspace");
|
|
465
465
|
```
|
|
466
466
|
|
|
467
|
-
`createAgentHarness(...)`
|
|
467
|
+
`createAgentHarness(...)` 会加载工作区、解析工作区 source、在 `runtimeRoot` 下初始化持久化,并启动运行时维护任务。
|
|
468
468
|
|
|
469
|
-
`runtime.spec.
|
|
469
|
+
`runtime.spec.sources` 现在是本地工具、npm 工具包与 skill package 的主公开发现入口。
|
|
470
470
|
|
|
471
471
|
```yaml
|
|
472
472
|
apiVersion: agent-harness/v1alpha1
|
|
@@ -474,11 +474,29 @@ kind: Runtime
|
|
|
474
474
|
metadata:
|
|
475
475
|
name: default
|
|
476
476
|
spec:
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
477
|
+
sources:
|
|
478
|
+
tools:
|
|
479
|
+
- file://./resources/tools
|
|
480
|
+
- file://../shared-tools
|
|
481
|
+
- npm://@acme/agent-tools
|
|
482
|
+
skills:
|
|
483
|
+
- file://./resources/skills
|
|
484
|
+
- https://example.com/skills/review/SKILL.md
|
|
480
485
|
```
|
|
481
486
|
|
|
487
|
+
tool source 规则:
|
|
488
|
+
|
|
489
|
+
- `file://...` 只扫描显式配置的目录
|
|
490
|
+
- `npm://...` 只解析单个包;本地缺失时会自动安装,再从包入口发现导出的 `tool({...})`
|
|
491
|
+
- tool 发现永远不会遍历 `node_modules/**`
|
|
492
|
+
|
|
493
|
+
skill source 规则:
|
|
494
|
+
|
|
495
|
+
- `file://...` 可以指向 skill 集合目录、单个 skill root,或直接指向 `SKILL.md`
|
|
496
|
+
- `http://...` 与 `https://...` 当前只支持一个远程 `SKILL.md`
|
|
497
|
+
|
|
498
|
+
`runtime.spec.resources` 仍保留为兼容旧 attached resource package 的路径。
|
|
499
|
+
|
|
482
500
|
`createAgentHarness(..., { load })` 支持工作区加载控制。
|
|
483
501
|
|
|
484
502
|
合并顺序是确定性的:
|
|
@@ -710,8 +728,10 @@ await stop(runtime);
|
|
|
710
728
|
|
|
711
729
|
- `config/**` 下的所有 YAML 文档都会被递归发现;文件名与子目录只用于组织,不参与语义
|
|
712
730
|
- YAML 对象语义由 `kind`、`metadata.name` 或 `id` 以及对象内容决定,而不是由文件路径决定
|
|
713
|
-
-
|
|
714
|
-
- skills
|
|
731
|
+
- `Runtime.spec.sources.tools` 默认值是 `file://./resources/tools`
|
|
732
|
+
- `Runtime.spec.sources.skills` 默认值是 `file://./resources/skills`
|
|
733
|
+
- file-based 工具会从每个声明的 tool source 自动发现,前提是模块导出 `tool({...})`
|
|
734
|
+
- file-based skills 会从每个声明的 skill source 自动发现
|
|
715
735
|
- 一个最小工作区只放 `config/models.yaml` 也可以启动;仓库默认值会补上 `Runtime`、默认 `orchestra` host,以及默认开启的 runtime-managed durable memory
|
|
716
736
|
- 如果不显式覆盖 runtime 放置位置,harness 生成的数据默认写到 `./.botbotgo/`
|
|
717
737
|
|
|
@@ -740,7 +760,9 @@ await stop(runtime);
|
|
|
740
760
|
|
|
741
761
|
配置大致分这几层(由下至上叠加):
|
|
742
762
|
|
|
743
|
-
-
|
|
763
|
+
- 先由 `Runtime.spec.sources` 声明工作区实际拥有的 tool / skill 来源
|
|
764
|
+
- workspace 启动时只扫描这些显式声明的 source,并建立统一 registry
|
|
765
|
+
- workspace 启动时扫描本地与附加的 `resources` 包,建立统一 registry;这条兼容路径仍可继续工作
|
|
744
766
|
- 各 agent 再按名称白名单选用 tools 与 skills
|
|
745
767
|
- `config/runtime/workspace.yaml` 承载运行时策略
|
|
746
768
|
- `config/catalogs/*.yaml` 承载可复用对象目录
|
|
@@ -776,6 +798,8 @@ await stop(runtime);
|
|
|
776
798
|
|
|
777
799
|
- `runtimeRoot`
|
|
778
800
|
- `concurrency.maxConcurrentRequests`
|
|
801
|
+
- `sources.tools`
|
|
802
|
+
- `sources.skills`
|
|
779
803
|
- `routing.defaultAgentId`
|
|
780
804
|
- `routing.rules`
|
|
781
805
|
- `routing.systemPrompt`
|
|
@@ -793,7 +817,17 @@ await stop(runtime);
|
|
|
793
817
|
- `maintenance.checkpoints` 清理后端用于 resume/recovery 的 checkpoint 状态
|
|
794
818
|
- `maintenance.records` 清理 harness 自己保存在 `runtime.sqlite` 中、已结束的 session/request 记录
|
|
795
819
|
|
|
796
|
-
`
|
|
820
|
+
`sources.tools` 用来声明哪些 tool root 或 package 参与工作区发现:
|
|
821
|
+
|
|
822
|
+
- `file://...` 用于目录扫描
|
|
823
|
+
- `npm://...` 用于包入口发现;本地缺失时自动安装
|
|
824
|
+
|
|
825
|
+
`sources.skills` 用来声明哪些 skill 目录或 skill 文档参与工作区发现:
|
|
826
|
+
|
|
827
|
+
- `file://...` 用于本地目录、skill root 或直接 `SKILL.md`
|
|
828
|
+
- `http://...` / `https://...` 用于单个远程 `SKILL.md`
|
|
829
|
+
|
|
830
|
+
`toolModuleDiscovery.scope` 用来控制本地 `resources/tools/` 风格 file source 的发现范围:
|
|
797
831
|
|
|
798
832
|
- `recursive` 是默认值,会继续扫描嵌套目录
|
|
799
833
|
- `top-level` 只发现每个工具根目录下一层文件,同时保留 YAML catalog 的递归发现
|
|
@@ -29,6 +29,22 @@ spec:
|
|
|
29
29
|
# agent-harness feature: stable runtime profile identifier for this data folder.
|
|
30
30
|
profile: default
|
|
31
31
|
|
|
32
|
+
# agent-harness feature: explicit tool and skill discovery sources.
|
|
33
|
+
# The default local workspace contract stays:
|
|
34
|
+
# - tools from ./resources/tools
|
|
35
|
+
# - skills from ./resources/skills
|
|
36
|
+
#
|
|
37
|
+
# Supported source forms today:
|
|
38
|
+
# - tools: file://<folder>, npm://<package-or-spec>
|
|
39
|
+
# - skills: file://<folder-or-SKILL.md>, http(s)://.../SKILL.md
|
|
40
|
+
#
|
|
41
|
+
# Discovery never traverses node_modules directories.
|
|
42
|
+
sources:
|
|
43
|
+
tools:
|
|
44
|
+
- file://./resources/tools
|
|
45
|
+
skills:
|
|
46
|
+
- file://./resources/skills
|
|
47
|
+
|
|
32
48
|
# agent-harness feature: runtime-level task queue and maximum number of concurrent requests.
|
|
33
49
|
# Additional requests wait in the harness queue until a slot becomes available.
|
|
34
50
|
concurrency:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export declare const AGENT_HARNESS_VERSION = "0.0.307";
|
package/dist/package-version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export const AGENT_HARNESS_VERSION = "0.0.307";
|
|
@@ -93,7 +93,9 @@ async function linkHarnessPackage(isolatedRoot) {
|
|
|
93
93
|
}
|
|
94
94
|
async function buildIsolatedResourceRoot(packageRoot) {
|
|
95
95
|
const packageJsonPath = path.join(packageRoot, "package.json");
|
|
96
|
-
const manifest =
|
|
96
|
+
const manifest = existsSync(packageJsonPath)
|
|
97
|
+
? JSON.parse(await readFile(packageJsonPath, "utf8"))
|
|
98
|
+
: {};
|
|
97
99
|
const isolatedRoot = createIsolatedSnapshotDir(packageRoot);
|
|
98
100
|
await mkdir(path.dirname(isolatedRoot), { recursive: true });
|
|
99
101
|
await cp(packageRoot, isolatedRoot, {
|
|
@@ -101,6 +103,9 @@ async function buildIsolatedResourceRoot(packageRoot) {
|
|
|
101
103
|
force: true,
|
|
102
104
|
filter: (source) => path.basename(source) !== "node_modules",
|
|
103
105
|
});
|
|
106
|
+
if (!existsSync(path.join(isolatedRoot, "package.json"))) {
|
|
107
|
+
await cp(path.join(HARNESS_PACKAGE_ROOT, "resources", "package.json"), path.join(isolatedRoot, "package.json"));
|
|
108
|
+
}
|
|
104
109
|
await mkdir(path.join(isolatedRoot, "node_modules"), { recursive: true });
|
|
105
110
|
await linkDeclaredDependencies(isolatedRoot, packageRoot, manifest);
|
|
106
111
|
await linkHarnessPackage(isolatedRoot);
|
|
@@ -2,6 +2,7 @@ import type { CompiledAgentBinding, MessageContent, TranscriptMessage } from "..
|
|
|
2
2
|
export declare function buildAgentMessages(history: TranscriptMessage[], input: MessageContent, options?: {
|
|
3
3
|
suppressExplicitResourceTurns?: boolean;
|
|
4
4
|
suppressAssistantTurns?: boolean;
|
|
5
|
+
suppressHistoryTurns?: boolean;
|
|
5
6
|
}): Array<{
|
|
6
7
|
role: string;
|
|
7
8
|
content: MessageContent;
|
|
@@ -84,6 +84,9 @@ function selectRelevantHistoryTurns(groupedHistory, inputText, options = {}) {
|
|
|
84
84
|
}
|
|
85
85
|
export function buildAgentMessages(history, input, options = {}) {
|
|
86
86
|
const inputText = extractMessageText(input).trim();
|
|
87
|
+
if (options.suppressHistoryTurns) {
|
|
88
|
+
return [{ role: "user", content: normalizeMessageContent(input) }];
|
|
89
|
+
}
|
|
87
90
|
const groupedHistory = history.reduce((groups, item) => {
|
|
88
91
|
const current = groups.at(-1);
|
|
89
92
|
if (current && current[0]?.requestId === item.requestId) {
|
|
@@ -156,6 +159,7 @@ export function buildInvocationRequest(binding, history, input, options = {}) {
|
|
|
156
159
|
const messages = buildAgentMessages(history, input, {
|
|
157
160
|
suppressExplicitResourceTurns: Boolean(memoryInstruction) && !hasExplicitResourceReference(inputText),
|
|
158
161
|
suppressAssistantTurns: Boolean(memoryInstruction) && !hasExplicitResourceReference(inputText),
|
|
162
|
+
suppressHistoryTurns: Boolean(memoryInstruction) && !hasExplicitResourceReference(inputText),
|
|
159
163
|
});
|
|
160
164
|
const contextualFollowUpInstruction = buildContextualFollowUpInstruction(inputText, Boolean(memoryInstruction));
|
|
161
165
|
const conversationLanguage = resolveConversationLanguage(history, inputText);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { readdir } from "node:fs/promises";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
4
5
|
import { ensureResourceSources } from "../resource/resource.js";
|
|
5
6
|
import { ensureExternalResourceSource, isExternalSourceLocator, resolveResourcePackageRoot } from "../resource/sources.js";
|
|
6
7
|
import { loadWorkspaceObjects, readToolModuleItems, readYamlItems } from "./object-loader.js";
|
|
@@ -10,10 +11,30 @@ import { validateAgent, validateTopology } from "./validate.js";
|
|
|
10
11
|
import { compileBinding } from "./agent-binding-compiler.js";
|
|
11
12
|
import { discoverSubagents, ensureDiscoverySources } from "./support/discovery.js";
|
|
12
13
|
import { collectAgentDiscoverySourceRefs, collectToolSourceRefs } from "./support/source-collectors.js";
|
|
13
|
-
import { getRoutingDefaultAgentId, getRuntimeResources, getRuntimeStorageRoots, getToolModuleDiscoveryConfig, getRoutingRules, resolveRefId, } from "./support/workspace-ref-utils.js";
|
|
14
|
+
import { getRoutingDefaultAgentId, getRuntimeSources, getRuntimeResources, getRuntimeStorageRoots, getToolModuleDiscoveryConfig, getRoutingRules, resolveRefId, } from "./support/workspace-ref-utils.js";
|
|
14
15
|
import { hydrateAgentMcpTools, hydrateResourceAndExternalTools } from "./tool-hydration.js";
|
|
15
16
|
import { traceStartupStage } from "../runtime/startup-tracing.js";
|
|
16
17
|
import { shouldSkipScanDirectory } from "../utils/fs.js";
|
|
18
|
+
import { ensureRemoteSkillSource, ensureToolPackageSource, isFileSourceUri, isHttpSourceUri, isNpmSourceUri, resolveFileSourcePath, } from "./support/source-protocols.js";
|
|
19
|
+
import { discoverToolModuleDefinitions } from "../tool-modules.js";
|
|
20
|
+
function mergeObjectValues(base, override) {
|
|
21
|
+
if (override === undefined) {
|
|
22
|
+
return base;
|
|
23
|
+
}
|
|
24
|
+
if (typeof base === "object" &&
|
|
25
|
+
base &&
|
|
26
|
+
typeof override === "object" &&
|
|
27
|
+
override &&
|
|
28
|
+
!Array.isArray(base) &&
|
|
29
|
+
!Array.isArray(override)) {
|
|
30
|
+
const merged = { ...base };
|
|
31
|
+
for (const [key, value] of Object.entries(override)) {
|
|
32
|
+
merged[key] = key in merged ? mergeObjectValues(merged[key], value) : value;
|
|
33
|
+
}
|
|
34
|
+
return merged;
|
|
35
|
+
}
|
|
36
|
+
return override;
|
|
37
|
+
}
|
|
17
38
|
function collectParsedResources(refs) {
|
|
18
39
|
const embeddings = new Map();
|
|
19
40
|
const mcpServers = new Map();
|
|
@@ -132,7 +153,7 @@ async function registerAttachedResourceTools(tools, resourceRoot, toolModuleDisc
|
|
|
132
153
|
sourcePath,
|
|
133
154
|
value: item,
|
|
134
155
|
});
|
|
135
|
-
tools.set(parsed.id, parsed);
|
|
156
|
+
tools.set(parsed.id, mergeParsedToolObject(tools.get(parsed.id), parsed));
|
|
136
157
|
}
|
|
137
158
|
for (const { item, sourcePath } of await readToolModuleItems(toolsRoot, { scope: toolModuleDiscoveryScope })) {
|
|
138
159
|
const parsed = parseToolObject({
|
|
@@ -141,7 +162,7 @@ async function registerAttachedResourceTools(tools, resourceRoot, toolModuleDisc
|
|
|
141
162
|
sourcePath,
|
|
142
163
|
value: item,
|
|
143
164
|
});
|
|
144
|
-
tools.set(parsed.id, parsed);
|
|
165
|
+
tools.set(parsed.id, mergeParsedToolObject(tools.get(parsed.id), parsed));
|
|
145
166
|
}
|
|
146
167
|
}
|
|
147
168
|
async function collectSkillRoots(root) {
|
|
@@ -160,6 +181,90 @@ async function collectSkillRoots(root) {
|
|
|
160
181
|
return [];
|
|
161
182
|
}
|
|
162
183
|
}
|
|
184
|
+
async function resolveConfiguredSkillSourceRoots(sources, workspaceRoot) {
|
|
185
|
+
const resolved = [];
|
|
186
|
+
for (const source of sources) {
|
|
187
|
+
if (isFileSourceUri(source)) {
|
|
188
|
+
const resolvedPath = resolveFileSourcePath(source, workspaceRoot);
|
|
189
|
+
resolved.push(resolvedPath.endsWith(`${path.sep}SKILL.md`) || resolvedPath.endsWith("/SKILL.md")
|
|
190
|
+
? path.dirname(resolvedPath)
|
|
191
|
+
: resolvedPath);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (isHttpSourceUri(source)) {
|
|
195
|
+
resolved.push(await ensureRemoteSkillSource(source));
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
throw new Error(`Unsupported skill source ${source}. Use file:// or https://.`);
|
|
199
|
+
}
|
|
200
|
+
return resolved;
|
|
201
|
+
}
|
|
202
|
+
async function registerToolFolderSource(tools, folderRoot, toolModuleDiscoveryScope) {
|
|
203
|
+
for (const { item, sourcePath } of await readYamlItems(folderRoot, undefined, { recursive: true })) {
|
|
204
|
+
const parsed = parseToolObject({
|
|
205
|
+
id: typeof item.id === "string" ? item.id : path.basename(sourcePath).replace(/\.(yaml|yml|json)$/i, ""),
|
|
206
|
+
kind: "tool",
|
|
207
|
+
sourcePath,
|
|
208
|
+
value: item,
|
|
209
|
+
});
|
|
210
|
+
tools.set(parsed.id, mergeParsedToolObject(tools.get(parsed.id), parsed));
|
|
211
|
+
}
|
|
212
|
+
for (const { item, sourcePath } of await readToolModuleItems(folderRoot, { scope: toolModuleDiscoveryScope })) {
|
|
213
|
+
const parsed = parseToolObject({
|
|
214
|
+
id: String(item.id),
|
|
215
|
+
kind: "tool",
|
|
216
|
+
sourcePath,
|
|
217
|
+
value: item,
|
|
218
|
+
});
|
|
219
|
+
tools.set(parsed.id, mergeParsedToolObject(tools.get(parsed.id), parsed));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async function registerToolPackageSource(tools, source, workspaceRoot) {
|
|
223
|
+
const installed = await ensureToolPackageSource(source, workspaceRoot);
|
|
224
|
+
const imported = await import(pathToFileURL(installed.entryPath).href);
|
|
225
|
+
const definitions = discoverToolModuleDefinitions("", imported);
|
|
226
|
+
for (const definition of definitions) {
|
|
227
|
+
const parsed = parseToolObject({
|
|
228
|
+
id: definition.implementationName,
|
|
229
|
+
kind: "tool",
|
|
230
|
+
sourcePath: installed.entryPath,
|
|
231
|
+
value: {
|
|
232
|
+
kind: "tool",
|
|
233
|
+
id: definition.implementationName,
|
|
234
|
+
type: "function",
|
|
235
|
+
name: definition.implementationName,
|
|
236
|
+
description: definition.description,
|
|
237
|
+
implementationName: definition.implementationName,
|
|
238
|
+
hasModuleSchema: definition.hasModuleSchema,
|
|
239
|
+
...(definition.modelSchema ? { modelSchema: definition.modelSchema } : {}),
|
|
240
|
+
...(definition.retryable !== undefined ? { retryable: definition.retryable } : {}),
|
|
241
|
+
...(definition.memory ? { config: { memory: definition.memory } } : {}),
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
tools.set(parsed.id, mergeParsedToolObject(tools.get(parsed.id), parsed));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function mergeParsedToolObject(current, incoming) {
|
|
248
|
+
if (!current) {
|
|
249
|
+
return incoming;
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
...current,
|
|
253
|
+
...incoming,
|
|
254
|
+
config: mergeObjectValues(current.config, incoming.config),
|
|
255
|
+
subprocess: incoming.subprocess ?? current.subprocess,
|
|
256
|
+
inputSchemaRef: incoming.inputSchemaRef ?? current.inputSchemaRef,
|
|
257
|
+
hasModuleSchema: incoming.hasModuleSchema ?? current.hasModuleSchema,
|
|
258
|
+
modelSchema: incoming.modelSchema ?? current.modelSchema,
|
|
259
|
+
embeddingModelRef: incoming.embeddingModelRef ?? current.embeddingModelRef,
|
|
260
|
+
backendOperation: incoming.backendOperation ?? current.backendOperation,
|
|
261
|
+
mcpRef: incoming.mcpRef ?? current.mcpRef,
|
|
262
|
+
bundleRefs: incoming.bundleRefs.length > 0 ? incoming.bundleRefs : current.bundleRefs,
|
|
263
|
+
hitl: incoming.hitl ?? current.hitl,
|
|
264
|
+
retryable: incoming.retryable ?? current.retryable,
|
|
265
|
+
sourcePath: incoming.sourcePath,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
163
268
|
async function registerWorkspaceSkillRegistry(skillCollectionRoots) {
|
|
164
269
|
const registry = new Map();
|
|
165
270
|
for (const skillsRoot of skillCollectionRoots) {
|
|
@@ -227,10 +332,29 @@ export async function loadWorkspace(workspaceRoot, options = {}) {
|
|
|
227
332
|
}
|
|
228
333
|
const { embeddings, mcpServers, models, vectorStores, tools } = collectParsedResources(loaded.refs);
|
|
229
334
|
const toolModuleDiscoveryConfig = getToolModuleDiscoveryConfig(loaded.refs);
|
|
335
|
+
const runtimeSources = getRuntimeSources(loaded.refs);
|
|
230
336
|
await traceStartupStage("workspace.hydrate.agentMcpTools", () => hydrateAgentMcpTools(loaded.agents, mcpServers, tools), {
|
|
231
337
|
workspaceRoot,
|
|
232
338
|
agentCount: loaded.agents.length,
|
|
233
339
|
});
|
|
340
|
+
for (const source of runtimeSources.tools) {
|
|
341
|
+
if (isFileSourceUri(source)) {
|
|
342
|
+
const folderRoot = resolveFileSourcePath(source, workspaceRoot);
|
|
343
|
+
await traceStartupStage("workspace.register.toolFolderSource", () => registerToolFolderSource(tools, folderRoot, toolModuleDiscoveryConfig.scope), {
|
|
344
|
+
workspaceRoot,
|
|
345
|
+
source,
|
|
346
|
+
});
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
if (isNpmSourceUri(source)) {
|
|
350
|
+
await traceStartupStage("workspace.register.toolPackageSource", () => registerToolPackageSource(tools, source, workspaceRoot), {
|
|
351
|
+
workspaceRoot,
|
|
352
|
+
source,
|
|
353
|
+
});
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
throw new Error(`Unsupported tool source ${source}. Use file:// or npm://.`);
|
|
357
|
+
}
|
|
234
358
|
const configuredResources = Array.from(new Set([
|
|
235
359
|
...getRuntimeResources(loaded.refs),
|
|
236
360
|
...(options.resources ?? []),
|
|
@@ -246,11 +370,16 @@ export async function loadWorkspace(workspaceRoot, options = {}) {
|
|
|
246
370
|
});
|
|
247
371
|
}
|
|
248
372
|
const localResourceRoot = resolveResourcePackageRoot(workspaceRoot);
|
|
249
|
-
const
|
|
373
|
+
const configuredSkillSourceRoots = await traceStartupStage("workspace.resolve.skillSources", () => resolveConfiguredSkillSourceRoots(runtimeSources.skills, workspaceRoot), {
|
|
374
|
+
workspaceRoot,
|
|
375
|
+
skillSourceCount: runtimeSources.skills.length,
|
|
376
|
+
});
|
|
377
|
+
const skillCollectionRoots = Array.from(new Set([
|
|
250
378
|
path.join(workspaceRoot, "modules", "skills"),
|
|
251
379
|
...(localResourceRoot ? [path.join(localResourceRoot, "skills")] : []),
|
|
380
|
+
...configuredSkillSourceRoots,
|
|
252
381
|
...resolvedConfiguredResources.map((resource) => path.join(resource.root, "skills")),
|
|
253
|
-
];
|
|
382
|
+
]));
|
|
254
383
|
const skillRegistry = await traceStartupStage("workspace.register.skillRegistry", () => registerWorkspaceSkillRegistry(skillCollectionRoots), {
|
|
255
384
|
workspaceRoot,
|
|
256
385
|
skillCollectionRootCount: skillCollectionRoots.length,
|
|
@@ -272,6 +401,7 @@ export async function loadWorkspace(workspaceRoot, options = {}) {
|
|
|
272
401
|
validateToolNameConflicts(tools);
|
|
273
402
|
const resources = Array.from(new Set([
|
|
274
403
|
...(localResourceRoot ? [localResourceRoot] : []),
|
|
404
|
+
...runtimeSources.tools.filter((source) => isNpmSourceUri(source)),
|
|
275
405
|
...collectedResources,
|
|
276
406
|
]));
|
|
277
407
|
await traceStartupStage("workspace.validate.resources", async () => {
|
|
@@ -6,6 +6,7 @@ import { resolveIsolatedResourceModulePath } from "../resource/isolation.js";
|
|
|
6
6
|
import { isExternalSourceLocator, resolveResourcePackageRoot } from "../resource/sources.js";
|
|
7
7
|
import { discoverToolModuleDefinitions, isSupportedToolModulePath, loadToolModuleDefinition } from "../tool-modules.js";
|
|
8
8
|
import { fileExists, shouldSkipScanDirectory } from "../utils/fs.js";
|
|
9
|
+
import { isFileSourceUri, readRuntimeSources, resolveFileSourcePath } from "./support/source-protocols.js";
|
|
9
10
|
import { readNamedYamlItems, readYamlItems, } from "./yaml-object-reader.js";
|
|
10
11
|
export { normalizeYamlItem, readYamlItems } from "./yaml-object-reader.js";
|
|
11
12
|
const CONVENTIONAL_OBJECT_DIRECTORIES = ["tools"];
|
|
@@ -682,23 +683,32 @@ function getMergedToolModuleDiscoveryScope(mergedObjects) {
|
|
|
682
683
|
: {};
|
|
683
684
|
return toolModuleDiscovery.scope === "top-level" ? "top-level" : "recursive";
|
|
684
685
|
}
|
|
685
|
-
async function loadConventionalObjectsForRoot(root, mergedObjects, toolModuleDiscoveryScope) {
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
686
|
+
async function loadConventionalObjectsForRoot(root, runtimeRoot, mergedObjects, toolModuleDiscoveryScope) {
|
|
687
|
+
const runtimeDefaults = mergedObjects.get("runtime/default")?.item;
|
|
688
|
+
const configuredToolRoots = readRuntimeSources(runtimeDefaults).tools
|
|
689
|
+
.filter((source) => isFileSourceUri(source))
|
|
690
|
+
.map((source) => resolveFileSourcePath(source, runtimeRoot));
|
|
691
|
+
const conventionalToolRoots = CONVENTIONAL_OBJECT_DIRECTORIES.flatMap((directory) => conventionalDirectoryRoots(root, directory));
|
|
692
|
+
const objectRoots = root === runtimeRoot
|
|
693
|
+
? Array.from(new Set([
|
|
694
|
+
...conventionalToolRoots,
|
|
695
|
+
...configuredToolRoots,
|
|
696
|
+
]))
|
|
697
|
+
: conventionalToolRoots;
|
|
698
|
+
for (const objectRoot of objectRoots) {
|
|
699
|
+
for (const { item, sourcePath } of await readYamlItemsIgnoringNodeModules(objectRoot)) {
|
|
700
|
+
const workspaceObject = parseWorkspaceObject(item, sourcePath);
|
|
701
|
+
if (!workspaceObject) {
|
|
702
|
+
continue;
|
|
694
703
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
704
|
+
mergeWorkspaceObjectRecord(mergedObjects, workspaceObject, item, sourcePath);
|
|
705
|
+
}
|
|
706
|
+
for (const { item, sourcePath } of await readToolModuleItems(objectRoot, { scope: toolModuleDiscoveryScope })) {
|
|
707
|
+
const workspaceObject = parseWorkspaceObject(item, sourcePath);
|
|
708
|
+
if (!workspaceObject) {
|
|
709
|
+
continue;
|
|
701
710
|
}
|
|
711
|
+
mergeWorkspaceObjectRecord(mergedObjects, workspaceObject, item, sourcePath);
|
|
702
712
|
}
|
|
703
713
|
}
|
|
704
714
|
}
|
|
@@ -882,7 +892,7 @@ export async function readToolModuleItems(root, options = {}) {
|
|
|
882
892
|
const records = [];
|
|
883
893
|
for (const filePath of files) {
|
|
884
894
|
const sourceText = await readFile(filePath, "utf8");
|
|
885
|
-
const packageRoot =
|
|
895
|
+
const packageRoot = findToolPackageRoot(filePath);
|
|
886
896
|
const isolatedSourcePath = await resolveIsolatedResourceModulePath(packageRoot, filePath);
|
|
887
897
|
const imported = await import(pathToFileURL(isolatedSourcePath).href);
|
|
888
898
|
const definitions = discoverToolModuleDefinitions(sourceText, imported);
|
|
@@ -958,7 +968,7 @@ export async function loadWorkspaceObjects(workspaceRoot, options = {}) {
|
|
|
958
968
|
await loadConfigYamlForRoot(root, configRoot, mergedAgents, mergedObjects);
|
|
959
969
|
await loadModuleAgentsForRoot(root, mergedAgents);
|
|
960
970
|
if (root !== defaultRoot) {
|
|
961
|
-
await loadConventionalObjectsForRoot(root, mergedObjects, getMergedToolModuleDiscoveryScope(mergedObjects));
|
|
971
|
+
await loadConventionalObjectsForRoot(root, workspaceRoot, mergedObjects, getMergedToolModuleDiscoveryScope(mergedObjects));
|
|
962
972
|
}
|
|
963
973
|
await loadModuleObjectsForRoot(root, mergedObjects);
|
|
964
974
|
await loadRootObjects(root, mergedObjects);
|
|
@@ -302,9 +302,9 @@ export function parseToolObject(object) {
|
|
|
302
302
|
...(mcpServerConfig && Object.keys(mcpServerConfig).length > 0 ? { mcpServer: mcpServerConfig } : {}),
|
|
303
303
|
}
|
|
304
304
|
: undefined),
|
|
305
|
-
subprocess: value.subprocess === true,
|
|
305
|
+
subprocess: value.subprocess === true ? true : undefined,
|
|
306
306
|
inputSchemaRef: typeof asObject(value.inputSchema)?.ref === "string" ? String(asObject(value.inputSchema)?.ref) : undefined,
|
|
307
|
-
hasModuleSchema: value.hasModuleSchema === true,
|
|
307
|
+
hasModuleSchema: value.hasModuleSchema === true ? true : undefined,
|
|
308
308
|
modelSchema: asObject(value.modelSchema),
|
|
309
309
|
embeddingModelRef: typeof value.embeddingModelRef === "string"
|
|
310
310
|
? value.embeddingModelRef
|
|
@@ -325,7 +325,7 @@ export function parseToolObject(object) {
|
|
|
325
325
|
: undefined,
|
|
326
326
|
bundleRefs,
|
|
327
327
|
hitl: parseHitlPolicy(value.hitl),
|
|
328
|
-
retryable: value.retryable === true,
|
|
328
|
+
retryable: value.retryable === true ? true : undefined,
|
|
329
329
|
sourcePath: object.sourcePath,
|
|
330
330
|
};
|
|
331
331
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const DEFAULT_TOOL_SOURCE_URIS: string[];
|
|
2
|
+
export declare const DEFAULT_SKILL_SOURCE_URIS: string[];
|
|
3
|
+
export type RuntimeSourceConfig = {
|
|
4
|
+
tools: string[];
|
|
5
|
+
skills: string[];
|
|
6
|
+
};
|
|
7
|
+
type ToolPackageInstallation = {
|
|
8
|
+
entryPath: string;
|
|
9
|
+
packageRoot: string;
|
|
10
|
+
packageName: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function readRuntimeSources(runtimeDefaults: Record<string, unknown> | undefined): RuntimeSourceConfig;
|
|
13
|
+
export declare function isFileSourceUri(value: string): boolean;
|
|
14
|
+
export declare function isNpmSourceUri(value: string): boolean;
|
|
15
|
+
export declare function isHttpSourceUri(value: string): boolean;
|
|
16
|
+
export declare function resolveFileSourcePath(uri: string, workspaceRoot: string): string;
|
|
17
|
+
export declare function ensureToolPackageSource(uri: string, workspaceRoot: string): Promise<ToolPackageInstallation>;
|
|
18
|
+
export declare function ensureRemoteSkillSource(uri: string): Promise<string>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
7
|
+
import { execFile } from "node:child_process";
|
|
8
|
+
import { promisify } from "node:util";
|
|
9
|
+
import { parse as parseYaml } from "yaml";
|
|
10
|
+
const execFileAsync = promisify(execFile);
|
|
11
|
+
export const DEFAULT_TOOL_SOURCE_URIS = ["file://./resources/tools"];
|
|
12
|
+
export const DEFAULT_SKILL_SOURCE_URIS = ["file://./resources/skills"];
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
function normalizeSourceList(value, fallback) {
|
|
15
|
+
if (!Array.isArray(value)) {
|
|
16
|
+
return [...fallback];
|
|
17
|
+
}
|
|
18
|
+
return value
|
|
19
|
+
.filter((item) => typeof item === "string" && item.trim().length > 0)
|
|
20
|
+
.map((item) => item.trim());
|
|
21
|
+
}
|
|
22
|
+
export function readRuntimeSources(runtimeDefaults) {
|
|
23
|
+
const rawSources = typeof runtimeDefaults?.sources === "object" && runtimeDefaults.sources && !Array.isArray(runtimeDefaults.sources)
|
|
24
|
+
? runtimeDefaults.sources
|
|
25
|
+
: {};
|
|
26
|
+
return {
|
|
27
|
+
tools: normalizeSourceList(rawSources.tools, DEFAULT_TOOL_SOURCE_URIS),
|
|
28
|
+
skills: normalizeSourceList(rawSources.skills, DEFAULT_SKILL_SOURCE_URIS),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export function isFileSourceUri(value) {
|
|
32
|
+
return value.startsWith("file://");
|
|
33
|
+
}
|
|
34
|
+
export function isNpmSourceUri(value) {
|
|
35
|
+
return value.startsWith("npm://");
|
|
36
|
+
}
|
|
37
|
+
export function isHttpSourceUri(value) {
|
|
38
|
+
return value.startsWith("http://") || value.startsWith("https://");
|
|
39
|
+
}
|
|
40
|
+
export function resolveFileSourcePath(uri, workspaceRoot) {
|
|
41
|
+
if (!isFileSourceUri(uri)) {
|
|
42
|
+
throw new Error(`Unsupported file source URI ${uri}`);
|
|
43
|
+
}
|
|
44
|
+
const rawPath = uri.slice("file://".length).trim();
|
|
45
|
+
if (!rawPath) {
|
|
46
|
+
throw new Error(`File source URI ${uri} must include a path`);
|
|
47
|
+
}
|
|
48
|
+
return path.isAbsolute(rawPath) ? rawPath : path.resolve(workspaceRoot, rawPath);
|
|
49
|
+
}
|
|
50
|
+
function parseToolPackageName(spec, workspaceRoot) {
|
|
51
|
+
if (spec.startsWith("file:")) {
|
|
52
|
+
const localPath = spec.slice("file:".length);
|
|
53
|
+
const resolvedPath = path.isAbsolute(localPath) ? localPath : path.resolve(workspaceRoot, localPath);
|
|
54
|
+
const packageJsonPath = path.join(resolvedPath, "package.json");
|
|
55
|
+
if (!existsSync(packageJsonPath)) {
|
|
56
|
+
throw new Error(`Package tool source ${spec} is missing package.json`);
|
|
57
|
+
}
|
|
58
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
59
|
+
if (typeof packageJson.name !== "string" || packageJson.name.trim().length === 0) {
|
|
60
|
+
throw new Error(`Package tool source ${spec} must define a package.json name`);
|
|
61
|
+
}
|
|
62
|
+
return packageJson.name.trim();
|
|
63
|
+
}
|
|
64
|
+
const match = spec.match(/^(@[^/]+\/[^@/]+|[^@/]+)(?:@.+)?$/);
|
|
65
|
+
if (!match?.[1]) {
|
|
66
|
+
throw new Error(`Unsupported package tool source ${spec}. Use npm://<package-name> or npm://<package-name>@<version>.`);
|
|
67
|
+
}
|
|
68
|
+
return match[1];
|
|
69
|
+
}
|
|
70
|
+
function packageInstallCacheRoot(spec) {
|
|
71
|
+
const digest = createHash("sha256").update(spec).digest("hex").slice(0, 16);
|
|
72
|
+
return path.join(os.tmpdir(), "agent-harness-package-tools", digest);
|
|
73
|
+
}
|
|
74
|
+
function resolvePackageEntry(packageRoot, pkg) {
|
|
75
|
+
const exportsField = pkg.exports;
|
|
76
|
+
if (typeof exportsField === "string") {
|
|
77
|
+
return path.resolve(packageRoot, exportsField);
|
|
78
|
+
}
|
|
79
|
+
if (exportsField && typeof exportsField === "object" && "." in exportsField) {
|
|
80
|
+
const rootExport = exportsField["."];
|
|
81
|
+
if (typeof rootExport === "string") {
|
|
82
|
+
return path.resolve(packageRoot, rootExport);
|
|
83
|
+
}
|
|
84
|
+
if (rootExport && typeof rootExport === "object") {
|
|
85
|
+
const importEntry = rootExport.import ?? rootExport.default;
|
|
86
|
+
if (typeof importEntry === "string") {
|
|
87
|
+
return path.resolve(packageRoot, importEntry);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (typeof pkg.module === "string") {
|
|
92
|
+
return path.resolve(packageRoot, pkg.module);
|
|
93
|
+
}
|
|
94
|
+
if (typeof pkg.main === "string") {
|
|
95
|
+
return path.resolve(packageRoot, pkg.main);
|
|
96
|
+
}
|
|
97
|
+
return path.resolve(packageRoot, "index.js");
|
|
98
|
+
}
|
|
99
|
+
function resolveInstalledPackageRoot(packageName, workspaceRoot) {
|
|
100
|
+
const packageJsonRef = `${packageName}/package.json`;
|
|
101
|
+
const directNodeModulesPath = path.join(workspaceRoot, "node_modules", ...packageName.split("/"), "package.json");
|
|
102
|
+
if (existsSync(directNodeModulesPath)) {
|
|
103
|
+
return path.dirname(directNodeModulesPath);
|
|
104
|
+
}
|
|
105
|
+
for (const candidateRoot of [workspaceRoot, process.cwd()]) {
|
|
106
|
+
try {
|
|
107
|
+
const candidateRequire = createRequire(path.join(candidateRoot, "__agent_harness_package_resolve__.cjs"));
|
|
108
|
+
return path.dirname(candidateRequire.resolve(packageJsonRef));
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
async function installPackageToolSource(spec, workspaceRoot) {
|
|
117
|
+
const packageName = parseToolPackageName(spec, workspaceRoot);
|
|
118
|
+
const installRoot = packageInstallCacheRoot(spec);
|
|
119
|
+
const packageJsonPath = path.join(installRoot, "package.json");
|
|
120
|
+
await mkdir(installRoot, { recursive: true });
|
|
121
|
+
if (!existsSync(packageJsonPath)) {
|
|
122
|
+
await writeFile(packageJsonPath, `${JSON.stringify({ name: "agent-harness-package-tool-cache", private: true }, null, 2)}\n`, "utf8");
|
|
123
|
+
}
|
|
124
|
+
const installedRoot = path.join(installRoot, "node_modules", ...packageName.split("/"));
|
|
125
|
+
if (!existsSync(path.join(installedRoot, "package.json"))) {
|
|
126
|
+
await execFileAsync("npm", ["install", "--no-save", spec], {
|
|
127
|
+
cwd: installRoot,
|
|
128
|
+
maxBuffer: 1024 * 1024 * 20,
|
|
129
|
+
env: process.env,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
const packageJson = JSON.parse(await readFile(path.join(installedRoot, "package.json"), "utf8"));
|
|
133
|
+
return {
|
|
134
|
+
packageName,
|
|
135
|
+
packageRoot: installedRoot,
|
|
136
|
+
entryPath: resolvePackageEntry(installedRoot, packageJson),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
export async function ensureToolPackageSource(uri, workspaceRoot) {
|
|
140
|
+
if (!isNpmSourceUri(uri)) {
|
|
141
|
+
throw new Error(`Unsupported package source URI ${uri}`);
|
|
142
|
+
}
|
|
143
|
+
const spec = uri.slice("npm://".length).trim();
|
|
144
|
+
if (!spec) {
|
|
145
|
+
throw new Error(`Package source URI ${uri} must include a package name`);
|
|
146
|
+
}
|
|
147
|
+
const packageName = parseToolPackageName(spec, workspaceRoot);
|
|
148
|
+
const installedRoot = resolveInstalledPackageRoot(packageName, workspaceRoot);
|
|
149
|
+
if (installedRoot && !spec.startsWith("file:") && !spec.includes("@", packageName.startsWith("@") ? packageName.length : 0)) {
|
|
150
|
+
const packageJson = JSON.parse(await readFile(path.join(installedRoot, "package.json"), "utf8"));
|
|
151
|
+
return {
|
|
152
|
+
packageName,
|
|
153
|
+
packageRoot: installedRoot,
|
|
154
|
+
entryPath: resolvePackageEntry(installedRoot, packageJson),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
return installPackageToolSource(spec, workspaceRoot);
|
|
158
|
+
}
|
|
159
|
+
function parseSkillFrontmatterName(document) {
|
|
160
|
+
const match = document.match(/^---\s*\n([\s\S]*?)\n---\s*(?:\n|$)/);
|
|
161
|
+
if (!match?.[1]) {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
const parsed = parseYaml(match[1]);
|
|
165
|
+
return typeof parsed?.name === "string" && parsed.name.trim().length > 0 ? parsed.name.trim() : undefined;
|
|
166
|
+
}
|
|
167
|
+
function sanitizeSkillDirName(input) {
|
|
168
|
+
const normalized = input.trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
169
|
+
return normalized || "remote-skill";
|
|
170
|
+
}
|
|
171
|
+
function remoteSkillCacheRoot(uri) {
|
|
172
|
+
const digest = createHash("sha256").update(uri).digest("hex").slice(0, 16);
|
|
173
|
+
return path.join(os.tmpdir(), "agent-harness-remote-skills", digest);
|
|
174
|
+
}
|
|
175
|
+
export async function ensureRemoteSkillSource(uri) {
|
|
176
|
+
if (!isHttpSourceUri(uri)) {
|
|
177
|
+
throw new Error(`Unsupported remote skill source ${uri}`);
|
|
178
|
+
}
|
|
179
|
+
const response = await fetch(uri);
|
|
180
|
+
if (!response.ok) {
|
|
181
|
+
throw new Error(`Remote skill source ${uri} returned ${response.status}`);
|
|
182
|
+
}
|
|
183
|
+
const document = await response.text();
|
|
184
|
+
const skillName = sanitizeSkillDirName(parseSkillFrontmatterName(document)
|
|
185
|
+
?? path.basename(new URL(uri).pathname).replace(/\.md$/i, "")
|
|
186
|
+
?? "remote-skill");
|
|
187
|
+
const cacheRoot = remoteSkillCacheRoot(uri);
|
|
188
|
+
const skillRoot = path.join(cacheRoot, skillName);
|
|
189
|
+
await mkdir(skillRoot, { recursive: true });
|
|
190
|
+
await writeFile(path.join(skillRoot, "SKILL.md"), document, "utf8");
|
|
191
|
+
return skillRoot;
|
|
192
|
+
}
|
|
@@ -44,6 +44,10 @@ export type RuntimeStorageRoots = {
|
|
|
44
44
|
};
|
|
45
45
|
export declare function getRuntimeStorageRoots(refs: Map<string, WorkspaceObject | ParsedAgentObject>, workspaceRoot: string): RuntimeStorageRoots;
|
|
46
46
|
export declare function getRuntimeResources(refs: Map<string, WorkspaceObject | ParsedAgentObject>): string[];
|
|
47
|
+
export declare function getRuntimeSources(refs: Map<string, WorkspaceObject | ParsedAgentObject>): {
|
|
48
|
+
tools: string[];
|
|
49
|
+
skills: string[];
|
|
50
|
+
};
|
|
47
51
|
export declare function getToolModuleDiscoveryConfig(refs: Map<string, WorkspaceObject | ParsedAgentObject>): ToolModuleDiscoveryConfig;
|
|
48
52
|
export declare function getRuntimeMemoryDefaults(refs: Map<string, WorkspaceObject | ParsedAgentObject>): Record<string, unknown> | undefined;
|
|
49
53
|
export declare function getProceduralMemoryDefaults(refs: Map<string, WorkspaceObject | ParsedAgentObject>): Record<string, unknown> | undefined;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { readRuntimeSources } from "./source-protocols.js";
|
|
3
4
|
function getRoutingObject(refs) {
|
|
4
5
|
const runtimeDefaults = getRuntimeDefaults(refs);
|
|
5
6
|
return typeof runtimeDefaults?.routing === "object" && runtimeDefaults.routing
|
|
@@ -60,6 +61,9 @@ export function getRuntimeResources(refs) {
|
|
|
60
61
|
.filter((value) => typeof value === "string" && value.trim().length > 0)
|
|
61
62
|
.map((value) => value.trim());
|
|
62
63
|
}
|
|
64
|
+
export function getRuntimeSources(refs) {
|
|
65
|
+
return readRuntimeSources(getRuntimeDefaults(refs));
|
|
66
|
+
}
|
|
63
67
|
export function getToolModuleDiscoveryConfig(refs) {
|
|
64
68
|
const runtimeDefaults = getRuntimeDefaults(refs);
|
|
65
69
|
const toolModuleDiscovery = typeof runtimeDefaults?.toolModuleDiscovery === "object" && runtimeDefaults.toolModuleDiscovery
|