@botbotgo/agent-harness 0.0.55 → 0.0.57

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.
@@ -1,4 +1,6 @@
1
1
  import path from "node:path";
2
+ import { existsSync, statSync } from "node:fs";
3
+ import { cp, mkdir, rm } from "node:fs/promises";
2
4
  import { Command, MemorySaver } from "@langchain/langgraph";
3
5
  import { tool as createLangChainTool } from "@langchain/core/tools";
4
6
  import { createDeepAgent, createMemoryMiddleware, createSkillsMiddleware, createSubAgentMiddleware, FilesystemBackend, } from "deepagents";
@@ -16,6 +18,7 @@ import { wrapToolForExecution } from "./tool-hitl.js";
16
18
  import { resolveDeclaredMiddleware } from "./declared-middleware.js";
17
19
  import { extractMessageText, normalizeMessageContent } from "../utils/message-content.js";
18
20
  import { getBindingDeepAgentParams, getBindingInterruptCompatibilityRules, getBindingLangChainParams, getBindingMiddlewareConfigs, getBindingModelInit, getBindingPrimaryModel, getBindingPrimaryTools, getBindingSystemPrompt, isDeepAgentBinding, isLangChainBinding, } from "./support/compiled-binding.js";
21
+ import { readSkillMetadata } from "./support/skill-metadata.js";
19
22
  function countConfiguredTools(binding) {
20
23
  return getBindingPrimaryTools(binding).length;
21
24
  }
