@bluecopa/harness 0.1.0-snapshot.21 → 0.1.0-snapshot.22

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bluecopa/harness",
3
- "version": "0.1.0-snapshot.21",
3
+ "version": "0.1.0-snapshot.22",
4
4
  "description": "Provider-agnostic TypeScript agent framework",
5
5
  "license": "UNLICENSED",
6
6
  "scripts": {
@@ -366,6 +366,8 @@ export interface CreateProcessConfig {
366
366
  parentSignal: AbortSignal;
367
367
  /** Custom system prompt for this process (overrides PROCESS_SYSTEM_PROMPT). */
368
368
  processSystemPrompt?: string;
369
+ /** Async skill instructions to prepend to system prompt (resolved during process startup). */
370
+ skillPromptPromise?: Promise<string | null>;
369
371
 
370
372
  // Runtime extras
371
373
  hookRunner?: HookRunner;
@@ -421,12 +423,21 @@ export function createProcess(
421
423
  process.status = 'running';
422
424
  const seed = await seedPromise;
423
425
 
426
+ // Build system prompt: base + optional skill instructions
427
+ let systemPrompt = config.processSystemPrompt ?? PROCESS_SYSTEM_PROMPT;
428
+ if (config.skillPromptPromise) {
429
+ const skillInstructions = await config.skillPromptPromise;
430
+ if (skillInstructions) {
431
+ systemPrompt += '\n\n## Skill Instructions\n' + skillInstructions;
432
+ }
433
+ }
434
+
424
435
  const result = await Promise.race([
425
436
  runner.run({
426
437
  model,
427
438
  prompt: request.action,
428
439
  tools: config.processTools,
429
- systemPrompt: config.processSystemPrompt ?? PROCESS_SYSTEM_PROMPT,
440
+ systemPrompt,
430
441
  toolProvider: config.toolProvider,
431
442
  maxSteps,
432
443
  signal: ac.signal,
@@ -27,6 +27,9 @@ import { createProcess, firstEvent } from './agent-runner';
27
27
  import { EpisodeCompressor } from './episode-compressor';
28
28
  import { runConsolidation } from './consolidation';
29
29
  import { pickDefined } from './utils';
30
+ import { SkillRouter } from '../skills/skill-router';
31
+ import { loadSkillFromFile } from '../skills/skill-loader';
32
+ import type { SkillSummary } from '../skills/skill-types';
30
33
 
31
34
  // ── Default orchestrator prompt ──
32
35
 
@@ -75,6 +78,9 @@ export class ArcLoop {
75
78
  private readonly traceWriter: ((event: TraceEvent) => void) | undefined;
76
79
  private readonly tracedRunning = new Set<string>();
77
80
  private readonly processListeners: Promise<void>[] = [];
81
+ private readonly skillRouter: SkillRouter | undefined;
82
+ private skillSummaries: SkillSummary[] | null = null;
83
+ private skillSummariesPromise: Promise<SkillSummary[]> | null = null;
78
84
 
79
85
  constructor(config: ArcLoopConfig) {
80
86
  this.config = config;
@@ -114,6 +120,15 @@ export class ArcLoop {
114
120
 
115
121
  this.resilience = config.resilience;
116
122
  this.traceWriter = (config as ArcLoopConfig & { traceWriter?: (event: TraceEvent) => void }).traceWriter;
123
+
124
+ if (config.skillIndexPath) {
125
+ this.skillRouter = new SkillRouter();
126
+ // Lazy-load skill summaries on first dispatch
127
+ this.skillSummariesPromise = import('node:fs/promises')
128
+ .then(fs => fs.readFile(config.skillIndexPath!, 'utf-8'))
129
+ .then(raw => JSON.parse(raw) as SkillSummary[])
130
+ .catch(() => []);
131
+ }
117
132
  }
118
133
 
119
134
  private trace(kind: TraceEvent['kind']): void {
@@ -457,6 +472,12 @@ export class ArcLoop {
457
472
  this.modelMap,
458
473
  this.modelMap.medium,
459
474
  );
475
+
476
+ // Resolve skill instructions only when skills are configured
477
+ const skillPromptPromise = this.skillRouter
478
+ ? this.resolveSkillPrompt(request.action)
479
+ : undefined;
480
+
460
481
  const proc = createProcess(request, {
461
482
  toolProvider: this.config.toolProvider,
462
483
  episodeStore: this.config.episodeStore,
@@ -469,6 +490,7 @@ export class ArcLoop {
469
490
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
470
491
  processTools: (profile?.tools ?? this.config.processTools ?? builtinTools) as any,
471
492
  processSystemPrompt: profile?.systemPrompt ?? this.config.processSystemPrompt,
493
+ skillPromptPromise,
472
494
  parentSignal,
473
495
  ...pickDefined(this.config, [
474
496
  'hookRunner',
@@ -483,6 +505,28 @@ export class ArcLoop {
483
505
  return proc;
484
506
  }
485
507
 
508
+ /** Resolve skill instructions for a process action. Returns null if no skill matched. */
509
+ private async resolveSkillPrompt(action: string): Promise<string | null> {
510
+ if (!this.skillRouter || !this.skillSummariesPromise) return null;
511
+
512
+ // Ensure summaries are loaded
513
+ if (!this.skillSummaries) {
514
+ this.skillSummaries = await this.skillSummariesPromise;
515
+ }
516
+ if (this.skillSummaries.length === 0) return null;
517
+
518
+ // Fast match only (keyword + alias, no LLM call)
519
+ const matched = await this.skillRouter.selectSkill(action, this.skillSummaries);
520
+ if (!matched) return null;
521
+
522
+ try {
523
+ const skill = await loadSkillFromFile(matched.path);
524
+ return skill.instructions || null;
525
+ } catch {
526
+ return null;
527
+ }
528
+ }
529
+
486
530
  private traceProcessRunning(procId: string): void {
487
531
  if (!this.tracedRunning.has(procId)) {
488
532
  this.tracedRunning.add(procId);