bopodev-agent-sdk 0.1.32 → 0.1.33

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,5 +1,5 @@
1
1
 
2
2
  
3
- > bopodev-agent-sdk@0.1.32 build /Users/danielkrusenstrahle/Documents/Projects/Monorepo/bopohq/packages/agent-sdk
3
+ > bopodev-agent-sdk@0.1.33 build /Users/danielkrusenstrahle/Documents/Projects/Monorepo/bopohq/packages/agent-sdk
4
4
  > tsc -p tsconfig.json --emitDeclarationOnly
5
5
 
@@ -1,4 +1,4 @@
1
1
 
2
- > bopodev-agent-sdk@0.1.30 typecheck /Users/danielkrusenstrahle/Documents/Projects/Monorepo/bopohq/packages/agent-sdk
2
+ > bopodev-agent-sdk@0.1.32 typecheck /Users/danielkrusenstrahle/Documents/Projects/Monorepo/bopohq/packages/agent-sdk
3
3
  > tsc -p tsconfig.json --noEmit
4
4
 
@@ -619,6 +619,7 @@ export declare const TemplateManifestAgentSchema: z.ZodObject<{
619
619
  auto: "auto";
620
620
  }>>>;
621
621
  bootstrapPrompt: z.ZodOptional<z.ZodOptional<z.ZodString>>;
622
+ enabledSkillIds: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>>;
622
623
  runtimeTimeoutSec: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
623
624
  interruptGraceSec: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