@@ -52,6 +55,65 @@ function computeRemainingTimeoutMs(deadlineAt, fallbackTimeoutMs) {
52
55
  function isPlaceholderApiKey(value) {
53
56
  return typeof value === "string" && value.trim().toLowerCase() === "dummy";
54
57
  }
58
+ export function relativizeDeepAgentSkillSourcePaths(workspaceRoot, skillPaths) {
59
+ if (!workspaceRoot || !skillPaths) {
60
+ return skillPaths;
61
+ }
62
+ return skillPaths.map((skillPath) => {
63
+ if (!path.isAbsolute(skillPath)) {
64
+ return skillPath;
65
+ }
66
+ const relative = path.relative(workspaceRoot, skillPath);
67
+ if (!relative || relative.startsWith("..")) {
68
+ return skillPath;
69
+ }
70
+ return relative.split(path.sep).join("/");
71
+ });
72
+ }
73
+ function isDeepAgentSkillDirectory(sourcePath) {
74
+ return existsSync(sourcePath) && statSync(sourcePath).isDirectory() && existsSync(path.join(sourcePath, "SKILL.md"));
75
+ }
76
+ function toWorkspaceRelativePath(workspaceRoot, targetPath) {
77
+ if (!workspaceRoot) {
78
+ return targetPath;
79
+ }
80
+ const relative = path.relative(workspaceRoot, targetPath);
81
+ if (!relative || relative.startsWith("..")) {
82
+ return targetPath;
83
+ }
84
+ return relative.split(path.sep).join("/");
85
+ }
86
+ export async function materializeDeepAgentSkillSourcePaths(options) {
87
+ const { workspaceRoot, runRoot, ownerId, skillPaths } = options;
88
+ if (!skillPaths) {
89
+ return skillPaths;
90
+ }
91
+ const materialized = relativizeDeepAgentSkillSourcePaths(workspaceRoot, skillPaths) ?? skillPaths;
92
+ if (!workspaceRoot || !runRoot) {
93
+ return materialized;
94
+ }
95
+ const sourceRoot = path.join(runRoot, "deepagent-skill-sources", ownerId);
96
+ let wroteSyntheticSource = false;
97
+ const resolvedSources = [];
98
+ for (const [index, sourcePath] of materialized.entries()) {
99
+ const absolutePath = path.isAbsolute(sourcePath) ? sourcePath : path.resolve(workspaceRoot, sourcePath);
100
+ if (!isDeepAgentSkillDirectory(absolutePath)) {
101
+ resolvedSources.push(sourcePath);
102
+ continue;
103
+ }
104
+ if (!wroteSyntheticSource) {
105
+ await rm(sourceRoot, { recursive: true, force: true });
106
+ await mkdir(sourceRoot, { recursive: true });
107
+ wroteSyntheticSource = true;
108
+ }
109
+ const skillDirectoryName = path.basename(absolutePath);
110
+ const syntheticSourcePath = path.join(sourceRoot, `${String(index + 1).padStart(3, "0")}-${skillDirectoryName}`);
111
+ await mkdir(syntheticSourcePath, { recursive: true });
112
+ await cp(absolutePath, path.join(syntheticSourcePath, skillDirectoryName), { recursive: true });
113
+ resolvedSources.push(toWorkspaceRelativePath(workspaceRoot, syntheticSourcePath));
114
+ }
115
+ return resolvedSources;
116
+ }
55
117
  function buildAuthOmittingFetch(baseFetch = fetch) {
56
118
  return async (input, init) => {
57
119
  const sanitizedHeaders = new Headers(input instanceof Request ? input.headers : undefined);
@@ -399,11 +461,42 @@ export class AgentRuntimeAdapter {
399
461
  { role: "user", content: normalizeMessageContent(input) },
400
462
  ];
401
463
  }
402
- buildInvocationRequest(history, input, options = {}) {
464
+ buildSlashCommandSkillInstruction(binding, input) {
465
+ const inputText = extractMessageText(input).trim();
466
+ const match = inputText.match(/^\/([a-z0-9]+(?:-[a-z0-9]+)*)(?:\s+([\s\S]*))?$/i);
467
+ if (!match) {
468
+ return undefined;
469
+ }
470
+ const invokedName = match[1].toLowerCase();
471
+ const argumentText = match[2]?.trim() ?? "";
472
+ const skillPaths = binding.deepAgentParams?.skills ?? binding.langchainAgentParams?.skills ?? [];
473
+ const matchedSkillPath = skillPaths.find((skillPath) => readSkillMetadata(skillPath).name.toLowerCase() === invokedName);
474
+ if (!matchedSkillPath) {
475
+ return undefined;
476
+ }
477
+ const metadata = readSkillMetadata(matchedSkillPath);
478
+ const skillQualifier = metadata.userInvocable === true ? "user-invocable skill" : "skill";
479
+ const dryRunHint = /\s--dry-run(?:\s|$)/.test(` ${argumentText} `)
480
+ ? "This invocation includes `--dry-run`. Perform the real fetch or inspection steps needed for dry-run output. Do not return hypothetical or mock results."
481
+ : undefined;
482
+ return [
483
+ `This user message is an explicit command-style invocation of the ${skillQualifier} \`${metadata.name}\`.`,
484
+ `Read the skill file for \`${metadata.name}\` before taking action, then follow its documented phases and constraints exactly.`,
485
+ `You must use the \`${metadata.name}\` skill for this request and follow its documented workflow.`,
486
+ `Treat everything after \`/${metadata.name}\` as the skill argument string: ${argumentText ? JSON.stringify(argumentText) : '""'}.`,
487
+ "Do not answer with a generic explanation of what the skill would do. Execute the skill workflow using the available tools unless the skill instructions explicitly require confirmation before acting.",
488
+ dryRunHint,
489
+ ].filter((line) => typeof line === "string" && line.length > 0).join("\n");
490
+ }
491
+ buildInvocationRequest(binding, history, input, options = {}) {
492
+ const userInvocableInstruction = this.buildSlashCommandSkillInstruction(binding, input);
493
+ const messages = this.buildAgentMessages(history, input);
403
494
  return {
404
495
  ...(options.state ?? {}),
405
496
  ...(options.files ? { files: options.files } : {}),
406
- messages: this.buildAgentMessages(history, input),
497
+ messages: userInvocableInstruction
498
+ ? [{ role: "system", content: userInvocableInstruction }, ...messages]
499
+ : messages,
407
500
  };
408
501
  }
409
502
  buildStateSnapshot(result) {
@@ -414,11 +507,15 @@ export class AgentRuntimeAdapter {
414
507
  delete snapshot.files;
415
508
  return Object.keys(snapshot).length > 0 ? snapshot : undefined;
416
509
  }
417
- buildRawModelMessages(systemPrompt, history, input) {
510
+ buildRawModelMessages(binding, systemPrompt, history, input) {
418
511
  const messages = [];
419
512
  if (systemPrompt) {
420
513
  messages.push({ role: "system", content: systemPrompt });
421
514
  }
515
+ const userInvocableInstruction = this.buildSlashCommandSkillInstruction(binding, input);
516
+ if (userInvocableInstruction) {
517
+ messages.push({ role: "system", content: userInvocableInstruction });
518
+ }
422
519
  messages.push(...this.buildAgentMessages(history, input));
423
520
  return messages;
424
521
  }
@@ -525,6 +622,8 @@ export class AgentRuntimeAdapter {
525
622
  async resolveMiddleware(binding, interruptOn) {
526
623
  const declarativeMiddleware = await resolveDeclaredMiddleware(getBindingMiddlewareConfigs(binding), {
527
624
  resolveModel: (model) => this.resolveModel(model),
625
+ resolveBackend: (resolvedBinding) => this.options.backendResolver?.(resolvedBinding ?? binding),
626
+ resolveFilesystemBackend: (resolvedBinding) => this.resolveFilesystemBackend(resolvedBinding ?? binding),
528
627
  resolveCustom: this.options.declaredMiddlewareResolver,
529
628
  binding,
530
629
  });
@@ -570,11 +669,25 @@ export class AgentRuntimeAdapter {
570
669
  ...(subagent.passthrough ?? {}),
571
670
  model: subagent.model ? (await this.resolveModel(subagent.model)) : undefined,
572
671
  tools: subagent.tools ? this.resolveTools(subagent.tools) : undefined,
672
+ skills: await materializeDeepAgentSkillSourcePaths({
673
+ workspaceRoot: binding?.harnessRuntime.workspaceRoot,
674
+ runRoot: binding?.harnessRuntime.runRoot,
675
+ ownerId: `${binding?.agent.id ?? "agent"}-${subagent.name}`,
676
+ skillPaths: subagent.skills,
677
+ }),
573
678
  interruptOn: this.compileInterruptOn(subagent.tools ?? [], subagent.interruptOn),
574
679
  responseFormat: subagent.responseFormat,
575
680
  contextSchema: subagent.contextSchema,
576
681
  middleware: (await resolveDeclaredMiddleware(subagent.middleware, {
577
682
  resolveModel: (model) => this.resolveModel(model),
683
+ resolveBackend: (resolvedBinding) => {
684
+ const targetBinding = resolvedBinding ?? binding;
685
+ return targetBinding ? this.options.backendResolver?.(targetBinding) : undefined;
686
+ },
687
+ resolveFilesystemBackend: (resolvedBinding) => {
688
+ const targetBinding = resolvedBinding ?? binding;
689
+ return targetBinding ? this.resolveFilesystemBackend(targetBinding) : undefined;
690
+ },
578
691
  resolveCustom: this.options.declaredMiddlewareResolver,
579
692
  binding,
580
693
  })),
@@ -625,7 +738,12 @@ export class AgentRuntimeAdapter {
625
738
  interruptOn: this.resolveInterruptOn(binding),
626
739
  name: params.name,
627
740
  memory: params.memory,
628
- skills: params.skills,
741
+ skills: await materializeDeepAgentSkillSourcePaths({
742
+ workspaceRoot: binding.harnessRuntime.workspaceRoot,
743
+ runRoot: binding.harnessRuntime.runRoot,
744
+ ownerId: binding.agent.id,
745
+ skillPaths: params.skills,
746
+ }),
629
747
  generalPurposeAgent: params.generalPurposeAgent,
630
748
  taskDescription: params.taskDescription,
631
749
  };
@@ -660,7 +778,7 @@ export class AgentRuntimeAdapter {
660
778
  }
661
779
  async invoke(binding, input, threadId, runId, resumePayload, history = [], options = {}) {
662
780
  const request = resumePayload === undefined
663
- ? this.buildInvocationRequest(history, input, options)
781
+ ? this.buildInvocationRequest(binding, history, input, options)
664
782
  : new Command({ resume: resumePayload });
665
783
  let result;
666
784
  try {
@@ -673,7 +791,7 @@ export class AgentRuntimeAdapter {
673
791
  }
674
792
  const retriedBinding = this.applyStrictToolJsonInstruction(binding);
675
793
  const runnable = await this.create(retriedBinding);
676
- result = (await this.withTimeout(() => runnable.invoke(this.buildInvocationRequest(history, input, options), { configurable: { thread_id: threadId }, ...(options.context ? { context: options.context } : {}) }), this.resolveBindingTimeout(retriedBinding), "agent invoke", "invoke"));
794
+ result = (await this.withTimeout(() => runnable.invoke(this.buildInvocationRequest(retriedBinding, history, input, options), { configurable: { thread_id: threadId }, ...(options.context ? { context: options.context } : {}) }), this.resolveBindingTimeout(retriedBinding), "agent invoke", "invoke"));
677
795
  }
678
796
  const interruptContent = Array.isArray(result.__interrupt__) && result.__interrupt__.length > 0 ? JSON.stringify(result.__interrupt__) : undefined;
679
797
  const extractedOutput = extractVisibleOutput(result);
@@ -729,7 +847,7 @@ export class AgentRuntimeAdapter {
729
847
  // agent loop and only adds an extra model round-trip before the runnable path.
730
848
  if (canUseDirectModelStream && typeof model.stream === "function") {
731
849
  let emitted = false;
732
- const stream = await this.withTimeout(() => model.stream(this.buildRawModelMessages(getBindingSystemPrompt(binding), history, input)), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "model stream start", "stream");
850
+ const stream = await this.withTimeout(() => model.stream(this.buildRawModelMessages(binding, getBindingSystemPrompt(binding), history, input)), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "model stream start", "stream");
733
851
  for await (const chunk of this.iterateWithTimeout(stream, streamIdleTimeoutMs, "model stream", streamDeadlineAt, invokeTimeoutMs)) {
734
852
  const delta = readStreamDelta(chunk);
735
853
  if (delta) {
@@ -748,7 +866,7 @@ export class AgentRuntimeAdapter {
748
866
  }
749
867
  }
750
868
  const runnable = await this.create(binding);
751
- const request = this.buildInvocationRequest(history, input, options);
869
+ const request = this.buildInvocationRequest(binding, history, input, options);
752
870
  if (typeof runnable.streamEvents === "function") {
753
871
  const events = await this.withTimeout(() => runnable.streamEvents(request, { configurable: { thread_id: threadId }, version: "v2", ...(options.context ? { context: options.context } : {}) }), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "agent streamEvents start", "stream");
754
872
  const allowVisibleStreamDeltas = isLangChainBinding(binding);
@@ -3,12 +3,17 @@ type MiddlewareConfig = Record<string, unknown>;
3
3
  type MiddlewareFactoryContext = {
4
4
  config: MiddlewareConfig;
5
5
  resolveModel: (model: CompiledModel) => Promise<unknown>;
6
+ resolveBackend?: () => unknown;
7
+ resolveFilesystemBackend?: () => unknown;
8
+ binding?: CompiledAgentBinding;
6
9
  };
7
10
  type MiddlewareFactory = (context: MiddlewareFactoryContext) => Promise<unknown | unknown[] | null | undefined>;
8
11
  export declare const DECLARATIVE_MIDDLEWARE_REGISTRY: Record<string, MiddlewareFactory>;
9
12
  export declare const SUPPORTED_DECLARATIVE_MIDDLEWARE_KINDS: readonly string[];
10
13
  export declare function resolveDeclaredMiddleware(middlewareConfigs: unknown[] | undefined, options: {
11
14
  resolveModel: (model: CompiledModel) => Promise<unknown>;
15
+ resolveBackend?: (binding?: CompiledAgentBinding) => unknown;
16
+ resolveFilesystemBackend?: (binding?: CompiledAgentBinding) => unknown;
12
17
  resolveCustom?: (input: {
13
18
  kind: string;
14
19
  config: Record<string, unknown>;
@@ -1,4 +1,5 @@
1
- import { anthropicPromptCachingMiddleware, contextEditingMiddleware, humanInTheLoopMiddleware, llmToolSelectorMiddleware, modelCallLimitMiddleware, modelFallbackMiddleware, modelRetryMiddleware, openAIModerationMiddleware, piiMiddleware, summarizationMiddleware, todoListMiddleware, toolCallLimitMiddleware, toolEmulatorMiddleware, toolRetryMiddleware, } from "langchain";
1
+ import { anthropicPromptCachingMiddleware, contextEditingMiddleware, dynamicSystemPromptMiddleware, humanInTheLoopMiddleware, llmToolSelectorMiddleware, modelCallLimitMiddleware, modelFallbackMiddleware, modelRetryMiddleware, openAIModerationMiddleware, piiMiddleware, piiRedactionMiddleware, summarizationMiddleware, todoListMiddleware, toolCallLimitMiddleware, toolEmulatorMiddleware, toolRetryMiddleware, } from "langchain";
2
+ import { createFilesystemMiddleware, createPatchToolCallsMiddleware, createSummarizationMiddleware as createDeepAgentSummarizationMiddleware, } from "deepagents";
2
3
  function asMiddlewareConfig(value) {
3
4
  return typeof value === "object" && value !== null && !Array.isArray(value) ? { ...value } : null;
4
5
  }
@@ -26,6 +27,18 @@ async function resolveReferencedModelList(values, options) {
26
27
  return Promise.all(values.map((value) => resolveReferencedModel(value, options)));
27
28
  }
28
29
  async function createSummarizationMiddleware({ config, resolveModel }) {
30
+ if (config.provider === "deepagents") {
31
+ const runtimeConfig = { ...config };
32
+ runtimeConfig.model = await resolveReferencedModel(runtimeConfig.model, { resolveModel });
33
+ if (runtimeConfig.model === undefined) {
34
+ throw new Error("deepagents summarization middleware requires model or modelRef");
35
+ }
36
+ if (runtimeConfig.backend === undefined) {
37
+ throw new Error("deepagents summarization middleware requires backend");
38
+ }
39
+ delete runtimeConfig.provider;
40
+ return createDeepAgentSummarizationMiddleware(runtimeConfig);
41
+ }
29
42
  const runtimeConfig = { ...config };
30
43
  runtimeConfig.model = await resolveReferencedModel(runtimeConfig.model, { resolveModel });
31
44
  if (runtimeConfig.model === undefined) {
@@ -33,6 +46,17 @@ async function createSummarizationMiddleware({ config, resolveModel }) {
33
46
  }
34
47
  return summarizationMiddleware(runtimeConfig);
35
48
  }
49
+ async function createBindingAwareSummarizationMiddleware(context) {
50
+ const runtimeConfig = { ...context.config };
51
+ if (context.binding?.agent.executionMode === "deepagent") {
52
+ runtimeConfig.provider = "deepagents";
53
+ runtimeConfig.backend ??= context.resolveBackend?.() ?? context.resolveFilesystemBackend?.();
54
+ }
55
+ return createSummarizationMiddleware({
56
+ ...context,
57
+ config: runtimeConfig,
58
+ });
59
+ }
36
60
  async function createLlmToolSelectorMiddleware({ config, resolveModel }) {
37
61
  const runtimeConfig = { ...config };
38
62
  runtimeConfig.model = await resolveReferencedModel(runtimeConfig.model, { resolveModel });
@@ -54,8 +78,15 @@ async function createPiiDeclarativeMiddleware({ config }) {
54
78
  const { piiType: _piiType, ...piiOptions } = config;
55
79
  return piiMiddleware(piiType, piiOptions);
56
80
  }
81
+ async function createFilesystemDeclarativeMiddleware({ config, resolveFilesystemBackend }) {
82
+ const runtimeConfig = { ...config };
83
+ runtimeConfig.backend ??= resolveFilesystemBackend?.();
84
+ return createFilesystemMiddleware(runtimeConfig);
85
+ }
57
86
  export const DECLARATIVE_MIDDLEWARE_REGISTRY = {
58
- summarization: createSummarizationMiddleware,
87
+ filesystem: createFilesystemDeclarativeMiddleware,
88
+ patchToolCalls: async () => createPatchToolCallsMiddleware(),
89
+ summarization: createBindingAwareSummarizationMiddleware,
59
90
  llmToolSelector: createLlmToolSelectorMiddleware,
60
91
  modelRetry: async ({ config }) => modelRetryMiddleware(config),
61
92
  modelFallback: createModelFallbackDeclarativeMiddleware,
@@ -64,10 +95,12 @@ export const DECLARATIVE_MIDDLEWARE_REGISTRY = {
64
95
  modelCallLimit: async ({ config }) => modelCallLimitMiddleware(config),
65
96
  todoList: async ({ config }) => todoListMiddleware(config),
66
97
  contextEditing: async ({ config }) => contextEditingMiddleware(config),
98
+ dynamicSystemPrompt: async ({ config }) => dynamicSystemPromptMiddleware(config),
67
99
  toolEmulator: async ({ config }) => toolEmulatorMiddleware(config),
68
100
  humanInTheLoop: async ({ config }) => humanInTheLoopMiddleware(config),
69
101
  openAIModeration: async ({ config }) => openAIModerationMiddleware(config),
70
102
  pii: createPiiDeclarativeMiddleware,
103
+ piiRedaction: async ({ config }) => piiRedactionMiddleware(config),
71
104
  anthropicPromptCaching: async ({ config }) => anthropicPromptCachingMiddleware(config),
72
105
  };
73
106
  export const SUPPORTED_DECLARATIVE_MIDDLEWARE_KINDS = Object.freeze(Object.keys(DECLARATIVE_MIDDLEWARE_REGISTRY).sort());
@@ -85,6 +118,9 @@ export async function resolveDeclaredMiddleware(middlewareConfigs, options) {
85
118
  const resolvedBuiltin = await registeredFactory({
86
119
  config: runtimeConfig,
87
120
  resolveModel: options.resolveModel,
121
+ resolveBackend: () => options.resolveBackend?.(options.binding),
122
+ resolveFilesystemBackend: () => options.resolveFilesystemBackend?.(options.binding),
123
+ binding: options.binding,
88
124
  });
89
125
  if (Array.isArray(resolvedBuiltin)) {
90
126
  resolved.push(...resolvedBuiltin);
@@ -1,5 +1,7 @@
1
1
  import type { ApprovalRecord, HarnessEvent, HarnessStreamItem, MessageContent, RunStartOptions, RestartConversationOptions, RuntimeAdapterOptions, ResumeOptions, RunOptions, RunResult, ThreadSummary, ThreadRecord, WorkspaceBundle } from "../contracts/types.js";
2
2
  import { type ToolMcpServerOptions } from "../mcp.js";
3
+ import { type InventoryAgentRecord, type InventorySkillRecord } from "./inventory.js";
4
+ import type { RequirementAssessmentOptions } from "./skill-requirements.js";
3
5
  export declare class AgentHarnessRuntime {
4
6
  private readonly workspace;
5
7
  private readonly runtimeAdapterOptions;
@@ -53,6 +55,11 @@ export declare class AgentHarnessRuntime {
53
55
  runId?: string;
54
56
  }): Promise<ApprovalRecord[]>;
55
57
  getApproval(approvalId: string): Promise<ApprovalRecord | null>;
58
+ listAgentSkills(agentId: string, options?: RequirementAssessmentOptions): InventorySkillRecord[];
59
+ describeWorkspaceInventory(options?: RequirementAssessmentOptions): {
60
+ workspaceRoot: string;
61
+ agents: InventoryAgentRecord[];
62
+ };
56
63
  private deleteThreadCheckpoints;
57
64
  deleteThread(threadId: string): Promise<boolean>;
58
65
  createToolMcpServer(options: ToolMcpServerOptions): Promise<import("@modelcontextprotocol/sdk/server/mcp.js").McpServer>;
@@ -16,6 +16,7 @@ import { CheckpointMaintenanceLoop, discoverCheckpointMaintenanceTargets, readCh
16
16
  import { extractMessageText, normalizeMessageContent } from "../utils/message-content.js";
17
17
  import { createToolMcpServerFromTools, serveToolsOverStdioFromHarness } from "../mcp.js";
18
18
  import { getBindingAdapterKind, getBindingPrimaryTools, getBindingStoreConfig } from "./support/compiled-binding.js";
19
+ import { describeWorkspaceInventory, listAgentSkills as listWorkspaceAgentSkills, } from "./inventory.js";
19
20
  export class AgentHarnessRuntime {
20
21
  workspace;
21
22
  runtimeAdapterOptions;
@@ -286,6 +287,12 @@ export class AgentHarnessRuntime {
286
287
  const approval = await this.persistence.getApproval(approvalId);
287
288
  return approval ? this.toPublicApprovalRecord(approval) : null;
288
289
  }
290
+ listAgentSkills(agentId, options = {}) {
291
+ return listWorkspaceAgentSkills(this.workspace, agentId, options);
292
+ }
293
+ describeWorkspaceInventory(options = {}) {
294
+ return describeWorkspaceInventory(this.workspace, options);
295
+ }
289
296
  async deleteThreadCheckpoints(threadId) {
290
297
  const resolver = this.resolvedRuntimeAdapterOptions.checkpointerResolver;
291
298
  if (!resolver) {
@@ -6,6 +6,7 @@ export { CheckpointMaintenanceLoop, discoverCheckpointMaintenanceTargets, mainta
6
6
  export { ManagedSqliteSaver } from "./sqlite-maintained-checkpoint-saver.js";
7
7
  export { AgentHarnessRuntime, AgentHarness } from "./harness.js";
8
8
  export { describeWorkspaceInventory, findAgentBinding, listAgentSkills, listAgentTools, listAvailableAgents, listSpecialists, } from "./inventory.js";
9
+ export { assessOpenClawRequirements, assessSkillRequirements, } from "./skill-requirements.js";
9
10
  export * from "./parsing/index.js";
10
11
  export { PolicyEngine } from "./policy-engine.js";
11
12
  export { createInMemoryStore, FileBackedStore } from "./store.js";
@@ -6,6 +6,7 @@ export { CheckpointMaintenanceLoop, discoverCheckpointMaintenanceTargets, mainta
6
6
  export { ManagedSqliteSaver } from "./sqlite-maintained-checkpoint-saver.js";
7
7
  export { AgentHarnessRuntime, AgentHarness } from "./harness.js";
8
8
  export { describeWorkspaceInventory, findAgentBinding, listAgentSkills, listAgentTools, listAvailableAgents, listSpecialists, } from "./inventory.js";
9
+ export { assessOpenClawRequirements, assessSkillRequirements, } from "./skill-requirements.js";
9
10
  export * from "./parsing/index.js";
10
11
  export { PolicyEngine } from "./policy-engine.js";
11
12
  export { createInMemoryStore, FileBackedStore } from "./store.js";
@@ -1,4 +1,6 @@
1
1
  import type { CompiledAgentBinding, WorkspaceBundle } from "../contracts/types.js";
2
+ import type { OpenClawSkillMetadata } from "./support/skill-metadata.js";
3
+ import { type RequirementAssessmentOptions, type SkillRequirementAssessment } from "./skill-requirements.js";
2
4
  export type InventoryToolRecord = {
3
5
  name: string;
4
6
  description: string;
@@ -9,8 +11,11 @@ export type InventorySkillRecord = {
9
11
  description?: string;
10
12
  license?: string;
11
13
  compatibility?: string;
12
- metadata?: Record<string, string>;
14
+ metadata?: Record<string, unknown>;
13
15
  allowedTools?: string[];
16
+ userInvocable?: boolean;
17
+ openclaw?: OpenClawSkillMetadata;
18
+ requirements?: SkillRequirementAssessment;
14
19
  };
15
20
  export type InventoryAgentRecord = {
16
21
  id: string;
@@ -21,10 +26,10 @@ export type InventoryAgentRecord = {
21
26
  };
22
27
  export declare function findAgentBinding(workspace: WorkspaceBundle, agentId: string): CompiledAgentBinding | undefined;
23
28
  export declare function listAgentTools(workspace: WorkspaceBundle, agentId: string): InventoryToolRecord[];
24
- export declare function listAgentSkills(workspace: WorkspaceBundle, agentId: string): InventorySkillRecord[];
25
- export declare function listSpecialists(workspace: WorkspaceBundle): InventoryAgentRecord[];
26
- export declare function listAvailableAgents(workspace: WorkspaceBundle): InventoryAgentRecord[];
27
- export declare function describeWorkspaceInventory(workspace: WorkspaceBundle): {
29
+ export declare function listAgentSkills(workspace: WorkspaceBundle, agentId: string, options?: RequirementAssessmentOptions): InventorySkillRecord[];
30
+ export declare function listSpecialists(workspace: WorkspaceBundle, options?: RequirementAssessmentOptions): InventoryAgentRecord[];
31
+ export declare function listAvailableAgents(workspace: WorkspaceBundle, options?: RequirementAssessmentOptions): InventoryAgentRecord[];
32
+ export declare function describeWorkspaceInventory(workspace: WorkspaceBundle, options?: RequirementAssessmentOptions): {
28
33
  workspaceRoot: string;
29
34
  agents: InventoryAgentRecord[];
30
35
  };
@@ -1,5 +1,7 @@
1
1
  import { readSkillMetadata } from "./support/skill-metadata.js";
2
2
  import { getBindingPrimaryTools } from "./support/compiled-binding.js";
3
+ import { assessSkillRequirements, } from "./skill-requirements.js";
4
+ import { createRuntimeEnv } from "./support/runtime-env.js";
3
5
  function listHostBindings(workspace) {
4
6
  return Array.from(workspace.bindings.values()).filter((binding) => binding.harnessRuntime.hostFacing);
5
7
  }
@@ -13,7 +15,36 @@ function dedupeTools(tools) {
13
15
  }
14
16
  return Array.from(deduped.values());
15
17
  }
16
- function toSkillRecords(skillPaths) {
18
+ function readBackendRequirementOptions(binding) {
19
+ const backend = binding.deepAgentParams?.backend && typeof binding.deepAgentParams.backend === "object"
20
+ ? binding.deepAgentParams.backend
21
+ : undefined;
22
+ if (!backend) {
23
+ return {};
24
+ }
25
+ const backendState = typeof backend.state === "object" && backend.state
26
+ ? backend.state
27
+ : backend;
28
+ const env = typeof backendState.env === "object" && backendState.env
29
+ ? Object.fromEntries(Object.entries(backendState.env).filter((entry) => typeof entry[1] === "string"))
30
+ : undefined;
31
+ const inheritedEnv = backendState.inheritEnv === false ? {} : process.env;
32
+ const runtimeEnv = createRuntimeEnv(env, inheritedEnv);
33
+ return {
34
+ env: runtimeEnv,
35
+ path: runtimeEnv.PATH,
36
+ };
37
+ }
38
+ function mergeRequirementOptions(binding, options = {}) {
39
+ const fromBackend = readBackendRequirementOptions(binding);
40
+ return {
41
+ ...fromBackend,
42
+ ...options,
43
+ env: options.env ? { ...(fromBackend.env ?? {}), ...options.env } : fromBackend.env,
44
+ path: options.path ?? fromBackend.path,
45
+ };
46
+ }
47
+ function toSkillRecords(skillPaths, options = {}) {
17
48
  return Array.from(new Set(skillPaths)).map((skillPath) => {
18
49
  const metadata = readSkillMetadata(skillPath);
19
50
  return {
@@ -24,6 +55,9 @@ function toSkillRecords(skillPaths) {
24
55
  compatibility: metadata.compatibility,
25
56
  metadata: metadata.metadata,
26
57
  allowedTools: metadata.allowedTools,
58
+ userInvocable: metadata.userInvocable,
59
+ openclaw: metadata.openclaw,
60
+ requirements: assessSkillRequirements(metadata, options),
27
61
  };
28
62
  });
29
63
  }
@@ -34,38 +68,42 @@ export function listAgentTools(workspace, agentId) {
34
68
  }
35
69
  return dedupeTools(getBindingPrimaryTools(binding));
36
70
  }
37
- export function listAgentSkills(workspace, agentId) {
71
+ export function listAgentSkills(workspace, agentId, options = {}) {
38
72
  const binding = findAgentBinding(workspace, agentId);
39
73
  if (!binding) {
40
74
  return [];
41
75
  }
42
- return toSkillRecords(binding.deepAgentParams?.skills ?? []);
76
+ const resolvedOptions = mergeRequirementOptions(binding, options);
77
+ return toSkillRecords(binding.deepAgentParams?.skills ?? binding.langchainAgentParams?.skills ?? [], resolvedOptions);
43
78
  }
44
- function describeSubagent(subagent) {
79
+ function describeSubagent(subagent, options = {}) {
45
80
  return {
46
81
  id: subagent.name,
47
82
  description: subagent.description,
48
83
  role: "specialist",
49
84
  tools: dedupeTools(subagent.tools ?? []),
50
- skills: toSkillRecords(subagent.skills ?? []),
85
+ skills: toSkillRecords(subagent.skills ?? [], options),
51
86
  };
52
87
  }
53
- export function listSpecialists(workspace) {
54
- return listHostBindings(workspace).flatMap((binding) => (binding.deepAgentParams?.subagents ?? []).map(describeSubagent));
88
+ export function listSpecialists(workspace, options = {}) {
89
+ return listHostBindings(workspace).flatMap((binding) => {
90
+ const resolvedOptions = mergeRequirementOptions(binding, options);
91
+ return (binding.deepAgentParams?.subagents ?? []).map((subagent) => describeSubagent(subagent, resolvedOptions));
92
+ });
55
93
  }
56
- export function listAvailableAgents(workspace) {
94
+ export function listAvailableAgents(workspace, options = {}) {
57
95
  const topLevel = listHostBindings(workspace).map((binding) => ({
58
96
  id: binding.agent.id,
59
97
  description: binding.agent.description,
60
98
  role: "host",
61
99
  tools: listAgentTools(workspace, binding.agent.id),
62
- skills: listAgentSkills(workspace, binding.agent.id),
100
+ skills: listAgentSkills(workspace, binding.agent.id, options),
63
101
  }));
64
- return [...topLevel, ...listSpecialists(workspace)];
102
+ return [...topLevel, ...listSpecialists(workspace, options)];
65
103
  }
66
- export function describeWorkspaceInventory(workspace) {
104
+ export function describeWorkspaceInventory(workspace, options = {}) {
67
105
  return {
68
106
  workspaceRoot: workspace.workspaceRoot,
69
- agents: listAvailableAgents(workspace),
107
+ agents: listAvailableAgents(workspace, options),
70
108
  };
71
109
  }
@@ -0,0 +1,27 @@
1
+ import type { OpenClawSkillMetadata, SkillMetadata } from "./support/skill-metadata.js";
2
+ export type RequirementStatus = "satisfied" | "missing" | "unknown";
3
+ export type RequirementCheckRecord = {
4
+ name: string;
5
+ status: RequirementStatus;
6
+ };
7
+ export type OpenClawRequirementAssessment = {
8
+ ready: boolean;
9
+ missing: boolean;
10
+ unknown: boolean;
11
+ bins: RequirementCheckRecord[];
12
+ anyBins: RequirementCheckRecord[];
13
+ env: RequirementCheckRecord[];
14
+ config: RequirementCheckRecord[];
15
+ primaryEnv?: RequirementCheckRecord;
16
+ };
17
+ export type SkillRequirementAssessment = {
18
+ openclaw?: OpenClawRequirementAssessment;
19
+ };
20
+ export type RequirementAssessmentOptions = {
21
+ env?: Record<string, string | undefined>;
22
+ path?: string;
23
+ availableBins?: string[];
24
+ config?: Record<string, unknown> | string[];
25
+ };
26
+ export declare function assessOpenClawRequirements(metadata: OpenClawSkillMetadata | undefined, options?: RequirementAssessmentOptions): OpenClawRequirementAssessment | undefined;
27
+ export declare function assessSkillRequirements(metadata: SkillMetadata, options?: RequirementAssessmentOptions): SkillRequirementAssessment;