@agntk/agent-harness 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE +41 -0
- package/README.md +445 -0
- package/defaults/agents/summarizer.md +49 -0
- package/defaults/instincts/lead-with-answer.md +24 -0
- package/defaults/instincts/qualify-before-recommending.md +40 -0
- package/defaults/instincts/read-before-edit.md +23 -0
- package/defaults/instincts/search-before-create.md +23 -0
- package/defaults/playbooks/ship-feature.md +31 -0
- package/defaults/rules/ask-before-assuming.md +35 -0
- package/defaults/rules/operations.md +35 -0
- package/defaults/rules/respect-the-user.md +39 -0
- package/defaults/skills/business-analyst.md +181 -0
- package/defaults/skills/content-marketer.md +184 -0
- package/defaults/skills/research.md +34 -0
- package/defaults/tools/example-web-search.md +60 -0
- package/defaults/workflows/daily-reflection.md +54 -0
- package/dist/agent-framework-K4GUIICH.js +344 -0
- package/dist/agent-framework-K4GUIICH.js.map +1 -0
- package/dist/analytics-RPT73WNM.js +12 -0
- package/dist/analytics-RPT73WNM.js.map +1 -0
- package/dist/auto-processor-OLE45UI3.js +13 -0
- package/dist/auto-processor-OLE45UI3.js.map +1 -0
- package/dist/chunk-274RV3YO.js +162 -0
- package/dist/chunk-274RV3YO.js.map +1 -0
- package/dist/chunk-4CWAGBNS.js +168 -0
- package/dist/chunk-4CWAGBNS.js.map +1 -0
- package/dist/chunk-4FDUOGSZ.js +69 -0
- package/dist/chunk-4FDUOGSZ.js.map +1 -0
- package/dist/chunk-5H34JPMB.js +199 -0
- package/dist/chunk-5H34JPMB.js.map +1 -0
- package/dist/chunk-6EMOEYGU.js +102 -0
- package/dist/chunk-6EMOEYGU.js.map +1 -0
- package/dist/chunk-A7BJPQQ6.js +236 -0
- package/dist/chunk-A7BJPQQ6.js.map +1 -0
- package/dist/chunk-AGAAFJEO.js +76 -0
- package/dist/chunk-AGAAFJEO.js.map +1 -0
- package/dist/chunk-BSKDOFRT.js +65 -0
- package/dist/chunk-BSKDOFRT.js.map +1 -0
- package/dist/chunk-CHJ5GNZC.js +100 -0
- package/dist/chunk-CHJ5GNZC.js.map +1 -0
- package/dist/chunk-CSL3ERUI.js +307 -0
- package/dist/chunk-CSL3ERUI.js.map +1 -0
- package/dist/chunk-DA7IKHC4.js +229 -0
- package/dist/chunk-DA7IKHC4.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/chunk-DGUM43GV.js.map +1 -0
- package/dist/chunk-DTTXPHFW.js +211 -0
- package/dist/chunk-DTTXPHFW.js.map +1 -0
- package/dist/chunk-FD55B3IO.js +204 -0
- package/dist/chunk-FD55B3IO.js.map +1 -0
- package/dist/chunk-FLZU44SV.js +230 -0
- package/dist/chunk-FLZU44SV.js.map +1 -0
- package/dist/chunk-GJNNR2RA.js +200 -0
- package/dist/chunk-GJNNR2RA.js.map +1 -0
- package/dist/chunk-GNUSHD2Y.js +111 -0
- package/dist/chunk-GNUSHD2Y.js.map +1 -0
- package/dist/chunk-GUJTBGVS.js +2212 -0
- package/dist/chunk-GUJTBGVS.js.map +1 -0
- package/dist/chunk-IZ6UZ3ZL.js +207 -0
- package/dist/chunk-IZ6UZ3ZL.js.map +1 -0
- package/dist/chunk-JKMGYWXB.js +197 -0
- package/dist/chunk-JKMGYWXB.js.map +1 -0
- package/dist/chunk-KFX54TQM.js +165 -0
- package/dist/chunk-KFX54TQM.js.map +1 -0
- package/dist/chunk-M7NXUK55.js +199 -0
- package/dist/chunk-M7NXUK55.js.map +1 -0
- package/dist/chunk-MPZ3BPUI.js +374 -0
- package/dist/chunk-MPZ3BPUI.js.map +1 -0
- package/dist/chunk-OC6YSTDX.js +119 -0
- package/dist/chunk-OC6YSTDX.js.map +1 -0
- package/dist/chunk-RC6MEZB6.js +469 -0
- package/dist/chunk-RC6MEZB6.js.map +1 -0
- package/dist/chunk-RY3ZFII7.js +3440 -0
- package/dist/chunk-RY3ZFII7.js.map +1 -0
- package/dist/chunk-TAT6JU3X.js +167 -0
- package/dist/chunk-TAT6JU3X.js.map +1 -0
- package/dist/chunk-UDZIS2AQ.js +79 -0
- package/dist/chunk-UDZIS2AQ.js.map +1 -0
- package/dist/chunk-UPLBF4RZ.js +115 -0
- package/dist/chunk-UPLBF4RZ.js.map +1 -0
- package/dist/chunk-UWQTZMNI.js +154 -0
- package/dist/chunk-UWQTZMNI.js.map +1 -0
- package/dist/chunk-W4T7PGI2.js +346 -0
- package/dist/chunk-W4T7PGI2.js.map +1 -0
- package/dist/chunk-XTBKL5BI.js +111 -0
- package/dist/chunk-XTBKL5BI.js.map +1 -0
- package/dist/chunk-YIJY5DBV.js +399 -0
- package/dist/chunk-YIJY5DBV.js.map +1 -0
- package/dist/chunk-YUFNYN2H.js +242 -0
- package/dist/chunk-YUFNYN2H.js.map +1 -0
- package/dist/chunk-Z2PUCXTZ.js +94 -0
- package/dist/chunk-Z2PUCXTZ.js.map +1 -0
- package/dist/chunk-ZZJOFKAT.js +13 -0
- package/dist/chunk-ZZJOFKAT.js.map +1 -0
- package/dist/cli/index.js +3661 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config-WVMRUOCA.js +13 -0
- package/dist/config-WVMRUOCA.js.map +1 -0
- package/dist/context-loader-3ORBPMHJ.js +13 -0
- package/dist/context-loader-3ORBPMHJ.js.map +1 -0
- package/dist/conversation-QDEIDQPH.js +22 -0
- package/dist/conversation-QDEIDQPH.js.map +1 -0
- package/dist/cost-tracker-RS3W7SVY.js +24 -0
- package/dist/cost-tracker-RS3W7SVY.js.map +1 -0
- package/dist/delegate-VJCJLYEK.js +29 -0
- package/dist/delegate-VJCJLYEK.js.map +1 -0
- package/dist/emotional-state-VQVRA6ED.js +206 -0
- package/dist/emotional-state-VQVRA6ED.js.map +1 -0
- package/dist/env-discovery-2BLVMAIM.js +251 -0
- package/dist/env-discovery-2BLVMAIM.js.map +1 -0
- package/dist/export-6GCYHEHQ.js +165 -0
- package/dist/export-6GCYHEHQ.js.map +1 -0
- package/dist/graph-YUIPOSOO.js +14 -0
- package/dist/graph-YUIPOSOO.js.map +1 -0
- package/dist/harness-LCHA3DWP.js +10 -0
- package/dist/harness-LCHA3DWP.js.map +1 -0
- package/dist/harness-WE4SLCML.js +26 -0
- package/dist/harness-WE4SLCML.js.map +1 -0
- package/dist/health-NZ6WNIMV.js +23 -0
- package/dist/health-NZ6WNIMV.js.map +1 -0
- package/dist/index.d.ts +3612 -0
- package/dist/index.js +13501 -0
- package/dist/index.js.map +1 -0
- package/dist/indexer-LONANRRM.js +16 -0
- package/dist/indexer-LONANRRM.js.map +1 -0
- package/dist/instinct-learner-SRM72DHF.js +20 -0
- package/dist/instinct-learner-SRM72DHF.js.map +1 -0
- package/dist/intake-4M3HNU43.js +21 -0
- package/dist/intake-4M3HNU43.js.map +1 -0
- package/dist/intelligence-HJOCA4SJ.js +1081 -0
- package/dist/intelligence-HJOCA4SJ.js.map +1 -0
- package/dist/journal-WANJL3MI.js +24 -0
- package/dist/journal-WANJL3MI.js.map +1 -0
- package/dist/loader-C3TKIKZR.js +23 -0
- package/dist/loader-C3TKIKZR.js.map +1 -0
- package/dist/mcp-WTQJJZAO.js +15 -0
- package/dist/mcp-WTQJJZAO.js.map +1 -0
- package/dist/mcp-discovery-WPAQFL6S.js +377 -0
- package/dist/mcp-discovery-WPAQFL6S.js.map +1 -0
- package/dist/mcp-installer-6O2XXD3V.js +394 -0
- package/dist/mcp-installer-6O2XXD3V.js.map +1 -0
- package/dist/metrics-KXGNFAAB.js +20 -0
- package/dist/metrics-KXGNFAAB.js.map +1 -0
- package/dist/primitive-registry-I6VTIR4W.js +512 -0
- package/dist/primitive-registry-I6VTIR4W.js.map +1 -0
- package/dist/project-discovery-C4UMD7JI.js +246 -0
- package/dist/project-discovery-C4UMD7JI.js.map +1 -0
- package/dist/provider-LQHQX7Z7.js +26 -0
- package/dist/provider-LQHQX7Z7.js.map +1 -0
- package/dist/provider-SXPQZ74H.js +28 -0
- package/dist/provider-SXPQZ74H.js.map +1 -0
- package/dist/rate-limiter-RLRVM325.js +22 -0
- package/dist/rate-limiter-RLRVM325.js.map +1 -0
- package/dist/rule-engine-YGQ3RYZM.js +182 -0
- package/dist/rule-engine-YGQ3RYZM.js.map +1 -0
- package/dist/scaffold-A3VRRCBV.js +347 -0
- package/dist/scaffold-A3VRRCBV.js.map +1 -0
- package/dist/scheduler-XHHIVHRI.js +397 -0
- package/dist/scheduler-XHHIVHRI.js.map +1 -0
- package/dist/search-V3W5JMJG.js +75 -0
- package/dist/search-V3W5JMJG.js.map +1 -0
- package/dist/semantic-search-2DTOO5UX.js +241 -0
- package/dist/semantic-search-2DTOO5UX.js.map +1 -0
- package/dist/serve-DTQ3HENY.js +291 -0
- package/dist/serve-DTQ3HENY.js.map +1 -0
- package/dist/sessions-CZGVXKQE.js +21 -0
- package/dist/sessions-CZGVXKQE.js.map +1 -0
- package/dist/sources-RW5DT56F.js +32 -0
- package/dist/sources-RW5DT56F.js.map +1 -0
- package/dist/starter-packs-76YUVHEU.js +893 -0
- package/dist/starter-packs-76YUVHEU.js.map +1 -0
- package/dist/state-GMXILIHW.js +13 -0
- package/dist/state-GMXILIHW.js.map +1 -0
- package/dist/state-merge-NKO5FRBA.js +174 -0
- package/dist/state-merge-NKO5FRBA.js.map +1 -0
- package/dist/telemetry-UC6PBXC7.js +22 -0
- package/dist/telemetry-UC6PBXC7.js.map +1 -0
- package/dist/tool-executor-MJ7IG7PQ.js +28 -0
- package/dist/tool-executor-MJ7IG7PQ.js.map +1 -0
- package/dist/tools-DZ4KETET.js +20 -0
- package/dist/tools-DZ4KETET.js.map +1 -0
- package/dist/types-EW7AIB3R.js +18 -0
- package/dist/types-EW7AIB3R.js.map +1 -0
- package/dist/types-WGDLSPO6.js +16 -0
- package/dist/types-WGDLSPO6.js.map +1 -0
- package/dist/universal-installer-QGS4SJGX.js +578 -0
- package/dist/universal-installer-QGS4SJGX.js.map +1 -0
- package/dist/validator-7WXMDIHH.js +22 -0
- package/dist/validator-7WXMDIHH.js.map +1 -0
- package/dist/verification-gate-FYXUX6LH.js +246 -0
- package/dist/verification-gate-FYXUX6LH.js.map +1 -0
- package/dist/versioning-Z3XNE2Q2.js +271 -0
- package/dist/versioning-Z3XNE2Q2.js.map +1 -0
- package/dist/watcher-ISJC7YKL.js +109 -0
- package/dist/watcher-ISJC7YKL.js.map +1 -0
- package/dist/web-server-DD7ZOP46.js +28 -0
- package/dist/web-server-DD7ZOP46.js.map +1 -0
- package/package.json +76 -0
- package/sources.yaml +121 -0
- package/templates/assistant/CORE.md +24 -0
- package/templates/assistant/SYSTEM.md +24 -0
- package/templates/assistant/config.yaml +51 -0
- package/templates/base/CORE.md +17 -0
- package/templates/base/SYSTEM.md +24 -0
- package/templates/base/config.yaml +51 -0
- package/templates/claude-opus/config.yaml +51 -0
- package/templates/code-reviewer/CORE.md +25 -0
- package/templates/code-reviewer/SYSTEM.md +30 -0
- package/templates/code-reviewer/config.yaml +51 -0
- package/templates/gpt4/config.yaml +51 -0
- package/templates/local/config.yaml +51 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
withFileLockSync
|
|
5
|
+
} from "./chunk-Z2PUCXTZ.js";
|
|
6
|
+
import {
|
|
7
|
+
loadAllPrimitives
|
|
8
|
+
} from "./chunk-UPLBF4RZ.js";
|
|
9
|
+
import "./chunk-4CWAGBNS.js";
|
|
10
|
+
import "./chunk-ZZJOFKAT.js";
|
|
11
|
+
|
|
12
|
+
// src/runtime/semantic-search.ts
|
|
13
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, statSync } from "fs";
|
|
14
|
+
import { join } from "path";
|
|
15
|
+
var STORE_FILE = "embeddings.json";
|
|
16
|
+
var STORE_DIR = "memory";
|
|
17
|
+
function loadEmbeddingStore(harnessDir) {
|
|
18
|
+
const storePath = join(harnessDir, STORE_DIR, STORE_FILE);
|
|
19
|
+
if (!existsSync(storePath)) return null;
|
|
20
|
+
try {
|
|
21
|
+
const raw = readFileSync(storePath, "utf-8");
|
|
22
|
+
return JSON.parse(raw);
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function saveEmbeddingStore(harnessDir, store) {
|
|
28
|
+
const storeDir = join(harnessDir, STORE_DIR);
|
|
29
|
+
if (!existsSync(storeDir)) {
|
|
30
|
+
mkdirSync(storeDir, { recursive: true });
|
|
31
|
+
}
|
|
32
|
+
const storePath = join(storeDir, STORE_FILE);
|
|
33
|
+
withFileLockSync(harnessDir, storePath, () => {
|
|
34
|
+
writeFileSync(storePath, JSON.stringify(store), "utf-8");
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function extractEmbeddableText(doc) {
|
|
38
|
+
const parts = [];
|
|
39
|
+
if (doc.frontmatter.tags.length > 0) {
|
|
40
|
+
parts.push(`Tags: ${doc.frontmatter.tags.join(", ")}`);
|
|
41
|
+
}
|
|
42
|
+
if (doc.l0) {
|
|
43
|
+
parts.push(doc.l0);
|
|
44
|
+
}
|
|
45
|
+
if (doc.l1) {
|
|
46
|
+
parts.push(doc.l1);
|
|
47
|
+
}
|
|
48
|
+
const bodyPreview = doc.body.slice(0, 500).trim();
|
|
49
|
+
if (bodyPreview) {
|
|
50
|
+
parts.push(bodyPreview);
|
|
51
|
+
}
|
|
52
|
+
return parts.join("\n").trim();
|
|
53
|
+
}
|
|
54
|
+
function detectStalePrimitives(harnessDir, store, modelId, config) {
|
|
55
|
+
const stale = [];
|
|
56
|
+
const allPrimitives = loadAllPrimitives(harnessDir, config?.extensions?.directories);
|
|
57
|
+
const modelChanged = store !== null && store.modelId !== modelId;
|
|
58
|
+
for (const [directory, docs] of allPrimitives) {
|
|
59
|
+
for (const doc of docs) {
|
|
60
|
+
if (doc.frontmatter.status !== "active") continue;
|
|
61
|
+
const id = doc.frontmatter.id;
|
|
62
|
+
if (modelChanged || !store) {
|
|
63
|
+
stale.push({ doc, directory });
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const existing = store.records[id];
|
|
67
|
+
if (!existing) {
|
|
68
|
+
stale.push({ doc, directory });
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const stat = statSync(doc.path);
|
|
73
|
+
if (stat.mtime.toISOString() !== existing.mtime) {
|
|
74
|
+
stale.push({ doc, directory });
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
stale.push({ doc, directory });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return stale;
|
|
82
|
+
}
|
|
83
|
+
async function indexPrimitives(harnessDir, searchConfig, harnessConfig) {
|
|
84
|
+
let store = loadEmbeddingStore(harnessDir);
|
|
85
|
+
const stale = detectStalePrimitives(harnessDir, store, searchConfig.modelId, harnessConfig);
|
|
86
|
+
if (stale.length === 0 && store) {
|
|
87
|
+
return store;
|
|
88
|
+
}
|
|
89
|
+
if (!store || store.modelId !== searchConfig.modelId) {
|
|
90
|
+
store = {
|
|
91
|
+
modelId: searchConfig.modelId,
|
|
92
|
+
dimensions: 0,
|
|
93
|
+
records: {},
|
|
94
|
+
lastIndexedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const textsToEmbed = [];
|
|
98
|
+
const docInfos = [];
|
|
99
|
+
for (const item of stale) {
|
|
100
|
+
const text = extractEmbeddableText(item.doc);
|
|
101
|
+
if (!text) continue;
|
|
102
|
+
textsToEmbed.push(text);
|
|
103
|
+
docInfos.push(item);
|
|
104
|
+
}
|
|
105
|
+
if (textsToEmbed.length === 0) {
|
|
106
|
+
return store;
|
|
107
|
+
}
|
|
108
|
+
const batchSize = 50;
|
|
109
|
+
for (let i = 0; i < textsToEmbed.length; i += batchSize) {
|
|
110
|
+
const batch = textsToEmbed.slice(i, i + batchSize);
|
|
111
|
+
const batchDocs = docInfos.slice(i, i + batchSize);
|
|
112
|
+
const vectors = await searchConfig.embed(batch);
|
|
113
|
+
for (let j = 0; j < vectors.length; j++) {
|
|
114
|
+
const doc = batchDocs[j].doc;
|
|
115
|
+
const vector = vectors[j];
|
|
116
|
+
if (store.dimensions === 0 && vector.length > 0) {
|
|
117
|
+
store.dimensions = vector.length;
|
|
118
|
+
}
|
|
119
|
+
let mtime;
|
|
120
|
+
try {
|
|
121
|
+
const stat = statSync(doc.path);
|
|
122
|
+
mtime = stat.mtime.toISOString();
|
|
123
|
+
} catch {
|
|
124
|
+
mtime = (/* @__PURE__ */ new Date()).toISOString();
|
|
125
|
+
}
|
|
126
|
+
store.records[doc.frontmatter.id] = {
|
|
127
|
+
id: doc.frontmatter.id,
|
|
128
|
+
path: doc.path,
|
|
129
|
+
directory: batchDocs[j].directory,
|
|
130
|
+
embeddedText: batch[j],
|
|
131
|
+
vector,
|
|
132
|
+
mtime,
|
|
133
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
store.lastIndexedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
138
|
+
const allIds = /* @__PURE__ */ new Set();
|
|
139
|
+
const allPrimitives = loadAllPrimitives(harnessDir, harnessConfig?.extensions?.directories);
|
|
140
|
+
for (const [, docs] of allPrimitives) {
|
|
141
|
+
for (const doc of docs) {
|
|
142
|
+
allIds.add(doc.frontmatter.id);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
for (const id of Object.keys(store.records)) {
|
|
146
|
+
if (!allIds.has(id)) {
|
|
147
|
+
delete store.records[id];
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
saveEmbeddingStore(harnessDir, store);
|
|
151
|
+
return store;
|
|
152
|
+
}
|
|
153
|
+
function cosineSimilarity(a, b) {
|
|
154
|
+
if (a.length !== b.length || a.length === 0) return 0;
|
|
155
|
+
let dotProduct = 0;
|
|
156
|
+
let normA = 0;
|
|
157
|
+
let normB = 0;
|
|
158
|
+
for (let i = 0; i < a.length; i++) {
|
|
159
|
+
dotProduct += a[i] * b[i];
|
|
160
|
+
normA += a[i] * a[i];
|
|
161
|
+
normB += b[i] * b[i];
|
|
162
|
+
}
|
|
163
|
+
const denominator = Math.sqrt(normA) * Math.sqrt(normB);
|
|
164
|
+
if (denominator === 0) return 0;
|
|
165
|
+
return dotProduct / denominator;
|
|
166
|
+
}
|
|
167
|
+
async function semanticSearch(harnessDir, query, searchConfig, harnessConfig) {
|
|
168
|
+
const store = loadEmbeddingStore(harnessDir);
|
|
169
|
+
if (!store || Object.keys(store.records).length === 0) {
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
const maxResults = searchConfig.maxResults ?? 10;
|
|
173
|
+
const minScore = searchConfig.minScore ?? 0.3;
|
|
174
|
+
const [queryVector] = await searchConfig.embed([query]);
|
|
175
|
+
if (!queryVector || queryVector.length === 0) {
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
const scored = [];
|
|
179
|
+
for (const record of Object.values(store.records)) {
|
|
180
|
+
const score = cosineSimilarity(queryVector, record.vector);
|
|
181
|
+
if (score >= minScore) {
|
|
182
|
+
scored.push({ record, score });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
scored.sort((a, b) => b.score - a.score);
|
|
186
|
+
const allPrimitives = loadAllPrimitives(harnessDir, harnessConfig?.extensions?.directories);
|
|
187
|
+
const docMap = /* @__PURE__ */ new Map();
|
|
188
|
+
for (const [directory, docs] of allPrimitives) {
|
|
189
|
+
for (const doc of docs) {
|
|
190
|
+
docMap.set(doc.frontmatter.id, { doc, directory });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const results = [];
|
|
194
|
+
for (const { record, score } of scored.slice(0, maxResults)) {
|
|
195
|
+
const entry = docMap.get(record.id);
|
|
196
|
+
if (!entry) continue;
|
|
197
|
+
results.push({
|
|
198
|
+
doc: entry.doc,
|
|
199
|
+
directory: entry.directory,
|
|
200
|
+
score,
|
|
201
|
+
embeddedText: record.embeddedText
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
return results;
|
|
205
|
+
}
|
|
206
|
+
function getEmbeddingStats(harnessDir) {
|
|
207
|
+
const store = loadEmbeddingStore(harnessDir);
|
|
208
|
+
if (!store) {
|
|
209
|
+
return {
|
|
210
|
+
indexed: 0,
|
|
211
|
+
modelId: null,
|
|
212
|
+
dimensions: 0,
|
|
213
|
+
lastIndexedAt: null,
|
|
214
|
+
storeSize: 0
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const storePath = join(harnessDir, STORE_DIR, STORE_FILE);
|
|
218
|
+
let storeSize = 0;
|
|
219
|
+
try {
|
|
220
|
+
storeSize = statSync(storePath).size;
|
|
221
|
+
} catch {
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
indexed: Object.keys(store.records).length,
|
|
225
|
+
modelId: store.modelId,
|
|
226
|
+
dimensions: store.dimensions,
|
|
227
|
+
lastIndexedAt: store.lastIndexedAt,
|
|
228
|
+
storeSize
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
export {
|
|
232
|
+
cosineSimilarity,
|
|
233
|
+
detectStalePrimitives,
|
|
234
|
+
extractEmbeddableText,
|
|
235
|
+
getEmbeddingStats,
|
|
236
|
+
indexPrimitives,
|
|
237
|
+
loadEmbeddingStore,
|
|
238
|
+
saveEmbeddingStore,
|
|
239
|
+
semanticSearch
|
|
240
|
+
};
|
|
241
|
+
//# sourceMappingURL=semantic-search-2DTOO5UX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/runtime/semantic-search.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync, mkdirSync, statSync } from 'fs';\nimport { join } from 'path';\nimport { loadAllPrimitives, estimateTokens, getAtLevel } from '../primitives/loader.js';\nimport type { HarnessDocument, HarnessConfig } from '../core/types.js';\nimport { withFileLockSync } from './file-lock.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** A stored embedding for a single primitive document. */\nexport interface EmbeddingRecord {\n /** Document ID from frontmatter */\n id: string;\n /** Path to the source markdown file */\n path: string;\n /** Primitive directory (rules, skills, etc.) */\n directory: string;\n /** Text that was embedded (L0 + L1 + tags) */\n embeddedText: string;\n /** The embedding vector */\n vector: number[];\n /** File modification time (to detect stale embeddings) */\n mtime: string;\n /** When the embedding was generated */\n createdAt: string;\n}\n\n/** Embedding store format — persisted as JSON. */\nexport interface EmbeddingStore {\n /** Model ID used for embeddings (invalidate cache if changed) */\n modelId: string;\n /** Embedding vector dimension */\n dimensions: number;\n /** Map of document ID → embedding record */\n records: Record<string, EmbeddingRecord>;\n /** Last full index time */\n lastIndexedAt: string;\n}\n\n/** Result of a semantic search query. */\nexport interface SemanticSearchResult {\n doc: HarnessDocument;\n directory: string;\n /** Cosine similarity score (0-1, higher is more relevant) */\n score: number;\n /** The embedded text that matched */\n embeddedText: string;\n}\n\n/** Function signature for embedding text → vector. */\nexport type EmbedFunction = (texts: string[]) => Promise<number[][]>;\n\n/** Configuration for the semantic search module. */\nexport interface SemanticSearchConfig {\n /** Function to embed text (wraps Vercel AI SDK embed/embedMany) */\n embed: EmbedFunction;\n /** Embedding model identifier (for cache invalidation) */\n modelId: string;\n /** Maximum results to return (default: 10) */\n maxResults?: number;\n /** Minimum similarity threshold (default: 0.3) */\n minScore?: number;\n}\n\n// ─── Constants ───────────────────────────────────────────────────────────────\n\nconst STORE_FILE = 'embeddings.json';\nconst STORE_DIR = 'memory';\n\n// ─── Store Management ────────────────────────────────────────────────────────\n\n/** Load the embedding store from disk. Returns null if not found or invalid. */\nexport function loadEmbeddingStore(harnessDir: string): EmbeddingStore | null {\n const storePath = join(harnessDir, STORE_DIR, STORE_FILE);\n if (!existsSync(storePath)) return null;\n\n try {\n const raw = readFileSync(storePath, 'utf-8');\n return JSON.parse(raw) as EmbeddingStore;\n } catch {\n return null;\n }\n}\n\n/** Save the embedding store to disk. */\nexport function saveEmbeddingStore(harnessDir: string, store: EmbeddingStore): void {\n const storeDir = join(harnessDir, STORE_DIR);\n if (!existsSync(storeDir)) {\n mkdirSync(storeDir, { recursive: true });\n }\n\n const storePath = join(storeDir, STORE_FILE);\n withFileLockSync(harnessDir, storePath, () => {\n writeFileSync(storePath, JSON.stringify(store), 'utf-8');\n });\n}\n\n// ─── Text Extraction ─────────────────────────────────────────────────────────\n\n/**\n * Extract embeddable text from a document.\n * Combines: tags, L0 summary, L1 summary, and first 500 chars of body.\n * This gives a compact representation for embedding.\n */\nexport function extractEmbeddableText(doc: HarnessDocument): string {\n const parts: string[] = [];\n\n // Tags provide topical context\n if (doc.frontmatter.tags.length > 0) {\n parts.push(`Tags: ${doc.frontmatter.tags.join(', ')}`);\n }\n\n // L0 — one-liner\n if (doc.l0) {\n parts.push(doc.l0);\n }\n\n // L1 — paragraph summary\n if (doc.l1) {\n parts.push(doc.l1);\n }\n\n // Truncated body for additional context\n const bodyPreview = doc.body.slice(0, 500).trim();\n if (bodyPreview) {\n parts.push(bodyPreview);\n }\n\n return parts.join('\\n').trim();\n}\n\n// ─── Indexing ────────────────────────────────────────────────────────────────\n\n/**\n * Detect which primitives need re-embedding.\n * A primitive is stale if:\n * - It doesn't exist in the store\n * - Its file mtime has changed since last embedding\n * - The embedding model has changed\n */\nexport function detectStalePrimitives(\n harnessDir: string,\n store: EmbeddingStore | null,\n modelId: string,\n config?: HarnessConfig,\n): Array<{ doc: HarnessDocument; directory: string }> {\n const stale: Array<{ doc: HarnessDocument; directory: string }> = [];\n const allPrimitives = loadAllPrimitives(harnessDir, config?.extensions?.directories);\n\n // If model changed, everything is stale\n const modelChanged = store !== null && store.modelId !== modelId;\n\n for (const [directory, docs] of allPrimitives) {\n for (const doc of docs) {\n if (doc.frontmatter.status !== 'active') continue;\n\n const id = doc.frontmatter.id;\n\n if (modelChanged || !store) {\n stale.push({ doc, directory });\n continue;\n }\n\n const existing = store.records[id];\n if (!existing) {\n stale.push({ doc, directory });\n continue;\n }\n\n // Check if file changed\n try {\n const stat = statSync(doc.path);\n if (stat.mtime.toISOString() !== existing.mtime) {\n stale.push({ doc, directory });\n }\n } catch {\n stale.push({ doc, directory });\n }\n }\n }\n\n return stale;\n}\n\n/**\n * Index (or re-index) all primitives that need embeddings.\n * Incrementally updates the store — only re-embeds stale documents.\n *\n * @param harnessDir - Harness directory path\n * @param config - Semantic search configuration with embed function\n * @param harnessConfig - Optional harness config for extension directories\n * @returns Updated embedding store\n */\nexport async function indexPrimitives(\n harnessDir: string,\n searchConfig: SemanticSearchConfig,\n harnessConfig?: HarnessConfig,\n): Promise<EmbeddingStore> {\n let store = loadEmbeddingStore(harnessDir);\n\n const stale = detectStalePrimitives(harnessDir, store, searchConfig.modelId, harnessConfig);\n\n if (stale.length === 0 && store) {\n return store;\n }\n\n // Initialize store if needed\n if (!store || store.modelId !== searchConfig.modelId) {\n store = {\n modelId: searchConfig.modelId,\n dimensions: 0,\n records: {},\n lastIndexedAt: new Date().toISOString(),\n };\n }\n\n // Extract texts to embed\n const textsToEmbed: string[] = [];\n const docInfos: Array<{ doc: HarnessDocument; directory: string }> = [];\n\n for (const item of stale) {\n const text = extractEmbeddableText(item.doc);\n if (!text) continue;\n textsToEmbed.push(text);\n docInfos.push(item);\n }\n\n if (textsToEmbed.length === 0) {\n return store;\n }\n\n // Batch embed (chunked to avoid hitting rate limits)\n const batchSize = 50;\n for (let i = 0; i < textsToEmbed.length; i += batchSize) {\n const batch = textsToEmbed.slice(i, i + batchSize);\n const batchDocs = docInfos.slice(i, i + batchSize);\n\n const vectors = await searchConfig.embed(batch);\n\n for (let j = 0; j < vectors.length; j++) {\n const doc = batchDocs[j].doc;\n const vector = vectors[j];\n\n if (store.dimensions === 0 && vector.length > 0) {\n store.dimensions = vector.length;\n }\n\n let mtime: string;\n try {\n const stat = statSync(doc.path);\n mtime = stat.mtime.toISOString();\n } catch {\n mtime = new Date().toISOString();\n }\n\n store.records[doc.frontmatter.id] = {\n id: doc.frontmatter.id,\n path: doc.path,\n directory: batchDocs[j].directory,\n embeddedText: batch[j],\n vector,\n mtime,\n createdAt: new Date().toISOString(),\n };\n }\n }\n\n store.lastIndexedAt = new Date().toISOString();\n\n // Clean up deleted docs\n const allIds = new Set<string>();\n const allPrimitives = loadAllPrimitives(harnessDir, harnessConfig?.extensions?.directories);\n for (const [, docs] of allPrimitives) {\n for (const doc of docs) {\n allIds.add(doc.frontmatter.id);\n }\n }\n\n for (const id of Object.keys(store.records)) {\n if (!allIds.has(id)) {\n delete store.records[id];\n }\n }\n\n saveEmbeddingStore(harnessDir, store);\n return store;\n}\n\n// ─── Search ──────────────────────────────────────────────────────────────────\n\n/**\n * Compute cosine similarity between two vectors.\n * Returns a value between -1 and 1 (1 = identical, 0 = orthogonal).\n */\nexport function cosineSimilarity(a: number[], b: number[]): number {\n if (a.length !== b.length || a.length === 0) return 0;\n\n let dotProduct = 0;\n let normA = 0;\n let normB = 0;\n\n for (let i = 0; i < a.length; i++) {\n dotProduct += a[i] * b[i];\n normA += a[i] * a[i];\n normB += b[i] * b[i];\n }\n\n const denominator = Math.sqrt(normA) * Math.sqrt(normB);\n if (denominator === 0) return 0;\n\n return dotProduct / denominator;\n}\n\n/**\n * Perform semantic search over indexed primitives.\n *\n * @param harnessDir - Harness directory path\n * @param query - Natural language query\n * @param searchConfig - Search configuration with embed function\n * @param harnessConfig - Optional harness config\n * @returns Ranked search results by cosine similarity\n */\nexport async function semanticSearch(\n harnessDir: string,\n query: string,\n searchConfig: SemanticSearchConfig,\n harnessConfig?: HarnessConfig,\n): Promise<SemanticSearchResult[]> {\n const store = loadEmbeddingStore(harnessDir);\n if (!store || Object.keys(store.records).length === 0) {\n return [];\n }\n\n const maxResults = searchConfig.maxResults ?? 10;\n const minScore = searchConfig.minScore ?? 0.3;\n\n // Embed the query\n const [queryVector] = await searchConfig.embed([query]);\n if (!queryVector || queryVector.length === 0) {\n return [];\n }\n\n // Score all documents\n const scored: Array<{ record: EmbeddingRecord; score: number }> = [];\n\n for (const record of Object.values(store.records)) {\n const score = cosineSimilarity(queryVector, record.vector);\n if (score >= minScore) {\n scored.push({ record, score });\n }\n }\n\n // Sort by score descending\n scored.sort((a, b) => b.score - a.score);\n\n // Load the actual documents for results\n const allPrimitives = loadAllPrimitives(harnessDir, harnessConfig?.extensions?.directories);\n const docMap = new Map<string, { doc: HarnessDocument; directory: string }>();\n for (const [directory, docs] of allPrimitives) {\n for (const doc of docs) {\n docMap.set(doc.frontmatter.id, { doc, directory });\n }\n }\n\n const results: SemanticSearchResult[] = [];\n\n for (const { record, score } of scored.slice(0, maxResults)) {\n const entry = docMap.get(record.id);\n if (!entry) continue;\n\n results.push({\n doc: entry.doc,\n directory: entry.directory,\n score,\n embeddedText: record.embeddedText,\n });\n }\n\n return results;\n}\n\n/**\n * Get embedding stats for the harness.\n */\nexport function getEmbeddingStats(harnessDir: string): {\n indexed: number;\n modelId: string | null;\n dimensions: number;\n lastIndexedAt: string | null;\n storeSize: number;\n} {\n const store = loadEmbeddingStore(harnessDir);\n if (!store) {\n return {\n indexed: 0,\n modelId: null,\n dimensions: 0,\n lastIndexedAt: null,\n storeSize: 0,\n };\n }\n\n const storePath = join(harnessDir, STORE_DIR, STORE_FILE);\n let storeSize = 0;\n try {\n storeSize = statSync(storePath).size;\n } catch {\n // Ignore\n }\n\n return {\n indexed: Object.keys(store.records).length,\n modelId: store.modelId,\n dimensions: store.dimensions,\n lastIndexedAt: store.lastIndexedAt,\n storeSize,\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Hono,
|
|
5
|
+
cors,
|
|
6
|
+
createWebApp,
|
|
7
|
+
serve
|
|
8
|
+
} from "./chunk-RY3ZFII7.js";
|
|
9
|
+
import "./chunk-A7BJPQQ6.js";
|
|
10
|
+
import "./chunk-6EMOEYGU.js";
|
|
11
|
+
import "./chunk-GNUSHD2Y.js";
|
|
12
|
+
import "./chunk-5H34JPMB.js";
|
|
13
|
+
import "./chunk-MPZ3BPUI.js";
|
|
14
|
+
import "./chunk-UWQTZMNI.js";
|
|
15
|
+
import "./chunk-UDZIS2AQ.js";
|
|
16
|
+
import "./chunk-DTTXPHFW.js";
|
|
17
|
+
import {
|
|
18
|
+
withFileLockSync
|
|
19
|
+
} from "./chunk-Z2PUCXTZ.js";
|
|
20
|
+
import "./chunk-TAT6JU3X.js";
|
|
21
|
+
import "./chunk-JKMGYWXB.js";
|
|
22
|
+
import "./chunk-UPLBF4RZ.js";
|
|
23
|
+
import {
|
|
24
|
+
log
|
|
25
|
+
} from "./chunk-BSKDOFRT.js";
|
|
26
|
+
import "./chunk-IZ6UZ3ZL.js";
|
|
27
|
+
import {
|
|
28
|
+
loadConfig
|
|
29
|
+
} from "./chunk-CHJ5GNZC.js";
|
|
30
|
+
import "./chunk-4CWAGBNS.js";
|
|
31
|
+
import "./chunk-ZZJOFKAT.js";
|
|
32
|
+
|
|
33
|
+
// src/runtime/serve.ts
|
|
34
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
35
|
+
import { join } from "path";
|
|
36
|
+
var WEBHOOK_FILE = "webhooks.json";
|
|
37
|
+
function loadWebhooks(harnessDir) {
|
|
38
|
+
const filePath = join(harnessDir, "memory", WEBHOOK_FILE);
|
|
39
|
+
if (!existsSync(filePath)) return { webhooks: [] };
|
|
40
|
+
try {
|
|
41
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
42
|
+
return JSON.parse(raw);
|
|
43
|
+
} catch {
|
|
44
|
+
return { webhooks: [] };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function saveWebhooks(harnessDir, store) {
|
|
48
|
+
const memDir = join(harnessDir, "memory");
|
|
49
|
+
if (!existsSync(memDir)) mkdirSync(memDir, { recursive: true });
|
|
50
|
+
const filePath = join(memDir, WEBHOOK_FILE);
|
|
51
|
+
withFileLockSync(harnessDir, filePath, () => {
|
|
52
|
+
writeFileSync(filePath, JSON.stringify(store, null, 2), "utf-8");
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
async function fireWebhookEvent(harnessDir, event, data) {
|
|
56
|
+
const store = loadWebhooks(harnessDir);
|
|
57
|
+
const subscribers = store.webhooks.filter(
|
|
58
|
+
(w) => w.active && (w.events.includes("*") || w.events.includes(event))
|
|
59
|
+
);
|
|
60
|
+
if (subscribers.length === 0) return;
|
|
61
|
+
const payload = {
|
|
62
|
+
event,
|
|
63
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
64
|
+
data
|
|
65
|
+
};
|
|
66
|
+
const deliveries = subscribers.map(async (webhook) => {
|
|
67
|
+
try {
|
|
68
|
+
const body = JSON.stringify({ ...payload, webhookId: webhook.id });
|
|
69
|
+
const headers = {
|
|
70
|
+
"Content-Type": "application/json",
|
|
71
|
+
"X-Harness-Event": event,
|
|
72
|
+
"X-Webhook-ID": webhook.id
|
|
73
|
+
};
|
|
74
|
+
if (webhook.secret) {
|
|
75
|
+
const crypto = await import("crypto");
|
|
76
|
+
const hmac = crypto.createHmac("sha256", webhook.secret);
|
|
77
|
+
hmac.update(body);
|
|
78
|
+
headers["X-Harness-Signature"] = `sha256=${hmac.digest("hex")}`;
|
|
79
|
+
}
|
|
80
|
+
const controller = new AbortController();
|
|
81
|
+
const timer = setTimeout(() => controller.abort(), 1e4);
|
|
82
|
+
const response = await fetch(webhook.url, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers,
|
|
85
|
+
body,
|
|
86
|
+
signal: controller.signal
|
|
87
|
+
});
|
|
88
|
+
clearTimeout(timer);
|
|
89
|
+
if (!response.ok) {
|
|
90
|
+
log.warn(`Webhook ${webhook.id} delivery failed: HTTP ${response.status}`);
|
|
91
|
+
}
|
|
92
|
+
} catch (err) {
|
|
93
|
+
log.warn(`Webhook ${webhook.id} delivery failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
await Promise.allSettled(deliveries);
|
|
97
|
+
}
|
|
98
|
+
function startServe(options) {
|
|
99
|
+
const {
|
|
100
|
+
harnessDir,
|
|
101
|
+
port = 8080,
|
|
102
|
+
apiKey,
|
|
103
|
+
webhookSecret
|
|
104
|
+
} = options;
|
|
105
|
+
const { app: baseApp, broadcaster } = createWebApp(harnessDir, { apiKey });
|
|
106
|
+
const app = new Hono();
|
|
107
|
+
app.use("*", cors());
|
|
108
|
+
const requireAuth = (secret) => {
|
|
109
|
+
return async (c, next) => {
|
|
110
|
+
if (!secret) return next();
|
|
111
|
+
const auth = c.req.header("Authorization");
|
|
112
|
+
if (!auth || auth !== `Bearer ${secret}`) {
|
|
113
|
+
return c.json({ error: "Unauthorized" }, 401);
|
|
114
|
+
}
|
|
115
|
+
return next();
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
app.get("/api/health", (c) => {
|
|
119
|
+
return c.json({
|
|
120
|
+
status: "ok",
|
|
121
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
122
|
+
harnessDir
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
app.get("/api/info", (c) => {
|
|
126
|
+
try {
|
|
127
|
+
const config = loadConfig(harnessDir);
|
|
128
|
+
return c.json({
|
|
129
|
+
name: config.agent.name,
|
|
130
|
+
version: config.agent.version,
|
|
131
|
+
model: config.model.id,
|
|
132
|
+
provider: config.model.provider
|
|
133
|
+
});
|
|
134
|
+
} catch {
|
|
135
|
+
return c.json({ error: "Failed to load config" }, 500);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
app.post("/api/run", async (c) => {
|
|
139
|
+
const body = await c.req.json().catch(() => ({}));
|
|
140
|
+
if (!body.prompt || body.prompt.trim().length === 0) {
|
|
141
|
+
return c.json({ error: "prompt is required" }, 400);
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
const { createHarness } = await import("./harness-WE4SLCML.js");
|
|
145
|
+
const harness = createHarness({
|
|
146
|
+
dir: harnessDir,
|
|
147
|
+
model: body.model,
|
|
148
|
+
apiKey
|
|
149
|
+
});
|
|
150
|
+
await harness.boot();
|
|
151
|
+
const result = await harness.run(body.prompt);
|
|
152
|
+
await harness.shutdown();
|
|
153
|
+
await fireWebhookEvent(harnessDir, "run_complete", {
|
|
154
|
+
prompt: body.prompt,
|
|
155
|
+
text: result.text
|
|
156
|
+
});
|
|
157
|
+
return c.json({
|
|
158
|
+
text: result.text,
|
|
159
|
+
usage: result.usage,
|
|
160
|
+
steps: result.steps
|
|
161
|
+
});
|
|
162
|
+
} catch (err) {
|
|
163
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
164
|
+
await fireWebhookEvent(harnessDir, "run_error", {
|
|
165
|
+
prompt: body.prompt,
|
|
166
|
+
error: message
|
|
167
|
+
});
|
|
168
|
+
return c.json({ error: message }, 500);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
app.get("/api/webhooks", requireAuth(webhookSecret), (c) => {
|
|
172
|
+
const store = loadWebhooks(harnessDir);
|
|
173
|
+
return c.json(store.webhooks.map((w) => ({
|
|
174
|
+
id: w.id,
|
|
175
|
+
url: w.url,
|
|
176
|
+
events: w.events,
|
|
177
|
+
active: w.active,
|
|
178
|
+
createdAt: w.createdAt
|
|
179
|
+
})));
|
|
180
|
+
});
|
|
181
|
+
app.post("/api/webhooks", requireAuth(webhookSecret), async (c) => {
|
|
182
|
+
const body = await c.req.json().catch(() => ({}));
|
|
183
|
+
if (!body.url) {
|
|
184
|
+
return c.json({ error: "url is required" }, 400);
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
new URL(body.url);
|
|
188
|
+
} catch {
|
|
189
|
+
return c.json({ error: "Invalid URL" }, 400);
|
|
190
|
+
}
|
|
191
|
+
const store = loadWebhooks(harnessDir);
|
|
192
|
+
const id = `wh_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
193
|
+
const webhook = {
|
|
194
|
+
id,
|
|
195
|
+
url: body.url,
|
|
196
|
+
events: body.events ?? ["*"],
|
|
197
|
+
secret: body.secret,
|
|
198
|
+
active: true,
|
|
199
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
200
|
+
};
|
|
201
|
+
store.webhooks.push(webhook);
|
|
202
|
+
saveWebhooks(harnessDir, store);
|
|
203
|
+
return c.json({ id, url: webhook.url, events: webhook.events }, 201);
|
|
204
|
+
});
|
|
205
|
+
app.delete("/api/webhooks/:id", requireAuth(webhookSecret), (c) => {
|
|
206
|
+
const id = c.req.param("id");
|
|
207
|
+
const store = loadWebhooks(harnessDir);
|
|
208
|
+
const index = store.webhooks.findIndex((w) => w.id === id);
|
|
209
|
+
if (index === -1) {
|
|
210
|
+
return c.json({ error: "Webhook not found" }, 404);
|
|
211
|
+
}
|
|
212
|
+
store.webhooks.splice(index, 1);
|
|
213
|
+
saveWebhooks(harnessDir, store);
|
|
214
|
+
return c.json({ deleted: id });
|
|
215
|
+
});
|
|
216
|
+
app.patch("/api/webhooks/:id", requireAuth(webhookSecret), async (c) => {
|
|
217
|
+
const id = c.req.param("id");
|
|
218
|
+
const body = await c.req.json().catch(() => ({}));
|
|
219
|
+
const store = loadWebhooks(harnessDir);
|
|
220
|
+
const webhook = store.webhooks.find((w) => w.id === id);
|
|
221
|
+
if (!webhook) {
|
|
222
|
+
return c.json({ error: "Webhook not found" }, 404);
|
|
223
|
+
}
|
|
224
|
+
if (body.active !== void 0) {
|
|
225
|
+
webhook.active = body.active;
|
|
226
|
+
}
|
|
227
|
+
saveWebhooks(harnessDir, store);
|
|
228
|
+
return c.json({ id, active: webhook.active });
|
|
229
|
+
});
|
|
230
|
+
app.post("/api/webhooks/:id/test", requireAuth(webhookSecret), async (c) => {
|
|
231
|
+
const id = c.req.param("id");
|
|
232
|
+
const store = loadWebhooks(harnessDir);
|
|
233
|
+
const webhook = store.webhooks.find((w) => w.id === id);
|
|
234
|
+
if (!webhook) {
|
|
235
|
+
return c.json({ error: "Webhook not found" }, 404);
|
|
236
|
+
}
|
|
237
|
+
try {
|
|
238
|
+
const body = JSON.stringify({
|
|
239
|
+
event: "test",
|
|
240
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
241
|
+
data: { message: "Webhook test from harness serve" },
|
|
242
|
+
webhookId: webhook.id
|
|
243
|
+
});
|
|
244
|
+
const headers = {
|
|
245
|
+
"Content-Type": "application/json",
|
|
246
|
+
"X-Harness-Event": "test",
|
|
247
|
+
"X-Webhook-ID": webhook.id
|
|
248
|
+
};
|
|
249
|
+
if (webhook.secret) {
|
|
250
|
+
const crypto = await import("crypto");
|
|
251
|
+
const hmac = crypto.createHmac("sha256", webhook.secret);
|
|
252
|
+
hmac.update(body);
|
|
253
|
+
headers["X-Harness-Signature"] = `sha256=${hmac.digest("hex")}`;
|
|
254
|
+
}
|
|
255
|
+
const controller = new AbortController();
|
|
256
|
+
const timer = setTimeout(() => controller.abort(), 1e4);
|
|
257
|
+
const response = await fetch(webhook.url, {
|
|
258
|
+
method: "POST",
|
|
259
|
+
headers,
|
|
260
|
+
body,
|
|
261
|
+
signal: controller.signal
|
|
262
|
+
});
|
|
263
|
+
clearTimeout(timer);
|
|
264
|
+
return c.json({
|
|
265
|
+
success: response.ok,
|
|
266
|
+
status: response.status,
|
|
267
|
+
statusText: response.statusText
|
|
268
|
+
});
|
|
269
|
+
} catch (err) {
|
|
270
|
+
return c.json({
|
|
271
|
+
success: false,
|
|
272
|
+
error: err instanceof Error ? err.message : String(err)
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
app.route("/", baseApp);
|
|
277
|
+
const server = serve({ fetch: app.fetch, port });
|
|
278
|
+
const stop = () => {
|
|
279
|
+
server.close();
|
|
280
|
+
};
|
|
281
|
+
return {
|
|
282
|
+
server,
|
|
283
|
+
port,
|
|
284
|
+
fireEvent: (event, data) => fireWebhookEvent(harnessDir, event, data),
|
|
285
|
+
stop
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
export {
|
|
289
|
+
startServe
|
|
290
|
+
};
|
|
291
|
+
//# sourceMappingURL=serve-DTQ3HENY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/runtime/serve.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { Hono } from 'hono';\nimport { cors } from 'hono/cors';\nimport { serve as honoServe } from '@hono/node-server';\nimport type { Server } from 'http';\nimport { createWebApp } from './web-server.js';\nimport { log } from '../core/logger.js';\nimport { loadConfig } from '../core/config.js';\nimport { Conversation } from './conversation.js';\nimport { withFileLockSync } from './file-lock.js';\nimport type { HarnessConfig } from '../core/types.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface ServeOptions {\n harnessDir: string;\n port?: number;\n apiKey?: string;\n /** Secret for authenticating incoming webhooks */\n webhookSecret?: string;\n /** Enable CORS for all origins (default: true) */\n corsEnabled?: boolean;\n}\n\nexport interface WebhookRegistration {\n /** Unique webhook ID */\n id: string;\n /** URL to send events to */\n url: string;\n /** Events to subscribe to (e.g., ['session_end', 'state_change']) */\n events: string[];\n /** Optional secret for signing payloads */\n secret?: string;\n /** Whether this webhook is active */\n active: boolean;\n /** Created timestamp */\n createdAt: string;\n}\n\nexport interface WebhookPayload {\n event: string;\n timestamp: string;\n data: unknown;\n webhookId: string;\n}\n\nexport interface WebhookStore {\n webhooks: WebhookRegistration[];\n}\n\nexport interface ServeResult {\n server: Server;\n port: number;\n /** Function to fire a webhook event */\n fireEvent: (event: string, data: unknown) => Promise<void>;\n /** Function to stop the server */\n stop: () => void;\n}\n\n// ─── Webhook Store ──────────────────────────────────────────────────────────\n\nconst WEBHOOK_FILE = 'webhooks.json';\n\nfunction loadWebhooks(harnessDir: string): WebhookStore {\n const filePath = join(harnessDir, 'memory', WEBHOOK_FILE);\n if (!existsSync(filePath)) return { webhooks: [] };\n\n try {\n const raw = readFileSync(filePath, 'utf-8');\n return JSON.parse(raw) as WebhookStore;\n } catch {\n return { webhooks: [] };\n }\n}\n\nfunction saveWebhooks(harnessDir: string, store: WebhookStore): void {\n const memDir = join(harnessDir, 'memory');\n if (!existsSync(memDir)) mkdirSync(memDir, { recursive: true });\n\n const filePath = join(memDir, WEBHOOK_FILE);\n withFileLockSync(harnessDir, filePath, () => {\n writeFileSync(filePath, JSON.stringify(store, null, 2), 'utf-8');\n });\n}\n\n// ─── Webhook Delivery ───────────────────────────────────────────────────────\n\n/**\n * Fire an event to all subscribed webhooks.\n * Non-blocking — logs failures but never throws.\n */\nasync function fireWebhookEvent(\n harnessDir: string,\n event: string,\n data: unknown,\n): Promise<void> {\n const store = loadWebhooks(harnessDir);\n const subscribers = store.webhooks.filter(\n (w) => w.active && (w.events.includes('*') || w.events.includes(event)),\n );\n\n if (subscribers.length === 0) return;\n\n const payload: Omit<WebhookPayload, 'webhookId'> = {\n event,\n timestamp: new Date().toISOString(),\n data,\n };\n\n const deliveries = subscribers.map(async (webhook) => {\n try {\n const body = JSON.stringify({ ...payload, webhookId: webhook.id });\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'X-Harness-Event': event,\n 'X-Webhook-ID': webhook.id,\n };\n\n // HMAC signing if secret is configured\n if (webhook.secret) {\n const crypto = await import('crypto');\n const hmac = crypto.createHmac('sha256', webhook.secret);\n hmac.update(body);\n headers['X-Harness-Signature'] = `sha256=${hmac.digest('hex')}`;\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 10000);\n\n const response = await fetch(webhook.url, {\n method: 'POST',\n headers,\n body,\n signal: controller.signal,\n });\n\n clearTimeout(timer);\n\n if (!response.ok) {\n log.warn(`Webhook ${webhook.id} delivery failed: HTTP ${response.status}`);\n }\n } catch (err) {\n log.warn(`Webhook ${webhook.id} delivery failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n });\n\n await Promise.allSettled(deliveries);\n}\n\n// ─── Server Factory ─────────────────────────────────────────────────────────\n\n/**\n * Create and start the harness API server.\n *\n * Includes:\n * - All dashboard endpoints from web-server.ts\n * - Webhook registration and management API\n * - Prompt execution endpoint (POST /api/run)\n * - Health check endpoint (GET /api/health)\n * - Version information endpoint (GET /api/info)\n *\n * Usage:\n * ```typescript\n * const result = startServe({\n * harnessDir: './my-harness',\n * port: 8080,\n * webhookSecret: 'my-secret',\n * });\n *\n * // Fire events to registered webhooks\n * await result.fireEvent('custom_event', { key: 'value' });\n *\n * // Stop the server\n * result.stop();\n * ```\n */\nexport function startServe(options: ServeOptions): ServeResult {\n const {\n harnessDir,\n port = 8080,\n apiKey,\n webhookSecret,\n } = options;\n\n // Build the base web app (dashboard + primitives + sessions + chat + SSE)\n const { app: baseApp, broadcaster } = createWebApp(harnessDir, { apiKey });\n\n // Create a new Hono app that wraps the base with additional endpoints\n const app = new Hono();\n app.use('*', cors());\n\n // ── Authentication middleware for webhook management ──\n const requireAuth = (secret: string | undefined) => {\n return async (c: { req: { header: (name: string) => string | undefined }; json: (body: unknown, status: number) => Response }, next: () => Promise<void>): Promise<Response | void> => {\n if (!secret) return next();\n const auth = c.req.header('Authorization');\n if (!auth || auth !== `Bearer ${secret}`) {\n return c.json({ error: 'Unauthorized' }, 401);\n }\n return next();\n };\n };\n\n // ── Health Check ──\n app.get('/api/health', (c) => {\n return c.json({\n status: 'ok',\n timestamp: new Date().toISOString(),\n harnessDir,\n });\n });\n\n // ── Agent Info ──\n app.get('/api/info', (c) => {\n try {\n const config = loadConfig(harnessDir);\n return c.json({\n name: config.agent.name,\n version: config.agent.version,\n model: config.model.id,\n provider: config.model.provider,\n });\n } catch {\n return c.json({ error: 'Failed to load config' }, 500);\n }\n });\n\n // ── Run prompt ──\n app.post('/api/run', async (c) => {\n const body = await c.req.json<{ prompt?: string; model?: string }>().catch(() => ({} as { prompt?: string; model?: string }));\n if (!body.prompt || body.prompt.trim().length === 0) {\n return c.json({ error: 'prompt is required' }, 400);\n }\n\n try {\n const { createHarness } = await import('../core/harness.js');\n const harness = createHarness({\n dir: harnessDir,\n model: body.model,\n apiKey,\n });\n\n await harness.boot();\n const result = await harness.run(body.prompt);\n await harness.shutdown();\n\n // Fire webhook\n await fireWebhookEvent(harnessDir, 'run_complete', {\n prompt: body.prompt,\n text: result.text,\n });\n\n return c.json({\n text: result.text,\n usage: result.usage,\n steps: result.steps,\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n await fireWebhookEvent(harnessDir, 'run_error', {\n prompt: body.prompt,\n error: message,\n });\n return c.json({ error: message }, 500);\n }\n });\n\n // ── Webhook Registration API ──\n\n // List registered webhooks\n app.get('/api/webhooks', requireAuth(webhookSecret) as never, (c) => {\n const store = loadWebhooks(harnessDir);\n return c.json(store.webhooks.map((w) => ({\n id: w.id,\n url: w.url,\n events: w.events,\n active: w.active,\n createdAt: w.createdAt,\n })));\n });\n\n // Register a new webhook\n app.post('/api/webhooks', requireAuth(webhookSecret) as never, async (c) => {\n const body = await c.req.json<{\n url?: string;\n events?: string[];\n secret?: string;\n }>().catch(() => ({} as { url?: string; events?: string[]; secret?: string }));\n\n if (!body.url) {\n return c.json({ error: 'url is required' }, 400);\n }\n\n // Validate URL\n try {\n new URL(body.url);\n } catch {\n return c.json({ error: 'Invalid URL' }, 400);\n }\n\n const store = loadWebhooks(harnessDir);\n const id = `wh_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;\n\n const webhook: WebhookRegistration = {\n id,\n url: body.url,\n events: body.events ?? ['*'],\n secret: body.secret,\n active: true,\n createdAt: new Date().toISOString(),\n };\n\n store.webhooks.push(webhook);\n saveWebhooks(harnessDir, store);\n\n return c.json({ id, url: webhook.url, events: webhook.events }, 201);\n });\n\n // Delete a webhook\n app.delete('/api/webhooks/:id', requireAuth(webhookSecret) as never, (c) => {\n const id = c.req.param('id');\n const store = loadWebhooks(harnessDir);\n const index = store.webhooks.findIndex((w) => w.id === id);\n\n if (index === -1) {\n return c.json({ error: 'Webhook not found' }, 404);\n }\n\n store.webhooks.splice(index, 1);\n saveWebhooks(harnessDir, store);\n\n return c.json({ deleted: id });\n });\n\n // Toggle webhook active/inactive\n app.patch('/api/webhooks/:id', requireAuth(webhookSecret) as never, async (c) => {\n const id = c.req.param('id');\n const body = await c.req.json<{ active?: boolean }>().catch(() => ({} as { active?: boolean }));\n\n const store = loadWebhooks(harnessDir);\n const webhook = store.webhooks.find((w) => w.id === id);\n\n if (!webhook) {\n return c.json({ error: 'Webhook not found' }, 404);\n }\n\n if (body.active !== undefined) {\n webhook.active = body.active;\n }\n\n saveWebhooks(harnessDir, store);\n return c.json({ id, active: webhook.active });\n });\n\n // Test a webhook (sends a test event)\n app.post('/api/webhooks/:id/test', requireAuth(webhookSecret) as never, async (c) => {\n const id = c.req.param('id');\n const store = loadWebhooks(harnessDir);\n const webhook = store.webhooks.find((w) => w.id === id);\n\n if (!webhook) {\n return c.json({ error: 'Webhook not found' }, 404);\n }\n\n try {\n const body = JSON.stringify({\n event: 'test',\n timestamp: new Date().toISOString(),\n data: { message: 'Webhook test from harness serve' },\n webhookId: webhook.id,\n });\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'X-Harness-Event': 'test',\n 'X-Webhook-ID': webhook.id,\n };\n\n if (webhook.secret) {\n const crypto = await import('crypto');\n const hmac = crypto.createHmac('sha256', webhook.secret);\n hmac.update(body);\n headers['X-Harness-Signature'] = `sha256=${hmac.digest('hex')}`;\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 10000);\n\n const response = await fetch(webhook.url, {\n method: 'POST',\n headers,\n body,\n signal: controller.signal,\n });\n\n clearTimeout(timer);\n\n return c.json({\n success: response.ok,\n status: response.status,\n statusText: response.statusText,\n });\n } catch (err) {\n return c.json({\n success: false,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n });\n\n // ── Mount base web app routes ──\n app.route('/', baseApp);\n\n // Start server\n const server = honoServe({ fetch: app.fetch, port }) as Server;\n\n const stop = (): void => {\n server.close();\n };\n\n return {\n server,\n port,\n fireEvent: (event: string, data: unknown) => fireWebhookEvent(harnessDir, event, data),\n stop,\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
archiveOldFiles,
|
|
5
|
+
cleanupOldFiles,
|
|
6
|
+
createSessionId,
|
|
7
|
+
listExpiredFiles,
|
|
8
|
+
listSessions,
|
|
9
|
+
writeSession
|
|
10
|
+
} from "./chunk-DTTXPHFW.js";
|
|
11
|
+
import "./chunk-Z2PUCXTZ.js";
|
|
12
|
+
import "./chunk-ZZJOFKAT.js";
|
|
13
|
+
export {
|
|
14
|
+
archiveOldFiles,
|
|
15
|
+
cleanupOldFiles,
|
|
16
|
+
createSessionId,
|
|
17
|
+
listExpiredFiles,
|
|
18
|
+
listSessions,
|
|
19
|
+
writeSession
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=sessions-CZGVXKQE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
addSource,
|
|
5
|
+
discoverRemote,
|
|
6
|
+
discoverSources,
|
|
7
|
+
fetchGitHubSource,
|
|
8
|
+
getSourcesForType,
|
|
9
|
+
getSourcesSummary,
|
|
10
|
+
loadAllSources,
|
|
11
|
+
loadShippedSources,
|
|
12
|
+
loadUserSources,
|
|
13
|
+
removeSource,
|
|
14
|
+
saveUserSources
|
|
15
|
+
} from "./chunk-RC6MEZB6.js";
|
|
16
|
+
import "./chunk-Z2PUCXTZ.js";
|
|
17
|
+
import "./chunk-BSKDOFRT.js";
|
|
18
|
+
import "./chunk-ZZJOFKAT.js";
|
|
19
|
+
export {
|
|
20
|
+
addSource,
|
|
21
|
+
discoverRemote,
|
|
22
|
+
discoverSources,
|
|
23
|
+
fetchGitHubSource,
|
|
24
|
+
getSourcesForType,
|
|
25
|
+
getSourcesSummary,
|
|
26
|
+
loadAllSources,
|
|
27
|
+
loadShippedSources,
|
|
28
|
+
loadUserSources,
|
|
29
|
+
removeSource,
|
|
30
|
+
saveUserSources
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=sources-RW5DT56F.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|