@botbotgo/agent-harness 0.0.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.
Files changed (87) hide show
  1. package/dist/api.d.ts +22 -0
  2. package/dist/api.js +48 -0
  3. package/dist/config/direct.yaml +48 -0
  4. package/dist/config/embedding-model.yaml +30 -0
  5. package/dist/config/model.yaml +44 -0
  6. package/dist/config/orchestra.yaml +92 -0
  7. package/dist/config/runtime.yaml +47 -0
  8. package/dist/config/vector-store.yaml +26 -0
  9. package/dist/contracts/types.d.ts +359 -0
  10. package/dist/contracts/types.js +1 -0
  11. package/dist/extensions.d.ts +16 -0
  12. package/dist/extensions.js +251 -0
  13. package/dist/index.d.ts +7 -0
  14. package/dist/index.js +7 -0
  15. package/dist/persistence/file-store.d.ts +39 -0
  16. package/dist/persistence/file-store.js +282 -0
  17. package/dist/presentation.d.ts +4 -0
  18. package/dist/presentation.js +175 -0
  19. package/dist/runtime/agent-runtime-adapter.d.ts +33 -0
  20. package/dist/runtime/agent-runtime-adapter.js +445 -0
  21. package/dist/runtime/event-bus.d.ts +6 -0
  22. package/dist/runtime/event-bus.js +15 -0
  23. package/dist/runtime/file-checkpoint-saver.d.ts +20 -0
  24. package/dist/runtime/file-checkpoint-saver.js +91 -0
  25. package/dist/runtime/harness.d.ts +57 -0
  26. package/dist/runtime/harness.js +696 -0
  27. package/dist/runtime/index.d.ts +10 -0
  28. package/dist/runtime/index.js +10 -0
  29. package/dist/runtime/inventory.d.ts +25 -0
  30. package/dist/runtime/inventory.js +62 -0
  31. package/dist/runtime/parsing/index.d.ts +2 -0
  32. package/dist/runtime/parsing/index.js +2 -0
  33. package/dist/runtime/parsing/output-parsing.d.ts +12 -0
  34. package/dist/runtime/parsing/output-parsing.js +424 -0
  35. package/dist/runtime/parsing/stream-event-parsing.d.ts +27 -0
  36. package/dist/runtime/parsing/stream-event-parsing.js +161 -0
  37. package/dist/runtime/policy-engine.d.ts +9 -0
  38. package/dist/runtime/policy-engine.js +23 -0
  39. package/dist/runtime/store.d.ts +50 -0
  40. package/dist/runtime/store.js +118 -0
  41. package/dist/runtime/support/embedding-models.d.ts +4 -0
  42. package/dist/runtime/support/embedding-models.js +33 -0
  43. package/dist/runtime/support/harness-support.d.ts +27 -0
  44. package/dist/runtime/support/harness-support.js +116 -0
  45. package/dist/runtime/support/index.d.ts +4 -0
  46. package/dist/runtime/support/index.js +4 -0
  47. package/dist/runtime/support/llamaindex.d.ts +24 -0
  48. package/dist/runtime/support/llamaindex.js +108 -0
  49. package/dist/runtime/support/runtime-factories.d.ts +3 -0
  50. package/dist/runtime/support/runtime-factories.js +39 -0
  51. package/dist/runtime/support/skill-metadata.d.ts +1 -0
  52. package/dist/runtime/support/skill-metadata.js +34 -0
  53. package/dist/runtime/support/vector-stores.d.ts +7 -0
  54. package/dist/runtime/support/vector-stores.js +130 -0
  55. package/dist/runtime/thread-memory-sync.d.ts +14 -0
  56. package/dist/runtime/thread-memory-sync.js +88 -0
  57. package/dist/runtime/tool-hitl.d.ts +5 -0
  58. package/dist/runtime/tool-hitl.js +108 -0
  59. package/dist/utils/fs.d.ts +6 -0
  60. package/dist/utils/fs.js +39 -0
  61. package/dist/utils/id.d.ts +1 -0
  62. package/dist/utils/id.js +8 -0
  63. package/dist/vendor/builtins.d.ts +23 -0
  64. package/dist/vendor/builtins.js +103 -0
  65. package/dist/vendor/sources.d.ts +12 -0
  66. package/dist/vendor/sources.js +115 -0
  67. package/dist/workspace/agent-binding-compiler.d.ts +4 -0
  68. package/dist/workspace/agent-binding-compiler.js +181 -0
  69. package/dist/workspace/compile.d.ts +2 -0
  70. package/dist/workspace/compile.js +107 -0
  71. package/dist/workspace/index.d.ts +6 -0
  72. package/dist/workspace/index.js +6 -0
  73. package/dist/workspace/object-loader.d.ts +16 -0
  74. package/dist/workspace/object-loader.js +405 -0
  75. package/dist/workspace/resource-compilers.d.ts +13 -0
  76. package/dist/workspace/resource-compilers.js +182 -0
  77. package/dist/workspace/support/discovery.d.ts +5 -0
  78. package/dist/workspace/support/discovery.js +108 -0
  79. package/dist/workspace/support/index.d.ts +2 -0
  80. package/dist/workspace/support/index.js +2 -0
  81. package/dist/workspace/support/source-collectors.d.ts +3 -0
  82. package/dist/workspace/support/source-collectors.js +30 -0
  83. package/dist/workspace/support/workspace-ref-utils.d.ts +8 -0
  84. package/dist/workspace/support/workspace-ref-utils.js +50 -0
  85. package/dist/workspace/validate.d.ts +3 -0
  86. package/dist/workspace/validate.js +65 -0
  87. package/package.json +32 -0
