@yansirplus/cli 0.5.17 → 0.5.18

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.
Files changed (52) hide show
  1. package/README.md +12 -6
  2. package/agent-catalog/agentOS/SKILL.md +22 -0
  3. package/agent-catalog/agentOS/references/agent/decision-graph.json +530 -0
  4. package/agent-catalog/agentOS/references/agent/errors.json +497 -0
  5. package/agent-catalog/agentOS/references/agent/invariant-matrix.json +337 -0
  6. package/agent-catalog/agentOS/references/agent/primitives.json +989 -0
  7. package/agent-catalog/agentOS/references/agent/recipes.json +109 -0
  8. package/agent-catalog/agentOS/references/agent/start-here.md +25 -0
  9. package/agent-catalog/agentOS/references/package-map.md +72 -0
  10. package/agent-catalog/agentOS/references/provenance.json +251 -0
  11. package/agent-catalog/agentOS/references/public-api/cli.md +20 -0
  12. package/agent-catalog/agentOS/references/public-api/client.md +88 -0
  13. package/agent-catalog/agentOS/references/public-api/core.md +1817 -0
  14. package/agent-catalog/agentOS/references/public-api/runtime.md +794 -0
  15. package/dist/build/agent-authoring/config.d.ts +20 -5
  16. package/dist/build/agent-authoring/config.js +132 -32
  17. package/dist/build/agent-authoring/manifest-compiler.d.ts +131 -2
  18. package/dist/build/agent-authoring/manifest-compiler.js +630 -8
  19. package/dist/build/agent-authoring/shared.d.ts +2 -0
  20. package/dist/build/agent-authoring/shared.js +2 -0
  21. package/dist/build/agent-authoring/static-target.d.ts +6 -3
  22. package/dist/build/agent-authoring/static-target.js +1807 -286
  23. package/dist/build/agent-authoring.d.ts +3 -3
  24. package/dist/build/agent-authoring.js +1 -1
  25. package/dist/build/build-cli.d.ts +1 -1
  26. package/dist/build/build-cli.js +1614 -26
  27. package/dist/check/algorithmic/client-boundary-checks.mjs +3 -34
  28. package/dist/check/algorithmic/convergence-smoke-checks.mjs +652 -6
  29. package/dist/check/algorithmic/distribution-checks.mjs +8 -7
  30. package/dist/check/algorithmic/package-boundary-checks.mjs +3 -2
  31. package/dist/check/algorithmic/repo-surface-checks.mjs +55 -1
  32. package/dist/check/algorithmic/static-target-checks.mjs +83 -5
  33. package/dist/check/algorithmic-checks.mjs +10 -17
  34. package/dist/check/default-gate.mjs +3 -3
  35. package/dist/check/effect-scan-gate.mjs +121 -0
  36. package/dist/check/package-graph.mjs +2 -32
  37. package/dist/consumer-overlay.mjs +802 -0
  38. package/dist/lib/public-api-model.mjs +19 -0
  39. package/dist/lib/repo-source-files.mjs +26 -0
  40. package/dist/lib/ts-module-loader.mjs +44 -0
  41. package/dist/lib/workspace-manifest.mjs +77 -0
  42. package/dist/main.mjs +151 -21
  43. package/package.json +8 -4
  44. package/dist/check/check-coverage.mjs +0 -231
  45. package/dist/generate/generate-agent-docs.mjs +0 -435
  46. package/dist/generate/generate-carrier-reference.mjs +0 -514
  47. package/dist/generate/generate-docs.mjs +0 -345
  48. package/dist/generate/generate-effect-skill-manifests.mjs +0 -193
  49. package/dist/generate/project-docs-site.mjs +0 -190
  50. package/dist/lib/boundary-rules.mjs +0 -63
  51. package/dist/lib/capability-routes.mjs +0 -354
  52. package/dist/lib/projection-sink.mjs +0 -113
@@ -4,10 +4,24 @@ import { isAuthorityRef } from "@yansirplus/core/effect-claim";
4
4
  import { isMaterialRef } from "@yansirplus/core/material-ref";
5
5
  import { BUILTIN_HANDLER_KINDS } from "@yansirplus/core/runtime-protocol";
6
6
  import { WORKSPACE_TOOL_DEFAULT_DECLARATIONS, } from "@yansirplus/runtime";
7
- import { AUTHORING_DEFAULTS_VERSION, digestText, findFunctionPath, hasFunction, isNonEmptyString, isRecord, isWorkspaceToolName, } from "./shared.js";
7
+ import { cronMinuteExpression } from "@yansirplus/runtime/schedule";
8
+ import { AUTHORING_DEFAULTS_VERSION, digestText, findFunctionPath, GENERATED_LOAD_SKILL_TOOL_NAME, GENERATED_READ_SKILL_FILE_TOOL_NAME, hasFunction, isNonEmptyString, isRecord, isWorkspaceToolName, } from "./shared.js";
9
+ const SKILL_DESCRIPTION_MAX_BYTES = 240;
10
+ const SKILL_BODY_MAX_BYTES = 131_072;
11
+ const SKILL_SUPPORT_FILE_MAX_BYTES = 65_536;
12
+ const SKILL_SUPPORT_FILES_MAX_COUNT = 64;
13
+ const SKILL_SUPPORT_PACKAGE_MAX_BYTES = 262_144;
14
+ const skillNamePattern = /^[a-z][a-z0-9_-]{0,63}$/u;
15
+ const channelNamePattern = /^[a-z][a-z0-9_-]{0,63}$/u;
16
+ const workflowNamePattern = /^[a-z][a-z0-9_-]{0,63}$/u;
17
+ const scheduleSlugPattern = /^[a-z][a-z0-9_-]{0,63}$/u;
18
+ const instructionFragmentIdPattern = /^[a-z][a-z0-9_-]{0,63}$/u;
19
+ const dynamicResolverIdPattern = /^[a-z][a-z0-9_-]{0,63}$/u;
20
+ const textEncoder = new TextEncoder();
21
+ const textDecoder = new TextDecoder("utf-8", { fatal: true });
8
22
  const defaultOrigin = (factKey) => `default:${AUTHORING_DEFAULTS_VERSION}#${factKey}`;
