@almadar/agent 1.2.2 → 1.3.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.
@@ -1,11 +1,12 @@
1
1
  import { AgentDomainCategorySchema, isOrbitalDefinition, isEntityReference, getTraitName } from '@almadar/core/types';
2
+ import { getFullOrbitalPrompt, getRequirementsTraitPrompt, getKeyBehaviorsReference, getSExprQuickRef, getCommonErrorsSection, getArchitectureSection } from '@almadar/skills';
3
+ import { tool } from '@langchain/core/tools';
4
+ import { z } from 'zod';
5
+ import { LLMClient, ANTHROPIC_MODELS, createAnthropicClient, OPENROUTER_MODELS, createOpenRouterClient, KIMI_MODELS, createKimiClient, OPENAI_MODELS, createOpenAIClient, DEEPSEEK_MODELS, createDeepSeekClient } from '@almadar/llm';
2
6
  import { FilesystemBackend, createDeepAgent } from 'deepagents';
3
7
  import { MemorySaver } from '@langchain/langgraph';
4
8
  export { Command } from '@langchain/langgraph';
5
9
  import { v4 } from 'uuid';
6
- import { ANTHROPIC_MODELS, createAnthropicClient, OPENAI_MODELS, createOpenAIClient, DEEPSEEK_MODELS, createDeepSeekClient, LLMClient } from '@almadar/llm';
7
- import { tool } from '@langchain/core/tools';
8
- import { z } from 'zod';
9
10
  import { exec, spawn } from 'child_process';
10
11
  import * as path from 'path';
11
12
  import { promisify } from 'util';
@@ -13,8 +14,6 @@ import * as fs4 from 'fs/promises';
13
14
  import * as domain_language_star from '@almadar/core/domain-language';
14
15
  import * as fs3 from 'fs';
15
16
  import crypto, { randomUUID } from 'crypto';
16
- import { getFullOrbitalPrompt, generateKflowDesignSkill, getRequirementsTraitPrompt, getKeyBehaviorsReference, getSExprQuickRef, getCommonErrorsSection, getArchitectureSection } from '@almadar/skills';
17
- import { formatRecommendationsForPrompt, buildRecommendationContext, recommendPatterns } from '@almadar/patterns';
18
17
  import { GitHubIntegration } from '@almadar/integrations';
19
18
  import { BaseCheckpointSaver } from '@langchain/langgraph-checkpoint';
20
19
 
@@ -309,6 +308,855 @@ var init_cache = __esm({
309
308
  init_prompt_assembler();
310
309
  }
311
310
  });
