@dot-agent/compiler 0.1.0 → 0.5.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-IT25ILTQ.js → chunk-JIYQMCQQ.js} +30 -4
- package/dist/chunk-JIYQMCQQ.js.map +1 -0
- package/dist/{core-DLZGpzev.d.cts → core-b-7c6X6q.d.cts} +29 -70
- package/dist/{core-DLZGpzev.d.ts → core-b-7c6X6q.d.ts} +29 -70
- package/dist/core.cjs +31 -5
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +2 -1
- package/dist/core.d.ts +2 -1
- package/dist/core.js +7 -3
- package/dist/index.cjs +286 -66
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -5
- package/dist/index.d.ts +14 -5
- package/dist/index.js +263 -65
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
- package/dist/chunk-IT25ILTQ.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
COMPILER_VERSION,
|
|
2
3
|
aboutmeToJson,
|
|
3
4
|
buildAboutme,
|
|
4
5
|
buildId,
|
|
@@ -7,13 +8,15 @@ import {
|
|
|
7
8
|
extractFiles,
|
|
8
9
|
extractName,
|
|
9
10
|
parseAboutme,
|
|
10
|
-
parseId
|
|
11
|
-
|
|
11
|
+
parseId,
|
|
12
|
+
validateMagicBytes,
|
|
13
|
+
validateZipBomb
|
|
14
|
+
} from "./chunk-JIYQMCQQ.js";
|
|
12
15
|
|
|
13
16
|
// src/parser.ts
|
|
14
17
|
import { createRequire } from "module";
|
|
15
18
|
import { Parser, Language } from "web-tree-sitter";
|
|
16
|
-
import bpInit,
|
|
19
|
+
import { init as bpInit, parse_behavior as bpParseBehavior, parse_description as bpParseDescription, get_graph } from "@dot-agent/parser-dsl";
|
|
17
20
|
var require2 = createRequire(import.meta.url);
|
|
18
21
|
var { descriptionWasmPath, behaviorWasmPath } = require2("@dot-agent/tree-sitter");
|
|
19
22
|
var _initialized = false;
|
|
@@ -177,6 +180,18 @@ async function lintDescription(text, file = "agent.description") {
|
|
|
177
180
|
message: "No domain declared \u2014 agent will be packaged as unknown/name. Interoperability between runtimes is not guaranteed."
|
|
178
181
|
});
|
|
179
182
|
}
|
|
183
|
+
const behaviorBlocks = nodesOfType(tree, "behavior_block");
|
|
184
|
+
if (behaviorBlocks.length > 1) {
|
|
185
|
+
const { line, col } = nodePosition(behaviorBlocks[1]);
|
|
186
|
+
messages.push({
|
|
187
|
+
file,
|
|
188
|
+
line,
|
|
189
|
+
col,
|
|
190
|
+
severity: "error",
|
|
191
|
+
code: "E017",
|
|
192
|
+
message: "Multiple 'behavior' declarations \u2014 only one is allowed. To combine multiple files, use 'merge' in your .behavior file."
|
|
193
|
+
});
|
|
194
|
+
}
|
|
180
195
|
const declaredTypes = new Set(
|
|
181
196
|
nodesOfType(tree, "type_decl").map((n) => n.childForFieldName("name")?.text).filter(Boolean)
|
|
182
197
|
);
|
|
@@ -275,7 +290,7 @@ function collectMergedStates(tree, docDir, states, visited = /* @__PURE__ */ new
|
|
|
275
290
|
collectMergedStates(mergedTree, dirname(absPath), states, visited);
|
|
276
291
|
}
|
|
277
292
|
}
|
|
278
|
-
async function lintBehavior(text, file = "agent.behavior", docPath) {
|
|
293
|
+
async function lintBehavior(text, file = "agent.behavior", docPath, consolidated = false, externalStates, mergedText) {
|
|
279
294
|
await initBehaviorParser();
|
|
280
295
|
if (!text.endsWith("\n")) text = text + "\n";
|
|
281
296
|
const tree = await parse("behavior", text);
|
|
@@ -296,7 +311,7 @@ async function lintBehavior(text, file = "agent.behavior", docPath) {
|
|
|
296
311
|
col,
|
|
297
312
|
severity: "warning",
|
|
298
313
|
code: "W013",
|
|
299
|
-
message: `'interact' without 'goal'
|
|
314
|
+
message: `'interact' without 'goal'. Add 'goal "..."' before 'interact'.`
|
|
300
315
|
});
|
|
301
316
|
}
|
|
302
317
|
if (hasGoal && !hasInteract) {
|
|
@@ -307,7 +322,7 @@ async function lintBehavior(text, file = "agent.behavior", docPath) {
|
|
|
307
322
|
col,
|
|
308
323
|
severity: "warning",
|
|
309
324
|
code: "W012",
|
|
310
|
-
message: `'goal' is only valid in an oriented state. Add 'interact' or remove 'goal'
|
|
325
|
+
message: `'goal' is only valid in an oriented state. Add 'interact' or remove 'goal'.`
|
|
311
326
|
});
|
|
312
327
|
}
|
|
313
328
|
if (hasInteract && intentHandlers.length === 0) {
|
|
@@ -438,7 +453,7 @@ async function lintBehavior(text, file = "agent.behavior", docPath) {
|
|
|
438
453
|
col,
|
|
439
454
|
severity: "warning",
|
|
440
455
|
code: "W002",
|
|
441
|
-
message: `Text block exceeds 280 characters (${content.length}). Consider using '
|
|
456
|
+
message: `Text block exceeds 280 characters (${content.length}). Consider using 'guide' to load long orientation text from an external text file.`
|
|
442
457
|
});
|
|
443
458
|
}
|
|
444
459
|
}
|
|
@@ -457,7 +472,7 @@ async function lintBehavior(text, file = "agent.behavior", docPath) {
|
|
|
457
472
|
col,
|
|
458
473
|
severity: "warning",
|
|
459
474
|
code: "W010",
|
|
460
|
-
message: `'guide' text in state '${stateName}' is ${content.length} characters
|
|
475
|
+
message: `'guide' text in state '${stateName}' is ${content.length} characters. Consider using an external file.`
|
|
461
476
|
});
|
|
462
477
|
}
|
|
463
478
|
}
|
|
@@ -470,6 +485,9 @@ async function lintBehavior(text, file = "agent.behavior", docPath) {
|
|
|
470
485
|
} catch {
|
|
471
486
|
}
|
|
472
487
|
}
|
|
488
|
+
if (externalStates) {
|
|
489
|
+
for (const name of externalStates) definedStates.add(name);
|
|
490
|
+
}
|
|
473
491
|
const stateNameList = Array.from(definedStates);
|
|
474
492
|
for (const transitionNode of nodesOfType(tree, "transition_stmt")) {
|
|
475
493
|
const stateNode = transitionNode.childForFieldName("state");
|
|
@@ -521,7 +539,9 @@ async function lintBehavior(text, file = "agent.behavior", docPath) {
|
|
|
521
539
|
return messages;
|
|
522
540
|
}
|
|
523
541
|
const fsm = fsmResult.ok;
|
|
524
|
-
const
|
|
542
|
+
const wholeFsm = mergedText !== void 0 ? parseBehaviorFile(mergedText).ok : null;
|
|
543
|
+
const reachabilityStates = wholeFsm?.states ?? fsm.states;
|
|
544
|
+
const allTargets = new Set(collectTransitionTargets(reachabilityStates.flatMap((s) => s.body)));
|
|
525
545
|
for (const state of fsm.states) {
|
|
526
546
|
const hasOutgoing = collectTransitionTargets(state.body).length > 0;
|
|
527
547
|
const hasIncoming = allTargets.has(state.name);
|
|
@@ -551,6 +571,49 @@ async function lintBehavior(text, file = "agent.behavior", docPath) {
|
|
|
551
571
|
});
|
|
552
572
|
}
|
|
553
573
|
}
|
|
574
|
+
if (consolidated) {
|
|
575
|
+
const wholeTreeFsm = wholeFsm ?? fsm;
|
|
576
|
+
const seenStates = /* @__PURE__ */ new Set();
|
|
577
|
+
for (const state of wholeTreeFsm.states) {
|
|
578
|
+
if (seenStates.has(state.name)) {
|
|
579
|
+
messages.push({
|
|
580
|
+
file,
|
|
581
|
+
line: 1,
|
|
582
|
+
col: 1,
|
|
583
|
+
severity: "error",
|
|
584
|
+
code: "E015",
|
|
585
|
+
message: `Duplicate state '${state.name}' across merged files \u2014 each state name must be unique in the consolidated FSM.`
|
|
586
|
+
});
|
|
587
|
+
} else {
|
|
588
|
+
seenStates.add(state.name);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
if (!wholeTreeFsm.states.some((s) => s.name === "init")) {
|
|
592
|
+
messages.push({
|
|
593
|
+
file,
|
|
594
|
+
line: 1,
|
|
595
|
+
col: 1,
|
|
596
|
+
severity: "error",
|
|
597
|
+
code: "E016",
|
|
598
|
+
message: `Required 'init' state is missing. Add a state named 'init' as the entry point of your behavior.`
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
const seenTriggers = /* @__PURE__ */ new Set();
|
|
602
|
+
for (const trigger of wholeTreeFsm.global_triggers) {
|
|
603
|
+
if (seenTriggers.has(trigger.event)) {
|
|
604
|
+
messages.push({
|
|
605
|
+
file,
|
|
606
|
+
line: 1,
|
|
607
|
+
col: 1,
|
|
608
|
+
severity: "warning",
|
|
609
|
+
code: "W014",
|
|
610
|
+
message: `Duplicate global trigger '${trigger.event}' across merged files \u2014 only the last definition takes effect.`
|
|
611
|
+
});
|
|
612
|
+
} else {
|
|
613
|
+
seenTriggers.add(trigger.event);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
554
617
|
return messages;
|
|
555
618
|
}
|
|
556
619
|
async function createLinter() {
|
|
@@ -560,37 +623,19 @@ async function createLinter() {
|
|
|
560
623
|
// src/zip.ts
|
|
561
624
|
import JSZip from "jszip";
|
|
562
625
|
import { readFile, writeFile } from "fs/promises";
|
|
563
|
-
var MAX_ZIP_SIZE = 500 * 1024 * 1024;
|
|
564
|
-
var MAX_COMPRESSION_RATIO = 100;
|
|
565
626
|
async function readZip(filePath) {
|
|
566
627
|
const data = await readFile(filePath);
|
|
567
628
|
return JSZip.loadAsync(data);
|
|
568
629
|
}
|
|
569
|
-
async function
|
|
630
|
+
async function validateZipBomb2(filePath) {
|
|
570
631
|
const data = await readFile(filePath);
|
|
571
632
|
const zip = await JSZip.loadAsync(data);
|
|
572
|
-
|
|
573
|
-
zip.forEach((_relativePath, file) => {
|
|
574
|
-
if (!file.dir) {
|
|
575
|
-
totalUncompressed += file._data?.uncompressedSize || 0;
|
|
576
|
-
}
|
|
577
|
-
});
|
|
578
|
-
const compressedSize = data.length;
|
|
579
|
-
const ratio = totalUncompressed / compressedSize;
|
|
580
|
-
if (totalUncompressed > MAX_ZIP_SIZE) {
|
|
581
|
-
throw new Error(`ZIP uncompressed size exceeds 500MB limit: ${totalUncompressed}`);
|
|
582
|
-
}
|
|
583
|
-
if (ratio > MAX_COMPRESSION_RATIO) {
|
|
584
|
-
throw new Error(`ZIP compression ratio exceeds 100x limit: ${ratio.toFixed(1)}x`);
|
|
585
|
-
}
|
|
633
|
+
validateZipBomb(zip, data.length);
|
|
586
634
|
return true;
|
|
587
635
|
}
|
|
588
|
-
async function
|
|
636
|
+
async function validateMagicBytes2(filePath) {
|
|
589
637
|
const data = await readFile(filePath);
|
|
590
|
-
|
|
591
|
-
if (!(magic[0] === 80 && magic[1] === 75 && magic[2] === 3 && magic[3] === 4)) {
|
|
592
|
-
throw new Error("File is not a valid ZIP (invalid magic bytes)");
|
|
593
|
-
}
|
|
638
|
+
validateMagicBytes(data);
|
|
594
639
|
return true;
|
|
595
640
|
}
|
|
596
641
|
async function writeZip(zip, outPath) {
|
|
@@ -600,7 +645,7 @@ async function writeZip(zip, outPath) {
|
|
|
600
645
|
|
|
601
646
|
// src/pack.ts
|
|
602
647
|
import { readFile as readFile2, stat, readdir } from "fs/promises";
|
|
603
|
-
import { join, basename } from "path";
|
|
648
|
+
import { join, basename, resolve as resolve2, relative, isAbsolute, normalize, dirname as dirname2 } from "path";
|
|
604
649
|
import { execSync } from "child_process";
|
|
605
650
|
import { createHash } from "crypto";
|
|
606
651
|
import JSZip2 from "jszip";
|
|
@@ -682,7 +727,81 @@ async function resolveCommit(explicit) {
|
|
|
682
727
|
if (explicit) return explicit;
|
|
683
728
|
return gitRevParseShort() ?? void 0;
|
|
684
729
|
}
|
|
685
|
-
|
|
730
|
+
function isExternalPath(agentRoot, declaredPath) {
|
|
731
|
+
if (isAbsolute(declaredPath)) return true;
|
|
732
|
+
const normRoot = normalize(resolve2(agentRoot));
|
|
733
|
+
const resolved = resolve2(normRoot, declaredPath);
|
|
734
|
+
return relative(normRoot, resolved).startsWith("..");
|
|
735
|
+
}
|
|
736
|
+
async function discoverDescriptionFile(dir, explicit) {
|
|
737
|
+
if (explicit) {
|
|
738
|
+
try {
|
|
739
|
+
await stat(join(dir, explicit));
|
|
740
|
+
return explicit;
|
|
741
|
+
} catch {
|
|
742
|
+
throw new Error(`E003: Description file '${explicit}' not found in ${dir}`);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
const entries = await readdir(dir);
|
|
746
|
+
const descFiles = entries.filter((f) => f.endsWith(".description"));
|
|
747
|
+
if (descFiles.length === 0) {
|
|
748
|
+
throw new Error(`E003: No .description file found in ${dir}`);
|
|
749
|
+
}
|
|
750
|
+
if (descFiles.length > 1) {
|
|
751
|
+
throw new Error(`E003: Multiple .description files found: ${descFiles.join(", ")} \u2014 use PackOptions.description to specify one`);
|
|
752
|
+
}
|
|
753
|
+
const name = descFiles[0];
|
|
754
|
+
if (name !== "agent.description") {
|
|
755
|
+
console.info(`[dot-agent] using description file: ${name}`);
|
|
756
|
+
}
|
|
757
|
+
return name;
|
|
758
|
+
}
|
|
759
|
+
async function consolidate(agentRoot, entryFile) {
|
|
760
|
+
await initBehaviorParser();
|
|
761
|
+
const normRoot = normalize(resolve2(agentRoot));
|
|
762
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
763
|
+
const visited = /* @__PURE__ */ new Set();
|
|
764
|
+
const order = [];
|
|
765
|
+
const textCache = /* @__PURE__ */ new Map();
|
|
766
|
+
async function dfs(relPath) {
|
|
767
|
+
const absPath = resolve2(normRoot, relPath);
|
|
768
|
+
if (visiting.has(absPath)) {
|
|
769
|
+
throw new Error(`E013: Circular merge dependency involving '${relPath}'`);
|
|
770
|
+
}
|
|
771
|
+
if (visited.has(absPath)) return;
|
|
772
|
+
visiting.add(absPath);
|
|
773
|
+
let fileText;
|
|
774
|
+
try {
|
|
775
|
+
fileText = await readFile2(absPath, "utf-8");
|
|
776
|
+
} catch {
|
|
777
|
+
throw new Error(`E012: Merge target not found: '${relPath}'`);
|
|
778
|
+
}
|
|
779
|
+
textCache.set(absPath, fileText);
|
|
780
|
+
const result = parseBehaviorFile(fileText);
|
|
781
|
+
const merges = result.ok?.merges ?? [];
|
|
782
|
+
for (const mergePath of merges) {
|
|
783
|
+
if (isAbsolute(mergePath)) {
|
|
784
|
+
throw new Error(`E014: Merge path '${mergePath}' in '${relPath}' is absolute \u2014 only paths within the agent root are allowed`);
|
|
785
|
+
}
|
|
786
|
+
const mergeAbs = resolve2(dirname2(absPath), mergePath);
|
|
787
|
+
const mergeRel = relative(normRoot, mergeAbs);
|
|
788
|
+
if (mergeRel.startsWith("..")) {
|
|
789
|
+
throw new Error(`E014: Merge path '${mergePath}' in '${relPath}' escapes the agent root`);
|
|
790
|
+
}
|
|
791
|
+
await dfs(mergeRel);
|
|
792
|
+
}
|
|
793
|
+
visiting.delete(absPath);
|
|
794
|
+
visited.add(absPath);
|
|
795
|
+
order.push(relPath);
|
|
796
|
+
}
|
|
797
|
+
await dfs(entryFile);
|
|
798
|
+
const texts = order.map((relPath) => textCache.get(resolve2(normRoot, relPath)));
|
|
799
|
+
return {
|
|
800
|
+
mergedText: texts.join("\n"),
|
|
801
|
+
mergeSources: order
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
async function collectFiles(dir, descriptionFile, mergedBehaviorText, mergeSources) {
|
|
686
805
|
const files = /* @__PURE__ */ new Map();
|
|
687
806
|
async function walk(subdir, prefix = "") {
|
|
688
807
|
try {
|
|
@@ -701,13 +820,16 @@ async function collectFiles(dir) {
|
|
|
701
820
|
} catch {
|
|
702
821
|
}
|
|
703
822
|
}
|
|
704
|
-
files.set(
|
|
705
|
-
|
|
823
|
+
files.set(descriptionFile, await readFile2(join(dir, descriptionFile), "utf-8"));
|
|
824
|
+
const flatBehaviorText = mergedBehaviorText.replace(/^\s*merge\s+"[^"]*"\s*$/gm, "");
|
|
825
|
+
files.set("agent.behavior", flatBehaviorText);
|
|
826
|
+
for (const relPath of mergeSources) {
|
|
827
|
+
files.set(`behaviors/${relPath}`, await readFile2(join(dir, relPath), "utf-8"));
|
|
828
|
+
}
|
|
706
829
|
try {
|
|
707
830
|
files.set("SOUL.md", await readFile2(join(dir, "SOUL.md"), "utf-8"));
|
|
708
831
|
} catch {
|
|
709
832
|
}
|
|
710
|
-
await walk(join(dir, "behaviors"), "behaviors");
|
|
711
833
|
await walk(join(dir, "guides"), "guides");
|
|
712
834
|
await walk(join(dir, "knowledge"), "knowledge");
|
|
713
835
|
return files;
|
|
@@ -715,29 +837,14 @@ async function collectFiles(dir) {
|
|
|
715
837
|
async function pack(options = {}) {
|
|
716
838
|
const dir = options.dir ?? process.cwd();
|
|
717
839
|
const outPath = options.out ?? join(dir, `${basename(dir)}.agent`);
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
throw new Error("E003: File agent.description not found");
|
|
724
|
-
}
|
|
725
|
-
try {
|
|
726
|
-
behaviorText = await readFile2(join(dir, "agent.behavior"), "utf-8");
|
|
727
|
-
} catch {
|
|
728
|
-
throw new Error("E007: File agent.behavior not found");
|
|
729
|
-
}
|
|
730
|
-
const [descriptionMessages, behaviorMessages] = await Promise.all([
|
|
731
|
-
lintDescription(descriptionText),
|
|
732
|
-
lintBehavior(behaviorText)
|
|
733
|
-
]);
|
|
734
|
-
const allMessages = [...descriptionMessages, ...behaviorMessages];
|
|
735
|
-
const errors = allMessages.filter((m) => m.severity === "error");
|
|
736
|
-
const warnings = allMessages.filter((m) => m.severity === "warning");
|
|
737
|
-
if (errors.length > 0) {
|
|
840
|
+
const descriptionFileName = await discoverDescriptionFile(dir, options.description);
|
|
841
|
+
const descriptionText = await readFile2(join(dir, descriptionFileName), "utf-8");
|
|
842
|
+
const descriptionMessages = await lintDescription(descriptionText, descriptionFileName);
|
|
843
|
+
const descErrors = descriptionMessages.filter((m) => m.severity === "error");
|
|
844
|
+
if (descErrors.length > 0) {
|
|
738
845
|
throw new Error(
|
|
739
846
|
`Lint failed:
|
|
740
|
-
${
|
|
847
|
+
${descErrors.map((e) => ` ${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`).join("\n")}`
|
|
741
848
|
);
|
|
742
849
|
}
|
|
743
850
|
await initBehaviorParser();
|
|
@@ -747,9 +854,26 @@ ${errors.map((e) => ` ${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`).joi
|
|
|
747
854
|
throw new Error(`E_DESC: ${firstError?.message ?? "parse failed"}`);
|
|
748
855
|
}
|
|
749
856
|
const df = descResult.ok;
|
|
857
|
+
if (!df.behavior) {
|
|
858
|
+
throw new Error(`E_DESC: '${descriptionFileName}' must declare a behavior file \u2014 add 'behavior "agent.behavior"' (or the name of your entry .behavior file)`);
|
|
859
|
+
}
|
|
860
|
+
if (isExternalPath(dir, df.behavior)) {
|
|
861
|
+
throw new Error(`E014: behavior path '${df.behavior}' in '${descriptionFileName}' is absolute or escapes the agent root`);
|
|
862
|
+
}
|
|
863
|
+
const { mergedText, mergeSources } = await consolidate(dir, df.behavior);
|
|
864
|
+
const behaviorMessages = await lintBehavior(mergedText, "agent.behavior", void 0, true);
|
|
865
|
+
const allMessages = [...descriptionMessages, ...behaviorMessages];
|
|
866
|
+
const errors = allMessages.filter((m) => m.severity === "error");
|
|
867
|
+
const warnings = allMessages.filter((m) => m.severity === "warning");
|
|
868
|
+
if (errors.length > 0) {
|
|
869
|
+
throw new Error(
|
|
870
|
+
`Lint failed:
|
|
871
|
+
${errors.map((e) => ` ${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`).join("\n")}`
|
|
872
|
+
);
|
|
873
|
+
}
|
|
750
874
|
const version = await resolveVersion(options.version);
|
|
751
875
|
const commit = await resolveCommit(options.commit);
|
|
752
|
-
const allFiles = await collectFiles(dir);
|
|
876
|
+
const allFiles = await collectFiles(dir, descriptionFileName, mergedText, mergeSources);
|
|
753
877
|
const contentForHash = Array.from(allFiles.values()).join("");
|
|
754
878
|
const sha256 = createHash("sha256").update(contentForHash).digest("hex");
|
|
755
879
|
const namespace = df.agent.domain ?? "unknown";
|
|
@@ -769,7 +893,7 @@ ${errors.map((e) => ` ${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`).joi
|
|
|
769
893
|
license: df.agent.license ?? "",
|
|
770
894
|
persona: df.persona ?? "SOUL.md",
|
|
771
895
|
purpose: "unknown",
|
|
772
|
-
compiler:
|
|
896
|
+
compiler: `dot-agent/${COMPILER_VERSION}`,
|
|
773
897
|
commit,
|
|
774
898
|
capabilities: df.capabilities.map((c) => ({ id: c.name, description: c.description ?? "" })),
|
|
775
899
|
requires: df.requires,
|
|
@@ -781,26 +905,100 @@ ${errors.map((e) => ` ${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`).joi
|
|
|
781
905
|
if (typesJson) {
|
|
782
906
|
agentFolder.file("types.json", typesJson);
|
|
783
907
|
}
|
|
784
|
-
const gitkeepPaths = /* @__PURE__ */ new Set(["
|
|
908
|
+
const gitkeepPaths = /* @__PURE__ */ new Set(["guides/.gitkeep", "knowledge/.gitkeep"]);
|
|
785
909
|
const filesJson = {
|
|
786
|
-
description:
|
|
910
|
+
description: descriptionFileName,
|
|
787
911
|
behavior: "agent.behavior",
|
|
788
|
-
behaviors: Array.from(allFiles.keys()).filter((f) => f.startsWith("behaviors/")
|
|
912
|
+
behaviors: Array.from(allFiles.keys()).filter((f) => f.startsWith("behaviors/")),
|
|
789
913
|
guides: Array.from(allFiles.keys()).filter((f) => f.startsWith("guides/") && !gitkeepPaths.has(f)),
|
|
790
914
|
knowledge: Array.from(allFiles.keys()).filter((f) => f.startsWith("knowledge/") && !gitkeepPaths.has(f))
|
|
791
915
|
};
|
|
792
916
|
agentFolder.file("files.json", JSON.stringify(filesJson, null, 2));
|
|
793
917
|
for (const [path, content] of allFiles) {
|
|
794
|
-
|
|
918
|
+
zip.file(path, content);
|
|
795
919
|
}
|
|
796
920
|
await writeZip(zip, outPath);
|
|
797
921
|
return { path: outPath, id, warnings };
|
|
798
922
|
}
|
|
923
|
+
|
|
924
|
+
// src/bundle.ts
|
|
925
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
926
|
+
import { join as join2 } from "path";
|
|
927
|
+
import { createHash as createHash2 } from "crypto";
|
|
928
|
+
var GITKEEP = /* @__PURE__ */ new Set(["guides/.gitkeep", "knowledge/.gitkeep"]);
|
|
929
|
+
async function bundleFromDir(dir) {
|
|
930
|
+
const descriptionFileName = await discoverDescriptionFile(dir);
|
|
931
|
+
const descriptionText = await readFile3(join2(dir, descriptionFileName), "utf-8");
|
|
932
|
+
const descMessages = await lintDescription(descriptionText, descriptionFileName);
|
|
933
|
+
const descErrors = descMessages.filter((m) => m.severity === "error");
|
|
934
|
+
if (descErrors.length > 0) {
|
|
935
|
+
throw new Error(
|
|
936
|
+
`Lint failed:
|
|
937
|
+
${descErrors.map((e) => ` ${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`).join("\n")}`
|
|
938
|
+
);
|
|
939
|
+
}
|
|
940
|
+
await initBehaviorParser();
|
|
941
|
+
const descResult = parseDescriptionFile(descriptionText);
|
|
942
|
+
if (descResult.ok === null) {
|
|
943
|
+
const firstError = descResult.diagnostics.find((d) => d.severity === "error");
|
|
944
|
+
throw new Error(`E_DESC: ${firstError?.message ?? "parse failed"}`);
|
|
945
|
+
}
|
|
946
|
+
const df = descResult.ok;
|
|
947
|
+
if (!df.behavior) {
|
|
948
|
+
throw new Error(`E_DESC: '${descriptionFileName}' must declare a behavior file`);
|
|
949
|
+
}
|
|
950
|
+
const { mergedText, mergeSources } = await consolidate(dir, df.behavior);
|
|
951
|
+
const behaviorMessages = await lintBehavior(mergedText, "agent.behavior", void 0, true);
|
|
952
|
+
const allMessages = [...descMessages, ...behaviorMessages];
|
|
953
|
+
const errors = allMessages.filter((m) => m.severity === "error");
|
|
954
|
+
const warnings = allMessages.filter((m) => m.severity === "warning");
|
|
955
|
+
if (errors.length > 0) {
|
|
956
|
+
throw new Error(
|
|
957
|
+
`Lint failed:
|
|
958
|
+
${errors.map((e) => ` ${e.file}:${e.line}:${e.col} ${e.code} ${e.message}`).join("\n")}`
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
for (const w of warnings) {
|
|
962
|
+
process.stderr.write(`\x1B[33m\u26A0\x1B[0m ${w.file}:${w.line}:${w.col} ${w.code} ${w.message}
|
|
963
|
+
`);
|
|
964
|
+
}
|
|
965
|
+
const allFiles = await collectFiles(dir, descriptionFileName, mergedText, mergeSources);
|
|
966
|
+
const sha256 = createHash2("sha256").update(Array.from(allFiles.values()).join("")).digest("hex");
|
|
967
|
+
const id = buildId({ namespace: df.agent.domain ?? "local", name: df.agent.name, version: "dev" });
|
|
968
|
+
const aboutme = buildAboutme({
|
|
969
|
+
id,
|
|
970
|
+
name: df.agent.name,
|
|
971
|
+
description: df.description ?? "",
|
|
972
|
+
version: "dev",
|
|
973
|
+
domain: df.agent.domain ?? "",
|
|
974
|
+
license: df.agent.license ?? "",
|
|
975
|
+
persona: df.persona ?? "SOUL.md",
|
|
976
|
+
purpose: "development",
|
|
977
|
+
compiler: `dot-agent/${COMPILER_VERSION}`,
|
|
978
|
+
capabilities: df.capabilities.map((c) => ({ id: c.name, description: c.description ?? "" })),
|
|
979
|
+
requires: df.requires,
|
|
980
|
+
integrity: { sha256, files: ".agent/files.json" }
|
|
981
|
+
});
|
|
982
|
+
const files = {
|
|
983
|
+
description: descriptionText,
|
|
984
|
+
// Reuse collectFiles()'s 'agent.behavior' entry rather than the raw
|
|
985
|
+
// mergedText — it has redundant `merge "..."` lines stripped (see
|
|
986
|
+
// collectFiles() for why they're dead weight once flattened).
|
|
987
|
+
behavior: allFiles.get("agent.behavior") ?? mergedText,
|
|
988
|
+
soul: allFiles.get("SOUL.md"),
|
|
989
|
+
guides: Array.from(allFiles.entries()).filter(([p]) => p.startsWith("guides/") && !GITKEEP.has(p)).map(([path, content]) => ({ path, content })),
|
|
990
|
+
knowledge: Array.from(allFiles.entries()).filter(([p]) => p.startsWith("knowledge/") && !GITKEEP.has(p)).map(([path, content]) => ({ path, content })),
|
|
991
|
+
behaviors: Array.from(allFiles.entries()).filter(([p]) => p.startsWith("behaviors/")).map(([path, content]) => ({ path, content }))
|
|
992
|
+
};
|
|
993
|
+
return { id, aboutme, files };
|
|
994
|
+
}
|
|
799
995
|
export {
|
|
800
996
|
aboutmeToJson,
|
|
801
997
|
buildAboutme,
|
|
802
998
|
buildId,
|
|
999
|
+
bundleFromDir,
|
|
803
1000
|
collectFiles,
|
|
1001
|
+
consolidate,
|
|
804
1002
|
createLinter,
|
|
805
1003
|
createZip,
|
|
806
1004
|
extractDigest,
|
|
@@ -824,8 +1022,8 @@ export {
|
|
|
824
1022
|
parseSync,
|
|
825
1023
|
positionToOffset,
|
|
826
1024
|
readZip,
|
|
827
|
-
validateMagicBytes,
|
|
828
|
-
validateZipBomb,
|
|
1025
|
+
validateMagicBytes2 as validateMagicBytes,
|
|
1026
|
+
validateZipBomb2 as validateZipBomb,
|
|
829
1027
|
writeZip
|
|
830
1028
|
};
|
|
831
1029
|
//# sourceMappingURL=index.js.map
|