9
23
  export const workspaceManifestMacroOrigin = (factKey) => `macro(workspace@1)#${factKey}`;
10
- const authoredPath = (path) => (path.startsWith("agent/") ? path : `agent/${path}`);
24
+ const authoredPath = (path) => path.startsWith("workflows/") ? path : `agent/${path}`;
11
25
  const authorOrigin = (path, pointer) => `author:${authoredPath(path)}#${pointer}`;
12
26
  const pathOrigin = (path) => `path:${authoredPath(path)}`;
13
27
  const isManifestMapId = (value) => value.length > 0 && !value.includes("/") && value !== "." && value !== "..";
@@ -28,6 +42,60 @@ const parseStringField = (state, path, field, value) => {
28
42
  }
29
43
  return value;
30
44
  };
45
+ const utf8ByteLength = (value) => textEncoder.encode(value).length;
46
+ const isSkillName = (value) => skillNamePattern.test(value);
47
+ const isChannelName = (value) => channelNamePattern.test(value);
48
+ const isWorkflowName = (value) => workflowNamePattern.test(value);
49
+ const isScheduleSlug = (value) => scheduleSlugPattern.test(value);
50
+ const isInstructionFragmentId = (value) => instructionFragmentIdPattern.test(value);
51
+ const isDynamicResolverId = (value) => dynamicResolverIdPattern.test(value);
52
+ const decodeUtf8Bytes = (state, path, field, bytes) => {
53
+ try {
54
+ return textDecoder.decode(bytes);
55
+ }
56
+ catch {
57
+ invalidAuthoredValue(state, path, field, "utf8_required");
58
+ return null;
59
+ }
60
+ };
61
+ const assertNoNulText = (state, path, field, text) => {
62
+ if (!text.includes("\0"))
63
+ return true;
64
+ invalidAuthoredValue(state, path, field, "nul_byte_forbidden");
65
+ return false;
66
+ };
67
+ const assertRegularAuthoredSource = (state, path, sourceKind) => {
68
+ const kind = sourceKind ?? "regular";
69
+ if (kind === "regular")
70
+ return true;
71
+ state.issues.push({
72
+ kind: "unsupported_path",
73
+ path,
74
+ reason: kind === "symlink" ? "symlink_forbidden" : "regular_file_required",
75
+ });
76
+ return false;
77
+ };
78
+ const parseSkillFrontmatterStringField = (state, path, field, value) => {
79
+ const parsed = parseStringField(state, path, field, value);
80
+ if (parsed === null)
81
+ return null;
82
+ const trimmed = parsed.trim();
83
+ if (trimmed.length === 0) {
84
+ invalidAuthoredValue(state, path, field, "non_empty_string_required");
85
+ return null;
86
+ }
87
+ return trimmed;
88
+ };
89
+ const parseSkillDescriptionField = (state, path, value) => {
90
+ const description = parseSkillFrontmatterStringField(state, path, "/frontmatter/description", value);
91
+ if (description === null)
92
+ return null;
93
+ if (utf8ByteLength(description) > SKILL_DESCRIPTION_MAX_BYTES) {
94
+ invalidAuthoredValue(state, path, "/frontmatter/description", "skill_description_too_large");
95
+ return null;
96
+ }
97
+ return description;
98
+ };
31
99
  const parseOptionalStringField = (state, path, field, value) => {
32
100
  if (value === undefined)
33
101
  return undefined;
@@ -133,7 +201,25 @@ const parseOutputSchema = (state, path, value) => {
133
201
  fingerprint: value.fingerprint,
134
202
  };
135
203
  };