311
+
312
+ // src/orbitals/shared/constants.ts
313
+ var PROVIDER_CONCURRENCY_LIMITS, PROVIDER_BATCH_SIZES, BATCH_MAX_TOKENS;
314
+ var init_constants = __esm({
315
+ "src/orbitals/shared/constants.ts"() {
316
+ PROVIDER_CONCURRENCY_LIMITS = {
317
+ anthropic: 3,
318
+ openai: 5,
319
+ deepseek: 3,
320
+ kimi: 3,
321
+ openrouter: 5
322
+ };
323
+ PROVIDER_BATCH_SIZES = {
324
+ anthropic: 3,
325
+ // Anthropic works well with 3 orbitals per call
326
+ openai: 5,
327
+ // OpenAI can handle more
328
+ deepseek: 3,
329
+ // Conservative for DeepSeek
330
+ kimi: 3,
331
+ // Conservative for Kimi
332
+ openrouter: 5
333
+ // OpenRouter proxies multiple providers
334
+ };
335
+ BATCH_MAX_TOKENS = 12e3;
336
+ }
337
+ });
338
+ function getEntityName2(entity) {
339
+ if (isEntityReference(entity)) {
340
+ return entity.replace(".entity", "");
341
+ }
342
+ return entity.name;
343
+ }
344
+ function getInlineEntity3(entity) {
345
+ if (isEntityReference(entity)) {
346
+ return null;
347
+ }
348
+ return entity;
349
+ }
350
+ function getFieldNames(entity) {
351
+ const inlineEntity = getInlineEntity3(entity);
352
+ if (!inlineEntity?.fields?.length) {
353
+ return "N/A";
354
+ }
355
+ return inlineEntity.fields.map((f) => f.name).join(", ");
356
+ }
357
+ function createLog(level, message, data) {
358
+ return {
359
+ timestamp: Date.now(),
360
+ level,
361
+ message,
362
+ data
363
+ };
364
+ }
365
+ function buildContextSection2(orbital) {
366
+ const parts = [];
367
+ if (orbital.domainContext) {
368
+ const ctx = orbital.domainContext;
369
+ parts.push(`DomainContext: category=${ctx.category || "business"}`);
370
+ if (ctx.vocabulary) {
371
+ parts.push(`Vocabulary: ${JSON.stringify(ctx.vocabulary)}`);
372
+ }
373
+ if (ctx.requestFragment) {
374
+ parts.push(`RequestFragment: "${ctx.requestFragment}"`);
375
+ }
376
+ }
377
+ if (orbital.design) {
378
+ const d = orbital.design;
379
+ if (d.style) parts.push(`Style: ${d.style}`);
380
+ if (d.uxHints) {
381
+ const hints = d.uxHints;
382
+ if (hints.flowPattern) parts.push(`FlowPattern: ${hints.flowPattern}`);
383
+ if (hints.listPattern) parts.push(`ListPattern: ${hints.listPattern}`);
384
+ if (hints.formPattern) parts.push(`FormPattern: ${hints.formPattern}`);
385
+ }
386
+ }
387
+ return parts.length > 0 ? `Context: ${parts.join(", ")}
388
+ ` : "";
389
+ }
390
+ function buildConnectivitySection(orbital) {
391
+ const parts = [];
392
+ if (orbital.emits?.length) {
393
+ parts.push(`Emits: ${orbital.emits.join(", ")}`);
394
+ }
395
+ if (orbital.listens?.length) {
396
+ parts.push(`Listens: ${orbital.listens.map((l) => `${l.event}\u2192${l.triggers}`).join(", ")}`);
397
+ }
398
+ return parts.join("\n");
399
+ }
400
+ function chunkArray(array, size) {
401
+ const chunks = [];
402
+ for (let i = 0; i < array.length; i += size) {
403
+ chunks.push(array.slice(i, i + size));
404
+ }
405
+ return chunks;
406
+ }
407
+ function getErrorMessage(error) {
408
+ if (error instanceof Error) {
409
+ return error.message;
410
+ }
411
+ return String(error);
412
+ }
413
+ var init_utils = __esm({
414
+ "src/orbitals/shared/utils.ts"() {
415
+ }
416
+ });
417
+
418
+ // src/orbitals/shared/index.ts
419
+ var init_shared = __esm({
420
+ "src/orbitals/shared/index.ts"() {
421
+ init_constants();
422
+ init_utils();
423
+ }
424
+ });
425
+ function extractSharedContext(orbitals) {
426
+ const vocabulary = {};
427
+ const styles = /* @__PURE__ */ new Set();
428
+ const patterns = /* @__PURE__ */ new Set();
429
+ const relationships = [];
430
+ for (const orbital of orbitals) {
431
+ if (orbital.domainContext?.vocabulary) {
432
+ Object.assign(vocabulary, orbital.domainContext.vocabulary);
433
+ }
434
+ if (orbital.design?.style) {
435
+ styles.add(orbital.design.style);
436
+ }
437
+ }
438
+ new Map(orbitals.map((o) => [o.name, o]));
439
+ for (const orbital of orbitals) {
440
+ if (orbital.emits) {
441
+ for (const event of orbital.emits) {
442
+ for (const other of orbitals) {
443
+ if (other === orbital) continue;
444
+ if (other.listens) {
445
+ const listener = other.listens.find((l) => l.event === event.event);
446
+ if (listener) {
447
+ relationships.push({
448
+ emitter: orbital.name,
449
+ event: event.event,
450
+ listener: other.name,
451
+ handler: listener.triggers
452
+ });
453
+ }
454
+ }
455
+ }
456
+ }
457
+ }
458
+ }
459
+ return {
460
+ domainVocabulary: Object.keys(vocabulary).length > 0 ? vocabulary : void 0,
461
+ designStyle: styles.size === 1 ? Array.from(styles)[0] : void 0,
462
+ commonPatterns: patterns.size > 0 ? Array.from(patterns) : void 0,
463
+ eventRelationships: relationships.length > 0 ? relationships : void 0
464
+ };
465
+ }
466
+ function assembleBatchPrompt(orbitals, options = {}) {
467
+ const {
468
+ baseSystemPrompt = getFullOrbitalPrompt(),
469
+ includeConnectivity = true
470
+ } = options;
471
+ if (orbitals.length === 0) {
472
+ throw new Error("Cannot assemble batch prompt for empty orbitals array");
473
+ }
474
+ const sharedContext = extractSharedContext(orbitals);
475
+ const batchHeader = buildBatchHeader(orbitals, sharedContext);
476
+ const orbitalSections = orbitals.map(
477
+ (orbital, index) => buildOrbitalSection(orbital, index + 1, orbitals.length, includeConnectivity)
478
+ );
479
+ const outputSection = buildOutputSection(orbitals);
480
+ const prompt = `${baseSystemPrompt}
481
+
482
+ ${"=".repeat(60)}
483
+ BATCH GENERATION: ${orbitals.length} ORBITALS
484
+ ${"=".repeat(60)}
485
+
486
+ ${batchHeader}
487
+
488
+ ${orbitalSections.join("\n\n")}
489
+
490
+ ${outputSection}`;
491
+ const fingerprint = generateBatchFingerprint(orbitals);
492
+ return {
493
+ prompt,
494
+ fingerprint,
495
+ usedCachedTemplate: false,
496
+ // Batch doesn't use templates yet
497
+ orbitalCount: orbitals.length,
498
+ orbitalNames: orbitals.map((o) => o.name)
499
+ };
500
+ }
501
+ function buildBatchHeader(orbitals, sharedContext) {
502
+ const lines = [
503
+ `## Batch Overview`,
504
+ `- Total Orbitals: ${orbitals.length}`,
505
+ `- Orbitals: ${orbitals.map((o) => o.name).join(", ")}`
506
+ ];
507
+ if (sharedContext.designStyle) {
508
+ lines.push(`- Design Style: ${sharedContext.designStyle}`);
509
+ }
510
+ if (sharedContext.commonPatterns?.length) {
511
+ lines.push(`- Common Patterns: ${sharedContext.commonPatterns.join(", ")}`);
512
+ }
513
+ if (sharedContext.domainVocabulary && Object.keys(sharedContext.domainVocabulary).length > 0) {
514
+ lines.push(`
515
+ ## Shared Domain Vocabulary`);
516
+ for (const [term, definition] of Object.entries(sharedContext.domainVocabulary)) {
517
+ lines.push(`- ${term}: ${definition}`);
518
+ }
519
+ }
520
+ if (sharedContext.eventRelationships?.length) {
521
+ lines.push(`
522
+ ## Cross-Orbital Event Relationships`);
523
+ for (const rel of sharedContext.eventRelationships) {
524
+ lines.push(`- ${rel.emitter} emits "${rel.event}" \u2192 ${rel.listener} handles with "${rel.handler}"`);
525
+ }
526
+ }
527
+ return lines.join("\n");
528
+ }
529
+ function buildOrbitalSection(orbital, index, total, includeConnectivity) {
530
+ const entityName = getEntityName2(orbital.entity);
531
+ const fieldNames = getFieldNames(orbital.entity);
532
+ const contextSection = buildContextSection2(orbital);
533
+ const connectivitySection = includeConnectivity ? buildConnectivitySection(orbital) : "";
534
+ return `---
535
+ ## ORBITAL ${index}/${total}: ${orbital.name}
536
+
537
+ **Entity**: ${entityName}
538
+ **Persistence**: ${orbital.entity.persistence || "persistent"}
539
+ **Fields**: ${fieldNames}
540
+ **Traits**: ${orbital.traits.map((t) => typeof t === "string" ? t : "ref" in t ? t.ref : t.name).join(", ")}
541
+ ${contextSection}${connectivitySection ? `**Connectivity**:
542
+ ${connectivitySection}
543
+ ` : ""}
544
+ Generate a complete FullOrbitalUnit for ${orbital.name} with:
545
+ - Full field definitions with types and validation
546
+ - Trait state machines with transitions and effects
547
+ - Business rule validation using "guard" S-expression on SAVE transitions
548
+ - Pages with trait references
549
+ - domainContext with category, vocabulary, and requestFragment
550
+ - design with style and uxHints (flowPattern, listPattern, formPattern)
551
+ ${orbital.emits?.length ? "- PRESERVE emits: " + orbital.emits.map((e) => e.event).join(", ") : ""}
552
+ ${orbital.listens?.length ? "- PRESERVE listens: " + orbital.listens.map((l) => l.event).join(", ") : ""}`;
553
+ }
554
+ function buildOutputSection(orbitals) {
555
+ return `---
556
+ ## OUTPUT FORMAT
557
+
558
+ Return a JSON object with this exact structure:
559
+
560
+ \`\`\`json
561
+ {
562
+ "orbitals": [
563
+ ${orbitals.map((o, i) => ` {
564
+ "name": "${o.name}",
565
+ // ... complete FullOrbitalUnit for ${o.name}
566
+ }${i < orbitals.length - 1 ? "," : ""}`).join("\n")}
567
+ ]
568
+ }
569
+ \`\`\`
570
+
571
+ **CRITICAL RULES:**
572
+ 1. Return a SINGLE JSON object with an "orbitals" array
573
+ 2. Each element in the array is a complete FullOrbitalUnit
574
+ 3. Maintain the order: ${orbitals.map((o) => o.name).join(", ")}
575
+ 4. PRESERVE all emits/listens as specified in each orbital section
576
+ 5. Use shared domain vocabulary consistently across all orbitals
577
+ 6. Ensure cross-orbital event wiring is maintained
578
+ `;
579
+ }
580
+ function generateBatchFingerprint(orbitals) {
581
+ const fingerprints = orbitals.map((o) => computeOrbitalFingerprint(o));
582
+ const combined = fingerprints.sort().join("|");
583
+ let hash = 0;
584
+ for (let i = 0; i < combined.length; i++) {
585
+ const char = combined.charCodeAt(i);
586
+ hash = (hash << 5) - hash + char;
587
+ hash = hash & hash;
588
+ }
589
+ return `batch:${orbitals.length}:${Math.abs(hash).toString(16).slice(0, 8)}`;
590
+ }
591
+ function splitIntoBatches(orbitals, options = {}) {
592
+ const {
593
+ maxBatchSize = 3,
594
+ preserveRelationships = true
595
+ } = options;
596
+ if (!preserveRelationships) {
597
+ return chunkArray(orbitals, maxBatchSize);
598
+ }
599
+ const clusters = groupByRelationships(orbitals);
600
+ const batches = [];
601
+ for (const cluster of clusters) {
602
+ if (cluster.length <= maxBatchSize) {
603
+ batches.push(cluster);
604
+ } else {
605
+ batches.push(...chunkArray(cluster, maxBatchSize));
606
+ }
607
+ }
608
+ return batches;
609
+ }
610
+ function groupByRelationships(orbitals) {
611
+ const visited = /* @__PURE__ */ new Set();
612
+ const clusters = [];
613
+ const adjacency = /* @__PURE__ */ new Map();
614
+ for (const orbital of orbitals) {
615
+ if (!adjacency.has(orbital.name)) {
616
+ adjacency.set(orbital.name, /* @__PURE__ */ new Set());
617
+ }
618
+ if (orbital.emits) {
619
+ for (const event of orbital.emits) {
620
+ for (const other of orbitals) {
621
+ if (other === orbital) continue;
622
+ if (other.listens?.some((l) => l.event === event.event)) {
623
+ adjacency.get(orbital.name)?.add(other.name);
624
+ if (!adjacency.has(other.name)) {
625
+ adjacency.set(other.name, /* @__PURE__ */ new Set());
626
+ }
627
+ adjacency.get(other.name)?.add(orbital.name);
628
+ }
629
+ }
630
+ }
631
+ }
632
+ }
633
+ function dfs(orbital, cluster) {
634
+ visited.add(orbital.name);
635
+ cluster.push(orbital);
636
+ const neighbors = adjacency.get(orbital.name) || /* @__PURE__ */ new Set();
637
+ for (const neighborName of neighbors) {
638
+ if (!visited.has(neighborName)) {
639
+ const neighbor = orbitals.find((o) => o.name === neighborName);
640
+ if (neighbor) {
641
+ dfs(neighbor, cluster);
642
+ }
643
+ }
644
+ }
645
+ }
646
+ for (const orbital of orbitals) {
647
+ if (!visited.has(orbital.name)) {
648
+ const cluster = [];
649
+ dfs(orbital, cluster);
650
+ clusters.push(cluster);
651
+ }
652
+ }
653
+ return clusters;
654
+ }
655
+ function estimateBatchTokens(orbitals) {
656
+ const baseTokens = 2e3;
657
+ const perOrbitalTokens = 800;
658
+ return baseTokens + orbitals.length * perOrbitalTokens;
659
+ }
660
+ function willBatchFit(orbitals, maxTokens = 12e3) {
661
+ const estimated = estimateBatchTokens(orbitals);
662
+ return estimated < maxTokens * 0.5;
663
+ }
664
+ var init_prompt_assembler2 = __esm({
665
+ "src/orbitals/batch/prompt-assembler.ts"() {
666
+ init_cache();
667
+ init_shared();
668
+ }
669
+ });
670
+
671
+ // src/orbitals/batch/concurrency.ts
672
+ function createConcurrencyController(maxConcurrency) {
673
+ let activeCount = 0;
674
+ const queue = [];
675
+ return {
676
+ async acquire() {
677
+ if (activeCount < maxConcurrency) {
678
+ activeCount++;
679
+ return Promise.resolve();
680
+ }
681
+ return new Promise((resolve2) => {
682
+ queue.push(() => {
683
+ activeCount++;
684
+ resolve2();
685
+ });
686
+ });
687
+ },
688
+ release() {
689
+ activeCount = Math.max(0, activeCount - 1);
690
+ const next = queue.shift();
691
+ if (next) {
692
+ next();
693
+ }
694
+ },
695
+ get activeCount() {
696
+ return activeCount;
697
+ },
698
+ get waitingCount() {
699
+ return queue.length;
700
+ }
701
+ };
702
+ }
703
+ async function runWithConcurrency(tasks, options) {
704
+ const { concurrency, onProgress } = options;
705
+ const controller = createConcurrencyController(concurrency);
706
+ const results = [];
707
+ let completed = 0;
708
+ await Promise.all(
709
+ tasks.map(async (task, index) => {
710
+ await controller.acquire();
711
+ try {
712
+ results[index] = await task();
713
+ completed++;
714
+ onProgress?.(completed, tasks.length);
715
+ } finally {
716
+ controller.release();
717
+ }
718
+ })
719
+ );
720
+ return results;
721
+ }
722
+ async function asyncMapWithConcurrency2(items, mapper, concurrency) {
723
+ return runWithConcurrency(
724
+ items.map((item, index) => () => mapper(item, index)),
725
+ { concurrency }
726
+ );
727
+ }
728
+ var init_concurrency = __esm({
729
+ "src/orbitals/batch/concurrency.ts"() {
730
+ }
731
+ });
732
+
733
+ // src/orbitals/batch/batch-generator.ts
734
+ async function generateOrbitalsBatch(client, orbitals, options = {}) {
735
+ const startTime = Date.now();
736
+ const aggregateLogs = [];
737
+ const provider = client.getProvider();
738
+ const mode = options.mode || "single-call";
739
+ const maxConcurrency = options.concurrency ?? PROVIDER_CONCURRENCY_LIMITS[provider];
740
+ const maxBatchSize = options.batchSize ?? PROVIDER_BATCH_SIZES[provider];
741
+ console.log(`[BatchGenerator] Starting batch generation: ${orbitals.length} orbitals, mode=${mode}, concurrency=${maxConcurrency}`);
742
+ aggregateLogs.push(createLog("info", `Starting batch generation`, {
743
+ totalOrbitals: orbitals.length,
744
+ mode,
745
+ provider,
746
+ maxConcurrency,
747
+ maxBatchSize
748
+ }));
749
+ const batches = splitIntoBatches(orbitals, {
750
+ maxBatchSize,
751
+ preserveRelationships: options.preserveRelationships ?? true
752
+ });
753
+ console.log(`[BatchGenerator] Split into ${batches.length} batches: [${batches.map((b) => b.length).join(", ")}]`);
754
+ aggregateLogs.push(createLog("info", `Split into ${batches.length} batches`, {
755
+ batchSizes: batches.map((b) => b.length)
756
+ }));
757
+ options.onBatchProgress?.({
758
+ type: "batch_start",
759
+ batchIndex: 0,
760
+ totalBatches: batches.length,
761
+ completedOrbitals: 0,
762
+ totalOrbitals: orbitals.length
763
+ });
764
+ let batchResults;
765
+ if (mode === "parallel-individual") {
766
+ batchResults = await generateParallelIndividual(client, orbitals, {
767
+ ...options,
768
+ concurrency: maxConcurrency,
769
+ onBatchProgress: options.onBatchProgress
770
+ });
771
+ } else if (mode === "single-call") {
772
+ batchResults = await generateSingleCallBatches(client, batches, {
773
+ ...options,
774
+ concurrency: maxConcurrency,
775
+ onBatchProgress: options.onBatchProgress
776
+ });
777
+ } else {
778
+ batchResults = await generateAdaptive(client, orbitals, batches, {
779
+ ...options,
780
+ concurrency: maxConcurrency,
781
+ maxBatchSize,
782
+ onBatchProgress: options.onBatchProgress
783
+ });
784
+ }
785
+ const totalDurationMs = Date.now() - startTime;
786
+ const allResults = batchResults.flatMap((b) => b.results);
787
+ const successful = allResults.filter((r) => r.success).length;
788
+ const failed = allResults.length - successful;
789
+ const totalTokens = batchResults.reduce(
790
+ (sum, b) => sum + (b.usage?.totalTokens ?? 0),
791
+ 0
792
+ );
793
+ console.log(`[BatchGenerator] Complete: ${successful}/${allResults.length} successful, ${totalTokens} tokens, ${totalDurationMs}ms`);
794
+ aggregateLogs.push(createLog("info", `Batch generation completed`, {
795
+ totalDurationMs,
796
+ successful,
797
+ failed,
798
+ totalTokens,
799
+ totalBatches: batches.length
800
+ }));
801
+ options.onBatchProgress?.({
802
+ type: "batch_complete",
803
+ batchIndex: batches.length - 1,
804
+ totalBatches: batches.length,
805
+ completedOrbitals: successful,
806
+ totalOrbitals: orbitals.length
807
+ });
808
+ return {
809
+ results: allResults.map((r) => ({
810
+ orbital: r.orbital,
811
+ fingerprint: "",
812
+ // TODO: compute from orbital
813
+ usedTemplate: false,
814
+ usage: void 0,
815
+ validation: r.success ? { valid: true, errorCount: 0, warningCount: 0 } : { valid: false, errorCount: 1, warningCount: 0 },
816
+ logs: []
817
+ })),
818
+ totalDurationMs,
819
+ aggregateLogs,
820
+ summary: {
821
+ total: allResults.length,
822
+ successful,
823
+ failed,
824
+ totalTokens
825
+ },
826
+ batchResults,
827
+ totalBatches: batches.length
828
+ };
829
+ }
830
+ async function generateSingleCallBatches(client, batches, options) {
831
+ let completedOrbitals = 0;
832
+ const totalOrbitals = batches.reduce((sum, b) => sum + b.length, 0);
833
+ return asyncMapWithConcurrency2(
834
+ batches,
835
+ async (batch, batchIndex) => {
836
+ return generateSingleBatch(client, batch, batchIndex, {
837
+ ...options,
838
+ onProgress: (completedInBatch) => {
839
+ const newCompleted = completedOrbitals + completedInBatch;
840
+ options.onBatchProgress?.({
841
+ type: "orbital_complete",
842
+ batchIndex,
843
+ totalBatches: batches.length,
844
+ completedOrbitals: newCompleted,
845
+ totalOrbitals
846
+ });
847
+ completedOrbitals = newCompleted;
848
+ }
849
+ });
850
+ },
851
+ options.concurrency
852
+ );
853
+ }
854
+ async function generateParallelIndividual(client, orbitals, options) {
855
+ const batches = orbitals.map((o) => [o]);
856
+ return generateSingleCallBatches(client, batches, {
857
+ ...options,
858
+ concurrency: options.concurrency
859
+ });
860
+ }
861
+ async function generateAdaptive(client, orbitals, batches, options) {
862
+ const allFit = batches.every((batch) => willBatchFit(batch, BATCH_MAX_TOKENS));
863
+ if (allFit) {
864
+ console.log(`[BatchGenerator] Adaptive: Using single-call batches (${batches.length} batches)`);
865
+ return generateSingleCallBatches(client, batches, options);
866
+ }
867
+ console.log(`[BatchGenerator] Adaptive: Using parallel-individual (batches too large)`);
868
+ return generateParallelIndividual(client, orbitals, options);
869
+ }
870
+ async function generateSingleBatch(client, orbitals, batchIndex, options) {
871
+ const batchStartTime = Date.now();
872
+ const logs = [];
873
+ console.log(`[BatchGenerator] Starting batch ${batchIndex + 1}: ${orbitals.map((o) => o.name).join(", ")}`);
874
+ logs.push(createLog("info", `Starting batch ${batchIndex + 1}`, {
875
+ orbitalCount: orbitals.length,
876
+ orbitals: orbitals.map((o) => o.name)
877
+ }));
878
+ try {
879
+ const batchPrompt = assembleBatchPrompt(orbitals);
880
+ console.log(`[BatchGenerator] Batch ${batchIndex + 1} prompt assembled: ${batchPrompt.prompt.length} chars`);
881
+ logs.push(createLog("info", `Batch prompt assembled`, {
882
+ promptLength: batchPrompt.prompt.length,
883
+ estimatedTokens: Math.ceil(batchPrompt.prompt.length / 4)
884
+ }));
885
+ const llmResult = await client.callWithMetadata({
886
+ systemPrompt: batchPrompt.prompt,
887
+ userPrompt: "Generate all orbitals in the batch. Return valid JSON matching the output format specified.",
888
+ maxTokens: BATCH_MAX_TOKENS,
889
+ skipSchemaValidation: true
890
+ });
891
+ logs.push(createLog("info", `LLM call completed`, {
892
+ promptTokens: llmResult.usage?.promptTokens,
893
+ completionTokens: llmResult.usage?.completionTokens
894
+ }));
895
+ const parsed = parseBatchResult(llmResult.data, orbitals);
896
+ console.log(`[BatchGenerator] Batch ${batchIndex + 1} parsed: ${parsed.filter((p) => p.success).length}/${parsed.length} successful`);
897
+ for (let i = 0; i < parsed.length; i++) {
898
+ options.onProgress?.(i + 1);
899
+ }
900
+ const durationMs = Date.now() - batchStartTime;
901
+ logs.push(createLog("info", `Batch ${batchIndex + 1} completed`, {
902
+ durationMs,
903
+ successful: parsed.filter((r) => r.success).length
904
+ }));
905
+ return {
906
+ orbitals,
907
+ results: parsed,
908
+ usage: llmResult.usage ?? void 0,
909
+ durationMs,
910
+ logs
911
+ };
912
+ } catch (error) {
913
+ const errorMessage = getErrorMessage(error);
914
+ console.error(`[BatchGenerator] Batch ${batchIndex + 1} FAILED: ${errorMessage}`);
915
+ logs.push(createLog("error", `Batch ${batchIndex + 1} failed`, {
916
+ error: errorMessage
917
+ }));
918
+ return {
919
+ orbitals,
920
+ results: orbitals.map((o) => ({
921
+ orbital: o,
922
+ success: false,
923
+ error: errorMessage
924
+ })),
925
+ durationMs: Date.now() - batchStartTime,
926
+ logs
927
+ };
928
+ }
929
+ }
930
+ function parseBatchResult(data, expectedOrbitals) {
931
+ if (!data || typeof data !== "object") {
932
+ return expectedOrbitals.map((o) => ({
933
+ orbital: o,
934
+ success: false,
935
+ error: "Invalid response: expected object"
936
+ }));
937
+ }
938
+ const obj = data;
939
+ if (!obj.orbitals || !Array.isArray(obj.orbitals)) {
940
+ return expectedOrbitals.map((o) => ({
941
+ orbital: o,
942
+ success: false,
943
+ error: "Invalid response: missing orbitals array"
944
+ }));
945
+ }
946
+ const results = obj.orbitals;
947
+ return expectedOrbitals.map((expected, index) => {
948
+ const result = results[index];
949
+ if (!result) {
950
+ return {
951
+ orbital: expected,
952
+ success: false,
953
+ error: `Missing result for orbital ${index + 1}`
954
+ };
955
+ }
956
+ if (!result.name) {
957
+ return {
958
+ orbital: expected,
959
+ success: false,
960
+ error: "Generated orbital missing name"
961
+ };
962
+ }
963
+ return {
964
+ orbital: result,
965
+ success: true
966
+ };
967
+ });
968
+ }
969
+ var init_batch_generator = __esm({
970
+ "src/orbitals/batch/batch-generator.ts"() {
971
+ init_shared();
972
+ init_prompt_assembler2();
973
+ init_concurrency();
974
+ }
975
+ });
976
+
977
+ // src/orbitals/batch/index.ts
978
+ var init_batch = __esm({
979
+ "src/orbitals/batch/index.ts"() {
980
+ init_batch_generator();
981
+ init_prompt_assembler2();
982
+ init_concurrency();
983
+ }
984
+ });
985
+
986
+ // src/tools/orbital-batch-subagent.ts
987
+ var orbital_batch_subagent_exports = {};
988
+ __export(orbital_batch_subagent_exports, {
989
+ createOrbitalBatchSubagentTool: () => createOrbitalBatchSubagentTool
990
+ });
991
+ function createOrbitalBatchSubagentTool(options = {}) {
992
+ let eventCallback = options.onSubagentEvent;
993
+ let completeCallback = options.onBatchComplete;
994
+ const requirements = options.requirements;
995
+ const workDir = options.workDir;
996
+ const setEventCallback = (callback) => {
997
+ eventCallback = callback;
998
+ };
999
+ const setBatchCompleteCallback = (callback) => {
1000
+ completeCallback = callback;
1001
+ };
1002
+ const emitEvent = (orbitalName, orbitalIndex, totalOrbitals, type, data) => {
1003
+ if (eventCallback) {
1004
+ eventCallback(orbitalName, orbitalIndex, totalOrbitals, {
1005
+ type,
1006
+ data,
1007
+ timestamp: Date.now()
1008
+ });
1009
+ }
1010
+ };
1011
+ const batchTool = tool(
1012
+ async ({ orbitals, options: batchOptions }) => {
1013
+ if (!orbitals || orbitals.length === 0) {
1014
+ return JSON.stringify({
1015
+ success: false,
1016
+ error: "No orbitals provided for batch generation.",
1017
+ orbitals: []
1018
+ });
1019
+ }
1020
+ console.log(`[OrbitalBatchSubagent] Starting batch generation for ${orbitals.length} orbitals`);
1021
+ try {
1022
+ emitEvent("batch", 0, 1, "message", {
1023
+ content: `Starting batch generation for ${orbitals.length} orbitals`,
1024
+ role: "assistant",
1025
+ isComplete: false
1026
+ });
1027
+ emitEvent("batch", 0, 1, "todo_update", {
1028
+ todos: orbitals.map((o, i) => ({
1029
+ id: `orbital-${i}`,
1030
+ task: `Generate ${o.name}`,
1031
+ status: "pending"
1032
+ }))
1033
+ });
1034
+ const client = new LLMClient({
1035
+ provider: options.provider || "anthropic",
1036
+ model: options.model || "claude-sonnet-4-20250514"
1037
+ });
1038
+ const generationOptions = {
1039
+ mode: batchOptions?.mode || "adaptive",
1040
+ batchSize: batchOptions?.batchSize,
1041
+ concurrency: batchOptions?.maxConcurrency ?? PROVIDER_CONCURRENCY_LIMITS[client.getProvider()],
1042
+ preserveRelationships: batchOptions?.preserveRelationships ?? true,
1043
+ requirements,
1044
+ onBatchProgress: (event) => {
1045
+ if (event.type === "orbital_complete" && event.orbitalName) {
1046
+ emitEvent(event.orbitalName || "batch", event.batchIndex, event.totalBatches, "todo_update", {
1047
+ todos: orbitals.map((o, i) => ({
1048
+ id: `orbital-${i}`,
1049
+ task: `Generate ${o.name}`,
1050
+ status: o.name === event.orbitalName ? "completed" : event.completedOrbitals > i ? "completed" : "pending"
1051
+ }))
1052
+ });
1053
+ }
1054
+ emitEvent(event.orbitalName || "batch", event.batchIndex, event.totalBatches, "generation_log", {
1055
+ level: "info",
1056
+ message: `Progress: ${event.completedOrbitals}/${event.totalOrbitals} orbitals complete`,
1057
+ data: {
1058
+ batchIndex: event.batchIndex,
1059
+ completedOrbitals: event.completedOrbitals,
1060
+ totalOrbitals: event.totalOrbitals
1061
+ }
1062
+ });
1063
+ }
1064
+ };
1065
+ emitEvent("batch", 0, 1, "tool_call", {
1066
+ tool: "generateOrbitalsBatch",
1067
+ args: {
1068
+ orbitalCount: orbitals.length,
1069
+ mode: generationOptions.mode,
1070
+ concurrency: generationOptions.concurrency
1071
+ }
1072
+ });
1073
+ const result = await generateOrbitalsBatch(client, orbitals, generationOptions);
1074
+ emitEvent("batch", result.totalBatches - 1, result.totalBatches, "todo_update", {
1075
+ todos: orbitals.map((o, i) => ({
1076
+ id: `orbital-${i}`,
1077
+ task: `Generate ${o.name}`,
1078
+ status: result.batchResults.some(
1079
+ (b) => b.results.some((r) => r.orbital.name === o.name && r.success)
1080
+ ) ? "completed" : "failed"
1081
+ }))
1082
+ });
1083
+ const generatedOrbitals = result.results.filter((r) => r.orbital).map((r) => r.orbital);
1084
+ const successCount = generatedOrbitals.length;
1085
+ const failedCount = orbitals.length - successCount;
1086
+ emitEvent("batch", result.totalBatches - 1, result.totalBatches, "message", {
1087
+ content: `Batch generation complete: ${successCount}/${orbitals.length} orbitals generated successfully`,
1088
+ role: "assistant",
1089
+ isComplete: true
1090
+ });
1091
+ if (workDir && completeCallback && generatedOrbitals.length > 0) {
1092
+ try {
1093
+ await completeCallback(generatedOrbitals, 0, 1);
1094
+ emitEvent("batch", result.totalBatches - 1, result.totalBatches, "generation_log", {
1095
+ level: "info",
1096
+ message: `Persisted ${generatedOrbitals.length} orbitals`
1097
+ });
1098
+ } catch (persistError) {
1099
+ console.error(`[OrbitalBatchSubagent] Failed to persist orbitals:`, persistError);
1100
+ emitEvent("batch", result.totalBatches - 1, result.totalBatches, "generation_log", {
1101
+ level: "warn",
1102
+ message: "Failed to persist some orbitals",
1103
+ data: { error: String(persistError) }
1104
+ });
1105
+ }
1106
+ }
1107
+ return JSON.stringify({
1108
+ success: successCount === orbitals.length,
1109
+ generated: successCount,
1110
+ failed: failedCount,
1111
+ total: orbitals.length,
1112
+ orbitals: generatedOrbitals,
1113
+ duration: result.totalDurationMs,
1114
+ totalTokens: result.summary.totalTokens,
1115
+ batches: result.totalBatches
1116
+ });
1117
+ } catch (error) {
1118
+ const errorMessage = error instanceof Error ? error.message : String(error);
1119
+ console.error(`[OrbitalBatchSubagent] Batch generation failed:`, errorMessage);
1120
+ emitEvent("batch", 0, 1, "error", {
1121
+ error: errorMessage,
1122
+ code: "BATCH_GENERATION_ERROR"
1123
+ });
1124
+ return JSON.stringify({
1125
+ success: false,
1126
+ error: errorMessage,
1127
+ orbitals: []
1128
+ });
1129
+ }
1130
+ },
1131
+ {
1132
+ name: "generate_orbitals_batch",
1133
+ description: "Generate multiple orbitals in optimized batches. MUCH FASTER than calling generate_orbital multiple times. Use this when generating 3+ orbitals. Supports parallel generation with automatic concurrency control.",
1134
+ schema: OrbitalBatchInputSchema
1135
+ }
1136
+ );
1137
+ return {
1138
+ tool: batchTool,
1139
+ setEventCallback,
1140
+ setBatchCompleteCallback
1141
+ };
1142
+ }
1143
+ var OrbitalBatchInputSchema;
1144
+ var init_orbital_batch_subagent = __esm({
1145
+ "src/tools/orbital-batch-subagent.ts"() {
1146
+ init_shared();
1147
+ init_batch();
1148
+ OrbitalBatchInputSchema = z.object({
1149
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1150
+ orbitals: z.array(z.any()).describe("Array of OrbitalUnits to generate"),
1151
+ options: z.object({
1152
+ mode: z.enum(["single-call", "parallel-individual", "adaptive"]).optional().default("adaptive"),
1153
+ batchSize: z.number().optional(),
1154
+ maxConcurrency: z.number().optional(),
1155
+ preserveRelationships: z.boolean().optional().default(true)
1156
+ }).optional().describe("Batch generation options")
1157
+ });
1158
+ }
1159
+ });
312
1160
  var DANGEROUS_COMMANDS = [
313
1161
  "rm",
314
1162
  "rmdir",
@@ -1051,6 +1899,77 @@ npx kflow domain:validate input.orb --verbose
1051
1899
 
1052
1900
  // src/tools/finish-task.ts
1053
1901
  var execAsync2 = promisify(exec);
1902
+ var PROP_CORRECTIONS = {
1903
+ onSubmit: "submitEvent",
1904
+ onCancel: "cancelEvent",
1905
+ headerActions: "actions",
1906
+ loading: "isLoading",
1907
+ fieldNames: "fields"
1908
+ };
1909
+ function autoCorrectProps(schema) {
1910
+ let corrections = 0;
1911
+ JSON.stringify(schema);
1912
+ function walkAndFix(obj) {
1913
+ if (Array.isArray(obj)) {
1914
+ return obj.map(walkAndFix);
1915
+ }
1916
+ if (obj && typeof obj === "object") {
1917
+ const result = {};
1918
+ for (const [key, value] of Object.entries(obj)) {
1919
+ if (key in PROP_CORRECTIONS) {
1920
+ result[PROP_CORRECTIONS[key]] = walkAndFix(value);
1921
+ corrections++;
1922
+ } else {
1923
+ result[key] = walkAndFix(value);
1924
+ }
1925
+ }
1926
+ return result;
1927
+ }
1928
+ return obj;
1929
+ }
1930
+ const fixed = walkAndFix(schema);
1931
+ Object.assign(schema, fixed);
1932
+ return corrections;
1933
+ }
1934
+ function checkCompositionQuality(schema) {
1935
+ const warnings = [];
1936
+ const orbitals = schema.orbitals;
1937
+ if (!Array.isArray(orbitals)) return warnings;
1938
+ for (const orbital of orbitals) {
1939
+ const orbObj = orbital;
1940
+ const traits = orbObj.traits;
1941
+ if (!Array.isArray(traits)) continue;
1942
+ for (const trait of traits) {
1943
+ const traitObj = trait;
1944
+ const transitions = traitObj.stateMachine?.transitions;
1945
+ if (!Array.isArray(transitions)) continue;
1946
+ for (const transition of transitions) {
1947
+ const trans = transition;
1948
+ if (trans.event !== "INIT") continue;
1949
+ const effects = trans.effects;
1950
+ if (!Array.isArray(effects)) continue;
1951
+ const mainRenderUIs = effects.filter(
1952
+ (e) => Array.isArray(e) && e[0] === "render-ui" && e[1] === "main"
1953
+ );
1954
+ if (mainRenderUIs.length > 1) {
1955
+ warnings.push(
1956
+ `\u26A0\uFE0F ${orbObj.name}/${traitObj.name} INIT has ${mainRenderUIs.length} flat render-ui calls to main. Should be a single composed stack with children.`
1957
+ );
1958
+ }
1959
+ if (mainRenderUIs.length === 1) {
1960
+ const renderPayload = mainRenderUIs[0];
1961
+ const payload = renderPayload[2];
1962
+ if (payload && payload.type !== "stack" && !payload.children) {
1963
+ warnings.push(
1964
+ `\u26A0\uFE0F ${orbObj.name}/${traitObj.name} INIT renders a single flat ${payload.type} to main. Should be a composed stack with header, metrics, and data sections.`
1965
+ );
1966
+ }
1967
+ }
1968
+ }
1969
+ }
1970
+ }
1971
+ return warnings;
1972
+ }
1054
1973
  async function collectOrbitalsFromDir(workDir) {
1055
1974
  const orbitalsDir = path.join(workDir, ".orbitals");
1056
1975
  try {
@@ -1105,6 +2024,8 @@ function createFinishTaskTool(workDir) {
1105
2024
  let stats = null;
1106
2025
  let validationResult = null;
1107
2026
  let source = null;
2027
+ let propCorrections = 0;
2028
+ let compositionWarnings = [];
1108
2029
  if (workDir) {
1109
2030
  const orbitals = await collectOrbitalsFromDir(workDir);
1110
2031
  if (orbitals.length > 0) {
@@ -1149,6 +2070,10 @@ function createFinishTaskTool(workDir) {
1149
2070
  } catch {
1150
2071
  }
1151
2072
  }
2073
+ if (combinedSchema) {
2074
+ propCorrections = autoCorrectProps(combinedSchema);
2075
+ compositionWarnings = checkCompositionQuality(combinedSchema);
2076
+ }
1152
2077
  if (combinedSchema) {
1153
2078
  const schemaPath = path.join(workDir, "schema.json");
1154
2079
  await fs4.writeFile(schemaPath, JSON.stringify(combinedSchema, null, 2));
@@ -1168,6 +2093,10 @@ function createFinishTaskTool(workDir) {
1168
2093
  errorCount: validationResult.errors?.length || 0,
1169
2094
  warningCount: validationResult.warnings?.length || 0
1170
2095
  } : void 0,
2096
+ designQuality: {
2097
+ propCorrections: propCorrections || 0,
2098
+ compositionWarnings: compositionWarnings || []
2099
+ },
1171
2100
  schemaPath: combinedSchema ? path.join(workDir, "schema.json") : input.schemaPath,
1172
2101
  nextAction: "NONE - Task is complete. Output a brief success message to the user."
1173
2102
  };
@@ -1455,8 +2384,8 @@ function createGenerateOrbitalDomainTool(options = {}) {
1455
2384
  orbitalName: spec.name
1456
2385
  });
1457
2386
  const client = new LLMClient({
1458
- provider: "anthropic",
1459
- model: "claude-sonnet-4-20250514",
2387
+ provider: options.provider ?? "anthropic",
2388
+ model: options.model ?? "claude-sonnet-4-20250514",
1460
2389
  temperature: 0
1461
2390
  });
1462
2391
  const userPrompt = buildDynamicUserPrompt(spec);
@@ -1662,20 +2591,8 @@ function createDomainOrbitalTools(options = {}) {
1662
2591
 
1663
2592
  // src/orbitals/generation/orbital-generator.ts
1664
2593
  init_cache();
1665
- function getEntityName2(entity) {
1666
- if (isEntityReference(entity)) {
1667
- return entity.replace(".entity", "");
1668
- }
1669
- return entity.name;
1670
- }
1671
- function createLog(level, message, data) {
1672
- return {
1673
- timestamp: Date.now(),
1674
- level,
1675
- message,
1676
- data
1677
- };
1678
- }
2594
+ init_shared();
2595
+ init_shared();
1679
2596
  async function generateFullOrbital(client, orbital, options = {}) {
1680
2597
  const {
1681
2598
  maxTokens = 8192,
@@ -1936,8 +2853,8 @@ function createOrbitalSubagentTool(options = {}) {
1936
2853
  ]
1937
2854
  });
1938
2855
  const client = new LLMClient({
1939
- provider: "anthropic",
1940
- model: "claude-sonnet-4-20250514"
2856
+ provider: options.provider ?? "anthropic",
2857
+ model: options.model ?? "claude-sonnet-4-20250514"
1941
2858
  });
1942
2859
  emitEvent(orbitalUnit.name, orbitalIndex, totalOrbitals, "tool_call", {
1943
2860
  tool: "llm_generate",
@@ -2054,6 +2971,9 @@ USAGE:
2054
2971
  setOrbitalCompleteCallback
2055
2972
  };
2056
2973
  }
2974
+
2975
+ // src/tools/index.ts
2976
+ init_orbital_batch_subagent();
2057
2977
  var TraitSpecSchema = z.object({
2058
2978
  name: z.string().describe("Name of the trait"),
2059
2979
  description: z.string().describe("Description of what this trait does"),
@@ -2328,273 +3248,6 @@ function createSchemaChunkingTools(workDir) {
2328
3248
  applyChunk: createApplyChunkTool(workDir)
2329
3249
  };
2330
3250
  }
2331
- var designCache = /* @__PURE__ */ new Map();
2332
- var CACHE_TTL_MS2 = 24 * 60 * 60 * 1e3;
2333
- var CACHE_VERSION2 = 1;
2334
- function generateFingerprint2(input) {
2335
- const normalized = JSON.stringify({
2336
- version: CACHE_VERSION2,
2337
- ...input
2338
- });
2339
- return crypto.createHash("sha256").update(normalized).digest("hex").slice(0, 16);
2340
- }
2341
- function getCached2(fingerprint) {
2342
- const entry = designCache.get(fingerprint);
2343
- if (!entry) return null;
2344
- if (Date.now() - entry.timestamp > CACHE_TTL_MS2) {
2345
- designCache.delete(fingerprint);
2346
- return null;
2347
- }
2348
- return entry;
2349
- }
2350
- var STATIC_DESIGN_PROMPT = null;
2351
- function getDesignSystemPrompt() {
2352
- if (!STATIC_DESIGN_PROMPT) {
2353
- const skill = generateKflowDesignSkill();
2354
- STATIC_DESIGN_PROMPT = skill.content;
2355
- }
2356
- return STATIC_DESIGN_PROMPT;
2357
- }
2358
- function getPatternRecommendations(input) {
2359
- const recContext = buildRecommendationContext({
2360
- state: input.from,
2361
- event: input.event,
2362
- slot: input.slot,
2363
- domainCategory: input.domainCategory,
2364
- entityFields: input.entityFields
2365
- });
2366
- return recommendPatterns(recContext, 8);
2367
- }
2368
- function buildDesignUserPrompt(input) {
2369
- const fieldList = input.entityFields.map((f) => {
2370
- let desc = ` - ${f.name}: ${f.type}`;
2371
- if (f.values) desc += ` (values: ${f.values.join(", ")})`;
2372
- return desc;
2373
- }).join("\n");
2374
- const hints = [];
2375
- if (input.designStyle) hints.push(`Style: ${input.designStyle}`);
2376
- if (input.flowPattern) hints.push(`Flow: ${input.flowPattern}`);
2377
- if (input.listPattern) hints.push(`List: ${input.listPattern}`);
2378
- if (input.formPattern) hints.push(`Form: ${input.formPattern}`);
2379
- if (input.detailPattern) hints.push(`Detail: ${input.detailPattern}`);
2380
- const vocab = input.vocabulary ? Object.entries(input.vocabulary).map(([k, v]) => ` ${k} \u2192 "${v}"`).join("\n") : "";
2381
- return `Design render-ui effects for this transition:
2382
-
2383
- ## Transition
2384
- - **From**: ${input.from}
2385
- - **To**: ${input.to}
2386
- - **Event**: ${input.event}
2387
- - **Slot**: ${input.slot}
2388
-
2389
- ## Entity: ${input.entityName}
2390
- ${fieldList}
2391
-
2392
- ## Domain
2393
- - **Category**: ${input.domainCategory || "business"}
2394
- ${vocab ? `- **Vocabulary**:
2395
- ${vocab}` : ""}
2396
- ${hints.length > 0 ? `- **Design Hints**: ${hints.join(", ")}` : ""}
2397
-
2398
- ${input.recommendationsSection || ""}
2399
-
2400
- ${input.existingEffects ? `## Existing Effects (enhance these)
2401
- \`\`\`json
2402
- ${JSON.stringify(input.existingEffects, null, 2)}
2403
- \`\`\`` : ""}
2404
-
2405
- Return ONLY the JSON array of render-ui effect tuples.`;
2406
- }
2407
- var DesignTransitionSchema = z.object({
2408
- from: z.string().describe('Source state name (e.g., "Browsing")'),
2409
- to: z.string().describe('Target state name (e.g., "Browsing" for self-loop, "Creating" for transition)'),
2410
- event: z.string().describe('Event name (e.g., "INIT", "CREATE", "VIEW", "EDIT", "DELETE", "SAVE", "CANCEL")'),
2411
- slot: z.string().describe('UI slot to render into: "main", "modal", "drawer", "sidebar", "overlay"'),
2412
- entityName: z.string().describe('Entity name (e.g., "Task", "Order")'),
2413
- entityFields: z.array(z.object({
2414
- name: z.string(),
2415
- type: z.string(),
2416
- values: z.array(z.string()).optional()
2417
- })).describe("Entity fields with types and optional enum values"),
2418
- domainCategory: AgentDomainCategorySchema.optional().describe("Domain category for pattern selection"),
2419
- vocabulary: z.record(z.string(), z.string()).optional().describe('Domain vocabulary mapping (e.g., { "create": "Place Order", "item": "Order" })'),
2420
- designStyle: z.enum(["minimal", "modern", "playful", "data-driven", "immersive"]).optional().describe("Visual style hint"),
2421
- flowPattern: z.enum(["hub-spoke", "master-detail", "crud-cycle", "linear", "role-based"]).optional().describe("Application flow pattern"),
2422
- listPattern: z.enum(["entity-table", "entity-cards", "entity-list"]).optional().describe("Preferred list pattern"),
2423
- formPattern: z.enum(["modal", "drawer", "page"]).optional().describe("Preferred form pattern"),
2424
- detailPattern: z.enum(["drawer", "page", "split"]).optional().describe("Preferred detail view pattern"),
2425
- existingEffects: z.array(z.any()).optional().describe("Existing render-ui effects to enhance (for refinement passes)")
2426
- });
2427
- function createDesignTransitionTool(options = {}) {
2428
- let eventCallback = options.onEvent;
2429
- const setEventCallback = (callback) => {
2430
- eventCallback = callback;
2431
- };
2432
- const emitEvent = (transitionId, type, data) => {
2433
- if (eventCallback) {
2434
- eventCallback(transitionId, {
2435
- type,
2436
- data,
2437
- timestamp: Date.now()
2438
- });
2439
- }
2440
- };
2441
- const designTransitionTool = tool(
2442
- async (input) => {
2443
- const transitionId = `${input.entityName}:${input.from}->${input.to}:${input.event}`;
2444
- const fingerprint = generateFingerprint2({
2445
- from: input.from,
2446
- to: input.to,
2447
- event: input.event,
2448
- slot: input.slot,
2449
- entityName: input.entityName,
2450
- entityFields: input.entityFields,
2451
- domainCategory: input.domainCategory,
2452
- designStyle: input.designStyle,
2453
- flowPattern: input.flowPattern,
2454
- listPattern: input.listPattern
2455
- });
2456
- try {
2457
- emitEvent(transitionId, "message", {
2458
- content: `Designing UI for ${transitionId}`,
2459
- role: "assistant",
2460
- isComplete: false
2461
- });
2462
- const cached = getCached2(fingerprint);
2463
- if (cached) {
2464
- emitEvent(transitionId, "generation_log", {
2465
- level: "info",
2466
- message: `Design cache HIT for ${transitionId}`,
2467
- data: { fingerprint }
2468
- });
2469
- return JSON.stringify({
2470
- success: true,
2471
- transitionId,
2472
- effects: cached.effects,
2473
- cached: true,
2474
- usage: cached.usage
2475
- });
2476
- }
2477
- const recommendations = getPatternRecommendations(input);
2478
- const recommendationsSection = formatRecommendationsForPrompt(recommendations);
2479
- const systemPrompt = getDesignSystemPrompt();
2480
- const userPrompt = buildDesignUserPrompt({
2481
- ...input,
2482
- recommendationsSection
2483
- });
2484
- emitEvent(transitionId, "tool_call", {
2485
- tool: "llm_design_transition",
2486
- args: {
2487
- transition: transitionId,
2488
- slot: input.slot,
2489
- domain: input.domainCategory
2490
- }
2491
- });
2492
- const client = new LLMClient({
2493
- provider: "anthropic",
2494
- model: "claude-sonnet-4-20250514",
2495
- temperature: 0.1
2496
- // Slight creativity for design
2497
- });
2498
- const response = await client.callWithCache({
2499
- systemPrompt: "",
2500
- systemBlocks: [{
2501
- type: "text",
2502
- text: systemPrompt,
2503
- cache_control: { type: "ephemeral" }
2504
- }],
2505
- userPrompt,
2506
- maxTokens: 4096,
2507
- rawText: true
2508
- });
2509
- const rawText = (response.raw || String(response.data) || "").trim();
2510
- let effects;
2511
- try {
2512
- const jsonText = rawText.replace(/^```(?:json)?\n?/m, "").replace(/\n?```$/m, "").trim();
2513
- effects = JSON.parse(jsonText);
2514
- if (!Array.isArray(effects)) {
2515
- effects = [effects];
2516
- }
2517
- } catch {
2518
- return JSON.stringify({
2519
- success: false,
2520
- transitionId,
2521
- error: "Failed to parse design output as JSON",
2522
- rawOutput: rawText
2523
- });
2524
- }
2525
- const usage = {
2526
- inputTokens: response.usage?.promptTokens || 0,
2527
- outputTokens: response.usage?.completionTokens || 0,
2528
- totalTokens: response.usage?.totalTokens || 0
2529
- };
2530
- designCache.set(fingerprint, {
2531
- effects,
2532
- timestamp: Date.now(),
2533
- usage
2534
- });
2535
- emitEvent(transitionId, "tool_result", {
2536
- tool: "llm_design_transition",
2537
- result: { fingerprint, effectCount: effects.length, usage },
2538
- success: true
2539
- });
2540
- emitEvent(transitionId, "message", {
2541
- content: `Designed ${effects.length} effect(s) for ${transitionId} (${usage.totalTokens} tokens)`,
2542
- role: "assistant",
2543
- isComplete: true
2544
- });
2545
- return JSON.stringify({
2546
- success: true,
2547
- transitionId,
2548
- effects,
2549
- cached: false,
2550
- usage,
2551
- recommendedPatterns: recommendations.map((r) => r.pattern)
2552
- });
2553
- } catch (error) {
2554
- const errorMessage = error instanceof Error ? error.message : String(error);
2555
- emitEvent(transitionId, "error", {
2556
- error: errorMessage,
2557
- code: "DESIGN_TRANSITION_ERROR"
2558
- });
2559
- return JSON.stringify({
2560
- success: false,
2561
- transitionId,
2562
- error: errorMessage
2563
- });
2564
- }
2565
- },
2566
- {
2567
- name: "design_transition",
2568
- description: `Design rich render-ui effects for a single orbital transition.
2569
-
2570
- Takes the transition context (from/to state, event, entity, domain) and produces
2571
- polished render-ui effects using the full pattern catalog.
2572
-
2573
- USE THIS TOOL WHEN:
2574
- - Generating INIT transitions (always compose header + stats + content)
2575
- - Generating CREATE/EDIT transitions (form with proper fields)
2576
- - Generating VIEW transitions (detail with tabs for related entities)
2577
- - Enhancing existing render-ui effects that are too basic
2578
-
2579
- The tool uses a specialized design skill with pattern catalog, layout composition,
2580
- and domain-aware pattern selection to produce rich UI.
2581
-
2582
- RETURNS: { success, effects: [["render-ui", slot, config], ...], transitionId, usage }
2583
-
2584
- The effects array contains ONLY render-ui tuples. Non-UI effects (persist, emit, set)
2585
- are NOT included \u2014 you must preserve those from the original transition.
2586
-
2587
- INTEGRATION: After calling this tool, use extract_chunk to get the orbital,
2588
- replace the render-ui effects in the target transition (keep persist/emit/set effects),
2589
- then apply_chunk to merge back into schema.json.`,
2590
- schema: DesignTransitionSchema
2591
- }
2592
- );
2593
- return {
2594
- tool: designTransitionTool,
2595
- setEventCallback
2596
- };
2597
- }
2598
3251
  function createGitHubTools(config) {
2599
3252
  const { token, owner = "", repo = "", workDir } = config;
2600
3253
  const integrationConfig = {
@@ -3693,6 +4346,21 @@ function createLLMClient(provider, model, verbose) {
3693
4346
  model: model || OPENAI_MODELS.GPT4O,
3694
4347
  temperature
3695
4348
  });
4349
+ case "kimi":
4350
+ if (verbose)
4351
+ console.log(`[SkillAgent] Using Kimi: ${model || KIMI_MODELS.K2_5}`);
4352
+ return createKimiClient({
4353
+ model: model || KIMI_MODELS.K2_5,
4354
+ temperature: 0.6
4355
+ // Kimi with thinking disabled requires 0.6
4356
+ });
4357
+ case "openrouter":
4358
+ if (verbose)
4359
+ console.log(`[SkillAgent] Using OpenRouter: ${model || OPENROUTER_MODELS.QWEN_2_5_72B}`);
4360
+ return createOpenRouterClient({
4361
+ model: model || OPENROUTER_MODELS.QWEN_2_5_72B,
4362
+ temperature: 0.3
4363
+ });
3696
4364
  case "anthropic":
3697
4365
  default:
3698
4366
  if (verbose)
@@ -3717,6 +4385,8 @@ async function createSkillAgent(options) {
3717
4385
  threadId: providedThreadId,
3718
4386
  provider = "anthropic",
3719
4387
  model,
4388
+ subagentProvider = "anthropic",
4389
+ subagentModel = "claude-sonnet-4-20250514",
3720
4390
  verbose = false,
3721
4391
  skillLoader,
3722
4392
  skillRefLoader
@@ -3813,20 +4483,24 @@ ${skillContents}`;
3813
4483
  const finishTaskTool = createFinishTaskTool(workDir);
3814
4484
  const validateSchemaTool = createValidateSchemaTool(workDir);
3815
4485
  const ORBITAL_SKILLS = ["kflow-orbitals", "kflow-orbital-games", "kflow-orbital-fixing"];
4486
+ const ORBITAL_BATCH_SKILLS = ["kflow-orbitals-batch"];
3816
4487
  const LEAN_SKILLS = ["kflow-lean-orbitals", "kflow-lean-fixing"];
3817
- const DESIGN_SKILLS = ["kflow-design", "kflow-lean-design"];
3818
4488
  const isOrbitalSkill = primarySkill.name === "kflow-orbitals";
4489
+ const isOrbitalBatchSkill = ORBITAL_BATCH_SKILLS.includes(primarySkill.name);
3819
4490
  const isLeanSkill = LEAN_SKILLS.includes(primarySkill.name);
3820
- const isDesignSkill = DESIGN_SKILLS.includes(primarySkill.name);
3821
- const needsChunkingTools = ORBITAL_SKILLS.includes(primarySkill.name) || isDesignSkill;
4491
+ const needsChunkingTools = ORBITAL_SKILLS.includes(primarySkill.name);
3822
4492
  let orbitalTool;
3823
4493
  let setOrbitalEventCallback;
3824
4494
  let setOrbitalCompleteCallback;
4495
+ let orbitalBatchTool;
4496
+ let setBatchEventCallback;
3825
4497
  let domainOrbitalTools;
3826
4498
  const chunkingTools = needsChunkingTools ? createSchemaChunkingTools(workDir) : null;
3827
4499
  if (isOrbitalSkill) {
3828
4500
  const orbitalResult = createOrbitalSubagentTool({
3829
- requirements: options.requirements
4501
+ requirements: options.requirements,
4502
+ provider: subagentProvider,
4503
+ model: subagentModel
3830
4504
  });
3831
4505
  orbitalTool = orbitalResult.tool;
3832
4506
  setOrbitalEventCallback = orbitalResult.setEventCallback;
@@ -3838,16 +4512,32 @@ ${skillContents}`;
3838
4512
  console.log(`[SkillAgent] Orbital tools enabled for kflow-orbitals skill`);
3839
4513
  }
3840
4514
  }
3841
- if (isLeanSkill) {
3842
- domainOrbitalTools = createDomainOrbitalTools({ workDir });
4515
+ if (isOrbitalBatchSkill) {
4516
+ const { createOrbitalBatchSubagentTool: createOrbitalBatchSubagentTool2 } = await Promise.resolve().then(() => (init_orbital_batch_subagent(), orbital_batch_subagent_exports));
4517
+ const batchResult = createOrbitalBatchSubagentTool2({
4518
+ requirements: options.requirements,
4519
+ provider: subagentProvider,
4520
+ model: subagentModel,
4521
+ workDir
4522
+ });
4523
+ orbitalBatchTool = batchResult.tool;
4524
+ setBatchEventCallback = batchResult.setEventCallback;
4525
+ if (options.onSubagentEvent) {
4526
+ setBatchEventCallback(options.onSubagentEvent);
4527
+ }
3843
4528
  if (verbose) {
3844
- console.log(`[SkillAgent] Domain orbital tools enabled for ${primarySkill.name} skill`);
4529
+ console.log(`[SkillAgent] Batch orbital tools enabled for kflow-orbitals-batch skill`);
3845
4530
  }
3846
4531
  }
3847
- const needsDesignTool = isDesignSkill || ORBITAL_SKILLS.includes(primarySkill.name);
3848
- const designTool = needsDesignTool ? createDesignTransitionTool() : null;
3849
- if (designTool && verbose) {
3850
- console.log(`[SkillAgent] Design transition tool enabled for ${primarySkill.name} skill`);
4532
+ if (isLeanSkill) {
4533
+ domainOrbitalTools = createDomainOrbitalTools({
4534
+ workDir,
4535
+ provider: subagentProvider,
4536
+ model: subagentModel
4537
+ });
4538
+ if (verbose) {
4539
+ console.log(`[SkillAgent] Domain orbital tools enabled for ${primarySkill.name} skill (provider: ${subagentProvider})`);
4540
+ }
3851
4541
  }
3852
4542
  const githubTools = options.githubConfig ? createGitHubToolsArray({
3853
4543
  token: options.githubConfig.token,
@@ -3861,8 +4551,9 @@ ${skillContents}`;
3861
4551
  const tools = [
3862
4552
  executeTool,
3863
4553
  finishTaskTool,
3864
- ...isOrbitalSkill ? [] : [validateSchemaTool],
4554
+ ...isOrbitalSkill || isOrbitalBatchSkill ? [] : [validateSchemaTool],
3865
4555
  ...orbitalTool ? [orbitalTool] : [],
4556
+ ...orbitalBatchTool ? [orbitalBatchTool] : [],
3866
4557
  ...domainOrbitalTools ? [
3867
4558
  domainOrbitalTools.generateOrbitalDomain,
3868
4559
  domainOrbitalTools.constructCombinedDomain
@@ -3872,7 +4563,6 @@ ${skillContents}`;
3872
4563
  chunkingTools.extractChunk,
3873
4564
  chunkingTools.applyChunk
3874
4565
  ] : [],
3875
- ...designTool ? [designTool.tool] : [],
3876
4566
  ...githubTools || []
3877
4567
  ];
3878
4568
  const checkpointer = sessions.getCheckpointer(threadId);