@tangle-network/agent-knowledge 17.1.10 → 19.0.0
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/CHANGELOG.md +35 -0
- package/README.md +18 -5
- package/dist/benchmarks-B3kU-Yxz.js.map +1 -1
- package/dist/cli.js +9 -5
- package/dist/cli.js.map +1 -1
- package/dist/index-DZeFm-BP.d.ts.map +1 -1
- package/dist/index-eDsIXWyM.d.ts.map +1 -1
- package/dist/index.d.ts +480 -482
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +310 -439
- package/dist/index.js.map +1 -1
- package/dist/{inspect-DrJnvx5Z.js → inspect-C6YK7af7.js} +251 -7
- package/dist/inspect-C6YK7af7.js.map +1 -0
- package/dist/memory/index.js +1 -1
- package/dist/{memory-Bn1Xs1hY.js → memory-BYWNir2t.js} +9 -6
- package/dist/{memory-Bn1Xs1hY.js.map → memory-BYWNir2t.js.map} +1 -1
- package/dist/sources/index.d.ts +25 -26
- package/dist/sources/index.d.ts.map +1 -1
- package/dist/sources/index.js +2 -2
- package/dist/sources/index.js.map +1 -1
- package/dist/viz/index.d.ts +9 -10
- package/dist/viz/index.d.ts.map +1 -1
- package/package.json +18 -43
- package/dist/inspect-DrJnvx5Z.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as FileSystemKbStore, $t as assertResearchClaimEvidenceIntegrity, A as visibilityIndexOf, An as normalizePagesDirectory, At as WIKILINK_REGEX, B as gradeClaims, Bn as renameDurable, Bt as KnowledgePageSchema, C as nonEmpty$1, Cn as assertKnowledgeMutationPath, Ct as initKnowledgeBase, D as validateOrigin, Dn as prepareKnowledgeFileTransaction, Dt as layoutFor, E as validateKnowledgePage, En as knowledgeFileTransactionPlanHash, Et as knowledgePageFromMarkdown, F as CHECKABLE_RUNG_THRESHOLD, Fn as listRegularFilesWithinRoot, Ft as KnowledgeEventSchema, G as writeKnowledgeIndex, Gn as writeFileDurableWithinRoot, Gt as ResearchClaimLedgerSchema, H as verdictFor, Hn as withSafeDescendant, Ht as KnowledgeRelationNodeSchema, I as DEADLINE_EXIT_CODE, In as readRegularFileNoFollow, It as KnowledgeGraphEdgeSchema, J as loadSourceRegistry, Jn as mediaTypeFor, Jt as SourceAnchorSchema, K as addSourcePath, Kn as writeJsonDurable, Kt as ResearchClaimRecordSchema, L as UncheckableClaimError, Ln as readRegularFileWithinRoot, Lt as KnowledgeGraphNodeSchema, M as normalizePageText, Mn as canonicalRelativeWithinRoot, Mt as normalizeLinkTarget, N as validateKnowledgeIndex, Nn as isKernelAnchoredPath, Nt as DeepQuestionSchema, O as verifyKnowledgeVisibilitySnapshot, On as rollbackKnowledgeFileTransaction, Ot as loadKnowledgePages, P as lintKnowledgeIndex, Pn as isMissingFile, Pt as KnowledgeBaseCandidateSchema, Q as ClaimLedgerMigrationRequiredError, Qt as assertDeepQuestionIntegrity, R as assertGradeableEvidence, Rn as relativeWithinRoot, Rt as KnowledgeIndexSchema, S as knowledgeVisibilityArtifactRef, Sn as applyKnowledgeFileTransaction, St as SCAFFOLD_PAGE_BASENAMES, T as normalizeVisibilityRef, Tn as finishKnowledgeFileTransaction, Tt as isScaffoldPath, U as verifyGradeableEvidence, Un as withSafeDirectory, Ut as KnowledgeRelationSchema, V as gradeFor, Vn as syncDirectory, Vt as KnowledgeRelationGraphSchema, W as buildKnowledgeIndex, Wn as writeFileDurable, Wt as ResearchClaimEvidenceSchema, X as sourceRegistryPath, Xt as KNOWLEDGE_EVENT_TYPES, Y as snapshotSourceTextInput, Yn as textSourceAdapter, Yt as SourceRecordSchema, Z as writeSourceRegistry, Zt as ClaimLedgerGoalConflictError, _ as createKnowledgeVisibilitySnapshot, _n as isKnowledgeMutationHeld, _t as parseKnowledgeCitationReference, a as applyKnowledgeWriteBlocksFile, an as deepQuestionId, at as assertClaimLedgerId, b as encodeKnowledgeVisibilitySnapshot, bn as withKnowledgeMutation, bt as resolveRunScopedCitation, c as KnowledgeDuplicateIntakeError, cn as materializeRegisteredClaimEvidence, ct as KnowledgeCitationAuditError, d as createFileRunLineageAuthority, dn as normalizeClaimText, dt as assertKnowledgeCitationAudit, en as assertResearchClaimLedgerIntegrity, et as KB_CLAIM_LEDGER_DIR, f as createRunScopedStores, fn as researchSourceVersionKey, ft as assertKnowledgeCitationsResolved, g as assertSchemaVersion, gn as inspectPendingKnowledgeMutation, gt as formatKnowledgeCitationReference, h as KNOWLEDGE_USE_RECEIPT_SCHEMA_VERSION, hn as acquireDurableFileLock, ht as auditKnowledgeCitations, i as applyKnowledgeWriteBlocks, in as claimSourceHost, it as MemoryKbStore, j as detectNearDuplicatePages, jn as canonicalPathsEqual, jt as extractWikilinks, k as visibilityIdentity, kn as DEFAULT_PAGES_DIRECTORY, kt as writeJson, l as assertKnowledgeWriteIntake, ln as mergeClaimLedgers, lt as KnowledgeCitationResolutionError, m as KNOWLEDGE_RECEIPT_DIGEST_ALGORITHM, mn as parseFrontmatter, mt as auditCurrentRunCitations, n as inspectKnowledgeIndex, nn as claimEvidenceId, nt as KB_INDEX_PATH, o as isSafeKnowledgePath, on as emptyClaimLedger, ot as buildKnowledgeGraph, p as originatedPages, pn as formatFrontmatter, pt as assertRunScopedCitationsResolved, q as addSourceText, qn as writeJsonDurableWithinRoot, qt as ResearchSourceVersionSchema, r as stringMetadata, rn as claimId, rt as KB_STORE_DIR, s as parseKnowledgeWriteBlocks, sn as linkClaimContradictions, st as knowledgePageRelations, t as explainKnowledgeTarget, tn as assertTrackedClaimIntegrity, tt as KB_EVENTS_PATH, u as RUN_LINEAGE_BASENAME, un as mergeTrackedClaims, ut as assertCurrentRunCitationsResolved, v as decodeKnowledgeVisibilitySnapshot, vn as recoverPendingKnowledgeMutation, vt as resolveKnowledgeCitation, w as normalizeVisibilityArtifact, wn as commitKnowledgeFileMutations, wt as isKnowledgePagePath, x as knowledgePageDigest, xn as withKnowledgeRead, xt as resolveRunScopedCitations, y as digest, yn as runInKnowledgeMutationScope, yt as resolveKnowledgeCitations, z as claimCheckKey, zn as removeDurable, zt as KnowledgePageInvalidationSchema } from "./inspect-C6YK7af7.js";
|
|
2
2
|
import { i as textSourceId, n as slugify, r as stableId, t as sha256 } from "./ids-Bevz_pXV.js";
|
|
3
|
-
import { A as runSerializedKnowledgeOptimization, C as deterministicRng, D as toOffPolicyTrajectory, E as retrievalHoldoutConfigHash, M as assertImmutableRef, O as jsonCandidateCodec, S as applySessionStickyRetrievalHoldout, T as resetRetrievalHoldoutRegistry, _ as createAgentMemoryBranch, a as createNeo4jAgentMemoryAdapter, b as renderMemoryContext, c as runAgentMemoryImprovement, d as compareAgentMemoryLearning, f as runAgentMemoryLearningExperiment, g as buildAgentMemorySequencesFromBenchmarkCases, h as buildAgentMemorySequenceScenarios, i as AgentMemoryWriteInputSchema, j as scenarioContentFingerprint, k as jsonObjectCandidateCodec, l as createGraphitiMemoryAdapter, m as agentMemorySequenceJudge, n as AgentMemoryKindSchema, o as createMem0MemoryAdapter, p as runAgentMemoryExperiment, r as AgentMemoryScopeSchema, s as mem0MemoryAdapterIdentity, t as AgentMemoryHitSchema, u as graphitiMemoryAdapterIdentity, v as forkAgentMemoryBranchSnapshot, w as emitRetrievalHoldoutBypass, x as applyRetrievalHoldout, y as defaultGetMemoryContext } from "./memory-
|
|
3
|
+
import { A as runSerializedKnowledgeOptimization, C as deterministicRng, D as toOffPolicyTrajectory, E as retrievalHoldoutConfigHash, M as assertImmutableRef, O as jsonCandidateCodec, S as applySessionStickyRetrievalHoldout, T as resetRetrievalHoldoutRegistry, _ as createAgentMemoryBranch, a as createNeo4jAgentMemoryAdapter, b as renderMemoryContext, c as runAgentMemoryImprovement, d as compareAgentMemoryLearning, f as runAgentMemoryLearningExperiment, g as buildAgentMemorySequencesFromBenchmarkCases, h as buildAgentMemorySequenceScenarios, i as AgentMemoryWriteInputSchema, j as scenarioContentFingerprint, k as jsonObjectCandidateCodec, l as createGraphitiMemoryAdapter, m as agentMemorySequenceJudge, n as AgentMemoryKindSchema, o as createMem0MemoryAdapter, p as runAgentMemoryExperiment, r as AgentMemoryScopeSchema, s as mem0MemoryAdapterIdentity, t as AgentMemoryHitSchema, u as graphitiMemoryAdapterIdentity, v as forkAgentMemoryBranchSnapshot, w as emitRetrievalHoldoutBypass, x as applyRetrievalHoldout, y as defaultGetMemoryContext } from "./memory-BYWNir2t.js";
|
|
4
4
|
import { C as sleepForMemoryRecovery, F as buildIndustryRagBenchmarkSmokeCases, G as buildRetrievalEvalDispatch, H as createNoopMemoryBenchmarkAdapter, I as respondToIndustryMemoryBenchmarkSmokeCase, J as retrievalConfigSurface, K as partitionRetrievalScenarios, L as respondToIndustryRagBenchmarkSmokeCase, M as INDUSTRY_RAG_BENCHMARKS, N as buildFirstPartyMemoryLifecycleBenchmarkCases, P as buildIndustryMemoryBenchmarkSmokeCases, R as isKnowledgeMemoryBenchmarkCase, S as runBoundedMemoryLifecycle, U as memoryHitToSourceRecord, V as createInMemoryBenchmarkAdapter, W as memoryWriteResultToSourceRecord, X as scoreRetrievalArtifact, Y as retrievalRecallJudge, Z as mean, a as buildKnowledgeBenchmarkScenarios, b as memoryRecoveryDelayMs, c as runKnowledgeBenchmarkSuite, d as summarizeKnowledgeBenchmarkCampaign, f as acquireAgentMemoryRunLease, h as DEFAULT_MEMORY_CLEANUP_TIMEOUT_MS, i as runMemoryAdapterBenchmark, j as INDUSTRY_MEMORY_BENCHMARKS, l as scoreKnowledgeBenchmarkArtifact, m as AgentMemoryLifecycleUnsafeError, n as parseKnowledgeBenchmarkJsonl, o as knowledgeBenchmarkJudge, p as AgentMemoryLifecycleTimeoutError, q as retrievalConfigFromSurface, r as parseKnowledgeBenchmarkQrels, s as renderKnowledgeBenchmarkReportMarkdown, t as buildRetrievalBenchmarkCasesFromQrels, u as scoreMemoryBenchmarkArtifact, x as resolveMemoryCleanupTimeoutMs, y as createMemoryExecutionPool } from "./benchmarks-B3kU-Yxz.js";
|
|
5
5
|
import { a as buildKnowledgeLexicalIndex, c as tokenizeText, i as searchKnowledgePages, n as reciprocalRankFusion, o as scoreBm25, r as searchKnowledge, s as tokenizeQuery, t as KNOWLEDGE_SEARCH_RETRIEVER_ID } from "./search-Cbyj2shJ.js";
|
|
6
6
|
import { IRS_DIMENSION_HINTS, MAX_RESPONSE_BYTES, MIN_REQUEST_GAP_MS, POLITE_USER_AGENT, __resetHttpThrottle, createCornellLiiSource, createIrsPublicationsSource, createStateSosSource, extractLinks, firstMatch, htmlToText, innerHtmlById, looksLikeBlockPage, politeFetch } from "./sources/index.js";
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
|
+
import { TCloudClient, TCloudError } from "@tangle-network/tcloud";
|
|
8
9
|
import { cp, lstat, mkdir, mkdtemp, readFile, rm, stat } from "node:fs/promises";
|
|
9
10
|
import { dirname, join } from "node:path";
|
|
10
|
-
import { agentImprovementActivationResultSchema, agentImprovementActivationSchema,
|
|
11
|
+
import { agentImprovementActivationResultSchema, agentImprovementActivationSchema, canonicalCandidateDigest, canonicalCandidateJson, omitTopLevelDigest, sha256Bytes, sha256DigestSchema } from "@tangle-network/agent-interface";
|
|
11
12
|
import { acquisitionPlansForKnowledgeGaps, blockingKnowledgeEval, canonicalJson, contentHash, evaluateReleaseConfidence, objectiveEval, scoreKnowledgeReadiness, userQuestionsForKnowledgeGaps, validateRunRecord } from "@tangle-network/agent-eval";
|
|
12
13
|
import { z } from "zod";
|
|
13
14
|
import { surfaceHash } from "@tangle-network/agent-eval/campaign";
|
|
@@ -39,10 +40,9 @@ import { pathToFileURL } from "node:url";
|
|
|
39
40
|
* worker ADDS; the driver GATES. Together they build a cleaner knowledge base
|
|
40
41
|
* than a single agent at the same compute budget.
|
|
41
42
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* passing `baseUrl`; supply the key via `apiKey` or `TANGLE_API_KEY`.
|
|
43
|
+
* Model and search requests use the published TCloud SDK. Knowledge owns the
|
|
44
|
+
* research policy, not HTTP, bearer headers, retries or provider rate cards.
|
|
45
|
+
* Point it at any router with `baseUrl`; supply `apiKey` or `TANGLE_API_KEY`.
|
|
46
46
|
*/
|
|
47
47
|
/** Default router model. Plain id (no namespace) — see CLAUDE/creds notes. */
|
|
48
48
|
const DEFAULT_MODEL = "glm-5.2";
|
|
@@ -54,35 +54,7 @@ const DEFAULT_BASE_URL = "https://router.tangle.tools/v1";
|
|
|
54
54
|
* reasoning model never silently yields nothing. (Verified creds note.)
|
|
55
55
|
*/
|
|
56
56
|
const MIN_MAX_TOKENS = 1200;
|
|
57
|
-
/**
|
|
58
|
-
const transientStatuses = /* @__PURE__ */ new Set([
|
|
59
|
-
429,
|
|
60
|
-
502,
|
|
61
|
-
503,
|
|
62
|
-
504
|
|
63
|
-
]);
|
|
64
|
-
/**
|
|
65
|
-
* POST with bounded exponential backoff on transient upstream statuses. Returns
|
|
66
|
-
* the first `res.ok` response, or the LAST response (so the caller throws the
|
|
67
|
-
* real status). A non-transient failure returns immediately — only 502/503/504/
|
|
68
|
-
* 429 are retried. Aborts propagate at once.
|
|
69
|
-
*/
|
|
70
|
-
async function fetchWithRetry(url, init, opts) {
|
|
71
|
-
let lastRes;
|
|
72
|
-
for (let attempt = 0; attempt <= opts.maxRetries; attempt += 1) {
|
|
73
|
-
if (opts.signal?.aborted) throw new RouterError(0, "aborted");
|
|
74
|
-
const res = await fetch(url, init);
|
|
75
|
-
if (res.ok || !transientStatuses.has(res.status)) return res;
|
|
76
|
-
lastRes = res;
|
|
77
|
-
if (attempt === opts.maxRetries) break;
|
|
78
|
-
await res.text().catch(() => "");
|
|
79
|
-
const jitter = opts.retryBaseMs * 2 ** attempt * (.75 + Math.random() * .5);
|
|
80
|
-
await new Promise((resolve) => setTimeout(resolve, jitter));
|
|
81
|
-
}
|
|
82
|
-
if (lastRes) return lastRes;
|
|
83
|
-
throw new RouterError(0, "fetchWithRetry produced no response");
|
|
84
|
-
}
|
|
85
|
-
/** A small error so a failed router call fails loud rather than returning junk. */
|
|
57
|
+
/** Compatibility error facade. HTTP execution and classification belong to TCloud. */
|
|
86
58
|
var RouterError = class extends Error {
|
|
87
59
|
status;
|
|
88
60
|
constructor(status, message) {
|
|
@@ -91,26 +63,26 @@ var RouterError = class extends Error {
|
|
|
91
63
|
this.name = "RouterError";
|
|
92
64
|
}
|
|
93
65
|
};
|
|
94
|
-
/**
|
|
95
|
-
* Build a dependency-free Tangle router client over `fetch`. This is the same
|
|
96
|
-
* wire surface the `tcloud` SDK + `tcloud mcp` use (`/v1/search` for web search,
|
|
97
|
-
* `/v1/chat/completions` for chat) so it needs no CLI installed.
|
|
98
|
-
*/
|
|
66
|
+
/** Adapt Knowledge's public contract to the published SDK, without a second transport. */
|
|
99
67
|
function createTangleRouterClient(options = {}) {
|
|
100
|
-
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
101
68
|
const apiKey = options.apiKey ?? process.env.TANGLE_API_KEY;
|
|
102
69
|
if (!apiKey) throw new RouterError(401, "no TANGLE_API_KEY (pass apiKey or set the env var)");
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
70
|
+
const client = new TCloudClient({
|
|
71
|
+
baseURL: (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, ""),
|
|
72
|
+
apiKey,
|
|
73
|
+
model: options.model ?? DEFAULT_MODEL,
|
|
74
|
+
timeout: 0,
|
|
75
|
+
retry: {
|
|
76
|
+
maxRetries: Math.max(0, options.maxRetries ?? 4),
|
|
77
|
+
initialBackoffMs: Math.max(1, options.retryBaseMs ?? 1500),
|
|
78
|
+
retryableStatuses: [
|
|
79
|
+
429,
|
|
80
|
+
502,
|
|
81
|
+
503,
|
|
82
|
+
504
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
});
|
|
114
86
|
const acc = {
|
|
115
87
|
chatCalls: 0,
|
|
116
88
|
searchCalls: 0,
|
|
@@ -119,70 +91,79 @@ function createTangleRouterClient(options = {}) {
|
|
|
119
91
|
usd: 0,
|
|
120
92
|
wallMs: 0
|
|
121
93
|
};
|
|
94
|
+
const recordCost = (cost) => {
|
|
95
|
+
acc.usd += typeof cost === "number" && Number.isFinite(cost) && cost >= 0 ? cost : NaN;
|
|
96
|
+
};
|
|
97
|
+
const translate = (error) => {
|
|
98
|
+
options.signal?.throwIfAborted();
|
|
99
|
+
if (error instanceof TCloudError) throw new RouterError(error.status, error.message);
|
|
100
|
+
throw error;
|
|
101
|
+
};
|
|
122
102
|
return {
|
|
123
103
|
async search(query, opts) {
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
signal: options.signal,
|
|
129
|
-
body: JSON.stringify({
|
|
104
|
+
options.signal?.throwIfAborted();
|
|
105
|
+
const started = Date.now();
|
|
106
|
+
try {
|
|
107
|
+
const response = await client.search({
|
|
130
108
|
query,
|
|
131
109
|
...options.searchProvider ? { provider: options.searchProvider } : {},
|
|
132
|
-
...opts?.maxResults != null ? { maxResults: opts.maxResults } : {}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
110
|
+
...opts?.maxResults != null ? { maxResults: opts.maxResults } : {},
|
|
111
|
+
signal: options.signal
|
|
112
|
+
});
|
|
113
|
+
recordCost(response.usage?.billed_cost);
|
|
114
|
+
return (response.data ?? []).filter((hit) => typeof hit?.url === "string" && hit.url.length > 0).map((hit) => ({
|
|
115
|
+
title: hit.title ?? hit.url,
|
|
116
|
+
url: hit.url,
|
|
117
|
+
snippet: hit.snippet
|
|
118
|
+
}));
|
|
119
|
+
} catch (error) {
|
|
120
|
+
return translate(error);
|
|
121
|
+
} finally {
|
|
122
|
+
acc.searchCalls += 1;
|
|
123
|
+
acc.wallMs += Date.now() - started;
|
|
124
|
+
}
|
|
147
125
|
},
|
|
148
126
|
async chat(messages, maxTokens) {
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
headers,
|
|
154
|
-
signal: options.signal,
|
|
155
|
-
body: JSON.stringify({
|
|
156
|
-
model,
|
|
127
|
+
options.signal?.throwIfAborted();
|
|
128
|
+
const started = Date.now();
|
|
129
|
+
try {
|
|
130
|
+
const response = await client.chat({
|
|
157
131
|
messages,
|
|
158
|
-
|
|
132
|
+
maxTokens: Math.max(MIN_MAX_TOKENS, maxTokens ?? MIN_MAX_TOKENS),
|
|
159
133
|
temperature: .2,
|
|
160
|
-
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
acc.promptTokens += promptTokens;
|
|
173
|
-
acc.completionTokens += completionTokens;
|
|
174
|
-
acc.usd += promptTokens * price.prompt + completionTokens * price.completion;
|
|
175
|
-
acc.wallMs += Date.now() - t0;
|
|
176
|
-
return body.choices?.[0]?.message?.content ?? "";
|
|
134
|
+
signal: options.signal
|
|
135
|
+
});
|
|
136
|
+
acc.chatCalls += 1;
|
|
137
|
+
acc.promptTokens += response.usage?.prompt_tokens ?? 0;
|
|
138
|
+
acc.completionTokens += response.usage?.completion_tokens ?? 0;
|
|
139
|
+
recordCost(response.tangle?.costSource === "receipt" ? response.tangle.costUsd : void 0);
|
|
140
|
+
return response.choices?.[0]?.message?.content ?? "";
|
|
141
|
+
} catch (error) {
|
|
142
|
+
return translate(error);
|
|
143
|
+
} finally {
|
|
144
|
+
acc.wallMs += Date.now() - started;
|
|
145
|
+
}
|
|
177
146
|
},
|
|
178
147
|
usage() {
|
|
179
148
|
return { ...acc };
|
|
180
149
|
}
|
|
181
150
|
};
|
|
182
151
|
}
|
|
183
|
-
/**
|
|
184
|
-
function resolveRouter(opts) {
|
|
185
|
-
|
|
152
|
+
/** Bind run cancellation to the default router without changing an injected client. */
|
|
153
|
+
function resolveRouter(opts, signal) {
|
|
154
|
+
if (opts.router) return opts.router;
|
|
155
|
+
const requestSignal = combineRouterSignals(signal, opts.router_options?.signal);
|
|
156
|
+
return createTangleRouterClient({
|
|
157
|
+
...opts.router_options,
|
|
158
|
+
signal: requestSignal
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
function combineRouterSignals(runSignal, configuredSignal) {
|
|
162
|
+
return runSignal && configuredSignal ? AbortSignal.any([runSignal, configuredSignal]) : runSignal ?? configuredSignal;
|
|
163
|
+
}
|
|
164
|
+
function throwIfRouterAborted(runSignal, opts) {
|
|
165
|
+
runSignal?.throwIfAborted();
|
|
166
|
+
if (!opts.router) opts.router_options?.signal?.throwIfAborted();
|
|
186
167
|
}
|
|
187
168
|
/**
|
|
188
169
|
* The real web-research worker. Conforms to the loop's `ResearchWorker`
|
|
@@ -196,33 +177,41 @@ function createWebResearchWorker(options = {}) {
|
|
|
196
177
|
const minTextChars = Math.max(1, options.minTextChars ?? 200);
|
|
197
178
|
const maxTextChars = Math.max(minTextChars, options.maxTextChars ?? 4e3);
|
|
198
179
|
return async (ctx) => {
|
|
199
|
-
const
|
|
180
|
+
const assertActive = () => throwIfRouterAborted(ctx.signal, options);
|
|
181
|
+
assertActive();
|
|
182
|
+
const router = resolveRouter(options, ctx.signal);
|
|
183
|
+
const fetchSignal = options.router ? ctx.signal : combineRouterSignals(ctx.signal, options.router_options?.signal);
|
|
200
184
|
const targetGaps = ctx.gaps.filter((gap) => gap.blocking);
|
|
201
185
|
const gaps = targetGaps.length > 0 ? targetGaps : ctx.gaps;
|
|
202
186
|
if (gaps.length === 0) return {
|
|
203
187
|
sources: [],
|
|
204
188
|
notes: "no open gaps to research"
|
|
205
189
|
};
|
|
206
|
-
const queries = await formSearchQueries(router, ctx, gaps, queriesPerGap);
|
|
190
|
+
const queries = await formSearchQueries(router, ctx, gaps, queriesPerGap, assertActive);
|
|
191
|
+
assertActive();
|
|
207
192
|
const proposals = [];
|
|
208
193
|
const seenUris = /* @__PURE__ */ new Set();
|
|
209
194
|
for (const query of queries) {
|
|
210
195
|
if (proposals.length >= maxSourcesPerRound) break;
|
|
211
|
-
|
|
196
|
+
assertActive();
|
|
212
197
|
let hits;
|
|
213
198
|
try {
|
|
214
199
|
hits = await router.search(query, { maxResults: resultsPerQuery });
|
|
215
200
|
} catch (error) {
|
|
216
|
-
|
|
201
|
+
assertActive();
|
|
202
|
+
if (error?.name === "AbortError") throw error;
|
|
217
203
|
continue;
|
|
218
204
|
}
|
|
205
|
+
assertActive();
|
|
219
206
|
for (const hit of hits) {
|
|
207
|
+
assertActive();
|
|
220
208
|
if (proposals.length >= maxSourcesPerRound) break;
|
|
221
209
|
if (seenUris.has(hit.url)) continue;
|
|
222
210
|
const fetched = await politeFetch(hit.url, {
|
|
223
|
-
signal:
|
|
211
|
+
signal: fetchSignal,
|
|
224
212
|
cacheDir: options.cacheDir
|
|
225
213
|
});
|
|
214
|
+
assertActive();
|
|
226
215
|
if (!fetched.verifiable) continue;
|
|
227
216
|
const text = htmlToText(fetched.body).slice(0, maxTextChars);
|
|
228
217
|
if (text.length < minTextChars) continue;
|
|
@@ -241,6 +230,7 @@ function createWebResearchWorker(options = {}) {
|
|
|
241
230
|
});
|
|
242
231
|
}
|
|
243
232
|
}
|
|
233
|
+
assertActive();
|
|
244
234
|
return {
|
|
245
235
|
sources: proposals,
|
|
246
236
|
buildPages: buildCitingPages(proposals),
|
|
@@ -253,7 +243,7 @@ function createWebResearchWorker(options = {}) {
|
|
|
253
243
|
* to the gap's own readiness query if the model returns nothing parseable, so a
|
|
254
244
|
* model hiccup degrades to a sane search rather than an empty round.
|
|
255
245
|
*/
|
|
256
|
-
async function formSearchQueries(router, ctx, gaps, queriesPerGap) {
|
|
246
|
+
async function formSearchQueries(router, ctx, gaps, queriesPerGap, assertActive) {
|
|
257
247
|
const gapLines = gaps.map((gap, i) => `${i + 1}. ${gap.description} (readiness query: "${gap.query}")`).join("\n");
|
|
258
248
|
const want = gaps.length * queriesPerGap;
|
|
259
249
|
const system = "You are a research librarian. Turn knowledge gaps into precise web search queries that will surface authoritative primary sources (papers, docs, standards, official pages). Return ONLY a JSON array of query strings, no prose.";
|
|
@@ -272,7 +262,10 @@ async function formSearchQueries(router, ctx, gaps, queriesPerGap) {
|
|
|
272
262
|
role: "user",
|
|
273
263
|
content: user
|
|
274
264
|
}], MIN_MAX_TOKENS);
|
|
275
|
-
|
|
265
|
+
assertActive();
|
|
266
|
+
} catch (error) {
|
|
267
|
+
assertActive();
|
|
268
|
+
if (error?.name === "AbortError") throw error;
|
|
276
269
|
raw = "";
|
|
277
270
|
}
|
|
278
271
|
const fromLlm = parseQueryList(raw).slice(0, want);
|
|
@@ -354,7 +347,9 @@ function escapeYaml(value) {
|
|
|
354
347
|
function createVerifyingResearchDriver(options = {}) {
|
|
355
348
|
const acceptOnParseFailure = options.acceptOnParseFailure ?? false;
|
|
356
349
|
return { async verifySource(source, ctx) {
|
|
357
|
-
const
|
|
350
|
+
const assertActive = () => throwIfRouterAborted(ctx.signal, options);
|
|
351
|
+
assertActive();
|
|
352
|
+
const router = resolveRouter(options, ctx.signal);
|
|
358
353
|
const gapLines = ctx.gaps.map((gap) => `- ${gap.description} (query: "${gap.query}")`).join("\n");
|
|
359
354
|
const acceptedTitles = ctx.acceptedThisRound.map((accepted) => `- ${accepted.title ?? accepted.uri}`).join("\n");
|
|
360
355
|
const excerpt = source.text.slice(0, 1500);
|
|
@@ -375,8 +370,10 @@ function createVerifyingResearchDriver(options = {}) {
|
|
|
375
370
|
role: "user",
|
|
376
371
|
content: user
|
|
377
372
|
}], MIN_MAX_TOKENS);
|
|
373
|
+
assertActive();
|
|
378
374
|
} catch (error) {
|
|
379
|
-
|
|
375
|
+
assertActive();
|
|
376
|
+
if (error?.name === "AbortError") throw error;
|
|
380
377
|
return acceptOnParseFailure ? { accept: true } : {
|
|
381
378
|
accept: false,
|
|
382
379
|
reason: `verifier unavailable: ${error.message}`
|
|
@@ -494,7 +491,8 @@ const trackingParams = /* @__PURE__ */ new Set([
|
|
|
494
491
|
* mirror/syndication of an already-accepted source even when the URL differs.
|
|
495
492
|
*/
|
|
496
493
|
function contentKey(text) {
|
|
497
|
-
|
|
494
|
+
const normalized = text.toLowerCase().replace(/[^\p{L}\p{N}\s]+/gu, " ").replace(/\s+/g, " ").trim();
|
|
495
|
+
return sha256(normalized);
|
|
498
496
|
}
|
|
499
497
|
/** Default authoritative host suffixes — papers, official docs, standards. */
|
|
500
498
|
const defaultAuthoritativeHosts = [
|
|
@@ -934,7 +932,8 @@ function knowledgeImprovementRunDir(root, runId) {
|
|
|
934
932
|
return runDir;
|
|
935
933
|
}
|
|
936
934
|
async function withKnowledgeImprovementRun(root, runId, create, use) {
|
|
937
|
-
const
|
|
935
|
+
const runDir = knowledgeImprovementRunDir(root, runId);
|
|
936
|
+
const relativePath = relativeWithinRoot(root, runDir);
|
|
938
937
|
if (!relativePath) throw new Error("knowledge improvement run directory escaped its root");
|
|
939
938
|
return withSafeDirectory(root, relativePath, create, async (openedRunDir) => {
|
|
940
939
|
const result = await use(openedRunDir);
|
|
@@ -1005,8 +1004,9 @@ function parseKnowledgeImprovementEvent(value) {
|
|
|
1005
1004
|
return event;
|
|
1006
1005
|
}
|
|
1007
1006
|
async function acquireRunLease(runDir, options) {
|
|
1007
|
+
const path = join(runDir, "run.lock.durable");
|
|
1008
1008
|
const acquired = await acquireDurableFileLock(runDir, {
|
|
1009
|
-
lockfilePath:
|
|
1009
|
+
lockfilePath: path,
|
|
1010
1010
|
staleMs: options.ttlMs
|
|
1011
1011
|
});
|
|
1012
1012
|
return {
|
|
@@ -2461,100 +2461,114 @@ async function runRagKnowledgeImprovementPhases(options, previous) {
|
|
|
2461
2461
|
let knowledgeUpdate = previous?.knowledgeUpdate;
|
|
2462
2462
|
let answerQuality = previous?.answerQuality;
|
|
2463
2463
|
let promotion = previous?.promotion;
|
|
2464
|
-
if (phaseEnabled(options, "gap-diagnosis"))
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2464
|
+
if (phaseEnabled(options, "gap-diagnosis")) {
|
|
2465
|
+
if (options.diagnose) findings = [...await runPhase(phases, now, "gap-diagnosis", async () => {
|
|
2466
|
+
assertNotAborted(options.signal);
|
|
2467
|
+
return options.diagnose({
|
|
2468
|
+
goal: options.goal,
|
|
2469
|
+
phases,
|
|
2470
|
+
optimization: selectRagOptimization(optimization),
|
|
2471
|
+
signal: options.signal,
|
|
2472
|
+
retrieval: selectRetrievalOptimization(retrieval)
|
|
2473
|
+
});
|
|
2474
|
+
}, (diagnosed) => `${diagnosed.length} finding(s)`)];
|
|
2475
|
+
else skipPhase(phases, now, "gap-diagnosis", "no diagnosis hook provided");
|
|
2476
|
+
}
|
|
2477
|
+
if (phaseEnabled(options, "knowledge-acquisition")) {
|
|
2478
|
+
if (options.acquireKnowledge) acquisition = await runPhase(phases, now, "knowledge-acquisition", async () => {
|
|
2479
|
+
assertNotAborted(options.signal);
|
|
2480
|
+
return options.acquireKnowledge({
|
|
2481
|
+
goal: options.goal,
|
|
2482
|
+
phases,
|
|
2483
|
+
optimization: selectRagOptimization(optimization),
|
|
2484
|
+
signal: options.signal,
|
|
2485
|
+
retrieval: selectRetrievalOptimization(retrieval),
|
|
2486
|
+
findings
|
|
2487
|
+
});
|
|
2488
|
+
}, summarizeAcquisitionDecision);
|
|
2489
|
+
else skipPhase(phases, now, "knowledge-acquisition", "no acquisition hook provided");
|
|
2490
|
+
}
|
|
2491
|
+
if (phaseEnabled(options, "knowledge-update")) {
|
|
2492
|
+
if (options.updateKnowledge) knowledgeUpdate = await runPhase(phases, now, "knowledge-update", async () => {
|
|
2493
|
+
assertNotAborted(options.signal);
|
|
2494
|
+
return options.updateKnowledge({
|
|
2495
|
+
goal: options.goal,
|
|
2496
|
+
phases,
|
|
2497
|
+
optimization: selectRagOptimization(optimization),
|
|
2498
|
+
signal: options.signal,
|
|
2499
|
+
retrieval: selectRetrievalOptimization(retrieval),
|
|
2500
|
+
findings,
|
|
2501
|
+
acquisition
|
|
2502
|
+
});
|
|
2503
|
+
}, (result) => result.summary);
|
|
2504
|
+
else if (options.knowledgeResearch) knowledgeUpdate = await runPhase(phases, now, "knowledge-update", async () => {
|
|
2505
|
+
assertNotAborted(options.signal);
|
|
2506
|
+
return runKnowledgeResearchUpdate(options, acquisition);
|
|
2507
|
+
}, (result) => result.summary);
|
|
2508
|
+
else skipPhase(phases, now, "knowledge-update", "no update hook or research loop provided");
|
|
2509
|
+
}
|
|
2510
|
+
if (phaseEnabled(options, "rag-optimization") && (options.optimization || options.enabledPhases?.includes("rag-optimization") || options.requiredPhases?.includes("rag-optimization"))) {
|
|
2511
|
+
if (options.optimization) optimization = await runPhase(phases, now, "rag-optimization", async () => {
|
|
2512
|
+
assertNotAborted(options.signal);
|
|
2513
|
+
return runRagOptimization(options.optimization);
|
|
2514
|
+
}, summarizeRagOptimization);
|
|
2515
|
+
else skipPhase(phases, now, "rag-optimization", "no full RAG optimization options provided");
|
|
2516
|
+
}
|
|
2517
|
+
if (phaseEnabled(options, "retrieval-tuning")) {
|
|
2518
|
+
if (options.retrieval) retrieval = await runPhase(phases, now, "retrieval-tuning", async () => {
|
|
2519
|
+
assertNotAborted(options.signal);
|
|
2520
|
+
return runRetrievalImprovementLoop(options.retrieval);
|
|
2521
|
+
}, summarizeRetrievalResult);
|
|
2522
|
+
else skipPhase(phases, now, "retrieval-tuning", "no retrieval options provided");
|
|
2523
|
+
}
|
|
2524
|
+
if (phaseEnabled(options, "answer-quality")) {
|
|
2525
|
+
if (options.evaluateAnswers) {
|
|
2526
|
+
answerQuality = await runPhase(phases, now, "answer-quality", async () => {
|
|
2527
|
+
assertNotAborted(options.signal);
|
|
2528
|
+
const result = await options.evaluateAnswers({
|
|
2529
|
+
goal: options.goal,
|
|
2530
|
+
phases,
|
|
2531
|
+
optimization: selectRagOptimization(optimization),
|
|
2532
|
+
signal: options.signal,
|
|
2533
|
+
retrieval: selectRetrievalOptimization(retrieval),
|
|
2534
|
+
findings,
|
|
2535
|
+
acquisition,
|
|
2536
|
+
knowledgeUpdate
|
|
2537
|
+
});
|
|
2538
|
+
assertRagAnswerEvidence(result);
|
|
2539
|
+
return result;
|
|
2540
|
+
}, summarizeAnswerQuality);
|
|
2541
|
+
findings = [...findings, ...answerQuality.findings ?? []];
|
|
2542
|
+
} else skipPhase(phases, now, "answer-quality", "no answer-quality hook provided");
|
|
2543
|
+
}
|
|
2544
|
+
if (phaseEnabled(options, "promotion")) {
|
|
2545
|
+
if (options.decidePromotion) promotion = await runPhase(phases, now, "promotion", async () => {
|
|
2516
2546
|
assertNotAborted(options.signal);
|
|
2517
|
-
const
|
|
2547
|
+
const evidenceRejection = rejectUnsafePromotionEvidence({
|
|
2548
|
+
optimization: optimization?.comparison,
|
|
2549
|
+
optimizationCostCeiling: options.optimization?.costCeiling,
|
|
2550
|
+
retrieval: retrieval?.comparison,
|
|
2551
|
+
retrievalCostCeiling: options.retrieval?.costCeiling,
|
|
2552
|
+
answerQuality,
|
|
2553
|
+
answerQualityCostCeiling: options.answerQualityCostCeiling
|
|
2554
|
+
});
|
|
2555
|
+
if (evidenceRejection) return evidenceRejection;
|
|
2556
|
+
return options.decidePromotion({
|
|
2518
2557
|
goal: options.goal,
|
|
2519
2558
|
phases,
|
|
2520
2559
|
optimization: selectRagOptimization(optimization),
|
|
2560
|
+
optimizationComparison: optimization?.comparison,
|
|
2521
2561
|
signal: options.signal,
|
|
2522
2562
|
retrieval: selectRetrievalOptimization(retrieval),
|
|
2563
|
+
retrievalComparison: retrieval?.comparison,
|
|
2523
2564
|
findings,
|
|
2524
2565
|
acquisition,
|
|
2525
|
-
knowledgeUpdate
|
|
2566
|
+
knowledgeUpdate,
|
|
2567
|
+
answerQuality
|
|
2526
2568
|
});
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
findings = [...findings, ...answerQuality.findings ?? []];
|
|
2531
|
-
} else skipPhase(phases, now, "answer-quality", "no answer-quality hook provided");
|
|
2532
|
-
if (phaseEnabled(options, "promotion")) if (options.decidePromotion) promotion = await runPhase(phases, now, "promotion", async () => {
|
|
2533
|
-
assertNotAborted(options.signal);
|
|
2534
|
-
const evidenceRejection = rejectUnsafePromotionEvidence({
|
|
2535
|
-
optimization: optimization?.comparison,
|
|
2536
|
-
optimizationCostCeiling: options.optimization?.costCeiling,
|
|
2537
|
-
retrieval: retrieval?.comparison,
|
|
2538
|
-
retrievalCostCeiling: options.retrieval?.costCeiling,
|
|
2539
|
-
answerQuality,
|
|
2540
|
-
answerQualityCostCeiling: options.answerQualityCostCeiling
|
|
2541
|
-
});
|
|
2542
|
-
if (evidenceRejection) return evidenceRejection;
|
|
2543
|
-
return options.decidePromotion({
|
|
2544
|
-
goal: options.goal,
|
|
2545
|
-
phases,
|
|
2546
|
-
optimization: selectRagOptimization(optimization),
|
|
2547
|
-
optimizationComparison: optimization?.comparison,
|
|
2548
|
-
signal: options.signal,
|
|
2549
|
-
retrieval: selectRetrievalOptimization(retrieval),
|
|
2550
|
-
retrievalComparison: retrieval?.comparison,
|
|
2551
|
-
findings,
|
|
2552
|
-
acquisition,
|
|
2553
|
-
knowledgeUpdate,
|
|
2554
|
-
answerQuality
|
|
2555
|
-
});
|
|
2556
|
-
}, (result) => `${result.promoted ? "promoted" : "held"}: ${result.reason}`);
|
|
2557
|
-
else skipPhase(phases, now, "promotion", "no promotion decision hook provided");
|
|
2569
|
+
}, (result) => `${result.promoted ? "promoted" : "held"}: ${result.reason}`);
|
|
2570
|
+
else skipPhase(phases, now, "promotion", "no promotion decision hook provided");
|
|
2571
|
+
}
|
|
2558
2572
|
return {
|
|
2559
2573
|
goal: options.goal,
|
|
2560
2574
|
phases,
|
|
@@ -2801,9 +2815,11 @@ async function withMeasuredCandidateSnapshot(liveRoot, runDir, state, candidateR
|
|
|
2801
2815
|
await assertStateIdentity(liveRoot, candidateRef, state);
|
|
2802
2816
|
const candidate = state.candidates.find((entry) => entry.candidateId === candidateRef.candidateId);
|
|
2803
2817
|
if (!candidate) throw new Error(`knowledge candidate '${candidateRef.candidateId}' does not exist`);
|
|
2804
|
-
|
|
2818
|
+
const expectedRef = candidateRefFor(candidateRef.runId, state, candidate);
|
|
2819
|
+
if (canonicalJson(expectedRef) !== canonicalJson(candidateRef)) throw new Error("knowledge candidate approval does not match the measured candidate");
|
|
2805
2820
|
const evidence = await assertCandidateEvidence(runDir, candidateRef, state.implementationRef);
|
|
2806
|
-
|
|
2821
|
+
const relativePath = join("candidates", candidate.candidateId, "snapshots", candidateRef.candidateHash);
|
|
2822
|
+
return withSafeDirectory(runDir, relativePath, false, async (root) => {
|
|
2807
2823
|
if (await hashKnowledgeBase(root, state.stateScope) !== candidateRef.candidateHash) throw new Error("knowledge candidate snapshot changed after approval");
|
|
2808
2824
|
const result = await use({
|
|
2809
2825
|
root,
|
|
@@ -2906,7 +2922,8 @@ async function withBaselineSnapshot(runDir, expectedHash, use, scope) {
|
|
|
2906
2922
|
});
|
|
2907
2923
|
}
|
|
2908
2924
|
async function withFrozenCandidateWorkspace(runDir, candidate, candidateRoot, use, scope) {
|
|
2909
|
-
|
|
2925
|
+
const snapshotsPath = join("candidates", safePathSegmentSchema.parse(candidate.candidateId), "snapshots");
|
|
2926
|
+
return withSafeDirectory(runDir, snapshotsPath, true, async (snapshotsDir) => {
|
|
2910
2927
|
const preparation = await mkdtemp(join(snapshotsDir, "prepare-"));
|
|
2911
2928
|
let activated = false;
|
|
2912
2929
|
try {
|
|
@@ -3325,7 +3342,8 @@ async function knowledgePlanMutations(targetRoot, plan) {
|
|
|
3325
3342
|
}));
|
|
3326
3343
|
}
|
|
3327
3344
|
function assertCandidateTransitionPlan(plan, candidate, target, scope) {
|
|
3328
|
-
const
|
|
3345
|
+
const approvedDirection = target === "candidate" ? plan : reverseKnowledgeFilePlan(plan);
|
|
3346
|
+
const actualPlanHash = knowledgeFileTransactionPlanHash(approvedDirection, normalizeKnowledgeStateScope(scope).pagesDirectory, scope?.researchState);
|
|
3329
3347
|
if (actualPlanHash !== candidate.promotionPlanHash) throw new Error(`knowledge candidate plan changed after approval: expected ${candidate.promotionPlanHash}, got ${actualPlanHash}`);
|
|
3330
3348
|
}
|
|
3331
3349
|
function assertCandidateTransitionTransaction(transaction, candidate, target, scope) {
|
|
@@ -3929,7 +3947,7 @@ async function improveSelectedKnowledgeCandidate(options) {
|
|
|
3929
3947
|
if (sourcePlanHash !== sourceCandidate.promotionPlanHash) throw new Error("source knowledge candidate plan no longer matches its measured identity");
|
|
3930
3948
|
const changedSourcePlan = sourcePlan.filter(planEntryChanged).filter((entry) => notDerivedPath(entry, scope));
|
|
3931
3949
|
const selectedPaths = normalizeSelectedPaths(options.selectedPaths, changedSourcePlan, scope);
|
|
3932
|
-
const
|
|
3950
|
+
const selectionMaterial = immutableJson({
|
|
3933
3951
|
kind: "measured-knowledge-change-selection",
|
|
3934
3952
|
version: 1,
|
|
3935
3953
|
sourceCandidate,
|
|
@@ -3937,7 +3955,8 @@ async function improveSelectedKnowledgeCandidate(options) {
|
|
|
3937
3955
|
selectedPaths,
|
|
3938
3956
|
...options.rationale?.trim() ? { rationale: options.rationale.trim() } : {},
|
|
3939
3957
|
...metadata ? { metadata } : {}
|
|
3940
|
-
})
|
|
3958
|
+
});
|
|
3959
|
+
const selectionDigest = contentHash(selectionMaterial);
|
|
3941
3960
|
const derivedImplementationRef = immutableRefSchema.parse(`sha256:${contentHash({
|
|
3942
3961
|
engine: "agent-knowledge/measured-selection-v1",
|
|
3943
3962
|
implementationRef: requestedImplementationRef,
|
|
@@ -4112,7 +4131,8 @@ async function persistSelectionReceipt(root, receipt) {
|
|
|
4112
4131
|
const runDir = knowledgeImprovementRunDir(root, receipt.runId);
|
|
4113
4132
|
const relativePath = join("candidates", safePathSegmentSchema.parse(receipt.derivedCandidateId), "selection.json").replace(/\\/g, "/");
|
|
4114
4133
|
try {
|
|
4115
|
-
|
|
4134
|
+
const existing = measuredSelectionReceiptSchema.parse(JSON.parse((await readRegularFileWithinRoot(runDir, relativePath)).bytes.toString("utf8")));
|
|
4135
|
+
if (canonicalJson(existing) !== canonicalJson(receipt)) throw new Error("measured knowledge selection receipt conflicts with durable content");
|
|
4116
4136
|
return;
|
|
4117
4137
|
} catch (error) {
|
|
4118
4138
|
if (!isMissingFile(error)) throw error;
|
|
@@ -5527,17 +5547,22 @@ async function materialFactsSurfaced(kb, checklist) {
|
|
|
5527
5547
|
* and `searchKnowledge` — and reinvents none of them.
|
|
5528
5548
|
*/
|
|
5529
5549
|
async function runVerifiedResearchLoop(options) {
|
|
5550
|
+
const assertActive = () => options.signal?.throwIfAborted();
|
|
5551
|
+
assertActive();
|
|
5530
5552
|
const maxRounds = Math.max(1, options.maxRounds ?? 3);
|
|
5531
5553
|
await initKnowledgeBase(options.root);
|
|
5554
|
+
assertActive();
|
|
5532
5555
|
const store = new FileSystemKbStore({ root: options.root });
|
|
5533
5556
|
const steps = [];
|
|
5534
5557
|
let index = await buildKnowledgeIndex(options.root);
|
|
5558
|
+
assertActive();
|
|
5535
5559
|
await confirmRegisteredSources(options.driver, index.sources);
|
|
5560
|
+
assertActive();
|
|
5536
5561
|
let readiness = readinessFor(options, index);
|
|
5537
5562
|
let ready = isReady(readiness?.report) && (options.driver.isComplete?.() ?? true);
|
|
5538
5563
|
let steer;
|
|
5539
5564
|
for (let round = 1; round <= maxRounds && !ready; round++) {
|
|
5540
|
-
|
|
5565
|
+
assertActive();
|
|
5541
5566
|
const gaps = gapsFromReadiness(readiness);
|
|
5542
5567
|
const workerContribution = await options.worker({
|
|
5543
5568
|
root: options.root,
|
|
@@ -5549,6 +5574,7 @@ async function runVerifiedResearchLoop(options) {
|
|
|
5549
5574
|
readiness: requireReadiness(readiness, options),
|
|
5550
5575
|
signal: options.signal
|
|
5551
5576
|
});
|
|
5577
|
+
assertActive();
|
|
5552
5578
|
const accepted = [];
|
|
5553
5579
|
const rejectedWorkerSources = [];
|
|
5554
5580
|
const existingUris = new Set(index.sources.flatMap((source) => typeof source.metadata?.originalUri === "string" ? [source.metadata.originalUri] : []));
|
|
@@ -5570,6 +5596,7 @@ async function runVerifiedResearchLoop(options) {
|
|
|
5570
5596
|
acceptedThisRound: accepted,
|
|
5571
5597
|
signal: options.signal
|
|
5572
5598
|
});
|
|
5599
|
+
assertActive();
|
|
5573
5600
|
if (verdict.accept) accepted.push(source);
|
|
5574
5601
|
else rejectedWorkerSources.push({
|
|
5575
5602
|
source,
|
|
@@ -5577,10 +5604,14 @@ async function runVerifiedResearchLoop(options) {
|
|
|
5577
5604
|
});
|
|
5578
5605
|
}
|
|
5579
5606
|
const acceptedWorkerSources = await registerSources(options, accepted);
|
|
5607
|
+
assertActive();
|
|
5580
5608
|
await confirmRegisteredSources(options.driver, acceptedWorkerSources);
|
|
5609
|
+
assertActive();
|
|
5581
5610
|
const writtenPages = [];
|
|
5582
5611
|
writtenPages.push(...await applyPages(options.root, workerContribution, acceptedWorkerSources));
|
|
5612
|
+
assertActive();
|
|
5583
5613
|
index = await buildKnowledgeIndex(options.root);
|
|
5614
|
+
assertActive();
|
|
5584
5615
|
readiness = readinessFor(options, index);
|
|
5585
5616
|
let driverSources = [];
|
|
5586
5617
|
let driverNotes;
|
|
@@ -5595,11 +5626,16 @@ async function runVerifiedResearchLoop(options) {
|
|
|
5595
5626
|
readiness: requireReadiness(readiness, options),
|
|
5596
5627
|
signal: options.signal
|
|
5597
5628
|
});
|
|
5629
|
+
assertActive();
|
|
5598
5630
|
driverNotes = driverContribution.notes;
|
|
5599
5631
|
driverSources = await registerSources(options, driverContribution.sources ?? []);
|
|
5632
|
+
assertActive();
|
|
5600
5633
|
await confirmRegisteredSources(options.driver, driverSources);
|
|
5634
|
+
assertActive();
|
|
5601
5635
|
writtenPages.push(...await applyPages(options.root, driverContribution, driverSources));
|
|
5636
|
+
assertActive();
|
|
5602
5637
|
index = await buildKnowledgeIndex(options.root);
|
|
5638
|
+
assertActive();
|
|
5603
5639
|
readiness = readinessFor(options, index);
|
|
5604
5640
|
}
|
|
5605
5641
|
const driverComplete = options.driver.isComplete?.() ?? true;
|
|
@@ -5608,6 +5644,7 @@ async function runVerifiedResearchLoop(options) {
|
|
|
5608
5644
|
if (ready || remainingGaps.length === 0 && driverComplete) steer = void 0;
|
|
5609
5645
|
else {
|
|
5610
5646
|
await options.driver.prepareFold?.();
|
|
5647
|
+
assertActive();
|
|
5611
5648
|
steer = foldGaps(options.driver, remainingGaps);
|
|
5612
5649
|
}
|
|
5613
5650
|
const step = {
|
|
@@ -5640,10 +5677,14 @@ async function runVerifiedResearchLoop(options) {
|
|
|
5640
5677
|
}
|
|
5641
5678
|
};
|
|
5642
5679
|
await options.driver.checkpoint?.();
|
|
5680
|
+
assertActive();
|
|
5643
5681
|
await store.putEvent(step.event);
|
|
5682
|
+
assertActive();
|
|
5644
5683
|
steps.push(step);
|
|
5645
5684
|
await options.onRound?.(step);
|
|
5685
|
+
assertActive();
|
|
5646
5686
|
}
|
|
5687
|
+
assertActive();
|
|
5647
5688
|
return {
|
|
5648
5689
|
root: options.root,
|
|
5649
5690
|
goal: options.goal,
|
|
@@ -5685,8 +5726,10 @@ function isDuplicate(source, existingUris, accepted) {
|
|
|
5685
5726
|
async function registerSources(options, sources) {
|
|
5686
5727
|
const records = [];
|
|
5687
5728
|
for (const candidate of sources) {
|
|
5729
|
+
options.signal?.throwIfAborted();
|
|
5688
5730
|
const source = snapshotSourceTextInput(candidate);
|
|
5689
5731
|
records.push(await addSourceText(options.root, source, options.sourceOptions));
|
|
5732
|
+
options.signal?.throwIfAborted();
|
|
5690
5733
|
}
|
|
5691
5734
|
return records;
|
|
5692
5735
|
}
|
|
@@ -5859,7 +5902,7 @@ function thesisSynthesisMessages(input, kbText) {
|
|
|
5859
5902
|
async function writeThesisPage(root, input, thesis) {
|
|
5860
5903
|
return withKnowledgeMutation(root, async () => {
|
|
5861
5904
|
const relativePath = `knowledge/thesis-${slugify(input.ticker)}.md`;
|
|
5862
|
-
|
|
5905
|
+
const body = [
|
|
5863
5906
|
"---",
|
|
5864
5907
|
`title: Investment thesis — ${input.company} (${input.ticker})`,
|
|
5865
5908
|
`ticker: ${input.ticker}`,
|
|
@@ -5870,7 +5913,8 @@ async function writeThesisPage(root, input, thesis) {
|
|
|
5870
5913
|
"",
|
|
5871
5914
|
thesis.trim(),
|
|
5872
5915
|
""
|
|
5873
|
-
].join("\n")
|
|
5916
|
+
].join("\n");
|
|
5917
|
+
await writeFileDurableWithinRoot(root, relativePath, body, { encoding: "utf8" });
|
|
5874
5918
|
return join(root, relativePath);
|
|
5875
5919
|
});
|
|
5876
5920
|
}
|
|
@@ -5942,7 +5986,7 @@ function buildKnowledgeBrief(visiblePages, question, options = {}) {
|
|
|
5942
5986
|
origin: entry.origin
|
|
5943
5987
|
});
|
|
5944
5988
|
const originals = /* @__PURE__ */ new Map();
|
|
5945
|
-
const
|
|
5989
|
+
const pages = visiblePages.map((entry) => {
|
|
5946
5990
|
const page = {
|
|
5947
5991
|
...entry.page,
|
|
5948
5992
|
id: qualified(entry),
|
|
@@ -5959,7 +6003,8 @@ function buildKnowledgeBrief(visiblePages, question, options = {}) {
|
|
|
5959
6003
|
};
|
|
5960
6004
|
originals.set(page, entry);
|
|
5961
6005
|
return page;
|
|
5962
|
-
})
|
|
6006
|
+
});
|
|
6007
|
+
const ranked = searchKnowledgePages(pages, question, {
|
|
5963
6008
|
limit,
|
|
5964
6009
|
excludeInvalidated,
|
|
5965
6010
|
...options.tags === void 0 ? {} : { tags: options.tags },
|
|
@@ -6011,212 +6056,6 @@ function briefLine(hit) {
|
|
|
6011
6056
|
return snippet === "" ? `- [${hit.citationId}] ${hit.page.title}` : `- [${hit.citationId}] ${hit.page.title} — ${snippet}`;
|
|
6012
6057
|
}
|
|
6013
6058
|
//#endregion
|
|
6014
|
-
//#region src/knowledge-visibility.ts
|
|
6015
|
-
const KNOWLEDGE_USE_RECEIPT_SCHEMA_VERSION = "2.0.0";
|
|
6016
|
-
const KNOWLEDGE_RECEIPT_DIGEST_ALGORITHM = "rfc8785-sha256";
|
|
6017
|
-
/** Stable content identity for one exact knowledge page. */
|
|
6018
|
-
function knowledgePageDigest(page) {
|
|
6019
|
-
validateKnowledgePage(page);
|
|
6020
|
-
return canonicalCandidateDigest({
|
|
6021
|
-
id: page.id,
|
|
6022
|
-
path: page.path,
|
|
6023
|
-
title: page.title,
|
|
6024
|
-
text: page.text,
|
|
6025
|
-
frontmatter: page.frontmatter,
|
|
6026
|
-
sourceIds: [...page.sourceIds],
|
|
6027
|
-
tags: [...page.tags],
|
|
6028
|
-
outLinks: [...page.outLinks],
|
|
6029
|
-
contradicts: [...page.contradicts ?? []],
|
|
6030
|
-
invalidation: page.invalidation ?? null
|
|
6031
|
-
});
|
|
6032
|
-
}
|
|
6033
|
-
/** Snapshot the exact ordered current/ancestor/shared page view. */
|
|
6034
|
-
function createKnowledgeVisibilitySnapshot(visiblePages) {
|
|
6035
|
-
if (!Array.isArray(visiblePages)) throw new TypeError("knowledge visibility must be an array");
|
|
6036
|
-
const identities = /* @__PURE__ */ new Set();
|
|
6037
|
-
const entries = visiblePages.map((entry, position) => {
|
|
6038
|
-
if (!entry || typeof entry !== "object") throw new TypeError(`knowledge visibility[${position}] must be an originated page`);
|
|
6039
|
-
const origin = validateOrigin(entry.origin, `knowledge visibility[${position}].origin`);
|
|
6040
|
-
validateKnowledgePage(entry.page);
|
|
6041
|
-
const identity = visibilityIdentity(origin, entry.page.path);
|
|
6042
|
-
if (identities.has(identity)) throw new Error(`knowledge visibility repeats path '${entry.page.path}' at origin '${origin}'`);
|
|
6043
|
-
identities.add(identity);
|
|
6044
|
-
return Object.freeze({
|
|
6045
|
-
position,
|
|
6046
|
-
pageId: entry.page.id,
|
|
6047
|
-
origin,
|
|
6048
|
-
path: entry.page.path,
|
|
6049
|
-
pageDigest: knowledgePageDigest(entry.page),
|
|
6050
|
-
sourceIds: Object.freeze([...entry.page.sourceIds]),
|
|
6051
|
-
invalidated: entry.page.invalidation !== void 0
|
|
6052
|
-
});
|
|
6053
|
-
});
|
|
6054
|
-
const material = visibilityMaterial(entries);
|
|
6055
|
-
const snapshot = Object.freeze({
|
|
6056
|
-
...material,
|
|
6057
|
-
snapshotDigest: canonicalCandidateDigest(material),
|
|
6058
|
-
entries: Object.freeze(entries)
|
|
6059
|
-
});
|
|
6060
|
-
visibilityIndexes.set(snapshot, indexVisibility(snapshot));
|
|
6061
|
-
return snapshot;
|
|
6062
|
-
}
|
|
6063
|
-
/**
|
|
6064
|
-
* Verify a snapshot's schema and canonical digest. The check runs once per
|
|
6065
|
-
* snapshot object; later calls with the same object are free.
|
|
6066
|
-
*/
|
|
6067
|
-
function verifyKnowledgeVisibilitySnapshot(snapshot) {
|
|
6068
|
-
return visibilityIndexOf(snapshot).snapshot;
|
|
6069
|
-
}
|
|
6070
|
-
/** Canonical bytes of a verified snapshot, for durable storage. */
|
|
6071
|
-
function encodeKnowledgeVisibilitySnapshot(snapshot) {
|
|
6072
|
-
const verified = verifyKnowledgeVisibilitySnapshot(snapshot);
|
|
6073
|
-
return canonicalCandidateBytes({
|
|
6074
|
-
schemaVersion: verified.schemaVersion,
|
|
6075
|
-
digestAlgorithm: verified.digestAlgorithm,
|
|
6076
|
-
snapshotDigest: verified.snapshotDigest,
|
|
6077
|
-
entries: visibilityMaterial(verified.entries).entries
|
|
6078
|
-
});
|
|
6079
|
-
}
|
|
6080
|
-
/** Parse stored snapshot bytes and verify their schema and digest. */
|
|
6081
|
-
function decodeKnowledgeVisibilitySnapshot(bytes) {
|
|
6082
|
-
if (!(bytes instanceof Uint8Array)) throw new TypeError("knowledge visibility snapshot bytes must be a Uint8Array");
|
|
6083
|
-
let parsed;
|
|
6084
|
-
try {
|
|
6085
|
-
parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
6086
|
-
} catch (error) {
|
|
6087
|
-
throw new Error(`knowledge visibility snapshot bytes are not JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
6088
|
-
}
|
|
6089
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new TypeError("knowledge visibility snapshot must be an object");
|
|
6090
|
-
const candidate = parsed;
|
|
6091
|
-
if (!Array.isArray(candidate.entries)) throw new TypeError("knowledge visibility snapshot entries must be an array");
|
|
6092
|
-
return verifyKnowledgeVisibilitySnapshot(Object.freeze({
|
|
6093
|
-
schemaVersion: candidate.schemaVersion,
|
|
6094
|
-
digestAlgorithm: candidate.digestAlgorithm,
|
|
6095
|
-
snapshotDigest: digest(candidate.snapshotDigest, "knowledge visibility snapshotDigest"),
|
|
6096
|
-
entries: Object.freeze(candidate.entries.map((entry, position) => {
|
|
6097
|
-
if (!entry || typeof entry !== "object") throw new TypeError(`knowledge visibility[${position}] must be an object`);
|
|
6098
|
-
const value = entry;
|
|
6099
|
-
if (!Array.isArray(value.sourceIds)) throw new TypeError(`knowledge visibility[${position}].sourceIds must be an array`);
|
|
6100
|
-
return Object.freeze({
|
|
6101
|
-
position: value.position,
|
|
6102
|
-
pageId: value.pageId,
|
|
6103
|
-
origin: value.origin,
|
|
6104
|
-
path: value.path,
|
|
6105
|
-
pageDigest: value.pageDigest,
|
|
6106
|
-
sourceIds: Object.freeze([...value.sourceIds]),
|
|
6107
|
-
invalidated: value.invalidated
|
|
6108
|
-
});
|
|
6109
|
-
}))
|
|
6110
|
-
}));
|
|
6111
|
-
}
|
|
6112
|
-
/** Locator for snapshot bytes stored at `uri`. */
|
|
6113
|
-
function knowledgeVisibilityArtifactRef(input) {
|
|
6114
|
-
if (!input || typeof input !== "object" || !(input.bytes instanceof Uint8Array)) throw new TypeError("knowledge visibility artifact bytes must be a Uint8Array");
|
|
6115
|
-
if (input.bytes.byteLength === 0) throw new TypeError("knowledge visibility artifact bytes must not be empty");
|
|
6116
|
-
return Object.freeze({
|
|
6117
|
-
uri: nonEmpty$1(input.uri, "knowledge visibility artifact uri"),
|
|
6118
|
-
digest: sha256Bytes(input.bytes),
|
|
6119
|
-
byteLength: input.bytes.byteLength
|
|
6120
|
-
});
|
|
6121
|
-
}
|
|
6122
|
-
const visibilityIndexes = /* @__PURE__ */ new WeakMap();
|
|
6123
|
-
function visibilityIndexOf(snapshot) {
|
|
6124
|
-
if (!snapshot || typeof snapshot !== "object") throw new TypeError("knowledge visibility snapshot is required");
|
|
6125
|
-
const cached = visibilityIndexes.get(snapshot);
|
|
6126
|
-
if (cached !== void 0) return cached;
|
|
6127
|
-
assertSchemaVersion(snapshot.schemaVersion, "knowledge visibility");
|
|
6128
|
-
if (snapshot.digestAlgorithm !== "rfc8785-sha256") throw new Error(`unsupported knowledge visibility digestAlgorithm '${snapshot.digestAlgorithm}'`);
|
|
6129
|
-
if (!Array.isArray(snapshot.entries)) throw new TypeError("knowledge visibility snapshot entries must be an array");
|
|
6130
|
-
if (canonicalCandidateDigest(visibilityMaterial(snapshot.entries)) !== digest(snapshot.snapshotDigest, "knowledge visibility snapshotDigest")) throw new Error("knowledge visibility snapshot digest mismatch");
|
|
6131
|
-
const index = indexVisibility(snapshot);
|
|
6132
|
-
visibilityIndexes.set(snapshot, index);
|
|
6133
|
-
return index;
|
|
6134
|
-
}
|
|
6135
|
-
function indexVisibility(snapshot) {
|
|
6136
|
-
const byIdentity = /* @__PURE__ */ new Map();
|
|
6137
|
-
for (const entry of snapshot.entries) {
|
|
6138
|
-
const identity = visibilityIdentity(entry.origin, entry.path);
|
|
6139
|
-
if (byIdentity.has(identity)) throw new Error(`knowledge visibility repeats path '${entry.path}' at origin '${entry.origin}'`);
|
|
6140
|
-
byIdentity.set(identity, entry);
|
|
6141
|
-
}
|
|
6142
|
-
return {
|
|
6143
|
-
snapshot,
|
|
6144
|
-
byIdentity
|
|
6145
|
-
};
|
|
6146
|
-
}
|
|
6147
|
-
function visibilityIdentity(origin, path) {
|
|
6148
|
-
return `${origin}\u0000${path}`;
|
|
6149
|
-
}
|
|
6150
|
-
function assertSchemaVersion(value, label) {
|
|
6151
|
-
if (value === "2.0.0") return;
|
|
6152
|
-
throw new Error(`unsupported ${label} schemaVersion '${String(value)}'${value === "1.0.0" ? "; 1.0.0 records embed the visibility snapshot and have no reader, create a 2.0.0 record from the snapshot" : ""}`);
|
|
6153
|
-
}
|
|
6154
|
-
function visibilityMaterial(entries) {
|
|
6155
|
-
return {
|
|
6156
|
-
schemaVersion: KNOWLEDGE_USE_RECEIPT_SCHEMA_VERSION,
|
|
6157
|
-
digestAlgorithm: KNOWLEDGE_RECEIPT_DIGEST_ALGORITHM,
|
|
6158
|
-
entries: entries.map((entry, position) => {
|
|
6159
|
-
if (!entry || typeof entry !== "object") throw new TypeError(`knowledge visibility[${position}] must be an object`);
|
|
6160
|
-
if (entry.position !== position) throw new Error(`knowledge visibility position mismatch: expected ${position}, observed ${entry.position}`);
|
|
6161
|
-
if (!Array.isArray(entry.sourceIds)) throw new TypeError(`knowledge visibility[${position}].sourceIds must be an array`);
|
|
6162
|
-
return {
|
|
6163
|
-
position,
|
|
6164
|
-
pageId: nonEmpty$1(entry.pageId, `knowledge visibility[${position}].pageId`),
|
|
6165
|
-
origin: validateOrigin(entry.origin, `knowledge visibility[${position}].origin`),
|
|
6166
|
-
path: nonEmpty$1(entry.path, `knowledge visibility[${position}].path`),
|
|
6167
|
-
pageDigest: digest(entry.pageDigest, `knowledge visibility[${position}].pageDigest`),
|
|
6168
|
-
sourceIds: entry.sourceIds.map((sourceId, sourceIndex) => nonEmpty$1(sourceId, `knowledge visibility[${position}].sourceIds[${sourceIndex}]`)),
|
|
6169
|
-
invalidated: Boolean(entry.invalidated)
|
|
6170
|
-
};
|
|
6171
|
-
})
|
|
6172
|
-
};
|
|
6173
|
-
}
|
|
6174
|
-
function normalizeVisibilityRef(input) {
|
|
6175
|
-
if (!input || typeof input !== "object") throw new TypeError("knowledge retrieval visibility reference is required");
|
|
6176
|
-
if (!Number.isSafeInteger(input.pageCount) || input.pageCount < 0) throw new TypeError("knowledge retrieval visibility pageCount must be a non-negative integer");
|
|
6177
|
-
return Object.freeze({
|
|
6178
|
-
snapshotDigest: digest(input.snapshotDigest, "knowledge retrieval visibility snapshotDigest"),
|
|
6179
|
-
pageCount: input.pageCount,
|
|
6180
|
-
...input.artifact === void 0 ? {} : { artifact: normalizeVisibilityArtifact(input.artifact, "knowledge retrieval visibility artifact") }
|
|
6181
|
-
});
|
|
6182
|
-
}
|
|
6183
|
-
function normalizeVisibilityArtifact(input, label) {
|
|
6184
|
-
if (!input || typeof input !== "object") throw new TypeError(`${label} must be an object`);
|
|
6185
|
-
if (!Number.isSafeInteger(input.byteLength) || input.byteLength < 1) throw new TypeError(`${label} byteLength must be a positive integer`);
|
|
6186
|
-
return Object.freeze({
|
|
6187
|
-
uri: nonEmpty$1(input.uri, `${label} uri`),
|
|
6188
|
-
digest: digest(input.digest, `${label} digest`),
|
|
6189
|
-
byteLength: input.byteLength
|
|
6190
|
-
});
|
|
6191
|
-
}
|
|
6192
|
-
function validateKnowledgePage(page) {
|
|
6193
|
-
if (!page || typeof page !== "object") throw new TypeError("knowledge page must be an object");
|
|
6194
|
-
nonEmpty$1(page.id, "knowledge page id");
|
|
6195
|
-
nonEmpty$1(page.path, "knowledge page path");
|
|
6196
|
-
nonEmpty$1(page.title, "knowledge page title");
|
|
6197
|
-
if (typeof page.text !== "string") throw new TypeError("knowledge page text must be a string");
|
|
6198
|
-
if (!page.frontmatter || typeof page.frontmatter !== "object" || Array.isArray(page.frontmatter)) throw new TypeError("knowledge page frontmatter must be an object");
|
|
6199
|
-
for (const [name, values] of [
|
|
6200
|
-
["sourceIds", page.sourceIds],
|
|
6201
|
-
["tags", page.tags],
|
|
6202
|
-
["outLinks", page.outLinks]
|
|
6203
|
-
]) if (!Array.isArray(values) || values.some((value) => typeof value !== "string")) throw new TypeError(`knowledge page ${name} must be a string array`);
|
|
6204
|
-
}
|
|
6205
|
-
function validateOrigin(value, label) {
|
|
6206
|
-
if (value === "here" || value === "shared") return value;
|
|
6207
|
-
if (typeof value === "string" && value.startsWith("inherited:") && value.slice(10).trim().length > 0) return value;
|
|
6208
|
-
throw new TypeError(`${label} is invalid: ${String(value)}`);
|
|
6209
|
-
}
|
|
6210
|
-
function digest(value, label) {
|
|
6211
|
-
const parsed = sha256DigestSchema.safeParse(value);
|
|
6212
|
-
if (!parsed.success) throw new TypeError(`${label} must be a lowercase sha256 digest`);
|
|
6213
|
-
return parsed.data;
|
|
6214
|
-
}
|
|
6215
|
-
function nonEmpty$1(value, label) {
|
|
6216
|
-
if (typeof value !== "string" || value.trim().length === 0) throw new TypeError(`${label} must be a non-empty string`);
|
|
6217
|
-
return value.trim();
|
|
6218
|
-
}
|
|
6219
|
-
//#endregion
|
|
6220
6059
|
//#region src/knowledge-use-receipts.ts
|
|
6221
6060
|
/**
|
|
6222
6061
|
* The visibility snapshot a receipt references could not be obtained, so the
|
|
@@ -6296,7 +6135,7 @@ function verifyKnowledgeRetrievalReceipt(receipt) {
|
|
|
6296
6135
|
if (receipt.digestAlgorithm !== "rfc8785-sha256") throw new Error(`unsupported knowledge retrieval digestAlgorithm '${receipt.digestAlgorithm}'`);
|
|
6297
6136
|
const visibility = normalizeVisibilityRef(receipt.visibility);
|
|
6298
6137
|
validateReceiptResultShape(receipt.results, visibility.pageCount);
|
|
6299
|
-
|
|
6138
|
+
const material = retrievalMaterial({
|
|
6300
6139
|
createdAt: isoTimestamp(receipt.createdAt, "knowledge retrieval createdAt"),
|
|
6301
6140
|
runId: nonEmpty$1(receipt.runId, "knowledge retrieval runId"),
|
|
6302
6141
|
actorId: optionalText(receipt.actorId, "knowledge retrieval actorId"),
|
|
@@ -6308,7 +6147,8 @@ function verifyKnowledgeRetrievalReceipt(receipt) {
|
|
|
6308
6147
|
results: receipt.results,
|
|
6309
6148
|
evidenceRefs: normalizeEvidenceRefs(receipt.evidenceRefs),
|
|
6310
6149
|
attributes: normalizeAttributes(receipt.attributes)
|
|
6311
|
-
})
|
|
6150
|
+
});
|
|
6151
|
+
if (canonicalCandidateDigest(material) !== receipt.receiptDigest) throw new Error("knowledge retrieval receipt digest mismatch");
|
|
6312
6152
|
return receipt;
|
|
6313
6153
|
}
|
|
6314
6154
|
/**
|
|
@@ -6400,7 +6240,7 @@ function verifyKnowledgeUseReceipt(receipt, retrieval) {
|
|
|
6400
6240
|
if (receipt.retrievalReceiptDigest !== verifiedRetrieval.receiptDigest) throw new Error("knowledge use receipt references a different retrieval receipt");
|
|
6401
6241
|
const selected = verifiedRetrieval.results.find((result) => result.rank === receipt.used.rank);
|
|
6402
6242
|
if (!selected || selected.pageId !== receipt.used.pageId || selected.origin !== receipt.used.origin || selected.path !== receipt.used.path || selected.pageDigest !== receipt.used.pageDigest) throw new Error("knowledge use receipt selected result does not match the retrieval receipt");
|
|
6403
|
-
|
|
6243
|
+
const material = useMaterial({
|
|
6404
6244
|
createdAt: isoTimestamp(receipt.createdAt, "knowledge use createdAt"),
|
|
6405
6245
|
retrieval: verifiedRetrieval,
|
|
6406
6246
|
used: receipt.used,
|
|
@@ -6408,7 +6248,8 @@ function verifyKnowledgeUseReceipt(receipt, retrieval) {
|
|
|
6408
6248
|
consumer: normalizeConsumer(receipt.consumer),
|
|
6409
6249
|
evidenceRefs: normalizeEvidenceRefs(receipt.evidenceRefs),
|
|
6410
6250
|
attributes: normalizeAttributes(receipt.attributes)
|
|
6411
|
-
})
|
|
6251
|
+
});
|
|
6252
|
+
if (canonicalCandidateDigest(material) !== receipt.receiptDigest) throw new Error("knowledge use receipt digest mismatch");
|
|
6412
6253
|
return receipt;
|
|
6413
6254
|
}
|
|
6414
6255
|
/** Record that one retrieval led to no use, with no rank to select. */
|
|
@@ -6435,14 +6276,15 @@ function verifyKnowledgeRetrievalDisposition(disposition, retrieval) {
|
|
|
6435
6276
|
if (disposition.kind !== "knowledge-retrieval-disposition") throw new Error(`knowledge retrieval disposition kind must be 'knowledge-retrieval-disposition'`);
|
|
6436
6277
|
if (disposition.digestAlgorithm !== "rfc8785-sha256") throw new Error(`unsupported knowledge retrieval disposition digestAlgorithm '${disposition.digestAlgorithm}'`);
|
|
6437
6278
|
if (disposition.retrievalReceiptDigest !== verifiedRetrieval.receiptDigest) throw new Error("knowledge retrieval disposition references a different retrieval receipt");
|
|
6438
|
-
|
|
6279
|
+
const material = dispositionMaterial({
|
|
6439
6280
|
createdAt: isoTimestamp(disposition.createdAt, "knowledge retrieval disposition createdAt"),
|
|
6440
6281
|
retrieval: verifiedRetrieval,
|
|
6441
6282
|
relation: validateDispositionRelation(disposition.relation),
|
|
6442
6283
|
consumer: normalizeConsumer(disposition.consumer),
|
|
6443
6284
|
evidenceRefs: normalizeEvidenceRefs(disposition.evidenceRefs),
|
|
6444
6285
|
attributes: normalizeAttributes(disposition.attributes)
|
|
6445
|
-
})
|
|
6286
|
+
});
|
|
6287
|
+
if (canonicalCandidateDigest(material) !== disposition.receiptDigest) throw new Error("knowledge retrieval disposition digest mismatch");
|
|
6446
6288
|
return disposition;
|
|
6447
6289
|
}
|
|
6448
6290
|
function assertReceiptJoinsVisibility(receipt, index) {
|
|
@@ -6557,7 +6399,8 @@ function validateReceiptResultShape(results, pageCount) {
|
|
|
6557
6399
|
const seen = /* @__PURE__ */ new Set();
|
|
6558
6400
|
results.forEach((result, position) => {
|
|
6559
6401
|
if (!result || typeof result !== "object") throw new TypeError(`knowledge retrieval results[${position}] must be an object`);
|
|
6560
|
-
const
|
|
6402
|
+
const origin = validateOrigin(result.origin, `knowledge retrieval results[${position}].origin`);
|
|
6403
|
+
const identity = visibilityIdentity(origin, nonEmpty$1(result.path, `knowledge retrieval results[${position}].path`));
|
|
6561
6404
|
if (seen.has(identity)) throw new Error(`knowledge retrieval repeats visible page '${result.path}'`);
|
|
6562
6405
|
seen.add(identity);
|
|
6563
6406
|
nonEmpty$1(result.pageId, `knowledge retrieval results[${position}].pageId`);
|
|
@@ -6680,7 +6523,10 @@ const searchInput = z.object({
|
|
|
6680
6523
|
kinds: z.array(z.string()).optional()
|
|
6681
6524
|
});
|
|
6682
6525
|
const readInput = z.object({ pageId: z.string().min(1) });
|
|
6683
|
-
const recordInput = z.object({
|
|
6526
|
+
const recordInput = z.object({
|
|
6527
|
+
expectedPageDigests: z.record(z.string().min(1), sha256DigestSchema.nullable()).optional().describe("For each existing page, pass its path and pageDigest from knowledge_read. Null requires a new page. Missing entries permit new pages only."),
|
|
6528
|
+
proposal: z.string().min(1).describe("One or more complete FILE blocks. Each begins with ---FILE: <page-path>--- and ends with ---END FILE---, each on its own line. Put the page content between them. Every block must be valid; malformed or empty proposals write nothing and return an error.")
|
|
6529
|
+
});
|
|
6684
6530
|
const resolveInput = z.object({ references: z.array(z.string().min(1)).min(1) });
|
|
6685
6531
|
/**
|
|
6686
6532
|
* The four tools an agent needs to use a knowledge store: search, read, record,
|
|
@@ -6736,6 +6582,7 @@ function createKnowledgeTools(options) {
|
|
|
6736
6582
|
origin: resolution.resolved.origin,
|
|
6737
6583
|
path: resolution.resolved.page.path,
|
|
6738
6584
|
title: resolution.resolved.page.title,
|
|
6585
|
+
pageDigest: knowledgePageDigest(resolution.resolved.page),
|
|
6739
6586
|
text: resolution.resolved.page.text
|
|
6740
6587
|
},
|
|
6741
6588
|
candidates: resolution.candidates.map((candidate) => ({
|
|
@@ -6745,13 +6592,16 @@ function createKnowledgeTools(options) {
|
|
|
6745
6592
|
}))
|
|
6746
6593
|
};
|
|
6747
6594
|
}),
|
|
6748
|
-
tool("knowledge_record", `Write pages into this run's store. Use complete blocks exactly like:\n---FILE: ${pagesDirectory}/example.md---\n# Example\nPage content.\n---END FILE---\nUse paths under ${pagesDirectory}/. Both delimiters must be on their own lines.
|
|
6595
|
+
tool("knowledge_record", `Write pages into this run's store. Use complete blocks exactly like:\n---FILE: ${pagesDirectory}/example.md---\n# Example\nPage content.\n---END FILE---\nUse paths under ${pagesDirectory}/. Both delimiters must be on their own lines. To update a page, first knowledge_read it and pass expectedPageDigests[path] = pageDigest. Stale edits and malformed, unsafe, or empty proposals are rejected before writing any pages.`, recordInput, async (input) => {
|
|
6749
6596
|
const parsed = parseKnowledgeWriteBlocks(input.proposal, [`${pagesDirectory}/`]);
|
|
6750
6597
|
if (parsed.blocks.length === 0 || parsed.warnings.length > 0) throw new Error(`knowledge_record rejected the proposal without writing any pages. Use ---FILE: ${pagesDirectory}/example.md--- followed by content and ---END FILE---, each delimiter on its own line. ${parsed.warnings.join(" ")}`);
|
|
6751
6598
|
const intake = options.intake;
|
|
6752
6599
|
return applyKnowledgeWriteBlocks(stores.storePath(runId), input.proposal, {
|
|
6753
6600
|
...pages,
|
|
6754
|
-
|
|
6601
|
+
actorId: options.actorId,
|
|
6602
|
+
runId,
|
|
6603
|
+
expectedPageDigests: input.expectedPageDigests ?? {},
|
|
6604
|
+
retainHistory: options.retainHistory ?? true,
|
|
6755
6605
|
...intake === void 0 ? {} : { intake: {
|
|
6756
6606
|
...intake,
|
|
6757
6607
|
inheritedPages: await inheritedOf(stores, runId)
|
|
@@ -6856,7 +6706,8 @@ async function promoteRunScopedPages(stores, runId, options) {
|
|
|
6856
6706
|
const existing = await loadKnowledgePages(sharedRoot, { pagesDirectory });
|
|
6857
6707
|
assertNoIdentityConflict(travellers, existing);
|
|
6858
6708
|
const promotedPaths = new Set(travellers.map((traveller) => traveller.entry.page.path));
|
|
6859
|
-
|
|
6709
|
+
const promotedView = originatedPages([...existing.filter((page) => !promotedPaths.has(page.path)), ...travellers.map((traveller) => traveller.entry.page)], "shared");
|
|
6710
|
+
assertKnowledgeCitationsResolved(promotedView, travellers.flatMap((traveller) => (traveller.entry.page.cites ?? []).map((persisted) => parseKnowledgeCitationReference(persisted))));
|
|
6860
6711
|
await commitKnowledgeFileMutations({
|
|
6861
6712
|
root: sharedRoot,
|
|
6862
6713
|
transactionRoot: lock.transactionRoot,
|
|
@@ -6947,7 +6798,8 @@ function assertNoIdentityConflict(travellers, existing) {
|
|
|
6947
6798
|
async function readPageBytes(stores, runId, entry, pagesDirectory) {
|
|
6948
6799
|
const sourceRunId = sourceRunOf(entry.origin, runId);
|
|
6949
6800
|
const content = (await readRegularFileWithinRoot(stores.storePath(sourceRunId), entry.page.path)).bytes.toString("utf8");
|
|
6950
|
-
|
|
6801
|
+
const page = knowledgePageFromMarkdown(entry.page.path, content, pagesDirectory);
|
|
6802
|
+
if (knowledgePageDigest(page) !== knowledgePageDigest(entry.page)) throw new KnowledgePromotionError("path-conflict", `source page "${entry.page.path}" in run "${sourceRunId}" changed after the promotion snapshot`);
|
|
6951
6803
|
return content;
|
|
6952
6804
|
}
|
|
6953
6805
|
function sourceRunOf(origin, runId) {
|
|
@@ -7672,8 +7524,18 @@ function buildDriver(options, persistence, restored) {
|
|
|
7672
7524
|
}
|
|
7673
7525
|
if (goal !== nextGoal) throw new Error(`claim ledger '${persistence?.ledgerId ?? "in-memory"}' accumulated evidence for goal '${goal}' and cannot be reused for '${nextGoal}'`);
|
|
7674
7526
|
}
|
|
7675
|
-
function resolveRouter() {
|
|
7676
|
-
|
|
7527
|
+
function resolveRouter(signal) {
|
|
7528
|
+
if (options.router) return options.router;
|
|
7529
|
+
const configuredSignal = options.router_options?.signal;
|
|
7530
|
+
const requestSignal = configuredSignal && signal ? AbortSignal.any([configuredSignal, signal]) : configuredSignal ?? signal;
|
|
7531
|
+
return createTangleRouterClient({
|
|
7532
|
+
...options.router_options,
|
|
7533
|
+
signal: requestSignal
|
|
7534
|
+
});
|
|
7535
|
+
}
|
|
7536
|
+
function throwIfRouterAborted(signal) {
|
|
7537
|
+
signal?.throwIfAborted();
|
|
7538
|
+
if (!options.router) options.router_options?.signal?.throwIfAborted();
|
|
7677
7539
|
}
|
|
7678
7540
|
/** Record a claim from a source, growing its independent-source support. */
|
|
7679
7541
|
function recordClaim(extracted, sourceUri, round) {
|
|
@@ -7835,12 +7697,14 @@ function buildDriver(options, persistence, restored) {
|
|
|
7835
7697
|
* because such a source cannot drive the research and pollutes the KB.
|
|
7836
7698
|
*/
|
|
7837
7699
|
async verifySource(source, ctx) {
|
|
7700
|
+
throwIfRouterAborted(ctx.signal);
|
|
7838
7701
|
const sourceSnapshot = snapshotSourceTextInput(source);
|
|
7839
7702
|
const goalSnapshot = ctx.goal;
|
|
7840
7703
|
const roundSnapshot = ctx.round;
|
|
7841
7704
|
const sourceVersion = sourceVersionOfProposal(sourceSnapshot);
|
|
7842
7705
|
bindGoal(goalSnapshot);
|
|
7843
|
-
const extracted = await extractClaims(sourceSnapshot, goalSnapshot);
|
|
7706
|
+
const extracted = await extractClaims(sourceSnapshot, goalSnapshot, ctx.signal);
|
|
7707
|
+
throwIfRouterAborted(ctx.signal);
|
|
7844
7708
|
if (extracted.length === 0) return {
|
|
7845
7709
|
accept: false,
|
|
7846
7710
|
reason: "no extractable claim: source yields nothing to drive the research deeper"
|
|
@@ -7855,6 +7719,7 @@ function buildDriver(options, persistence, restored) {
|
|
|
7855
7719
|
if (registered && researchSourceVersionKey(registered) === researchSourceVersionKey(sourceVersion)) markAddressed(materializeEvidenceFor(/* @__PURE__ */ new Set([researchSourceVersionKey(sourceVersion)])));
|
|
7856
7720
|
}
|
|
7857
7721
|
await persist();
|
|
7722
|
+
throwIfRouterAborted(ctx.signal);
|
|
7858
7723
|
return { accept: true };
|
|
7859
7724
|
},
|
|
7860
7725
|
/**
|
|
@@ -7898,8 +7763,9 @@ function buildDriver(options, persistence, restored) {
|
|
|
7898
7763
|
commitSources,
|
|
7899
7764
|
toLedger
|
|
7900
7765
|
};
|
|
7901
|
-
async function extractClaims(source, goal) {
|
|
7902
|
-
const fromLlm = await extractClaimsWithLlm(source, goal, claimsForExtraction());
|
|
7766
|
+
async function extractClaims(source, goal, signal) {
|
|
7767
|
+
const fromLlm = await extractClaimsWithLlm(source, goal, claimsForExtraction(), signal);
|
|
7768
|
+
throwIfRouterAborted(signal);
|
|
7903
7769
|
if (fromLlm.length > 0) return fromLlm.slice(0, maxClaimsPerSource);
|
|
7904
7770
|
if (deterministicFallback) return deterministicClaims(source).slice(0, maxClaimsPerSource);
|
|
7905
7771
|
return [];
|
|
@@ -7920,11 +7786,13 @@ function buildDriver(options, persistence, restored) {
|
|
|
7920
7786
|
}
|
|
7921
7787
|
return [...known.values()];
|
|
7922
7788
|
}
|
|
7923
|
-
async function extractClaimsWithLlm(source, goal, ledger) {
|
|
7789
|
+
async function extractClaimsWithLlm(source, goal, ledger, signal) {
|
|
7924
7790
|
let router;
|
|
7925
7791
|
try {
|
|
7926
|
-
router = resolveRouter();
|
|
7927
|
-
} catch {
|
|
7792
|
+
router = resolveRouter(signal);
|
|
7793
|
+
} catch (error) {
|
|
7794
|
+
throwIfRouterAborted(signal);
|
|
7795
|
+
if (error?.name === "AbortError") throw error;
|
|
7928
7796
|
return [];
|
|
7929
7797
|
}
|
|
7930
7798
|
const excerpt = source.text.slice(0, 1800);
|
|
@@ -7946,9 +7814,12 @@ function buildDriver(options, persistence, restored) {
|
|
|
7946
7814
|
role: "user",
|
|
7947
7815
|
content: user
|
|
7948
7816
|
}], 1200);
|
|
7949
|
-
} catch {
|
|
7817
|
+
} catch (error) {
|
|
7818
|
+
throwIfRouterAborted(signal);
|
|
7819
|
+
if (error?.name === "AbortError") throw error;
|
|
7950
7820
|
return [];
|
|
7951
7821
|
}
|
|
7822
|
+
throwIfRouterAborted(signal);
|
|
7952
7823
|
return parseExtractedClaims(raw, ledger);
|
|
7953
7824
|
}
|
|
7954
7825
|
/**
|