@bluecopa/harness 0.0.0-snapshot.137
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/README.md +316 -0
- package/dist/arc/index.d.ts +796 -0
- package/dist/arc/index.js +2863 -0
- package/dist/arc/index.js.map +1 -0
- package/dist/observability/otel.d.ts +36 -0
- package/dist/observability/otel.js +73 -0
- package/dist/observability/otel.js.map +1 -0
- package/dist/shared-types-DRxnerLT.d.ts +138 -0
- package/dist/skills/index.d.ts +67 -0
- package/dist/skills/index.js +282 -0
- package/dist/skills/index.js.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
interface SpanRecord {
|
|
2
|
+
traceId: string;
|
|
3
|
+
spanId: string;
|
|
4
|
+
parentSpanId?: string | undefined;
|
|
5
|
+
name: string;
|
|
6
|
+
attributes: Record<string, string | number | boolean>;
|
|
7
|
+
startTime: number;
|
|
8
|
+
endTime: number;
|
|
9
|
+
}
|
|
10
|
+
interface MetricRecord {
|
|
11
|
+
name: string;
|
|
12
|
+
value: number;
|
|
13
|
+
type: 'counter' | 'histogram';
|
|
14
|
+
attributes: Record<string, string | number | boolean>;
|
|
15
|
+
}
|
|
16
|
+
interface SpanHandle {
|
|
17
|
+
traceId: string;
|
|
18
|
+
spanId: string;
|
|
19
|
+
end(attributes?: Record<string, string | number | boolean>): void;
|
|
20
|
+
}
|
|
21
|
+
declare class HarnessTelemetry {
|
|
22
|
+
private readonly enabled;
|
|
23
|
+
private readonly context;
|
|
24
|
+
private readonly spans;
|
|
25
|
+
private readonly metrics;
|
|
26
|
+
constructor(enabled?: boolean);
|
|
27
|
+
isEnabled(): boolean;
|
|
28
|
+
startSpan(name: string, attributes?: Record<string, string | number | boolean>): SpanHandle;
|
|
29
|
+
withSpan<T>(name: string, attributes: Record<string, string | number | boolean>, fn: () => Promise<T>): Promise<T>;
|
|
30
|
+
counter(name: string, value?: number, attributes?: Record<string, string | number | boolean>): void;
|
|
31
|
+
histogram(name: string, value: number, attributes?: Record<string, string | number | boolean>): void;
|
|
32
|
+
getSpans(): SpanRecord[];
|
|
33
|
+
getMetrics(): MetricRecord[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export { HarnessTelemetry, type MetricRecord, type SpanHandle, type SpanRecord };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
|
|
4
|
+
// src/observability/otel.ts
|
|
5
|
+
var HarnessTelemetry = class {
|
|
6
|
+
constructor(enabled = true) {
|
|
7
|
+
this.enabled = enabled;
|
|
8
|
+
}
|
|
9
|
+
context = new AsyncLocalStorage();
|
|
10
|
+
spans = [];
|
|
11
|
+
metrics = [];
|
|
12
|
+
isEnabled() {
|
|
13
|
+
return this.enabled;
|
|
14
|
+
}
|
|
15
|
+
startSpan(name, attributes = {}) {
|
|
16
|
+
const now = Date.now();
|
|
17
|
+
const parent = this.context.getStore();
|
|
18
|
+
const traceId = parent?.traceId ?? randomUUID().replace(/-/g, "");
|
|
19
|
+
const spanId = randomUUID().replace(/-/g, "").slice(0, 16);
|
|
20
|
+
const record = {
|
|
21
|
+
traceId,
|
|
22
|
+
spanId,
|
|
23
|
+
parentSpanId: parent?.spanId,
|
|
24
|
+
name,
|
|
25
|
+
attributes: { ...attributes },
|
|
26
|
+
startTime: now,
|
|
27
|
+
endTime: now
|
|
28
|
+
};
|
|
29
|
+
this.spans.push(record);
|
|
30
|
+
return {
|
|
31
|
+
traceId,
|
|
32
|
+
spanId,
|
|
33
|
+
end: (extra = {}) => {
|
|
34
|
+
record.endTime = Date.now();
|
|
35
|
+
record.attributes = { ...record.attributes, ...extra };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
async withSpan(name, attributes, fn) {
|
|
40
|
+
if (!this.enabled) {
|
|
41
|
+
return fn();
|
|
42
|
+
}
|
|
43
|
+
const span = this.startSpan(name, attributes);
|
|
44
|
+
return this.context.run({ traceId: span.traceId, spanId: span.spanId }, async () => {
|
|
45
|
+
try {
|
|
46
|
+
const result = await fn();
|
|
47
|
+
span.end({ success: true });
|
|
48
|
+
return result;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
span.end({ success: false, error: error instanceof Error ? error.message : "unknown" });
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
counter(name, value = 1, attributes = {}) {
|
|
56
|
+
if (!this.enabled) return;
|
|
57
|
+
this.metrics.push({ name, value, type: "counter", attributes });
|
|
58
|
+
}
|
|
59
|
+
histogram(name, value, attributes = {}) {
|
|
60
|
+
if (!this.enabled) return;
|
|
61
|
+
this.metrics.push({ name, value, type: "histogram", attributes });
|
|
62
|
+
}
|
|
63
|
+
getSpans() {
|
|
64
|
+
return [...this.spans];
|
|
65
|
+
}
|
|
66
|
+
getMetrics() {
|
|
67
|
+
return [...this.metrics];
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export { HarnessTelemetry };
|
|
72
|
+
//# sourceMappingURL=otel.js.map
|
|
73
|
+
//# sourceMappingURL=otel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/observability/otel.ts"],"names":[],"mappings":"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","file":"otel.js","sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport { randomUUID } from 'node:crypto';\n\nexport interface SpanRecord {\n traceId: string;\n spanId: string;\n parentSpanId?: string | undefined;\n name: string;\n attributes: Record<string, string | number | boolean>;\n startTime: number;\n endTime: number;\n}\n\nexport interface MetricRecord {\n name: string;\n value: number;\n type: 'counter' | 'histogram';\n attributes: Record<string, string | number | boolean>;\n}\n\nexport interface SpanHandle {\n traceId: string;\n spanId: string;\n end(attributes?: Record<string, string | number | boolean>): void;\n}\n\nexport class HarnessTelemetry {\n private readonly context = new AsyncLocalStorage<{ traceId: string; spanId: string }>();\n private readonly spans: SpanRecord[] = [];\n private readonly metrics: MetricRecord[] = [];\n\n constructor(private readonly enabled = true) {}\n\n isEnabled(): boolean {\n return this.enabled;\n }\n\n startSpan(name: string, attributes: Record<string, string | number | boolean> = {}): SpanHandle {\n const now = Date.now();\n const parent = this.context.getStore();\n const traceId = parent?.traceId ?? randomUUID().replace(/-/g, '');\n const spanId = randomUUID().replace(/-/g, '').slice(0, 16);\n\n const record: SpanRecord = {\n traceId,\n spanId,\n parentSpanId: parent?.spanId,\n name,\n attributes: { ...attributes },\n startTime: now,\n endTime: now\n };\n\n this.spans.push(record);\n\n return {\n traceId,\n spanId,\n end: (extra = {}) => {\n record.endTime = Date.now();\n record.attributes = { ...record.attributes, ...extra };\n }\n };\n }\n\n async withSpan<T>(\n name: string,\n attributes: Record<string, string | number | boolean>,\n fn: () => Promise<T>\n ): Promise<T> {\n if (!this.enabled) {\n return fn();\n }\n\n const span = this.startSpan(name, attributes);\n return this.context.run({ traceId: span.traceId, spanId: span.spanId }, async () => {\n try {\n const result = await fn();\n span.end({ success: true });\n return result;\n } catch (error) {\n span.end({ success: false, error: error instanceof Error ? error.message : 'unknown' });\n throw error;\n }\n });\n }\n\n counter(name: string, value = 1, attributes: Record<string, string | number | boolean> = {}): void {\n if (!this.enabled) return;\n this.metrics.push({ name, value, type: 'counter', attributes });\n }\n\n histogram(name: string, value: number, attributes: Record<string, string | number | boolean> = {}): void {\n if (!this.enabled) return;\n this.metrics.push({ name, value, type: 'histogram', attributes });\n }\n\n getSpans(): SpanRecord[] {\n return [...this.spans];\n }\n\n getMetrics(): MetricRecord[] {\n return [...this.metrics];\n }\n}\n"]}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { Tool, LanguageModel } from 'ai';
|
|
2
|
+
|
|
3
|
+
/** What a tool call actually did to the world */
|
|
4
|
+
type ActionType = "observed" | "produced" | "modified";
|
|
5
|
+
interface ToolResultArtifact {
|
|
6
|
+
action: ActionType;
|
|
7
|
+
/** file, url, command_output, value */
|
|
8
|
+
kind: string;
|
|
9
|
+
uri: string;
|
|
10
|
+
}
|
|
11
|
+
interface ToolResult {
|
|
12
|
+
success: boolean;
|
|
13
|
+
output: string;
|
|
14
|
+
/** Compact summary for LLM context. When present, sent to the model instead of `output`. */
|
|
15
|
+
modelOutput?: string | undefined;
|
|
16
|
+
error?: string | undefined;
|
|
17
|
+
metadata?: Record<string, unknown> | undefined;
|
|
18
|
+
/** What this tool call affected — set by execute, read by worker/compactor */
|
|
19
|
+
artifact?: ToolResultArtifact | undefined;
|
|
20
|
+
}
|
|
21
|
+
interface BashOptions {
|
|
22
|
+
timeout?: number | undefined;
|
|
23
|
+
cwd?: string | undefined;
|
|
24
|
+
}
|
|
25
|
+
interface ReadOptions {
|
|
26
|
+
lineRange?: [number, number] | undefined;
|
|
27
|
+
maxLines?: number | undefined;
|
|
28
|
+
}
|
|
29
|
+
interface GlobOptions {
|
|
30
|
+
ignore?: string[] | undefined;
|
|
31
|
+
maxResults?: number | undefined;
|
|
32
|
+
}
|
|
33
|
+
interface GrepOptions {
|
|
34
|
+
caseInsensitive?: boolean | undefined;
|
|
35
|
+
wholeWord?: boolean | undefined;
|
|
36
|
+
maxResults?: number | undefined;
|
|
37
|
+
include?: string | undefined;
|
|
38
|
+
}
|
|
39
|
+
interface WebFetchOptions {
|
|
40
|
+
url: string;
|
|
41
|
+
selector?: string | undefined;
|
|
42
|
+
maxContentLength?: number | undefined;
|
|
43
|
+
headers?: Record<string, string> | undefined;
|
|
44
|
+
}
|
|
45
|
+
type TextEditorRequest = {
|
|
46
|
+
command: "view";
|
|
47
|
+
path: string;
|
|
48
|
+
startLine?: number | undefined;
|
|
49
|
+
endLine?: number | undefined;
|
|
50
|
+
} | {
|
|
51
|
+
command: "create";
|
|
52
|
+
path: string;
|
|
53
|
+
fileText: string;
|
|
54
|
+
} | {
|
|
55
|
+
command: "str_replace";
|
|
56
|
+
path: string;
|
|
57
|
+
oldText: string;
|
|
58
|
+
newText: string;
|
|
59
|
+
replaceAll?: boolean | undefined;
|
|
60
|
+
} | {
|
|
61
|
+
command: "insert";
|
|
62
|
+
path: string;
|
|
63
|
+
insertLine: number;
|
|
64
|
+
newText: string;
|
|
65
|
+
};
|
|
66
|
+
type BatchOp = {
|
|
67
|
+
op: "exec";
|
|
68
|
+
command: string;
|
|
69
|
+
cwd?: string;
|
|
70
|
+
timeoutMs?: number;
|
|
71
|
+
} | {
|
|
72
|
+
op: "write_file";
|
|
73
|
+
path: string;
|
|
74
|
+
content: string;
|
|
75
|
+
encoding?: "utf8" | "base64";
|
|
76
|
+
} | {
|
|
77
|
+
op: "read_file";
|
|
78
|
+
path: string;
|
|
79
|
+
encoding?: "utf8" | "base64";
|
|
80
|
+
};
|
|
81
|
+
type BatchResult = {
|
|
82
|
+
success: true;
|
|
83
|
+
op: "exec";
|
|
84
|
+
exitCode: number;
|
|
85
|
+
stdout: string;
|
|
86
|
+
stderr: string;
|
|
87
|
+
} | {
|
|
88
|
+
success: true;
|
|
89
|
+
op: "write_file";
|
|
90
|
+
} | {
|
|
91
|
+
success: true;
|
|
92
|
+
op: "read_file";
|
|
93
|
+
content: string;
|
|
94
|
+
} | {
|
|
95
|
+
success: false;
|
|
96
|
+
op: string;
|
|
97
|
+
error: string;
|
|
98
|
+
};
|
|
99
|
+
interface ToolProviderCapabilities {
|
|
100
|
+
bash: boolean;
|
|
101
|
+
fileSystem: boolean;
|
|
102
|
+
webFetch: boolean;
|
|
103
|
+
webSearch: boolean;
|
|
104
|
+
codeExecution: boolean;
|
|
105
|
+
sandboxed: boolean;
|
|
106
|
+
}
|
|
107
|
+
interface ThreadStatus {
|
|
108
|
+
threadId: string;
|
|
109
|
+
isComplete: boolean;
|
|
110
|
+
exitCode: number | null;
|
|
111
|
+
output: string;
|
|
112
|
+
error?: string;
|
|
113
|
+
}
|
|
114
|
+
interface ToolProvider {
|
|
115
|
+
bash(command: string, options?: BashOptions): Promise<ToolResult>;
|
|
116
|
+
readFile(path: string, options?: ReadOptions): Promise<ToolResult>;
|
|
117
|
+
writeFile(path: string, content: string): Promise<ToolResult>;
|
|
118
|
+
editFile(path: string, oldText: string, newText: string): Promise<ToolResult>;
|
|
119
|
+
textEditor?: ((request: TextEditorRequest) => Promise<ToolResult>) | undefined;
|
|
120
|
+
glob(pattern: string, options?: GlobOptions): Promise<ToolResult>;
|
|
121
|
+
grep(pattern: string, path?: string, options?: GrepOptions): Promise<ToolResult>;
|
|
122
|
+
webFetch?: ((options: WebFetchOptions) => Promise<ToolResult>) | undefined;
|
|
123
|
+
webSearch?: ((query: string) => Promise<ToolResult>) | undefined;
|
|
124
|
+
thread?: ((command: string, options?: BashOptions) => Promise<ToolResult>) | undefined;
|
|
125
|
+
check?: ((threadId: string) => Promise<ToolResult>) | undefined;
|
|
126
|
+
cancel?: ((threadId: string) => Promise<ToolResult>) | undefined;
|
|
127
|
+
activeThreads?: (() => ThreadStatus[]) | undefined;
|
|
128
|
+
batch?: ((ops: BatchOp[]) => Promise<BatchResult[]>) | undefined;
|
|
129
|
+
initialize?: (() => Promise<void>) | undefined;
|
|
130
|
+
destroy?: (() => Promise<void>) | undefined;
|
|
131
|
+
capabilities(): ToolProviderCapabilities;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
type AnyTool = Tool<any, any>;
|
|
135
|
+
|
|
136
|
+
type ModelFactory = (modelId: string) => LanguageModel;
|
|
137
|
+
|
|
138
|
+
export type { AnyTool as A, BashOptions as B, GlobOptions as G, ModelFactory as M, ReadOptions as R, ToolProvider as T, WebFetchOptions as W, ToolResult as a, ToolResultArtifact as b, ActionType as c, BatchOp as d, BatchResult as e, GrepOptions as f, TextEditorRequest as g, ThreadStatus as h, ToolProviderCapabilities as i };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { T as ToolProvider, M as ModelFactory } from '../shared-types-DRxnerLT.js';
|
|
2
|
+
import { HarnessTelemetry } from '../observability/otel.js';
|
|
3
|
+
import 'ai';
|
|
4
|
+
|
|
5
|
+
interface SkillSummary {
|
|
6
|
+
name: string;
|
|
7
|
+
description: string;
|
|
8
|
+
path: string;
|
|
9
|
+
}
|
|
10
|
+
interface SkillDefinition extends SkillSummary {
|
|
11
|
+
instructions?: string;
|
|
12
|
+
contextMode?: 'inline' | 'fork';
|
|
13
|
+
dependencies?: {
|
|
14
|
+
python?: string[];
|
|
15
|
+
npm?: string[];
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
interface SkillInvokeResult {
|
|
19
|
+
skill: SkillSummary;
|
|
20
|
+
instructions: string;
|
|
21
|
+
execution?: {
|
|
22
|
+
attempted: boolean;
|
|
23
|
+
success: boolean;
|
|
24
|
+
output: string;
|
|
25
|
+
error?: string;
|
|
26
|
+
commandsRun?: number;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
declare class SkillManager {
|
|
31
|
+
private readonly toolProvider;
|
|
32
|
+
private readonly telemetry?;
|
|
33
|
+
private readonly summaries;
|
|
34
|
+
private readonly fullSkills;
|
|
35
|
+
private readonly installState;
|
|
36
|
+
constructor(toolProvider: ToolProvider, telemetry?: HarnessTelemetry | undefined);
|
|
37
|
+
registerSummary(skill: SkillSummary): void;
|
|
38
|
+
getSkillSummaryForPrompt(): SkillSummary[];
|
|
39
|
+
discover(skillIndexPath: string): Promise<SkillSummary[]>;
|
|
40
|
+
private assertSafeSkillPath;
|
|
41
|
+
private extractShellBlocks;
|
|
42
|
+
invoke(name: string, options?: {
|
|
43
|
+
mode?: 'execute' | 'instructions_only';
|
|
44
|
+
}): Promise<SkillInvokeResult>;
|
|
45
|
+
installDependencies(name: string): Promise<void>;
|
|
46
|
+
getInstallState(name: string): 'ready' | 'degraded' | 'installing' | 'unknown';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
declare function loadSkillFromFile(path: string): Promise<SkillDefinition>;
|
|
50
|
+
|
|
51
|
+
interface SkillRouterConfig {
|
|
52
|
+
model?: string;
|
|
53
|
+
createModel?: ModelFactory;
|
|
54
|
+
minConfidence?: number;
|
|
55
|
+
aliases?: Record<string, string[]>;
|
|
56
|
+
}
|
|
57
|
+
declare class SkillRouter {
|
|
58
|
+
private readonly model;
|
|
59
|
+
private readonly createModel;
|
|
60
|
+
private readonly minConfidence;
|
|
61
|
+
private readonly aliases;
|
|
62
|
+
constructor(config?: SkillRouterConfig);
|
|
63
|
+
selectSkill(prompt: string, summaries: SkillSummary[]): Promise<SkillSummary | null>;
|
|
64
|
+
private containsToken;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { type SkillDefinition, type SkillInvokeResult, SkillManager, SkillRouter, type SkillRouterConfig, type SkillSummary, loadSkillFromFile };
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
import { generateObject } from 'ai';
|
|
4
|
+
import { anthropic } from '@ai-sdk/anthropic';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
|
|
7
|
+
// src/skills/skill-manager.ts
|
|
8
|
+
|
|
9
|
+
// src/observability/tracing.ts
|
|
10
|
+
async function traceStep(telemetry, name, attributes, fn) {
|
|
11
|
+
if (!telemetry?.isEnabled()) {
|
|
12
|
+
return fn();
|
|
13
|
+
}
|
|
14
|
+
return telemetry.withSpan(name, attributes, fn);
|
|
15
|
+
}
|
|
16
|
+
function parseFrontmatter(raw) {
|
|
17
|
+
if (!raw.startsWith("---\n")) {
|
|
18
|
+
return { meta: {}, body: raw };
|
|
19
|
+
}
|
|
20
|
+
const end = raw.indexOf("\n---\n", 4);
|
|
21
|
+
if (end === -1) {
|
|
22
|
+
return { meta: {}, body: raw };
|
|
23
|
+
}
|
|
24
|
+
const header = raw.slice(4, end).split("\n");
|
|
25
|
+
const meta = {};
|
|
26
|
+
for (const line of header) {
|
|
27
|
+
const idx = line.indexOf(":");
|
|
28
|
+
if (idx <= 0) continue;
|
|
29
|
+
const key = line.slice(0, idx).trim();
|
|
30
|
+
const value = line.slice(idx + 1).trim().replace(/^"|"$/g, "");
|
|
31
|
+
meta[key] = value;
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
meta,
|
|
35
|
+
body: raw.slice(end + 5).trim()
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
async function loadSkillFromFile(path) {
|
|
39
|
+
const raw = await readFile(path, "utf8");
|
|
40
|
+
const parsed = parseFrontmatter(raw);
|
|
41
|
+
const pythonDeps = parsed.meta.python_deps ? parsed.meta.python_deps.split(",").map((item) => item.trim()).filter(Boolean) : [];
|
|
42
|
+
const npmDeps = parsed.meta.npm_deps ? parsed.meta.npm_deps.split(",").map((item) => item.trim()).filter(Boolean) : [];
|
|
43
|
+
return {
|
|
44
|
+
name: parsed.meta.name ?? path.split("/").slice(-2, -1)[0] ?? "unknown-skill",
|
|
45
|
+
description: parsed.meta.description ?? "No description provided",
|
|
46
|
+
path,
|
|
47
|
+
contextMode: parsed.meta.context === "fork" ? "fork" : "inline",
|
|
48
|
+
dependencies: {
|
|
49
|
+
python: pythonDeps,
|
|
50
|
+
npm: npmDeps
|
|
51
|
+
},
|
|
52
|
+
instructions: parsed.body
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/skills/skill-manager.ts
|
|
57
|
+
function assertSafeDependencyName(dep) {
|
|
58
|
+
if (!/^[@a-zA-Z0-9._\-/>=<~^]+$/.test(dep)) {
|
|
59
|
+
throw new Error(`unsafe dependency name: ${dep}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
var SkillManager = class {
|
|
63
|
+
constructor(toolProvider, telemetry) {
|
|
64
|
+
this.toolProvider = toolProvider;
|
|
65
|
+
this.telemetry = telemetry;
|
|
66
|
+
}
|
|
67
|
+
summaries = /* @__PURE__ */ new Map();
|
|
68
|
+
fullSkills = /* @__PURE__ */ new Map();
|
|
69
|
+
installState = /* @__PURE__ */ new Map();
|
|
70
|
+
registerSummary(skill) {
|
|
71
|
+
this.summaries.set(skill.name, skill);
|
|
72
|
+
}
|
|
73
|
+
getSkillSummaryForPrompt() {
|
|
74
|
+
return [...this.summaries.values()];
|
|
75
|
+
}
|
|
76
|
+
async discover(skillIndexPath) {
|
|
77
|
+
const raw = await readFile(skillIndexPath, "utf8");
|
|
78
|
+
const entries = JSON.parse(raw);
|
|
79
|
+
for (const entry of entries) {
|
|
80
|
+
this.registerSummary(entry);
|
|
81
|
+
}
|
|
82
|
+
return this.getSkillSummaryForPrompt();
|
|
83
|
+
}
|
|
84
|
+
assertSafeSkillPath(path) {
|
|
85
|
+
if (path.includes("..")) {
|
|
86
|
+
throw new Error("unsafe skill path");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
extractShellBlocks(instructions) {
|
|
90
|
+
const blocks = [];
|
|
91
|
+
const regex = /```(?:bash|sh|shell)\n([\s\S]*?)```/g;
|
|
92
|
+
let match;
|
|
93
|
+
while ((match = regex.exec(instructions)) !== null) {
|
|
94
|
+
const code = (match[1] ?? "").trim();
|
|
95
|
+
if (code.length > 0) {
|
|
96
|
+
blocks.push(code);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return blocks;
|
|
100
|
+
}
|
|
101
|
+
async invoke(name, options) {
|
|
102
|
+
return traceStep(this.telemetry, "skill.exec", { skill: name }, async () => {
|
|
103
|
+
const summary = this.summaries.get(name);
|
|
104
|
+
if (!summary) {
|
|
105
|
+
throw new Error(`unknown skill: ${name}`);
|
|
106
|
+
}
|
|
107
|
+
this.assertSafeSkillPath(summary.path);
|
|
108
|
+
let full = this.fullSkills.get(name);
|
|
109
|
+
if (!full) {
|
|
110
|
+
full = await loadSkillFromFile(resolve(summary.path));
|
|
111
|
+
this.fullSkills.set(name, full);
|
|
112
|
+
}
|
|
113
|
+
const mode = options?.mode ?? "execute";
|
|
114
|
+
if (mode === "instructions_only") {
|
|
115
|
+
return {
|
|
116
|
+
skill: summary,
|
|
117
|
+
instructions: full.instructions ?? "",
|
|
118
|
+
execution: {
|
|
119
|
+
attempted: false,
|
|
120
|
+
success: true,
|
|
121
|
+
output: "instructions_only mode",
|
|
122
|
+
commandsRun: 0
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const shellBlocks = this.extractShellBlocks(full.instructions ?? "");
|
|
127
|
+
if (shellBlocks.length === 0) {
|
|
128
|
+
return {
|
|
129
|
+
skill: summary,
|
|
130
|
+
instructions: full.instructions ?? "",
|
|
131
|
+
execution: {
|
|
132
|
+
attempted: false,
|
|
133
|
+
success: true,
|
|
134
|
+
output: "no executable shell blocks found",
|
|
135
|
+
commandsRun: 0
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
if (this.getInstallState(name) === "unknown") {
|
|
140
|
+
await this.installDependencies(name);
|
|
141
|
+
}
|
|
142
|
+
let aggregateOutput = "";
|
|
143
|
+
for (const block of shellBlocks) {
|
|
144
|
+
const result = await this.toolProvider.bash(block);
|
|
145
|
+
aggregateOutput += result.output;
|
|
146
|
+
if (!result.success) {
|
|
147
|
+
return {
|
|
148
|
+
skill: summary,
|
|
149
|
+
instructions: full.instructions ?? "",
|
|
150
|
+
execution: {
|
|
151
|
+
attempted: true,
|
|
152
|
+
success: false,
|
|
153
|
+
output: aggregateOutput,
|
|
154
|
+
error: result.error || "skill block failed",
|
|
155
|
+
commandsRun: shellBlocks.length
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
skill: summary,
|
|
162
|
+
instructions: full.instructions ?? "",
|
|
163
|
+
execution: {
|
|
164
|
+
attempted: true,
|
|
165
|
+
success: true,
|
|
166
|
+
output: aggregateOutput,
|
|
167
|
+
commandsRun: shellBlocks.length
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
async installDependencies(name) {
|
|
173
|
+
const skill = this.fullSkills.get(name);
|
|
174
|
+
if (!skill) {
|
|
175
|
+
throw new Error(`skill must be invoked before install: ${name}`);
|
|
176
|
+
}
|
|
177
|
+
this.installState.set(name, "installing");
|
|
178
|
+
try {
|
|
179
|
+
const pythonDeps = skill.dependencies?.python ?? [];
|
|
180
|
+
const npmDeps = skill.dependencies?.npm ?? [];
|
|
181
|
+
for (const dep of pythonDeps) {
|
|
182
|
+
assertSafeDependencyName(dep);
|
|
183
|
+
const result = await this.toolProvider.bash(`pip install '${dep}'`);
|
|
184
|
+
if (!result.success) {
|
|
185
|
+
throw new Error(result.error || `pip install failed for ${dep}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
for (const dep of npmDeps) {
|
|
189
|
+
assertSafeDependencyName(dep);
|
|
190
|
+
const result = await this.toolProvider.bash(`npm install '${dep}'`);
|
|
191
|
+
if (!result.success) {
|
|
192
|
+
throw new Error(result.error || `npm install failed for ${dep}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
this.installState.set(name, "ready");
|
|
196
|
+
} catch (error) {
|
|
197
|
+
this.installState.set(name, "degraded");
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
getInstallState(name) {
|
|
202
|
+
return this.installState.get(name) ?? "unknown";
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
var routeSchema = z.object({
|
|
206
|
+
skillName: z.string().nullable(),
|
|
207
|
+
confidence: z.number().min(0).max(1),
|
|
208
|
+
rationale: z.string()
|
|
209
|
+
});
|
|
210
|
+
var DEFAULT_ALIASES = {
|
|
211
|
+
xlsx: ["excel", "spreadsheet", "workbook", "csv"],
|
|
212
|
+
docx: ["word", "document", "doc"],
|
|
213
|
+
pptx: ["powerpoint", "slides", "presentation"],
|
|
214
|
+
"visual-explainer": ["visual explainer", "visual explanation", "interactive visual", "visualize", "diagram d3", "data visualization", "d3"],
|
|
215
|
+
"excalidraw-diagram": ["excalidraw", "architecture diagram", "flowchart", "sequence diagram"],
|
|
216
|
+
"json-render": ["jsonrender", "interactive ui", "action buttons"]
|
|
217
|
+
};
|
|
218
|
+
var SkillRouter = class {
|
|
219
|
+
model;
|
|
220
|
+
createModel;
|
|
221
|
+
minConfidence;
|
|
222
|
+
aliases;
|
|
223
|
+
constructor(config = {}) {
|
|
224
|
+
this.model = config.model ?? process.env.HARNESS_SKILL_ROUTER_MODEL ?? "claude-3-5-haiku-latest";
|
|
225
|
+
this.createModel = config.createModel ?? anthropic;
|
|
226
|
+
this.minConfidence = config.minConfidence ?? Number(process.env.HARNESS_SKILL_ROUTER_THRESHOLD ?? "0.55");
|
|
227
|
+
this.aliases = {
|
|
228
|
+
...DEFAULT_ALIASES,
|
|
229
|
+
...config.aliases ?? {}
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
async selectSkill(prompt, summaries) {
|
|
233
|
+
if (summaries.length === 0) return null;
|
|
234
|
+
const lower = prompt.toLowerCase();
|
|
235
|
+
const direct = summaries.find((skill) => this.containsToken(lower, skill.name.toLowerCase()));
|
|
236
|
+
if (direct) return direct;
|
|
237
|
+
for (const summary of summaries) {
|
|
238
|
+
const aliasList = this.aliases[summary.name.toLowerCase()] ?? [];
|
|
239
|
+
if (aliasList.some((alias) => this.containsToken(lower, alias.toLowerCase()))) {
|
|
240
|
+
return summary;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
const skillList = summaries.map((s) => `- ${s.name}: ${s.description}`).join("\n");
|
|
245
|
+
const { object } = await generateObject({
|
|
246
|
+
model: this.createModel(this.model),
|
|
247
|
+
schema: routeSchema,
|
|
248
|
+
system: [
|
|
249
|
+
"You are a skill router.",
|
|
250
|
+
"Pick at most one skill name from the provided list.",
|
|
251
|
+
"Only choose a skill when it clearly helps with the user request.",
|
|
252
|
+
"If nothing clearly matches, return null skillName and low confidence."
|
|
253
|
+
].join(" "),
|
|
254
|
+
prompt: [
|
|
255
|
+
"User request:",
|
|
256
|
+
prompt,
|
|
257
|
+
"",
|
|
258
|
+
"Available skills:",
|
|
259
|
+
skillList,
|
|
260
|
+
"",
|
|
261
|
+
"Return one object with skillName, confidence, rationale."
|
|
262
|
+
].join("\n")
|
|
263
|
+
});
|
|
264
|
+
if (!object.skillName || object.confidence < this.minConfidence) {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
return summaries.find((s) => s.name === object.skillName) ?? null;
|
|
268
|
+
} catch {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
containsToken(haystack, needle) {
|
|
273
|
+
if (!needle) return false;
|
|
274
|
+
const escaped = needle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
275
|
+
const regex = new RegExp(`\\b${escaped}\\b`, "i");
|
|
276
|
+
return regex.test(haystack);
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
export { SkillManager, SkillRouter, loadSkillFromFile };
|
|
281
|
+
//# sourceMappingURL=index.js.map
|
|
282
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/observability/tracing.ts","../../src/skills/skill-loader.ts","../../src/skills/skill-manager.ts","../../src/skills/skill-router.ts"],"names":["readFile","defaultAnthropicProvider"],"mappings":"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","file":"index.js","sourcesContent":["import { HarnessTelemetry } from './otel';\n\nexport async function traceStep<T>(\n telemetry: HarnessTelemetry | undefined,\n name: string,\n attributes: Record<string, string | number | boolean>,\n fn: () => Promise<T>\n): Promise<T> {\n if (!telemetry?.isEnabled()) {\n return fn();\n }\n return telemetry.withSpan(name, attributes, fn);\n}\n","import { readFile } from 'node:fs/promises';\n\nimport type { SkillDefinition } from './skill-types';\n\nfunction parseFrontmatter(raw: string): { meta: Record<string, string>; body: string } {\n if (!raw.startsWith('---\\n')) {\n return { meta: {}, body: raw };\n }\n\n const end = raw.indexOf('\\n---\\n', 4);\n if (end === -1) {\n return { meta: {}, body: raw };\n }\n\n const header = raw.slice(4, end).split('\\n');\n const meta: Record<string, string> = {};\n for (const line of header) {\n const idx = line.indexOf(':');\n if (idx <= 0) continue;\n const key = line.slice(0, idx).trim();\n const value = line.slice(idx + 1).trim().replace(/^\"|\"$/g, '');\n meta[key] = value;\n }\n\n return {\n meta,\n body: raw.slice(end + 5).trim()\n };\n}\n\nexport async function loadSkillFromFile(path: string): Promise<SkillDefinition> {\n const raw = await readFile(path, 'utf8');\n const parsed = parseFrontmatter(raw);\n const pythonDeps = parsed.meta.python_deps\n ? parsed.meta.python_deps.split(',').map((item) => item.trim()).filter(Boolean)\n : [];\n const npmDeps = parsed.meta.npm_deps\n ? parsed.meta.npm_deps.split(',').map((item) => item.trim()).filter(Boolean)\n : [];\n\n return {\n name: parsed.meta.name ?? path.split('/').slice(-2, -1)[0] ?? 'unknown-skill',\n description: parsed.meta.description ?? 'No description provided',\n path,\n contextMode: parsed.meta.context === 'fork' ? 'fork' : 'inline',\n dependencies: {\n python: pythonDeps,\n npm: npmDeps\n },\n instructions: parsed.body\n };\n}\n","import { readFile } from 'node:fs/promises';\nimport { resolve } from 'node:path';\n\nimport type { ToolProvider } from '../interfaces/tool-provider';\nimport type { HarnessTelemetry } from '../observability/otel';\nimport { traceStep } from '../observability/tracing';\nimport type { SkillDefinition, SkillInvokeResult, SkillSummary } from './skill-types';\nimport { loadSkillFromFile } from './skill-loader';\n\n/** Validate a dependency name to prevent command injection */\nfunction assertSafeDependencyName(dep: string): void {\n // Allow: alphanumeric, hyphens, underscores, dots, slashes (scoped packages), @, =, >=, <=, ~, ^\n // Block: semicolons, pipes, backticks, $(), etc.\n if (!/^[@a-zA-Z0-9._\\-/>=<~^]+$/.test(dep)) {\n throw new Error(`unsafe dependency name: ${dep}`);\n }\n}\n\nexport class SkillManager {\n private readonly summaries = new Map<string, SkillSummary>();\n private readonly fullSkills = new Map<string, SkillDefinition>();\n private readonly installState = new Map<string, 'ready' | 'degraded' | 'installing'>();\n\n constructor(\n private readonly toolProvider: ToolProvider,\n private readonly telemetry?: HarnessTelemetry\n ) {}\n\n registerSummary(skill: SkillSummary): void {\n this.summaries.set(skill.name, skill);\n }\n\n getSkillSummaryForPrompt(): SkillSummary[] {\n return [...this.summaries.values()];\n }\n\n async discover(skillIndexPath: string): Promise<SkillSummary[]> {\n const raw = await readFile(skillIndexPath, 'utf8');\n const entries = JSON.parse(raw) as Array<{ name: string; description: string; path: string }>;\n for (const entry of entries) {\n this.registerSummary(entry);\n }\n return this.getSkillSummaryForPrompt();\n }\n\n private assertSafeSkillPath(path: string): void {\n if (path.includes('..')) {\n throw new Error('unsafe skill path');\n }\n }\n\n private extractShellBlocks(instructions: string): string[] {\n const blocks: string[] = [];\n const regex = /```(?:bash|sh|shell)\\n([\\s\\S]*?)```/g;\n let match: RegExpExecArray | null;\n while ((match = regex.exec(instructions)) !== null) {\n const code = (match[1] ?? '').trim();\n if (code.length > 0) {\n blocks.push(code);\n }\n }\n return blocks;\n }\n\n async invoke(\n name: string,\n options?: { mode?: 'execute' | 'instructions_only' }\n ): Promise<SkillInvokeResult> {\n return traceStep(this.telemetry, 'skill.exec', { skill: name }, async () => {\n const summary = this.summaries.get(name);\n if (!summary) {\n throw new Error(`unknown skill: ${name}`);\n }\n\n this.assertSafeSkillPath(summary.path);\n\n let full = this.fullSkills.get(name);\n if (!full) {\n full = await loadSkillFromFile(resolve(summary.path));\n this.fullSkills.set(name, full);\n }\n\n const mode = options?.mode ?? 'execute';\n if (mode === 'instructions_only') {\n return {\n skill: summary,\n instructions: full.instructions ?? '',\n execution: {\n attempted: false,\n success: true,\n output: 'instructions_only mode',\n commandsRun: 0\n }\n };\n }\n\n const shellBlocks = this.extractShellBlocks(full.instructions ?? '');\n if (shellBlocks.length === 0) {\n return {\n skill: summary,\n instructions: full.instructions ?? '',\n execution: {\n attempted: false,\n success: true,\n output: 'no executable shell blocks found',\n commandsRun: 0\n }\n };\n }\n\n if (this.getInstallState(name) === 'unknown') {\n await this.installDependencies(name);\n }\n\n let aggregateOutput = '';\n for (const block of shellBlocks) {\n const result = await this.toolProvider.bash(block);\n aggregateOutput += result.output;\n if (!result.success) {\n return {\n skill: summary,\n instructions: full.instructions ?? '',\n execution: {\n attempted: true,\n success: false,\n output: aggregateOutput,\n error: result.error || 'skill block failed',\n commandsRun: shellBlocks.length\n }\n };\n }\n }\n\n return {\n skill: summary,\n instructions: full.instructions ?? '',\n execution: {\n attempted: true,\n success: true,\n output: aggregateOutput,\n commandsRun: shellBlocks.length\n }\n };\n });\n }\n\n async installDependencies(name: string): Promise<void> {\n const skill = this.fullSkills.get(name);\n if (!skill) {\n throw new Error(`skill must be invoked before install: ${name}`);\n }\n\n this.installState.set(name, 'installing');\n\n try {\n const pythonDeps = skill.dependencies?.python ?? [];\n const npmDeps = skill.dependencies?.npm ?? [];\n\n for (const dep of pythonDeps) {\n assertSafeDependencyName(dep);\n const result = await this.toolProvider.bash(`pip install '${dep}'`);\n if (!result.success) {\n throw new Error(result.error || `pip install failed for ${dep}`);\n }\n }\n\n for (const dep of npmDeps) {\n assertSafeDependencyName(dep);\n const result = await this.toolProvider.bash(`npm install '${dep}'`);\n if (!result.success) {\n throw new Error(result.error || `npm install failed for ${dep}`);\n }\n }\n\n this.installState.set(name, 'ready');\n } catch (error) {\n this.installState.set(name, 'degraded');\n throw error;\n }\n }\n\n getInstallState(name: string): 'ready' | 'degraded' | 'installing' | 'unknown' {\n return this.installState.get(name) ?? 'unknown';\n }\n}\n","import { generateObject } from 'ai';\nimport { anthropic as defaultAnthropicProvider } from '@ai-sdk/anthropic';\nimport { z } from 'zod';\n\nimport type { SkillSummary } from './skill-types';\nimport type { ModelFactory } from '../arc/types';\n\nconst routeSchema = z.object({\n skillName: z.string().nullable(),\n confidence: z.number().min(0).max(1),\n rationale: z.string()\n});\n\nexport interface SkillRouterConfig {\n model?: string;\n createModel?: ModelFactory;\n minConfidence?: number;\n aliases?: Record<string, string[]>;\n}\n\nconst DEFAULT_ALIASES: Record<string, string[]> = {\n xlsx: ['excel', 'spreadsheet', 'workbook', 'csv'],\n docx: ['word', 'document', 'doc'],\n pptx: ['powerpoint', 'slides', 'presentation'],\n 'visual-explainer': ['visual explainer', 'visual explanation', 'interactive visual', 'visualize', 'diagram d3', 'data visualization', 'd3'],\n 'excalidraw-diagram': ['excalidraw', 'architecture diagram', 'flowchart', 'sequence diagram'],\n 'json-render': ['jsonrender', 'interactive ui', 'action buttons'],\n};\n\nexport class SkillRouter {\n private readonly model: string;\n private readonly createModel: ModelFactory;\n private readonly minConfidence: number;\n private readonly aliases: Record<string, string[]>;\n\n constructor(config: SkillRouterConfig = {}) {\n this.model = config.model ?? process.env.HARNESS_SKILL_ROUTER_MODEL ?? 'claude-3-5-haiku-latest';\n this.createModel = config.createModel ?? defaultAnthropicProvider;\n this.minConfidence = config.minConfidence ?? Number(process.env.HARNESS_SKILL_ROUTER_THRESHOLD ?? '0.55');\n this.aliases = {\n ...DEFAULT_ALIASES,\n ...(config.aliases ?? {})\n };\n }\n\n async selectSkill(prompt: string, summaries: SkillSummary[]): Promise<SkillSummary | null> {\n if (summaries.length === 0) return null;\n const lower = prompt.toLowerCase();\n\n const direct = summaries.find((skill) => this.containsToken(lower, skill.name.toLowerCase()));\n if (direct) return direct;\n\n for (const summary of summaries) {\n const aliasList = this.aliases[summary.name.toLowerCase()] ?? [];\n if (aliasList.some((alias) => this.containsToken(lower, alias.toLowerCase()))) {\n return summary;\n }\n }\n\n // LLM-based routing — if no API key is configured, the provider will fail\n // and the catch block below will return null (graceful fallback).\n\n try {\n const skillList = summaries\n .map((s) => `- ${s.name}: ${s.description}`)\n .join('\\n');\n\n const { object } = await generateObject({\n model: this.createModel(this.model),\n schema: routeSchema,\n system: [\n 'You are a skill router.',\n 'Pick at most one skill name from the provided list.',\n 'Only choose a skill when it clearly helps with the user request.',\n 'If nothing clearly matches, return null skillName and low confidence.'\n ].join(' '),\n prompt: [\n 'User request:',\n prompt,\n '',\n 'Available skills:',\n skillList,\n '',\n 'Return one object with skillName, confidence, rationale.'\n ].join('\\n')\n });\n\n if (!object.skillName || object.confidence < this.minConfidence) {\n return null;\n }\n\n return summaries.find((s) => s.name === object.skillName) ?? null;\n } catch {\n return null;\n }\n }\n\n private containsToken(haystack: string, needle: string): boolean {\n if (!needle) return false;\n const escaped = needle.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const regex = new RegExp(`\\\\b${escaped}\\\\b`, 'i');\n return regex.test(haystack);\n }\n}\n\n"]}
|