@@ -0,0 +1,108 @@
1
+ import path from "node:path";
2
+ import { mkdir } from "node:fs/promises";
3
+ import { Document, NodeRelationship, SimpleVectorStore, VectorStoreQueryMode } from "llamaindex";
4
+ import { OllamaEmbedding } from "@llamaindex/ollama";
5
+ function resolveLlamaIndexOllamaInit(init) {
6
+ const config = typeof init.config === "object" && init.config
7
+ ? { ...init.config }
8
+ : {};
9
+ if (typeof init.baseUrl === "string" && !("host" in config)) {
10
+ config.host = init.baseUrl;
11
+ }
12
+ const options = typeof init.options === "object" && init.options
13
+ ? { ...init.options }
14
+ : undefined;
15
+ return {
16
+ model: String(init.model ?? ""),
17
+ config,
18
+ ...(options ? { options } : {}),
19
+ };
20
+ }
21
+ function resolveFilePath(rawUrl, workspaceRoot) {
22
+ const normalized = rawUrl.startsWith("file:") ? rawUrl.slice("file:".length) : rawUrl;
23
+ return path.isAbsolute(normalized) ? normalized : path.resolve(workspaceRoot, normalized);
24
+ }
25
+ export function createLlamaIndexEmbeddingModel(embeddingModel) {
26
+ if (embeddingModel.provider !== "llamaindex-ollama") {
27
+ throw new Error(`Embedding model provider ${embeddingModel.provider} is not supported by the built-in LlamaIndex runtime.`);
28
+ }
29
+ const runtime = new OllamaEmbedding({
30
+ ...resolveLlamaIndexOllamaInit({
31
+ ...embeddingModel.init,
32
+ model: embeddingModel.model,
33
+ }),
34
+ });
35
+ return {
36
+ async embedDocuments(texts) {
37
+ return Promise.all(texts.map((text) => runtime.getTextEmbedding(text)));
38
+ },
39
+ async embedQuery(text) {
40
+ return runtime.getTextEmbedding(text);
41
+ },
42
+ };
43
+ }
44
+ export async function createLlamaIndexVectorStore(workspaceRoot, vectorStore, embeddings) {
45
+ const persistPath = resolveFilePath(vectorStore.url ?? ".agent/vector_store.json", workspaceRoot);
46
+ await mkdir(path.dirname(persistPath), { recursive: true });
47
+ const emptyStore = () => SimpleVectorStore.fromDict({
48
+ embeddingDict: {},
49
+ textIdToRefDocId: {},
50
+ metadataDict: {},
51
+ }, embeddings);
52
+ let store = await SimpleVectorStore.fromPersistPath(persistPath, embeddings);
53
+ return {
54
+ kind: vectorStore.kind,
55
+ embeddingModelRef: vectorStore.embeddingModelRef,
56
+ async addDocuments(documents) {
57
+ const ids = documents.map((_, index) => `${Date.now()}-${index}-${Math.random().toString(36).slice(2, 10)}`);
58
+ const vectors = documents.length > 0 ? await embeddings.embedDocuments(documents.map((document) => document.pageContent)) : [];
59
+ const nodes = documents.map((document, index) => new Document({
60
+ id_: ids[index],
61
+ text: document.pageContent,
62
+ embedding: vectors[index] ?? [],
63
+ metadata: {
64
+ ...(document.metadata ?? {}),
65
+ __pageContent: document.pageContent,
66
+ },
67
+ relationships: {
68
+ [NodeRelationship.SOURCE]: {
69
+ nodeId: ids[index] ?? "",
70
+ metadata: document.metadata ?? {},
71
+ },
72
+ },
73
+ }));
74
+ const created = await store.add(nodes);
75
+ await store.persist(persistPath);
76
+ return created;
77
+ },
78
+ async similaritySearch(query, limit) {
79
+ const queryEmbedding = await embeddings.embedQuery(query);
80
+ const result = await store.query({
81
+ queryEmbedding,
82
+ similarityTopK: limit,
83
+ mode: VectorStoreQueryMode.DEFAULT,
84
+ });
85
+ return result.ids.map((id, index) => {
86
+ const metadata = (store.toDict().metadataDict[id] ?? {});
87
+ const pageContent = typeof metadata.__pageContent === "string" ? metadata.__pageContent : "";
88
+ const { __pageContent: _, ...rest } = metadata;
89
+ return {
90
+ pageContent,
91
+ metadata: rest,
92
+ score: result.similarities[index],
93
+ };
94
+ });
95
+ },
96
+ async delete(params) {
97
+ if (params.deleteAll) {
98
+ store = emptyStore();
99
+ await store.persist(persistPath);
100
+ return;
101
+ }
102
+ for (const id of params.ids ?? []) {
103
+ await store.delete(id);
104
+ }
105
+ await store.persist(persistPath);
106
+ },
107
+ };
108
+ }
@@ -0,0 +1,3 @@
1
+ import { type StoreLike } from "../store.js";
2
+ export declare function createStoreForConfig(storeConfig: Record<string, unknown>, runRoot: string): StoreLike;
3
+ export declare function createCheckpointerForConfig(checkpointerConfig: Record<string, unknown>, runRoot: string): unknown;
@@ -0,0 +1,39 @@
1
+ import path from "node:path";
2
+ import { MemorySaver } from "@langchain/langgraph";
3
+ import { SqliteSaver } from "@langchain/langgraph-checkpoint-sqlite";
4
+ import { FileCheckpointSaver } from "../file-checkpoint-saver.js";
5
+ import { createInMemoryStore, FileBackedStore } from "../store.js";
6
+ export function createStoreForConfig(storeConfig, runRoot) {
7
+ const kind = typeof storeConfig.kind === "string" ? storeConfig.kind : "FileStore";
8
+ switch (kind) {
9
+ case "FileStore": {
10
+ const configuredPath = typeof storeConfig.path === "string" ? storeConfig.path : "store.json";
11
+ return new FileBackedStore(path.isAbsolute(configuredPath) ? configuredPath : path.join(runRoot, configuredPath));
12
+ }
13
+ case "InMemoryStore":
14
+ return createInMemoryStore();
15
+ case "RedisStore":
16
+ throw new Error("Store kind RedisStore is not implemented in this runtime yet");
17
+ case "PostgresStore":
18
+ throw new Error("Store kind PostgresStore is not implemented in this runtime yet");
19
+ default:
20
+ throw new Error(`Unsupported store kind ${String(kind)}`);
21
+ }
22
+ }
23
+ export function createCheckpointerForConfig(checkpointerConfig, runRoot) {
24
+ const kind = typeof checkpointerConfig.kind === "string" ? checkpointerConfig.kind : "FileCheckpointer";
25
+ switch (kind) {
26
+ case "MemorySaver":
27
+ return new MemorySaver();
28
+ case "SqliteSaver": {
29
+ const configuredPath = typeof checkpointerConfig.path === "string" ? String(checkpointerConfig.path) : "checkpoints.sqlite";
30
+ return SqliteSaver.fromConnString(path.isAbsolute(configuredPath) ? configuredPath : path.join(runRoot, configuredPath));
31
+ }
32
+ case "FileCheckpointer": {
33
+ const configuredPath = typeof checkpointerConfig.path === "string" ? String(checkpointerConfig.path) : "checkpoints.json";
34
+ return new FileCheckpointSaver(path.isAbsolute(configuredPath) ? configuredPath : path.join(runRoot, configuredPath));
35
+ }
36
+ default:
37
+ throw new Error(`Unsupported checkpointer kind ${String(kind)}`);
38
+ }
39
+ }
@@ -0,0 +1 @@
1
+ export declare function readSkillName(skillPath: string): string;
@@ -0,0 +1,34 @@
1
+ import { readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ const skillNameCache = new Map();
4
+ function parseFrontmatterName(document) {
5
+ const match = document.match(/^---\s*\n([\s\S]*?)\n---\s*(?:\n|$)/);
6
+ if (!match) {
7
+ return undefined;
8
+ }
9
+ const nameLine = match[1]
10
+ .split("\n")
11
+ .map((line) => line.trim())
12
+ .find((line) => line.startsWith("name:"));
13
+ if (!nameLine) {
14
+ return undefined;
15
+ }
16
+ const value = nameLine.slice("name:".length).trim().replace(/^["']|["']$/g, "");
17
+ return value || undefined;
18
+ }
19
+ export function readSkillName(skillPath) {
20
+ const cached = skillNameCache.get(skillPath);
21
+ if (cached) {
22
+ return cached;
23
+ }
24
+ let resolvedName = path.basename(skillPath);
25
+ try {
26
+ const document = readFileSync(path.join(skillPath, "SKILL.md"), "utf8");
27
+ resolvedName = parseFrontmatterName(document) ?? resolvedName;
28
+ }
29
+ catch {
30
+ // Fall back to the directory name when the skill doc cannot be read.
31
+ }
32
+ skillNameCache.set(skillPath, resolvedName);
33
+ return resolvedName;
34
+ }
@@ -0,0 +1,7 @@
1
+ import type { CompiledVectorStore, RuntimeEmbeddingModelResolver, RuntimeVectorStoreResolver, WorkspaceBundle } from "../../contracts/types.js";
2
+ import { type VectorStoreRuntimeLike } from "./llamaindex.js";
3
+ export declare function resolveCompiledVectorStoreRef(workspace: WorkspaceBundle, vectorStoreRef?: string): CompiledVectorStore;
4
+ export declare function resolveCompiledVectorStore(workspace: WorkspaceBundle, vectorStore: CompiledVectorStore, options?: {
5
+ embeddingModelResolver?: RuntimeEmbeddingModelResolver;
6
+ vectorStoreResolver?: RuntimeVectorStoreResolver;
7
+ }): Promise<VectorStoreRuntimeLike>;
@@ -0,0 +1,130 @@
1
+ import path from "node:path";
2
+ import { mkdir } from "node:fs/promises";
3
+ import { Document } from "@langchain/core/documents";
4
+ import { createClient } from "@libsql/client";
5
+ import { LibSQLVectorStore } from "@langchain/community/vectorstores/libsql";
6
+ import { compileVectorStore } from "../../workspace/resource-compilers.js";
7
+ import { resolveRefId } from "../../workspace/support/workspace-ref-utils.js";
8
+ import { resolveCompiledEmbeddingModel, resolveCompiledEmbeddingModelRef } from "./embedding-models.js";
9
+ import { createLlamaIndexVectorStore } from "./llamaindex.js";
10
+ function resolveFileUrl(rawUrl, workspaceRoot) {
11
+ if (!rawUrl.startsWith("file:")) {
12
+ return rawUrl;
13
+ }
14
+ const filePath = rawUrl.slice("file:".length);
15
+ if (!filePath) {
16
+ return rawUrl;
17
+ }
18
+ if (path.isAbsolute(filePath)) {
19
+ return `file:${filePath}`;
20
+ }
21
+ return `file:${path.resolve(workspaceRoot, filePath)}`;
22
+ }
23
+ async function ensureFileUrlDirectory(rawUrl, workspaceRoot) {
24
+ const resolvedUrl = resolveFileUrl(rawUrl, workspaceRoot);
25
+ if (!resolvedUrl.startsWith("file:")) {
26
+ return resolvedUrl;
27
+ }
28
+ const filePath = resolvedUrl.slice("file:".length);
29
+ await mkdir(path.dirname(filePath), { recursive: true });
30
+ return resolvedUrl;
31
+ }
32
+ function assertIdentifier(value, label) {
33
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
34
+ throw new Error(`Invalid ${label} ${value}. Use only letters, numbers, and underscores.`);
35
+ }
36
+ return value;
37
+ }
38
+ async function ensureLibSqlSchema(db, table, column, dimensions) {
39
+ const safeTable = assertIdentifier(table, "table name");
40
+ const safeColumn = assertIdentifier(column, "column name");
41
+ const safeIndex = assertIdentifier(`idx_${safeTable}_${safeColumn}`, "index name");
42
+ await db.execute(`
43
+ CREATE TABLE IF NOT EXISTS ${safeTable} (
44
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
45
+ content TEXT NOT NULL,
46
+ metadata TEXT NOT NULL,
47
+ ${safeColumn} F32_BLOB(${dimensions}) NOT NULL
48
+ )
49
+ `);
50
+ await db.execute(`
51
+ CREATE INDEX IF NOT EXISTS ${safeIndex}
52
+ ON ${safeTable}(libsql_vector_idx(${safeColumn}))
53
+ `);
54
+ }
55
+ export function resolveCompiledVectorStoreRef(workspace, vectorStoreRef) {
56
+ const resolvedId = vectorStoreRef ? resolveRefId(vectorStoreRef) : "default";
57
+ const vectorStore = workspace.vectorStores.get(resolvedId);
58
+ if (vectorStore) {
59
+ return compileVectorStore(vectorStore);
60
+ }
61
+ if (!vectorStoreRef && workspace.vectorStores.size === 1) {
62
+ const soleStore = workspace.vectorStores.values().next().value;
63
+ if (soleStore) {
64
+ return compileVectorStore(soleStore);
65
+ }
66
+ }
67
+ throw new Error(`Missing vector store ${vectorStoreRef ?? "vector-store/default"}`);
68
+ }
69
+ export async function resolveCompiledVectorStore(workspace, vectorStore, options = {}) {
70
+ if (options.vectorStoreResolver) {
71
+ return options.vectorStoreResolver(vectorStore.id);
72
+ }
73
+ if (vectorStore.kind === "LlamaIndexSimpleVectorStore") {
74
+ const embeddingModel = resolveCompiledEmbeddingModelRef(workspace, vectorStore.embeddingModelRef);
75
+ const embeddings = await resolveCompiledEmbeddingModel(embeddingModel, options.embeddingModelResolver);
76
+ return createLlamaIndexVectorStore(workspace.workspaceRoot, vectorStore, embeddings);
77
+ }
78
+ if (vectorStore.kind !== "LibSQLVectorStore") {
79
+ throw new Error(`Vector store kind ${vectorStore.kind} is not supported by the built-in runtime.`);
80
+ }
81
+ const embeddingModel = resolveCompiledEmbeddingModelRef(workspace, vectorStore.embeddingModelRef);
82
+ const embeddings = await resolveCompiledEmbeddingModel(embeddingModel, options.embeddingModelResolver);
83
+ const db = createClient({
84
+ url: await ensureFileUrlDirectory(vectorStore.url ?? "", workspace.workspaceRoot),
85
+ ...(vectorStore.authToken ? { authToken: vectorStore.authToken } : {}),
86
+ });
87
+ const table = vectorStore.table ?? "vectors";
88
+ const column = vectorStore.column ?? "embedding";
89
+ const ensureSchemaForTexts = async (texts) => {
90
+ const seedText = texts.find((text) => text.trim().length > 0) ?? "seed";
91
+ const sample = await embeddings.embedQuery(seedText);
92
+ await ensureLibSqlSchema(db, table, column, sample.length);
93
+ };
94
+ return {
95
+ kind: vectorStore.kind,
96
+ embeddingModelRef: vectorStore.embeddingModelRef,
97
+ addDocuments: async (documents) => {
98
+ await ensureSchemaForTexts(documents.map((document) => document.pageContent));
99
+ const store = new LibSQLVectorStore(embeddings, {
100
+ db,
101
+ table,
102
+ column,
103
+ });
104
+ return store.addDocuments(documents.map((document) => new Document(document)));
105
+ },
106
+ similaritySearch: async (query, limit) => {
107
+ await ensureSchemaForTexts([query]);
108
+ const store = new LibSQLVectorStore(embeddings, {
109
+ db,
110
+ table,
111
+ column,
112
+ });
113
+ const rows = await store.similaritySearchWithScore(query, limit);
114
+ return rows.map(([document, score]) => ({
115
+ pageContent: document.pageContent,
116
+ metadata: document.metadata,
117
+ score: typeof score === "number" ? 1 - score : score,
118
+ }));
119
+ },
120
+ delete: async (params) => {
121
+ await ensureSchemaForTexts(["seed"]);
122
+ const store = new LibSQLVectorStore(embeddings, {
123
+ db,
124
+ table,
125
+ column,
126
+ });
127
+ await store.delete(params);
128
+ },
129
+ };
130
+ }
@@ -0,0 +1,14 @@
1
+ import type { HarnessEvent } from "../contracts/types.js";
2
+ import type { FilePersistence } from "../persistence/file-store.js";
3
+ export declare class ThreadMemorySync {
4
+ private readonly persistence;
5
+ private readonly store?;
6
+ private readonly pending;
7
+ constructor(persistence: FilePersistence, store?: {
8
+ put: (namespace: string[], key: string, value: Record<string, any>) => Promise<void>;
9
+ } | undefined);
10
+ handleEvent(event: HarnessEvent): Promise<void>;
11
+ private syncThread;
12
+ close(): Promise<void>;
13
+ }
14
+ export { ThreadMemorySync as MemoryWorker };
@@ -0,0 +1,88 @@
1
+ function excerpt(message) {
2
+ if (!message?.content) {
3
+ return "(none)";
4
+ }
5
+ const normalized = message.content.replace(/\s+/g, " ").trim();
6
+ return normalized.length > 240 ? `${normalized.slice(0, 237)}...` : normalized;
7
+ }
8
+ function renderStatusMarkdown(session, messages) {
9
+ const userMessages = messages.filter((message) => message.role === "user");
10
+ const assistantMessages = messages.filter((message) => message.role === "assistant");
11
+ return [
12
+ "# Thread Status",
13
+ "",
14
+ `- thread_id: ${session.threadId}`,
15
+ `- latest_run_id: ${session.latestRunId}`,
16
+ `- agent_id: ${session.agentId}`,
17
+ `- status: ${session.status}`,
18
+ `- updated_at: ${session.updatedAt}`,
19
+ "",
20
+ "## Recent User Message",
21
+ excerpt(userMessages.at(-1)),
22
+ "",
23
+ "## Recent Assistant Message",
24
+ excerpt(assistantMessages.at(-1)),
25
+ "",
26
+ ].join("\n");
27
+ }
28
+ function renderOpenApprovalsMarkdown(approvals) {
29
+ if (approvals.length === 0) {
30
+ return ["# Open Approvals", "", "(none)", ""].join("\n");
31
+ }
32
+ const lines = ["# Open Approvals", ""];
33
+ for (const approval of approvals) {
34
+ lines.push(`## ${approval.approvalId}`);
35
+ lines.push(`- pending_action_id: ${approval.pendingActionId}`);
36
+ lines.push(`- tool: ${approval.toolName}`);
37
+ lines.push(`- run_id: ${approval.runId}`);
38
+ lines.push(`- requested_at: ${approval.requestedAt}`);
39
+ lines.push(`- allowed: ${approval.allowedDecisions.join(", ")}`);
40
+ lines.push("");
41
+ }
42
+ return lines.join("\n");
43
+ }
44
+ export class ThreadMemorySync {
45
+ persistence;
46
+ store;
47
+ pending = new Set();
48
+ constructor(persistence, store) {
49
+ this.persistence = persistence;
50
+ this.store = store;
51
+ }
52
+ async handleEvent(event) {
53
+ if (event.eventType !== "run.state.changed" && event.eventType !== "approval.resolved" && event.eventType !== "approval.requested") {
54
+ return;
55
+ }
56
+ const task = this.syncThread(event.threadId)
57
+ .catch(() => {
58
+ // Fail open: background memory digestion must not break the hot path.
59
+ })
60
+ .finally(() => {
61
+ this.pending.delete(task);
62
+ });
63
+ this.pending.add(task);
64
+ await task;
65
+ }
66
+ async syncThread(threadId) {
67
+ const session = await this.persistence.getSession(threadId);
68
+ if (!session) {
69
+ return;
70
+ }
71
+ const [messages, approvals] = await Promise.all([
72
+ this.persistence.listThreadMessages(threadId, 24),
73
+ this.persistence.listApprovals(),
74
+ ]);
75
+ const pendingApprovals = approvals.filter((approval) => approval.threadId === threadId && approval.status === "pending");
76
+ if (!this.store) {
77
+ return;
78
+ }
79
+ await Promise.all([
80
+ this.store.put(["memories", "threads", threadId], "status.md", { content: `${renderStatusMarkdown(session, messages)}\n` }),
81
+ this.store.put(["memories", "threads", threadId], "open-approvals.md", { content: `${renderOpenApprovalsMarkdown(pendingApprovals)}\n` }),
82
+ ]);
83
+ }
84
+ async close() {
85
+ await Promise.allSettled(Array.from(this.pending));
86
+ }
87
+ }
88
+ export { ThreadMemorySync as MemoryWorker };
@@ -0,0 +1,5 @@
1
+ import type { CompiledTool } from "../contracts/types.js";
2
+ type InterruptFn = <I = unknown, R = unknown>(value: I) => R;
3
+ export declare function wrapToolForHumanInTheLoop<T>(resolvedTool: T, compiledTool: CompiledTool, interruptFn?: InterruptFn): T;
4
+ export declare function wrapToolForExecution<T>(resolvedTool: T, compiledTool: CompiledTool, interruptFn?: InterruptFn): T;
5
+ export {};
@@ -0,0 +1,108 @@
1
+ import { interrupt } from "@langchain/langgraph";
2
+ const DEDUPED_REMOTE_TOOL_NAMES = new Set(["builtin.fetch_url", "builtin.web_search"]);
3
+ function toInputPreview(input) {
4
+ if (typeof input === "object" && input && !Array.isArray(input)) {
5
+ return input;
6
+ }
7
+ return { input };
8
+ }
9
+ function resolveApprovedInput(originalInput, resumeValue) {
10
+ if (resumeValue === "reject") {
11
+ return Symbol.for("agent-harness.hitl.reject");
12
+ }
13
+ if (typeof resumeValue === "object" && resumeValue && "decision" in resumeValue) {
14
+ const typed = resumeValue;
15
+ if (typed.decision === "reject") {
16
+ return Symbol.for("agent-harness.hitl.reject");
17
+ }
18
+ if (typed.decision === "edit" && typed.editedInput !== undefined) {
19
+ return typed.editedInput;
20
+ }
21
+ }
22
+ return originalInput;
23
+ }
24
+ export function wrapToolForHumanInTheLoop(resolvedTool, compiledTool, interruptFn = interrupt) {
25
+ if (!compiledTool.hitl?.enabled || typeof resolvedTool !== "object" || resolvedTool === null) {
26
+ return resolvedTool;
27
+ }
28
+ const target = resolvedTool;
29
+ const runWithApproval = async (input, config, invokeOriginal) => {
30
+ const resumed = interruptFn({
31
+ toolName: compiledTool.name,
32
+ toolId: compiledTool.id,
33
+ allowedDecisions: compiledTool.hitl?.allow ?? ["approve", "edit", "reject"],
34
+ inputPreview: toInputPreview(input),
35
+ });
36
+ const approvedInput = resolveApprovedInput(input, resumed);
37
+ if (approvedInput === Symbol.for("agent-harness.hitl.reject")) {
38
+ return "Tool execution rejected by human reviewer.";
39
+ }
40
+ return invokeOriginal(approvedInput, config);
41
+ };
42
+ return new Proxy(target, {
43
+ get(obj, prop, receiver) {
44
+ const value = Reflect.get(obj, prop, receiver);
45
+ if (prop === "invoke" && typeof value === "function") {
46
+ return (input, config) => runWithApproval(input, config, (approvedInput, approvedConfig) => obj.invoke(approvedInput, approvedConfig));
47
+ }
48
+ if (prop === "call" && typeof value === "function") {
49
+ return (input, config) => runWithApproval(input, config, (approvedInput, approvedConfig) => obj.call(approvedInput, approvedConfig));
50
+ }
51
+ if (prop === "func" && typeof value === "function") {
52
+ return (input, config) => runWithApproval(input, config, (approvedInput, approvedConfig) => obj.func(approvedInput, approvedConfig));
53
+ }
54
+ return value;
55
+ },
56
+ });
57
+ }
58
+ function stableStringify(value) {
59
+ if (value === null || typeof value !== "object") {
60
+ return JSON.stringify(value);
61
+ }
62
+ if (Array.isArray(value)) {
63
+ return `[${value.map((item) => stableStringify(item)).join(",")}]`;
64
+ }
65
+ const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right));
66
+ return `{${entries.map(([key, nested]) => `${JSON.stringify(key)}:${stableStringify(nested)}`).join(",")}}`;
67
+ }
68
+ function createDedupedToolKey(toolName, input) {
69
+ return `${toolName}:${stableStringify(input)}`;
70
+ }
71
+ function wrapToolWithDedupe(resolvedTool, compiledTool) {
72
+ if (!DEDUPED_REMOTE_TOOL_NAMES.has(compiledTool.name) || typeof resolvedTool !== "object" || resolvedTool === null) {
73
+ return resolvedTool;
74
+ }
75
+ const target = resolvedTool;
76
+ const cache = new Map();
77
+ const dedupeCall = (input, config, invokeOriginal) => {
78
+ const cacheKey = createDedupedToolKey(compiledTool.name, input);
79
+ const cached = cache.get(cacheKey);
80
+ if (cached) {
81
+ return cached;
82
+ }
83
+ const pending = Promise.resolve(invokeOriginal(input, config)).catch((error) => {
84
+ cache.delete(cacheKey);
85
+ throw error;
86
+ });
87
+ cache.set(cacheKey, pending);
88
+ return pending;
89
+ };
90
+ return new Proxy(target, {
91
+ get(obj, prop, receiver) {
92
+ const value = Reflect.get(obj, prop, receiver);
93
+ if (prop === "invoke" && typeof value === "function") {
94
+ return (input, config) => dedupeCall(input, config, (approvedInput, approvedConfig) => obj.invoke(approvedInput, approvedConfig));
95
+ }
96
+ if (prop === "call" && typeof value === "function") {
97
+ return (input, config) => dedupeCall(input, config, (approvedInput, approvedConfig) => obj.call(approvedInput, approvedConfig));
98
+ }
99
+ if (prop === "func" && typeof value === "function") {
100
+ return (input, config) => dedupeCall(input, config, (approvedInput, approvedConfig) => obj.func(approvedInput, approvedConfig));
101
+ }
102
+ return value;
103
+ },
104
+ });
105
+ }
106
+ export function wrapToolForExecution(resolvedTool, compiledTool, interruptFn = interrupt) {
107
+ return wrapToolWithDedupe(wrapToolForHumanInTheLoop(resolvedTool, compiledTool, interruptFn), compiledTool);
108
+ }
@@ -0,0 +1,6 @@
1
+ export declare function ensureDir(dirPath: string): Promise<void>;
2
+ export declare function readYamlOrJson(filePath: string): Promise<string>;
3
+ export declare function writeJson(filePath: string, value: unknown): Promise<void>;
4
+ export declare function readJson<T>(filePath: string): Promise<T>;
5
+ export declare function fileExists(filePath: string): Promise<boolean>;
6
+ export declare function listFilesRecursive(root: string, suffix: string): Promise<string[]>;
@@ -0,0 +1,39 @@
1
+ import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ export async function ensureDir(dirPath) {
4
+ await mkdir(dirPath, { recursive: true });
5
+ }
6
+ export async function readYamlOrJson(filePath) {
7
+ return readFile(filePath, "utf8");
8
+ }
9
+ export async function writeJson(filePath, value) {
10
+ await ensureDir(path.dirname(filePath));
11
+ await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
12
+ }
13
+ export async function readJson(filePath) {
14
+ return JSON.parse(await readFile(filePath, "utf8"));
15
+ }
16
+ export async function fileExists(filePath) {
17
+ try {
18
+ await stat(filePath);
19
+ return true;
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ }
25
+ export async function listFilesRecursive(root, suffix) {
26
+ const items = await readdir(root, { withFileTypes: true });
27
+ const results = [];
28
+ for (const item of items) {
29
+ const fullPath = path.join(root, item.name);
30
+ if (item.isDirectory()) {
31
+ results.push(...(await listFilesRecursive(fullPath, suffix)));
32
+ continue;
33
+ }
34
+ if (item.isFile() && fullPath.endsWith(suffix)) {
35
+ results.push(fullPath);
36
+ }
37
+ }
38
+ return results.sort();
39
+ }
@@ -0,0 +1 @@
1
+ export declare function createPersistentId(now?: Date): string;
@@ -0,0 +1,8 @@
1
+ import { randomUUID } from "node:crypto";
2
+ export function createPersistentId(now = new Date()) {
3
+ const iso = now.toISOString();
4
+ const compact = iso.replace(/[-:]/g, "").replace(/\./g, "").replace("Z", "");
5
+ const [date, time] = compact.split("T");
6
+ const hhmmss = time.slice(0, 9);
7
+ return `${date}T${hhmmss}-${randomUUID()}`;
8
+ }
@@ -0,0 +1,23 @@
1
+ import type { RuntimeAdapterOptions, WorkspaceBundle } from "../contracts/types.js";
2
+ type BuiltinToolInfo = {
3
+ builtinPath: string;
4
+ backendOperation: string;
5
+ name: string;
6
+ description: string;
7
+ hitl?: {
8
+ enabled: boolean;
9
+ allow: Array<"approve" | "edit" | "reject">;
10
+ };
11
+ };
12
+ export declare function ensureBuiltinSources(sources?: string[], workspaceRoot?: string): Promise<void>;
13
+ export declare const builtinSkillsRoot: () => string;
14
+ export declare const builtinConfigRoot: () => string;
15
+ export declare function listBuiltinTools(sources?: string[], workspaceRoot?: string): Promise<BuiltinToolInfo[]>;
16
+ export declare function listBuiltinToolsForSource(source: string, workspaceRoot?: string): Promise<BuiltinToolInfo[]>;
17
+ export declare function createBuiltinBackendResolver(workspace: WorkspaceBundle): NonNullable<RuntimeAdapterOptions["backendResolver"]>;
18
+ export declare function createBuiltinToolResolver(workspace: WorkspaceBundle, options?: {
19
+ getStore?: (_binding?: WorkspaceBundle["bindings"] extends Map<any, infer T> ? T : never) => unknown;
20
+ getEmbeddingModel?: (embeddingModelRef?: string, _binding?: WorkspaceBundle["bindings"] extends Map<any, infer T> ? T : never) => Promise<unknown>;
21
+ getVectorStore?: (vectorStoreRef?: string, _binding?: WorkspaceBundle["bindings"] extends Map<any, infer T> ? T : never) => Promise<unknown>;
22
+ }): NonNullable<RuntimeAdapterOptions["toolResolver"]>;
23
+ export {};