@bluecopa/harness 0.1.0-snapshot.21 → 0.1.0-snapshot.23
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 +1 -1
- package/src/arc/agent-runner.ts +12 -1
- package/src/arc/arc-loop.ts +48 -2
package/package.json
CHANGED
package/src/arc/agent-runner.ts
CHANGED
|
@@ -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
|
|
440
|
+
systemPrompt,
|
|
430
441
|
toolProvider: config.toolProvider,
|
|
431
442
|
maxSteps,
|
|
432
443
|
signal: ac.signal,
|
package/src/arc/arc-loop.ts
CHANGED
|
@@ -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 {
|
|
@@ -375,8 +390,10 @@ export class ArcLoop {
|
|
|
375
390
|
}],
|
|
376
391
|
});
|
|
377
392
|
} else if (this.config.onOrchestratorTool) {
|
|
378
|
-
await this.config.onOrchestratorTool(call.toolName, call.args);
|
|
379
|
-
const resultText =
|
|
393
|
+
const action = await this.config.onOrchestratorTool(call.toolName, call.args);
|
|
394
|
+
const resultText = (action && 'content' in action && typeof action.content === 'string')
|
|
395
|
+
? action.content
|
|
396
|
+
: `${call.toolName}: handled`;
|
|
380
397
|
toolResultMessages.push({
|
|
381
398
|
role: 'tool',
|
|
382
399
|
content: resultText,
|
|
@@ -457,6 +474,12 @@ export class ArcLoop {
|
|
|
457
474
|
this.modelMap,
|
|
458
475
|
this.modelMap.medium,
|
|
459
476
|
);
|
|
477
|
+
|
|
478
|
+
// Resolve skill instructions only when skills are configured
|
|
479
|
+
const skillPromptPromise = this.skillRouter
|
|
480
|
+
? this.resolveSkillPrompt(request.action)
|
|
481
|
+
: undefined;
|
|
482
|
+
|
|
460
483
|
const proc = createProcess(request, {
|
|
461
484
|
toolProvider: this.config.toolProvider,
|
|
462
485
|
episodeStore: this.config.episodeStore,
|
|
@@ -469,6 +492,7 @@ export class ArcLoop {
|
|
|
469
492
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
470
493
|
processTools: (profile?.tools ?? this.config.processTools ?? builtinTools) as any,
|
|
471
494
|
processSystemPrompt: profile?.systemPrompt ?? this.config.processSystemPrompt,
|
|
495
|
+
skillPromptPromise,
|
|
472
496
|
parentSignal,
|
|
473
497
|
...pickDefined(this.config, [
|
|
474
498
|
'hookRunner',
|
|
@@ -483,6 +507,28 @@ export class ArcLoop {
|
|
|
483
507
|
return proc;
|
|
484
508
|
}
|
|
485
509
|
|
|
510
|
+
/** Resolve skill instructions for a process action. Returns null if no skill matched. */
|
|
511
|
+
private async resolveSkillPrompt(action: string): Promise<string | null> {
|
|
512
|
+
if (!this.skillRouter || !this.skillSummariesPromise) return null;
|
|
513
|
+
|
|
514
|
+
// Ensure summaries are loaded
|
|
515
|
+
if (!this.skillSummaries) {
|
|
516
|
+
this.skillSummaries = await this.skillSummariesPromise;
|
|
517
|
+
}
|
|
518
|
+
if (this.skillSummaries.length === 0) return null;
|
|
519
|
+
|
|
520
|
+
// Fast match only (keyword + alias, no LLM call)
|
|
521
|
+
const matched = await this.skillRouter.selectSkill(action, this.skillSummaries);
|
|
522
|
+
if (!matched) return null;
|
|
523
|
+
|
|
524
|
+
try {
|
|
525
|
+
const skill = await loadSkillFromFile(matched.path);
|
|
526
|
+
return skill.instructions || null;
|
|
527
|
+
} catch {
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
486
532
|
private traceProcessRunning(procId: string): void {
|
|
487
533
|
if (!this.tracedRunning.has(procId)) {
|
|
488
534
|
this.tracedRunning.add(procId);
|