136
- const stripAgentPrefix = (path) => path.startsWith("agent/") ? path.slice("agent/".length) : path;
204
+ const agentGrammarRoots = new Set([
205
+ "agent.json",
206
+ "channels",
207
+ "domains",
208
+ "instructions",
209
+ "instructions.md",
210
+ "interactions",
211
+ "materials",
212
+ "skills",
213
+ "schedules",
214
+ "tools",
215
+ ]);
216
+ const stripAgentPrefix = (path) => {
217
+ if (!path.startsWith("agent/"))
218
+ return path;
219
+ const rest = path.slice("agent/".length);
220
+ const root = rest.split("/")[0] ?? "";
221
+ return agentGrammarRoots.has(root) ? rest : path;
222
+ };
137
223
  const normalizePath = (path) => {
138
224
  if (path.length === 0 || path.startsWith("/") || path.includes("\\"))
139
225
  return null;
@@ -233,6 +319,12 @@ const toolAllowedFields = new Set([
233
319
  "effects",
234
320
  "receiptPolicy",
235
321
  ]);
322
+ const dynamicResolverAllowedFields = new Set(["outputs", "id", "name"]);
323
+ const dynamicOutputSlots = [
324
+ "tools",
325
+ "skills",
326
+ "instructions",
327
+ ];
236
328
  const domainAllowedFields = new Set(["bindingRef"]);
237
329
  const interactionAllowedFields = new Set(["bindingRef"]);
238
330
  const agentAllowedFields = new Set([
@@ -241,6 +333,7 @@ const agentAllowedFields = new Set([
241
333
  "scope",
242
334
  "effectAuthorityRef",
243
335
  "handlers",
336
+ "capabilities",
244
337
  "llmRoutes",
245
338
  "tools",
246
339
  "materials",
@@ -380,6 +473,9 @@ const recordAgentJson = (state, path, value) => {
380
473
  invalidAuthoredValue(state, path, "/effectAuthorityRef", "authority_ref_invalid");
381
474
  }
382
475
  const handlers = value.handlers === undefined ? undefined : parseHandlers(state, path, value.handlers);
476
+ const capabilities = value.capabilities === undefined
477
+ ? undefined
478
+ : parseRecordMap(state, path, "/capabilities", value.capabilities, (_capability, child) => parseBindingRefObject(state, path, "/capabilities", child));
383
479
  const llmRoutes = value.llmRoutes === undefined
384
480
  ? undefined
385
481
  : parseRecordMap(state, path, "/llmRoutes", value.llmRoutes, (_route, child) => parseBindingRefObject(state, path, "/llmRoutes", child));
@@ -421,6 +517,11 @@ const recordAgentJson = (state, path, value) => {
421
517
  }
422
518
  if (handlers !== undefined && handlers !== null)
423
519
  putAuthored(state, "/handlers", handlers, authorOrigin(path, "/handlers"));
520
+ if (capabilities !== undefined && capabilities !== null) {
521
+ for (const [capabilityId, ref] of Object.entries(capabilities)) {
522
+ putAuthored(state, `/capabilities/${capabilityId}/bindingRef`, ref.bindingRef, authorOrigin(path, `/capabilities/${capabilityId}/bindingRef`));
523
+ }
524
+ }
424
525
  if (llmRoutes !== undefined && llmRoutes !== null) {
425
526
  for (const [route, ref] of Object.entries(llmRoutes)) {
426
527
  putAuthored(state, `/llmRoutes/${route}/bindingRef`, ref.bindingRef, authorOrigin(path, `/llmRoutes/${route}/bindingRef`));
@@ -503,7 +604,236 @@ const recordJsonFile = (state, path, value) => {
503
604
  state.issues.push({ kind: "unsupported_path", path, reason: "json_path_not_in_grammar" });
504
605
  }
505
606
  };
506
- const recordMarkdownFile = (state, path, text) => {
607
+ const skillIdentityForPath = (path) => {
608
+ const parts = path.split("/");
609
+ if (parts[0] !== "skills")
610
+ return null;
611
+ if (parts.length === 2 && parts[1]?.endsWith(".md")) {
612
+ const name = parts[1].slice(0, -".md".length);
613
+ return isSkillName(name) ? name : null;
614
+ }
615
+ if (parts.length === 3 && parts[2] === "SKILL.md") {
616
+ const name = parts[1] ?? "";
617
+ return isSkillName(name) ? name : null;
618
+ }
619
+ return null;
620
+ };
621
+ const isPackagedSkillPath = (skill) => skill.path === `agent/skills/${skill.name}/SKILL.md`;
622
+ const skillSupportPathForPath = (path) => {
623
+ const parts = path.split("/");
624
+ if (parts[0] !== "skills" || parts.length < 4)
625
+ return null;
626
+ const skillName = parts[1] ?? "";
627
+ if (!isSkillName(skillName))
628
+ return null;
629
+ const root = parts[2];
630
+ if (root !== "references" && root !== "scripts")
631
+ return null;
632
+ return {
633
+ skillName,
634
+ packagePath: [root, ...parts.slice(3)].join("/"),
635
+ };
636
+ };
637
+ const instructionFragmentIdForPath = (path) => {
638
+ const parts = path.split("/");
639
+ if (parts.length !== 2 || parts[0] !== "instructions" || !parts[1]?.endsWith(".md")) {
640
+ return null;
641
+ }
642
+ const fragmentId = parts[1].slice(0, -".md".length);
643
+ return isInstructionFragmentId(fragmentId) ? fragmentId : null;
644
+ };
645
+ const dynamicResolverIdentityForPath = (path) => {
646
+ const parts = path.split("/");
647
+ if (parts.length !== 2 || !parts[1]?.endsWith(".dynamic.ts"))
648
+ return null;
649
+ const slot = parts[0];
650
+ if (slot !== "tools" && slot !== "skills" && slot !== "instructions")
651
+ return null;
652
+ const resolverId = parts[1].slice(0, -".dynamic.ts".length);
653
+ if (!isDynamicResolverId(resolverId))
654
+ return null;
655
+ return { slot, resolverId };
656
+ };
657
+ const stripYamlQuotes = (value) => {
658
+ if (((value.startsWith('"') && value.endsWith('"')) ||
659
+ (value.startsWith("'") && value.endsWith("'"))) &&
660
+ value.length >= 2) {
661
+ return value.slice(1, -1);
662
+ }
663
+ return value;
664
+ };
665
+ const parseSkillFrontmatter = (state, path, text) => {
666
+ const normalized = text.replace(/\r\n?/gu, "\n");
667
+ const lines = normalized.split("\n");
668
+ if (lines[0]?.trim() !== "---") {
669
+ invalidAuthoredValue(state, path, "/frontmatter", "frontmatter_required");
670
+ return null;
671
+ }
672
+ const end = lines.findIndex((line, index) => index > 0 && line.trim() === "---");
673
+ if (end < 0) {
674
+ invalidAuthoredValue(state, path, "/frontmatter", "frontmatter_not_closed");
675
+ return null;
676
+ }
677
+ const issueCount = state.issues.length;
678
+ const fields = {};
679
+ for (let index = 1; index < end; index += 1) {
680
+ const line = lines[index]?.trim() ?? "";
681
+ if (line.length === 0)
682
+ continue;
683
+ const match = /^([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$/u.exec(line);
684
+ if (match === null) {
685
+ invalidAuthoredValue(state, path, `/frontmatter/${index}`, "frontmatter_line_invalid");
686
+ continue;
687
+ }
688
+ const [, key, rawValue] = match;
689
+ if (Object.prototype.hasOwnProperty.call(fields, key)) {
690
+ invalidAuthoredValue(state, path, `/frontmatter/${key}`, "frontmatter_field_duplicate");
691
+ continue;
692
+ }
693
+ fields[key] = stripYamlQuotes(rawValue.trim());
694
+ }
695
+ assertAllowedFields(state, path, fields, new Set(["name", "description"]));
696
+ const name = parseSkillFrontmatterStringField(state, path, "/frontmatter/name", fields.name);
697
+ const description = parseSkillDescriptionField(state, path, fields.description);
698
+ if (name === null || description === null || state.issues.length > issueCount)
699
+ return null;
700
+ return {
701
+ name,
702
+ description,
703
+ body: lines
704
+ .slice(end + 1)
705
+ .join("\n")
706
+ .replace(/^\n/u, "")
707
+ .replace(/\s+$/u, ""),
708
+ };
709
+ };
710
+ const recordInstructionFragmentFile = (state, path, fragmentId, text, sourceKind) => {
711
+ if (!assertRegularAuthoredSource(state, path, sourceKind))
712
+ return;
713
+ if (text.trim().length === 0) {
714
+ invalidAuthoredValue(state, path, "/body", "instruction_fragment_empty");
715
+ return;
716
+ }
717
+ if (!assertNoNulText(state, path, "/body", text))
718
+ return;
719
+ const existing = state.instructionFragments.get(fragmentId);
720
+ if (existing !== undefined) {
721
+ state.issues.push({
722
+ kind: "duplicate_instruction_fragment",
723
+ fragmentId,
724
+ path,
725
+ existingPath: existing.path,
726
+ });
727
+ return;
728
+ }
729
+ state.instructionFragments.set(fragmentId, {
730
+ fragmentId,
731
+ path: authoredPath(path),
732
+ digest: digestText(text),
733
+ text,
734
+ origin: pathOrigin(path),
735
+ });
736
+ };
737
+ const recordSkillFile = (state, path, expectedName, text, sourceKind) => {
738
+ if (!isSkillName(expectedName)) {
739
+ state.issues.push({ kind: "unsupported_path", path, reason: "skill_name_invalid" });
740
+ return;
741
+ }
742
+ if (!assertRegularAuthoredSource(state, path, sourceKind))
743
+ return;
744
+ const parsed = parseSkillFrontmatter(state, path, text);
745
+ if (parsed === null)
746
+ return;
747
+ if (!isSkillName(parsed.name)) {
748
+ invalidAuthoredValue(state, path, "/frontmatter/name", "skill_name_invalid");
749
+ return;
750
+ }
751
+ if (parsed.name !== expectedName) {
752
+ state.issues.push({
753
+ kind: "skill_identity_mismatch",
754
+ path,
755
+ expectedName,
756
+ actualName: parsed.name,
757
+ });
758
+ return;
759
+ }
760
+ if (utf8ByteLength(parsed.body) > SKILL_BODY_MAX_BYTES) {
761
+ invalidAuthoredValue(state, path, "/body", "skill_body_too_large");
762
+ return;
763
+ }
764
+ if (!assertNoNulText(state, path, "/body", parsed.body))
765
+ return;
766
+ const existing = state.skills.get(expectedName);
767
+ if (existing !== undefined) {
768
+ state.issues.push({
769
+ kind: "duplicate_skill",
770
+ name: expectedName,
771
+ path,
772
+ existingPath: existing.path,
773
+ });
774
+ return;
775
+ }
776
+ state.skills.set(expectedName, {
777
+ name: expectedName,
778
+ description: parsed.description,
779
+ path: authoredPath(path),
780
+ digest: digestText(parsed.body),
781
+ text: parsed.body,
782
+ files: [],
783
+ });
784
+ };
785
+ const recordSkillSupportFile = (state, path, supportPath, bytes, sourceKind) => {
786
+ if (!assertRegularAuthoredSource(state, path, sourceKind))
787
+ return;
788
+ if (bytes.byteLength > SKILL_SUPPORT_FILE_MAX_BYTES) {
789
+ invalidAuthoredValue(state, path, "/bytes", "skill_file_too_large");
790
+ return;
791
+ }
792
+ const files = state.skillSupportFiles.get(supportPath.skillName) ?? [];
793
+ if (files.length >= SKILL_SUPPORT_FILES_MAX_COUNT) {
794
+ invalidAuthoredValue(state, path, "/bytes", "skill_package_too_many_files");
795
+ return;
796
+ }
797
+ const currentBytes = state.skillSupportByteTotals.get(supportPath.skillName) ?? 0;
798
+ if (currentBytes + bytes.byteLength > SKILL_SUPPORT_PACKAGE_MAX_BYTES) {
799
+ invalidAuthoredValue(state, path, "/bytes", "skill_package_too_large");
800
+ return;
801
+ }
802
+ const text = decodeUtf8Bytes(state, path, "/bytes", bytes);
803
+ if (text === null || !assertNoNulText(state, path, "/bytes", text))
804
+ return;
805
+ files.push({
806
+ path: supportPath.packagePath,
807
+ digest: digestText(text),
808
+ bytes: bytes.byteLength,
809
+ text,
810
+ });
811
+ state.skillSupportFiles.set(supportPath.skillName, files);
812
+ state.skillSupportByteTotals.set(supportPath.skillName, currentBytes + bytes.byteLength);
813
+ };
814
+ const recordMarkdownFile = (state, path, text, sourceKind) => {
815
+ const skillName = skillIdentityForPath(path);
816
+ if (path.startsWith("skills/")) {
817
+ if (skillName === null) {
818
+ state.issues.push({ kind: "unsupported_path", path, reason: "skill_path_not_in_grammar" });
819
+ return;
820
+ }
821
+ recordSkillFile(state, path, skillName, text, sourceKind);
822
+ return;
823
+ }
824
+ if (path.startsWith("instructions/")) {
825
+ const fragmentId = instructionFragmentIdForPath(path);
826
+ if (fragmentId === null) {
827
+ state.issues.push({
828
+ kind: "unsupported_path",
829
+ path,
830
+ reason: "instruction_fragment_path_not_in_grammar",
831
+ });
832
+ return;
833
+ }
834
+ recordInstructionFragmentFile(state, path, fragmentId, text, sourceKind);
835
+ return;
836
+ }
507
837
  if (path !== "instructions.md") {
508
838
  state.issues.push({ kind: "unsupported_path", path, reason: "markdown_path_not_in_grammar" });
509
839
  return;
@@ -516,20 +846,225 @@ const recordMarkdownFile = (state, path, text) => {
516
846
  };
517
847
  putAuthored(state, "/instructions", instructions, pathOrigin(path));
518
848
  };
849
+ const recordTextFile = (state, path, bytes, sourceKind) => {
850
+ const supportPath = skillSupportPathForPath(path);
851
+ if (supportPath === null) {
852
+ state.issues.push({ kind: "unsupported_path", path, reason: "text_path_not_in_grammar" });
853
+ return;
854
+ }
855
+ recordSkillSupportFile(state, path, supportPath, bytes, sourceKind);
856
+ };
519
857
  const recordToolFile = (state, path, declaration) => {
520
858
  const parts = path.split("/");
521
859
  if (parts.length !== 2 || parts[0] !== "tools" || !parts[1]?.endsWith(".ts")) {
522
860
  state.issues.push({ kind: "unsupported_path", path, reason: "tool_path_not_in_grammar" });
523
861
  return;
524
862
  }
863
+ if (parts[1].endsWith(".dynamic.ts")) {
864
+ state.issues.push({ kind: "unsupported_path", path, reason: "dynamic_path_not_in_grammar" });
865
+ return;
866
+ }
525
867
  const toolId = parts[1].slice(0, -".ts".length);
526
868
  if (toolId.length === 0) {
527
869
  state.issues.push({ kind: "unsupported_path", path, reason: "empty_path_identity" });
528
870
  return;
529
871
  }
872
+ if (toolId === GENERATED_LOAD_SKILL_TOOL_NAME || toolId === GENERATED_READ_SKILL_FILE_TOOL_NAME) {
873
+ state.issues.push({ kind: "reserved_tool_name", path, toolId });
874
+ return;
875
+ }
530
876
  state.toolFilePaths.set(toolId, path);
531
877
  recordToolFacts(state, toolId, path, declaration ?? {}, (field) => authorOrigin(path, field));
532
878
  };
879
+ const parseDynamicOutputs = (state, path, slot, value) => {
880
+ if (!isRecord(value)) {
881
+ invalidAuthoredValue(state, path, "/outputs", "object_required");
882
+ return null;
883
+ }
884
+ for (const outputSlot of Object.keys(value)) {
885
+ if (!dynamicOutputSlots.includes(outputSlot)) {
886
+ state.issues.push({ kind: "unknown_field", path, field: `/outputs/${outputSlot}` });
887
+ continue;
888
+ }
889
+ if (outputSlot !== slot) {
890
+ state.issues.push({
891
+ kind: "dynamic_resolver_cross_slot_output",
892
+ path,
893
+ slot,
894
+ outputSlot: outputSlot,
895
+ });
896
+ }
897
+ }
898
+ if (!Object.prototype.hasOwnProperty.call(value, slot)) {
899
+ invalidAuthoredValue(state, path, `/outputs/${slot}`, "dynamic_resolver_output_required");
900
+ return null;
901
+ }
902
+ return parseStringArrayField(state, path, `/outputs/${slot}`, value[slot]);
903
+ };
904
+ const recordDynamicResolverFile = (state, path, declaration, sourceKind) => {
905
+ const identity = dynamicResolverIdentityForPath(path);
906
+ if (identity === null) {
907
+ state.issues.push({ kind: "unsupported_path", path, reason: "dynamic_path_not_in_grammar" });
908
+ return;
909
+ }
910
+ if (!assertRegularAuthoredSource(state, path, sourceKind))
911
+ return;
912
+ if (!isRecord(declaration)) {
913
+ invalidAuthoredValue(state, path, "/", "dynamic_resolver_declaration_object_required");
914
+ return;
915
+ }
916
+ const localIssueCount = state.issues.length;
917
+ assertNoPathIdentityFields(state, path, declaration);
918
+ assertAllowedFields(state, path, declaration, dynamicResolverAllowedFields);
919
+ assertNoRuntimeFactFields(state, path, declaration);
920
+ const outputs = parseDynamicOutputs(state, path, identity.slot, declaration.outputs);
921
+ if (outputs === null || state.issues.length > localIssueCount)
922
+ return;
923
+ const key = `${identity.slot}/${identity.resolverId}`;
924
+ const existing = state.dynamicResolvers.get(key);
925
+ if (existing !== undefined) {
926
+ state.issues.push({
927
+ kind: "duplicate_dynamic_resolver",
928
+ slot: identity.slot,
929
+ resolverId: identity.resolverId,
930
+ path,
931
+ existingPath: existing.path,
932
+ });
933
+ return;
934
+ }
935
+ state.dynamicResolvers.set(key, {
936
+ resolverId: identity.resolverId,
937
+ slot: identity.slot,
938
+ path: authoredPath(path),
939
+ outputs,
940
+ origin: pathOrigin(path),
941
+ });
942
+ };
943
+ const recordChannelFile = (state, path, sourceKind) => {
944
+ const parts = path.split("/");
945
+ if (parts.length !== 2 || parts[0] !== "channels" || !parts[1]?.endsWith(".ts")) {
946
+ state.issues.push({ kind: "unsupported_path", path, reason: "channel_path_not_in_grammar" });
947
+ return;
948
+ }
949
+ const name = parts[1].slice(0, -".ts".length);
950
+ if (name.length === 0) {
951
+ state.issues.push({ kind: "unsupported_path", path, reason: "empty_path_identity" });
952
+ return;
953
+ }
954
+ if (!isChannelName(name)) {
955
+ state.issues.push({ kind: "unsupported_path", path, reason: "channel_name_invalid" });
956
+ return;
957
+ }
958
+ if (!assertRegularAuthoredSource(state, path, sourceKind))
959
+ return;
960
+ state.channels.set(name, {
961
+ name,
962
+ path: authoredPath(path),
963
+ origin: pathOrigin(path),
964
+ });
965
+ };
966
+ const recordWorkflowFile = (state, path, sourceKind) => {
967
+ const parts = path.split("/");
968
+ if (parts.length !== 2 || parts[0] !== "workflows" || !parts[1]?.endsWith(".ts")) {
969
+ state.issues.push({ kind: "unsupported_path", path, reason: "workflow_path_not_in_grammar" });
970
+ return;
971
+ }
972
+ const name = parts[1].slice(0, -".ts".length);
973
+ if (name.length === 0) {
974
+ state.issues.push({ kind: "unsupported_path", path, reason: "empty_path_identity" });
975
+ return;
976
+ }
977
+ if (!isWorkflowName(name)) {
978
+ state.issues.push({ kind: "unsupported_path", path, reason: "workflow_name_invalid" });
979
+ return;
980
+ }
981
+ if (!assertRegularAuthoredSource(state, path, sourceKind))
982
+ return;
983
+ state.workflows.set(name, {
984
+ name,
985
+ path: authoredPath(path),
986
+ origin: pathOrigin(path),
987
+ });
988
+ };
989
+ const scheduleAllowedFields = new Set(["cron", "handler", "id", "name", "kind"]);
990
+ const scheduleIdFromPath = (state, path) => {
991
+ const parts = path.split("/");
992
+ if (parts.length < 2 || parts[0] !== "schedules" || !parts[parts.length - 1]?.endsWith(".ts")) {
993
+ state.issues.push({ kind: "unsupported_path", path, reason: "schedule_path_not_in_grammar" });
994
+ return null;
995
+ }
996
+ const slugs = [...parts.slice(1, -1), parts[parts.length - 1].slice(0, -".ts".length)];
997
+ if (slugs.some((slug) => slug.length === 0)) {
998
+ state.issues.push({ kind: "unsupported_path", path, reason: "empty_path_identity" });
999
+ return null;
1000
+ }
1001
+ if (slugs.includes("subagents")) {
1002
+ state.issues.push({
1003
+ kind: "unsupported_path",
1004
+ path,
1005
+ reason: "subagent_schedule_directory_forbidden",
1006
+ });
1007
+ return null;
1008
+ }
1009
+ const invalid = slugs.find((slug) => !isScheduleSlug(slug));
1010
+ if (invalid !== undefined) {
1011
+ state.issues.push({ kind: "unsupported_path", path, reason: "schedule_slug_invalid" });
1012
+ return null;
1013
+ }
1014
+ return slugs.join("/");
1015
+ };
1016
+ const recordScheduleFile = (state, path, declaration, sourceKind) => {
1017
+ const scheduleId = scheduleIdFromPath(state, path);
1018
+ if (scheduleId === null)
1019
+ return;
1020
+ if (!assertRegularAuthoredSource(state, path, sourceKind))
1021
+ return;
1022
+ if (!isRecord(declaration)) {
1023
+ invalidAuthoredValue(state, path, "/", "schedule_declaration_object_required");
1024
+ return;
1025
+ }
1026
+ const declarationRecord = declaration;
1027
+ const localIssueCount = state.issues.length;
1028
+ assertNoPathIdentityFields(state, path, declarationRecord);
1029
+ if (Object.prototype.hasOwnProperty.call(declarationRecord, "kind")) {
1030
+ state.issues.push({ kind: "identity_field_forbidden", path, field: "kind" });
1031
+ }
1032
+ assertAllowedFields(state, path, declarationRecord, scheduleAllowedFields);
1033
+ assertNoRuntimeFactFields(state, path, declarationRecord);
1034
+ if (state.issues.length > localIssueCount)
1035
+ return;
1036
+ if (typeof declaration.cron !== "string") {
1037
+ invalidAuthoredValue(state, path, "/cron", "cron_string_required");
1038
+ return;
1039
+ }
1040
+ if (typeof declaration.handler !== "function") {
1041
+ invalidAuthoredValue(state, path, "/handler", "schedule_handler_function_required");
1042
+ return;
1043
+ }
1044
+ let cron;
1045
+ try {
1046
+ cron = cronMinuteExpression(declaration.cron);
1047
+ }
1048
+ catch {
1049
+ invalidAuthoredValue(state, path, "/cron", "cron_minute_expression_invalid");
1050
+ return;
1051
+ }
1052
+ if (state.schedules.has(scheduleId)) {
1053
+ const existing = state.schedules.get(scheduleId);
1054
+ state.issues.push({
1055
+ kind: "duplicate_path",
1056
+ path: authoredPath(path),
1057
+ existingPath: existing?.path ?? authoredPath(path),
1058
+ });
1059
+ return;
1060
+ }
1061
+ state.schedules.set(scheduleId, {
1062
+ scheduleId,
1063
+ cron,
1064
+ path: authoredPath(path),
1065
+ origin: pathOrigin(path),
1066
+ });
1067
+ };
533
1068
  const applyDefaults = (state) => {
534
1069
  putDefault(state, "/agentId", "agent");
535
1070
  const agentId = state.facts.get("/agentId")?.value;
@@ -629,6 +1164,10 @@ const buildManifest = (state) => {
629
1164
  const bindingRef = factValue(state, `/llmRoutes/${id}/bindingRef`);
630
1165
  return bindingRef === undefined ? null : { bindingRef };
631
1166
  });
1167
+ const capabilities = collectRecord(state, "/capabilities/", (id) => {
1168
+ const bindingRef = factValue(state, `/capabilities/${id}/bindingRef`);
1169
+ return bindingRef === undefined ? null : { bindingRef };
1170
+ });
632
1171
  const tools = collectRecord(state, "/tools/", (id) => collectTool(state, id));
633
1172
  const materials = collectRecord(state, "/materials/", (id) => factValue(state, `/materials/${id}`) ?? null);
634
1173
  const executionDomains = collectRecord(state, "/executionDomains/", (id) => {
@@ -649,6 +1188,7 @@ const buildManifest = (state) => {
649
1188
  handlers: factValue(state, "/handlers") ?? [],
650
1189
  ...(version === undefined ? {} : { version }),
651
1190
  ...(instructions === undefined ? {} : { instructions }),
1191
+ ...(capabilities === undefined ? {} : { capabilities }),
652
1192
  ...(llmRoutes === undefined ? {} : { llmRoutes }),
653
1193
  ...(tools === undefined ? {} : { tools }),
654
1194
  ...(materials === undefined ? {} : { materials }),
@@ -678,10 +1218,61 @@ const buildToolFilePaths = (state) => {
678
1218
  }
679
1219
  return paths;
680
1220
  };
1221
+ const buildChannels = (state) => [...state.channels.values()].sort((left, right) => left.name.localeCompare(right.name));
1222
+ const buildWorkflows = (state) => [...state.workflows.values()].sort((left, right) => left.name.localeCompare(right.name));
1223
+ const buildSchedules = (state) => [...state.schedules.values()].sort((left, right) => left.scheduleId.localeCompare(right.scheduleId));
1224
+ const buildInstructionFragments = (state) => [...state.instructionFragments.values()].sort((left, right) => left.fragmentId.localeCompare(right.fragmentId));
1225
+ const buildDynamicResolvers = (state) => [...state.dynamicResolvers.values()].sort((left, right) => left.slot.localeCompare(right.slot) || left.resolverId.localeCompare(right.resolverId));
1226
+ const validateSkillSupportFiles = (state) => {
1227
+ for (const [skillName, files] of state.skillSupportFiles.entries()) {
1228
+ const skill = state.skills.get(skillName);
1229
+ if (skill !== undefined && isPackagedSkillPath(skill))
1230
+ continue;
1231
+ for (const file of files) {
1232
+ state.issues.push({
1233
+ kind: "unsupported_path",
1234
+ path: `skills/${skillName}/${file.path}`,
1235
+ reason: "skill_support_requires_packaged_skill",
1236
+ });
1237
+ }
1238
+ }
1239
+ };
1240
+ const dynamicResolverTargets = (state, slot) => {
1241
+ switch (slot) {
1242
+ case "tools":
1243
+ return state.toolIds;
1244
+ case "skills":
1245
+ return new Set(state.skills.keys());
1246
+ case "instructions":
1247
+ return new Set(state.instructionFragments.keys());
1248
+ }
1249
+ };
1250
+ const validateDynamicResolvers = (state) => {
1251
+ for (const resolver of state.dynamicResolvers.values()) {
1252
+ const targetIds = dynamicResolverTargets(state, resolver.slot);
1253
+ for (const targetId of resolver.outputs) {
1254
+ if (targetIds.has(targetId))
1255
+ continue;
1256
+ state.issues.push({
1257
+ kind: "dynamic_resolver_unknown_target",
1258
+ path: resolver.path,
1259
+ slot: resolver.slot,
1260
+ targetId,
1261
+ });
1262
+ }
1263
+ }
1264
+ };
1265
+ const buildSkills = (state) => [...state.skills.values()]
1266
+ .map((skill) => ({
1267
+ ...skill,
1268
+ files: [...(state.skillSupportFiles.get(skill.name) ?? [])].sort((left, right) => left.path.localeCompare(right.path)),
1269
+ }))
1270
+ .sort((left, right) => left.name.localeCompare(right.name));
681
1271
  /**
682
- * Compile an authored `agent/` tree into one normalized manifest plus
683
- * provenance. This is the app-author entrypoint; runtime facts and provider
684
- * material are rejected before they can become manifest truth.
1272
+ * Compile authored `agent/` and `workflows/` roots into one normalized
1273
+ * manifest plus authoring facts. This is the app-author entrypoint; runtime
1274
+ * facts and provider material are rejected before they can become manifest
1275
+ * truth.
685
1276
  *
686
1277
  * @agentosPrimitive primitive.agent-authoring.compileAgentTree
687
1278
  * @agentosInvariant invariant.docs.agent-projection
@@ -695,6 +1286,14 @@ export const compileAgentTree = (tree) => {
695
1286
  pathKeys: new Map(),
696
1287
  toolIds: new Set(),
697
1288
  toolFilePaths: new Map(),
1289
+ channels: new Map(),
1290
+ workflows: new Map(),
1291
+ schedules: new Map(),
1292
+ skills: new Map(),
1293
+ skillSupportFiles: new Map(),
1294
+ skillSupportByteTotals: new Map(),
1295
+ instructionFragments: new Map(),
1296
+ dynamicResolvers: new Map(),
698
1297
  workspaceToolControls: new Map(),
699
1298
  };
700
1299
  for (const file of tree.files) {
@@ -703,7 +1302,10 @@ export const compileAgentTree = (tree) => {
703
1302
  continue;
704
1303
  switch (file.kind) {
705
1304
  case "markdown":
706
- recordMarkdownFile(state, path, file.text);
1305
+ recordMarkdownFile(state, path, file.text, file.sourceKind);
1306
+ break;
1307
+ case "text":
1308
+ recordTextFile(state, path, file.bytes, file.sourceKind);
707
1309
  break;
708
1310
  case "json":
709
1311
  recordJsonFile(state, path, file.value);
@@ -711,8 +1313,22 @@ export const compileAgentTree = (tree) => {
711
1313
  case "tool":
712
1314
  recordToolFile(state, path, file.declaration);
713
1315
  break;
1316
+ case "dynamic":
1317
+ recordDynamicResolverFile(state, path, file.declaration, file.sourceKind);
1318
+ break;
1319
+ case "channel":
1320
+ recordChannelFile(state, path, file.sourceKind);
1321
+ break;
1322
+ case "workflow":
1323
+ recordWorkflowFile(state, path, file.sourceKind);
1324
+ break;
1325
+ case "schedule":
1326
+ recordScheduleFile(state, path, file.declaration, file.sourceKind);
1327
+ break;
714
1328
  }
715
1329
  }
1330
+ validateSkillSupportFiles(state);
1331
+ validateDynamicResolvers(state);
716
1332
  applyDefaults(state);
717
1333
  enforceL0(state);
718
1334
  if (state.issues.length > 0)
@@ -732,6 +1348,12 @@ export const compileAgentTree = (tree) => {
732
1348
  provenance: buildProvenance(state),
733
1349
  workspaceToolControls: buildWorkspaceToolControls(state),
734
1350
  toolFilePaths: buildToolFilePaths(state),
1351
+ channels: buildChannels(state),
1352
+ workflows: buildWorkflows(state),
1353
+ schedules: buildSchedules(state),
1354
+ skills: buildSkills(state),
1355
+ instructionFragments: buildInstructionFragments(state),
1356
+ dynamicResolvers: buildDynamicResolvers(state),
735
1357
  },
736
1358
  };
737
1359
  };