624
625
  runPolicy: z.ZodOptional<z.ZodDefault<z.ZodObject<{
@@ -748,6 +749,7 @@ export declare const TemplateManifestSchema: z.ZodObject<{
748
749
  auto: "auto";
749
750
  }>>>;
750
751
  bootstrapPrompt: z.ZodOptional<z.ZodOptional<z.ZodString>>;
752
+ enabledSkillIds: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>>;
751
753
  runtimeTimeoutSec: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
752
754
  interruptGraceSec: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
753
755
  runPolicy: z.ZodOptional<z.ZodDefault<z.ZodObject<{
@@ -910,6 +912,7 @@ export declare const TemplateSchema: z.ZodObject<{
910
912
  auto: "auto";
911
913
  }>>>;
912
914
  bootstrapPrompt: z.ZodOptional<z.ZodOptional<z.ZodString>>;
915
+ enabledSkillIds: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>>;
913
916
  runtimeTimeoutSec: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
914
917
  interruptGraceSec: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
915
918
  runPolicy: z.ZodOptional<z.ZodDefault<z.ZodObject<{
@@ -1072,6 +1075,7 @@ export declare const TemplateCreateRequestSchema: z.ZodObject<{
1072
1075
  auto: "auto";
1073
1076
  }>>>;
1074
1077
  bootstrapPrompt: z.ZodOptional<z.ZodOptional<z.ZodString>>;
1078
+ enabledSkillIds: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>>;
1075
1079
  runtimeTimeoutSec: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
1076
1080
  interruptGraceSec: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
1077
1081
  runPolicy: z.ZodOptional<z.ZodDefault<z.ZodObject<{
@@ -1232,6 +1236,7 @@ export declare const TemplateUpdateRequestSchema: z.ZodObject<{
1232
1236
  auto: "auto";
1233
1237
  }>>>;
1234
1238
  bootstrapPrompt: z.ZodOptional<z.ZodOptional<z.ZodString>>;
1239
+ enabledSkillIds: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>>;
1235
1240
  runtimeTimeoutSec: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
1236
1241
  interruptGraceSec: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
1237
1242
  runPolicy: z.ZodOptional<z.ZodDefault<z.ZodObject<{
@@ -1443,6 +1448,7 @@ export declare const TemplateExportSchema: z.ZodObject<{
1443
1448
  auto: "auto";
1444
1449
  }>>>;
1445
1450
  bootstrapPrompt: z.ZodOptional<z.ZodOptional<z.ZodString>>;
1451
+ enabledSkillIds: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>>;
1446
1452
  runtimeTimeoutSec: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
1447
1453
  interruptGraceSec: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
1448
1454
  runPolicy: z.ZodOptional<z.ZodDefault<z.ZodObject<{
@@ -1607,6 +1613,7 @@ export declare const TemplateImportRequestSchema: z.ZodObject<{
1607
1613
  auto: "auto";
1608
1614
  }>>>;
1609
1615
  bootstrapPrompt: z.ZodOptional<z.ZodOptional<z.ZodString>>;
1616
+ enabledSkillIds: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>>;
1610
1617
  runtimeTimeoutSec: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
1611
1618
  interruptGraceSec: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
1612
1619
  runPolicy: z.ZodOptional<z.ZodDefault<z.ZodObject<{
@@ -1748,6 +1755,7 @@ export declare const TemplateVersionSchema: z.ZodObject<{
1748
1755
  auto: "auto";
1749
1756
  }>>>;
1750
1757
  bootstrapPrompt: z.ZodOptional<z.ZodOptional<z.ZodString>>;
1758
+ enabledSkillIds: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>>;
1751
1759
  runtimeTimeoutSec: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
1752
1760
  interruptGraceSec: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
1753
1761
  runPolicy: z.ZodOptional<z.ZodDefault<z.ZodObject<{
@@ -1892,6 +1900,8 @@ export declare const ControlPlaneRuntimeEnvSchema: z.ZodObject<{
1892
1900
  BOPODEV_COMPANY_WORKSPACE_ROOT: z.ZodOptional<z.ZodString>;
1893
1901
  BOPODEV_AGENT_HOME: z.ZodOptional<z.ZodString>;
1894
1902
  BOPODEV_AGENT_OPERATING_DIR: z.ZodOptional<z.ZodString>;
1903
+ BOPODEV_MATERIALIZED_LINKED_SKILLS_ROOT: z.ZodOptional<z.ZodString>;
1904
+ BOPODEV_ENABLED_SKILL_IDS: z.ZodOptional<z.ZodString>;
1895
1905
  }, z.core.$strip>;
1896
1906
  export type ControlPlaneRuntimeEnv = z.infer<typeof ControlPlaneRuntimeEnvSchema>;
1897
1907
  export declare const ExecutionOutcomeKindSchema: z.ZodEnum<{
@@ -2184,6 +2194,13 @@ export declare const RunPolicySchema: z.ZodObject<{
2184
2194
  allowWebSearch: z.ZodDefault<z.ZodBoolean>;
2185
2195
  }, z.core.$strip>;
2186
2196
  export type RunPolicy = z.infer<typeof RunPolicySchema>;
2197
+ /** Bundled Bopo skill folder names under `skills/`; always injected when using an explicit company allowlist. */
2198
+ export declare const BUILTIN_BOPO_SKILL_IDS: readonly string[];
2199
+ export declare function isBuiltinBopoSkillId(id: string): boolean;
2200
+ /** Removes bundled skill ids from a stored list (legacy rows may still include them). */
2201
+ export declare function companySkillAllowlistOnly(ids: string[]): string[];
2202
+ /** Builds the full injection allowlist: bundled skills plus company skill ids. `undefined` = caller should not set a filter (inject all). */
2203
+ export declare function mergeBuiltinSkillIdsForInjection(companySkillIds: string[] | undefined): string[] | undefined;
2187
2204
  export declare const AgentRuntimeConfigSchema: z.ZodObject<{
2188
2205
  runtimeCommand: z.ZodOptional<z.ZodString>;
2189
2206
  runtimeArgs: z.ZodDefault<z.ZodArray<z.ZodString>>;
@@ -2197,6 +2214,7 @@ export declare const AgentRuntimeConfigSchema: z.ZodObject<{
2197
2214
  auto: "auto";
2198
2215
  }>>;
2199
2216
  bootstrapPrompt: z.ZodOptional<z.ZodString>;
2217
+ enabledSkillIds: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>;
2200
2218
  runtimeTimeoutSec: z.ZodDefault<z.ZodNumber>;
2201
2219
  interruptGraceSec: z.ZodDefault<z.ZodNumber>;
2202
2220
  runPolicy: z.ZodDefault<z.ZodObject<{
@@ -2434,6 +2452,7 @@ export declare const AgentCreateRequestSchema: z.ZodObject<{
2434
2452
  auto: "auto";
2435
2453
  }>>>;
2436
2454
  bootstrapPrompt: z.ZodOptional<z.ZodOptional<z.ZodString>>;
2455
+ enabledSkillIds: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>>;
2437
2456
  runtimeTimeoutSec: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
2438
2457
  interruptGraceSec: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
2439
2458
  runPolicy: z.ZodOptional<z.ZodDefault<z.ZodObject<{
@@ -2499,6 +2518,7 @@ export declare const AgentUpdateRequestSchema: z.ZodObject<{
2499
2518
  auto: "auto";
2500
2519
  }>>>;
2501
2520
  bootstrapPrompt: z.ZodOptional<z.ZodOptional<z.ZodString>>;
2521
+ enabledSkillIds: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>>;
2502
2522
  runtimeTimeoutSec: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
2503
2523
  interruptGraceSec: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
2504
2524
  runPolicy: z.ZodOptional<z.ZodDefault<z.ZodObject<{
@@ -2571,6 +2591,7 @@ export declare const AgentSchema: z.ZodObject<{
2571
2591
  runtimeTimeoutSec: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
2572
2592
  interruptGraceSec: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
2573
2593
  runPolicyJson: z.ZodOptional<z.ZodNullable<z.ZodString>>;
2594
+ enabledSkillIds: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>;
2574
2595
  createdAt: z.ZodString;
2575
2596
  updatedAt: z.ZodString;
2576
2597
  }, z.core.$strip>;
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "bopodev-agent-sdk",
3
- "version": "0.1.32",
3
+ "version": "0.1.33",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
7
7
  "types": "src/index.ts",
8
8
  "dependencies": {
9
- "bopodev-contracts": "0.1.32"
9
+ "bopodev-contracts": "0.1.33"
10
10
  },
11
11
  "scripts": {
12
12
  "build": "tsc -p tsconfig.json --emitDeclarationOnly",
package/src/runtime.ts CHANGED
@@ -3,7 +3,11 @@ import { access, cp, lstat, mkdir, mkdtemp, readdir, rm, symlink } from "node:fs
3
3
  import { homedir, tmpdir } from "node:os";
4
4
  import { delimiter, dirname, join, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
- import { AgentFinalRunOutputSchema, type AgentFinalRunOutput } from "bopodev-contracts";
6
+ import {
7
+ AgentFinalRunOutputSchema,
8
+ BUILTIN_BOPO_SKILL_IDS,
9
+ type AgentFinalRunOutput
10
+ } from "bopodev-contracts";
7
11
  import type { AgentRuntimeConfig } from "./types";
8
12
 
9
13
  type LocalProvider = "claude_code" | "codex" | "cursor" | "opencode" | "gemini_cli";
@@ -750,6 +754,31 @@ type SkillInjectionContext = {
750
754
  cleanup: () => Promise<void>;
751
755
  };
752
756
 
757
+ const BUILTIN_BOPO_SKILL_ID_SET = new Set<string>(BUILTIN_BOPO_SKILL_IDS);
758
+
759
+ function resolveEnabledSkillIdFilter(env: NodeJS.ProcessEnv): Set<string> | null {
760
+ const key = "BOPODEV_ENABLED_SKILL_IDS";
761
+ // Missing key = legacy "all skills". Present + empty = explicit none (matches heartbeat env contract).
762
+ if (!Object.hasOwn(env, key)) {
763
+ return null;
764
+ }
765
+ const raw = String(env[key] ?? "").trim();
766
+ if (raw === "") {
767
+ return new Set<string>();
768
+ }
769
+ return new Set(raw.split(",").map((s) => s.trim()).filter(Boolean));
770
+ }
771
+
772
+ function shouldInjectSkill(skillDirName: string, filter: Set<string> | null): boolean {
773
+ if (filter === null) {
774
+ return true;
775
+ }
776
+ if (BUILTIN_BOPO_SKILL_ID_SET.has(skillDirName)) {
777
+ return true;
778
+ }
779
+ return filter.has(skillDirName);
780
+ }
781
+
753
782
  async function prepareSkillInjection(
754
783
  provider: LocalProvider | undefined,
755
784
  env: NodeJS.ProcessEnv
@@ -758,17 +787,21 @@ async function prepareSkillInjection(
758
787
  return noSkillInjection();
759
788
  }
760
789
 
761
- const skillsSource = await resolveSkillsSourceDir();
762
- if (!skillsSource) {
790
+ const skillRoots = await resolveSkillInjectionSourceRoots(env);
791
+ if (skillRoots.length === 0) {
763
792
  return {
764
793
  ...noSkillInjection(),
765
794
  warning: "[bopodev] skills injection skipped: no skills directory found."
766
795
  };
767
796
  }
768
797
 
798
+ const skillFilter = resolveEnabledSkillIdFilter(env);
799
+
769
800
  if (provider === "codex") {
770
801
  try {
771
- await ensureCodexSkillsInjected(skillsSource, env);
802
+ for (const dir of skillRoots) {
803
+ await ensureCodexSkillsInjected(dir, env, skillFilter);
804
+ }
772
805
  return noSkillInjection();
773
806
  } catch (error) {
774
807
  return {
@@ -780,7 +813,9 @@ async function prepareSkillInjection(
780
813
 
781
814
  if (provider === "cursor") {
782
815
  try {
783
- await ensureSkillsInjectedAtHome(skillsSource, join(homedir(), ".cursor", "skills"));
816
+ for (const dir of skillRoots) {
817
+ await ensureSkillsInjectedAtHome(dir, join(homedir(), ".cursor", "skills"), skillFilter);
818
+ }
784
819
  return noSkillInjection();
785
820
  } catch (error) {
786
821
  return {
@@ -792,7 +827,9 @@ async function prepareSkillInjection(
792
827
 
793
828
  if (provider === "opencode") {
794
829
  try {
795
- await ensureSkillsInjectedAtHome(skillsSource, join(homedir(), ".claude", "skills"));
830
+ for (const dir of skillRoots) {
831
+ await ensureSkillsInjectedAtHome(dir, join(homedir(), ".claude", "skills"), skillFilter);
832
+ }
796
833
  return noSkillInjection();
797
834
  } catch (error) {
798
835
  return {
@@ -808,7 +845,7 @@ async function prepareSkillInjection(
808
845
  }
809
846
 
810
847
  try {
811
- const tempSkillsRoot = await buildClaudeSkillsAddDir(skillsSource);
848
+ const tempSkillsRoot = await buildClaudeSkillsAddDirFromRoots(skillRoots, skillFilter);
812
849
  return {
813
850
  additionalArgs: ["--add-dir", tempSkillsRoot],
814
851
  cleanup: async () => {
@@ -1462,7 +1499,32 @@ async function resolveSkillsSourceDir() {
1462
1499
  return null;
1463
1500
  }
1464
1501
 
1465
- async function ensureCodexSkillsInjected(skillsSourceDir: string, env: NodeJS.ProcessEnv) {
1502
+ /** Company `skills/`, then per-run materialized linked skills (URL-only), then bundled — first wins per skill id. */
1503
+ async function resolveSkillInjectionSourceRoots(env: NodeJS.ProcessEnv): Promise<string[]> {
1504
+ const roots: string[] = [];
1505
+ const workspaceRoot = resolveControlPlaneEnvValue(env, "COMPANY_WORKSPACE_ROOT").trim();
1506
+ if (workspaceRoot.length > 0) {
1507
+ const companySkills = join(workspaceRoot, SKILLS_DIR_NAME);
1508
+ if (await isDirectory(companySkills)) {
1509
+ roots.push(companySkills);
1510
+ }
1511
+ }
1512
+ const linkedRoot = resolveControlPlaneEnvValue(env, "MATERIALIZED_LINKED_SKILLS_ROOT").trim();
1513
+ if (linkedRoot.length > 0 && (await isDirectory(linkedRoot))) {
1514
+ roots.push(linkedRoot);
1515
+ }
1516
+ const bundled = await resolveSkillsSourceDir();
1517
+ if (bundled) {
1518
+ roots.push(bundled);
1519
+ }
1520
+ return roots;
1521
+ }
1522
+
1523
+ async function ensureCodexSkillsInjected(
1524
+ skillsSourceDir: string,
1525
+ env: NodeJS.ProcessEnv,
1526
+ skillFilter: Set<string> | null
1527
+ ) {
1466
1528
  const codexHome = resolveCodexHome(env);
1467
1529
  const targetRoot = join(codexHome, SKILLS_DIR_NAME);
1468
1530
  await mkdir(targetRoot, { recursive: true });
@@ -1473,6 +1535,9 @@ async function ensureCodexSkillsInjected(skillsSourceDir: string, env: NodeJS.Pr
1473
1535
  if (!entry.isDirectory()) {
1474
1536
  continue;
1475
1537
  }
1538
+ if (!shouldInjectSkill(entry.name, skillFilter)) {
1539
+ continue;
1540
+ }
1476
1541
  const source = join(skillsSourceDir, entry.name);
1477
1542
  if (!(await hasSkillManifest(source))) {
1478
1543
  continue;
@@ -1567,12 +1632,19 @@ async function withProviderRuntimeIsolation(
1567
1632
  };
1568
1633
  }
1569
1634
 
1570
- async function ensureSkillsInjectedAtHome(skillsSourceDir: string, targetRoot: string) {
1635
+ async function ensureSkillsInjectedAtHome(
1636
+ skillsSourceDir: string,
1637
+ targetRoot: string,
1638
+ skillFilter: Set<string> | null
1639
+ ) {
1571
1640
  await mkdir(targetRoot, { recursive: true });
1572
1641
  await pruneBrokenSkillSymlinks(targetRoot);
1573
1642
  const entries = await readdir(skillsSourceDir, { withFileTypes: true });
1574
1643
  for (const entry of entries) {
1575
1644
  if (!entry.isDirectory()) continue;
1645
+ if (!shouldInjectSkill(entry.name, skillFilter)) {
1646
+ continue;
1647
+ }
1576
1648
  const source = join(skillsSourceDir, entry.name);
1577
1649
  if (!(await hasSkillManifest(source))) continue;
1578
1650
  const target = join(targetRoot, entry.name);
@@ -1678,21 +1750,34 @@ function sanitizePathSegment(value: string | undefined) {
1678
1750
  return trimmed.replace(/[^a-zA-Z0-9._-]/g, "_");
1679
1751
  }
1680
1752
 
1681
- async function buildClaudeSkillsAddDir(skillsSourceDir: string) {
1753
+ async function buildClaudeSkillsAddDirFromRoots(
1754
+ skillsSourceRoots: string[],
1755
+ skillFilter: Set<string> | null
1756
+ ) {
1682
1757
  const tempRoot = await mkdtemp(join(tmpdir(), "bopodev-skills-"));
1683
1758
  const skillsTargetDir = join(tempRoot, CLAUDE_SKILLS_DIR);
1684
1759
  await mkdir(skillsTargetDir, { recursive: true });
1685
1760
 
1686
- const entries = await readdir(skillsSourceDir, { withFileTypes: true });
1687
- for (const entry of entries) {
1688
- if (!entry.isDirectory()) {
1689
- continue;
1690
- }
1691
- const source = join(skillsSourceDir, entry.name);
1692
- if (!(await hasSkillManifest(source))) {
1693
- continue;
1761
+ for (const skillsSourceDir of skillsSourceRoots) {
1762
+ const entries = await readdir(skillsSourceDir, { withFileTypes: true });
1763
+ for (const entry of entries) {
1764
+ if (!entry.isDirectory()) {
1765
+ continue;
1766
+ }
1767
+ if (!shouldInjectSkill(entry.name, skillFilter)) {
1768
+ continue;
1769
+ }
1770
+ const source = join(skillsSourceDir, entry.name);
1771
+ if (!(await hasSkillManifest(source))) {
1772
+ continue;
1773
+ }
1774
+ const target = join(skillsTargetDir, entry.name);
1775
+ const existing = await lstat(target).catch(() => null);
1776
+ if (existing) {
1777
+ continue;
1778
+ }
1779
+ await symlink(source, target);
1694
1780
  }
1695
- await symlink(source, join(skillsTargetDir, entry.name));
1696
1781
  }
1697
1782
 
1698
1783
  return tempRoot;