@hasna/knowledge 0.2.10 → 0.2.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -2
- package/bin/open-knowledge-mcp.js +386 -11
- package/bin/open-knowledge.js +46 -32
- package/docs/architecture/ai-native-knowledge-base.md +27 -0
- package/package.json +6 -2
- package/src/cli.ts +58 -4
- package/src/knowledge-db.ts +2 -0
- package/src/mcp.js +26 -0
- package/src/providers.ts +308 -0
- package/src/service.ts +35 -2
- package/src/storage-contract.ts +265 -0
- package/src/wiki-layout.ts +22 -6
- package/src/workspace.ts +41 -0
package/README.md
CHANGED
|
@@ -62,6 +62,9 @@ open-knowledge export --format jsonl
|
|
|
62
62
|
# Show resolved workspace paths
|
|
63
63
|
open-knowledge paths --scope project --json
|
|
64
64
|
|
|
65
|
+
# Inspect local/S3 artifact storage and source ownership
|
|
66
|
+
open-knowledge storage status --scope project --json
|
|
67
|
+
|
|
65
68
|
# Initialize the project SQLite catalog
|
|
66
69
|
open-knowledge db init --scope project
|
|
67
70
|
|
|
@@ -82,6 +85,10 @@ open-knowledge source resolve open-files://file/f_123/revision/rev_456 --scope p
|
|
|
82
85
|
|
|
83
86
|
# Inspect local safety policy and approvals
|
|
84
87
|
open-knowledge safety status --scope project --json
|
|
88
|
+
|
|
89
|
+
# Inspect AI SDK provider credentials and model aliases
|
|
90
|
+
open-knowledge providers status --scope project --json
|
|
91
|
+
open-knowledge providers models --scope project --json
|
|
85
92
|
```
|
|
86
93
|
|
|
87
94
|
## Commands
|
|
@@ -164,6 +171,18 @@ open-knowledge paths [--scope global|project|local] [--json]
|
|
|
164
171
|
Show the resolved Hasna app workspace, JSON compatibility store, SQLite path,
|
|
165
172
|
artifact directories, and config.
|
|
166
173
|
|
|
174
|
+
### storage
|
|
175
|
+
```bash
|
|
176
|
+
open-knowledge storage status [--scope project] [--json]
|
|
177
|
+
open-knowledge storage validate [--scope project] [--json]
|
|
178
|
+
```
|
|
179
|
+
Show the storage contract for local or S3-backed generated artifacts. Local mode
|
|
180
|
+
uses `.hasna/apps/knowledge` for config, SQLite, indexes, wiki artifacts, logs,
|
|
181
|
+
runs, and exports. S3 mode stores generated artifacts under the configured
|
|
182
|
+
knowledge bucket/prefix while `open-files` remains the source of truth for raw
|
|
183
|
+
source bytes. The command also reports artifact classes, allowed source ref
|
|
184
|
+
schemes, and warnings for non-scalable or unsafe config.
|
|
185
|
+
|
|
167
186
|
### db
|
|
168
187
|
```bash
|
|
169
188
|
open-knowledge db init [--scope project]
|
|
@@ -226,6 +245,19 @@ Inspect and operate the local safety model. Source reads are read-only by
|
|
|
226
245
|
default, web search and S3 reads are opt-in, generated writes require approval
|
|
227
246
|
by default, and known secret patterns are redacted before chunk storage.
|
|
228
247
|
|
|
248
|
+
### providers
|
|
249
|
+
```bash
|
|
250
|
+
open-knowledge providers status [--scope project] [--json]
|
|
251
|
+
open-knowledge providers models [--scope project] [--json]
|
|
252
|
+
open-knowledge providers check [provider|model-alias] [--scope project] [--json]
|
|
253
|
+
```
|
|
254
|
+
Inspect AI SDK v6 provider readiness for OpenAI, Anthropic, and DeepSeek. The
|
|
255
|
+
provider layer resolves BYOK credentials from `OPENAI_API_KEY`,
|
|
256
|
+
`ANTHROPIC_API_KEY`, and `DEEPSEEK_API_KEY` by default, exposes model aliases
|
|
257
|
+
such as `default`, `fast`, `reasoning`, `sonnet`, and `deepseek`, and records
|
|
258
|
+
provider capability metadata for structured output, tool use, tool streaming,
|
|
259
|
+
reasoning, embeddings, and native web-search support.
|
|
260
|
+
|
|
229
261
|
### help
|
|
230
262
|
```bash
|
|
231
263
|
open-knowledge help [command]
|
|
@@ -260,8 +292,9 @@ open-knowledge-mcp
|
|
|
260
292
|
The MCP server exposes item tools (`ok_add`, `ok_list`, `ok_get`, `ok_update`,
|
|
261
293
|
`ok_delete`, `ok_archive`, `ok_restore`, `ok_upsert`, `ok_untag`,
|
|
262
294
|
`ok_bulk_delete`, `ok_prune`, `ok_dedupe`, `ok_stats`, `ok_export`,
|
|
263
|
-
`ok_import`, `ok_batch`), workspace inspection (`ok_paths
|
|
264
|
-
parsing/resolution
|
|
295
|
+
`ok_import`, `ok_batch`), workspace/storage inspection (`ok_paths`,
|
|
296
|
+
`ok_storage_status`), and source-ref parsing/resolution
|
|
297
|
+
(`ok_parse_source_ref`, `ok_resolve_source`).
|
|
265
298
|
|
|
266
299
|
## Source And Artifact Boundary
|
|
267
300
|
|
|
@@ -281,6 +314,11 @@ source ref. It does not copy raw files into the knowledge workspace; local file,
|
|
|
281
314
|
S3, web, and open-files inputs are converted into redacted chunks with offsets,
|
|
282
315
|
hashes, revision metadata, and FTS rows.
|
|
283
316
|
|
|
317
|
+
AI provider configuration is local/BYOK by default. `open-knowledge` declares
|
|
318
|
+
AI SDK v6 provider support through `ai`, `@ai-sdk/openai`,
|
|
319
|
+
`@ai-sdk/anthropic`, and `@ai-sdk/deepseek`, but does not call providers until a
|
|
320
|
+
future prompt/agent command explicitly requests a model.
|
|
321
|
+
|
|
284
322
|
Generated knowledge artifacts can be stored locally under
|
|
285
323
|
`.hasna/apps/knowledge/artifacts` or through the S3 artifact-store adapter.
|
|
286
324
|
|
|
@@ -13660,7 +13660,7 @@ import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync
|
|
|
13660
13660
|
// package.json
|
|
13661
13661
|
var package_default = {
|
|
13662
13662
|
name: "@hasna/knowledge",
|
|
13663
|
-
version: "0.2.
|
|
13663
|
+
version: "0.2.12",
|
|
13664
13664
|
description: "Agent-friendly local knowledge CLI with JSON output, pagination, and safe destructive actions",
|
|
13665
13665
|
type: "module",
|
|
13666
13666
|
bin: {
|
|
@@ -13677,7 +13677,7 @@ var package_default = {
|
|
|
13677
13677
|
scripts: {
|
|
13678
13678
|
test: "bun test",
|
|
13679
13679
|
"test:cli": "bun test tests/cli.test.ts",
|
|
13680
|
-
build: "bun build --target=bun --outfile=bin/open-knowledge.js --minify --external @aws-sdk/client-s3 --external @aws-sdk/credential-providers src/cli.ts && bun build --target=bun --outfile=bin/open-knowledge-mcp.js --external @modelcontextprotocol/sdk --external @aws-sdk/client-s3 --external @aws-sdk/credential-providers src/mcp.js",
|
|
13680
|
+
build: "bun build --target=bun --outfile=bin/open-knowledge.js --minify --external @aws-sdk/client-s3 --external @aws-sdk/credential-providers --external ai --external @ai-sdk/openai --external @ai-sdk/anthropic --external @ai-sdk/deepseek src/cli.ts && bun build --target=bun --outfile=bin/open-knowledge-mcp.js --external @modelcontextprotocol/sdk --external @aws-sdk/client-s3 --external @aws-sdk/credential-providers --external ai --external @ai-sdk/openai --external @ai-sdk/anthropic --external @ai-sdk/deepseek src/mcp.js",
|
|
13681
13681
|
prepublishOnly: "bun run build",
|
|
13682
13682
|
postinstall: "bun run build"
|
|
13683
13683
|
},
|
|
@@ -13710,7 +13710,11 @@ var package_default = {
|
|
|
13710
13710
|
dependencies: {
|
|
13711
13711
|
"@aws-sdk/client-s3": "^3.1063.0",
|
|
13712
13712
|
"@aws-sdk/credential-providers": "^3.1063.0",
|
|
13713
|
+
"@ai-sdk/anthropic": "^3.0.81",
|
|
13714
|
+
"@ai-sdk/deepseek": "^2.0.35",
|
|
13715
|
+
"@ai-sdk/openai": "^3.0.68",
|
|
13713
13716
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
13717
|
+
ai: "^6.0.197",
|
|
13714
13718
|
zod: "^4.3.6"
|
|
13715
13719
|
},
|
|
13716
13720
|
devDependencies: {
|
|
@@ -13764,6 +13768,28 @@ function defaultKnowledgeConfig() {
|
|
|
13764
13768
|
preferred_ref: "open-files",
|
|
13765
13769
|
allowed_schemes: ["open-files", "s3", "file", "https", "http"]
|
|
13766
13770
|
},
|
|
13771
|
+
providers: {
|
|
13772
|
+
default_model: "openai:gpt-5.2",
|
|
13773
|
+
aliases: {
|
|
13774
|
+
fast: "openai:gpt-5-mini",
|
|
13775
|
+
reasoning: "anthropic:claude-opus-4-6",
|
|
13776
|
+
sonnet: "anthropic:claude-sonnet-4-6",
|
|
13777
|
+
deepseek: "deepseek:deepseek-chat",
|
|
13778
|
+
"deepseek-reasoning": "deepseek:deepseek-reasoner"
|
|
13779
|
+
},
|
|
13780
|
+
openai: {
|
|
13781
|
+
api_key_env: "OPENAI_API_KEY",
|
|
13782
|
+
default_model: "gpt-5.2"
|
|
13783
|
+
},
|
|
13784
|
+
anthropic: {
|
|
13785
|
+
api_key_env: "ANTHROPIC_API_KEY",
|
|
13786
|
+
default_model: "claude-sonnet-4-6"
|
|
13787
|
+
},
|
|
13788
|
+
deepseek: {
|
|
13789
|
+
api_key_env: "DEEPSEEK_API_KEY",
|
|
13790
|
+
default_model: "deepseek-chat"
|
|
13791
|
+
}
|
|
13792
|
+
},
|
|
13767
13793
|
safety: {
|
|
13768
13794
|
network: {
|
|
13769
13795
|
web_search_enabled: false,
|
|
@@ -14365,7 +14391,8 @@ function getKnowledgeDbStats(path) {
|
|
|
14365
14391
|
run_events: count(db, "run_events"),
|
|
14366
14392
|
redaction_findings: count(db, "redaction_findings"),
|
|
14367
14393
|
audit_events: count(db, "audit_events"),
|
|
14368
|
-
approval_gates: count(db, "approval_gates")
|
|
14394
|
+
approval_gates: count(db, "approval_gates"),
|
|
14395
|
+
storage_objects: count(db, "storage_objects")
|
|
14369
14396
|
};
|
|
14370
14397
|
} finally {
|
|
14371
14398
|
db.close();
|
|
@@ -15773,6 +15800,300 @@ async function ingestSourceRef(options) {
|
|
|
15773
15800
|
};
|
|
15774
15801
|
}
|
|
15775
15802
|
|
|
15803
|
+
// src/providers.ts
|
|
15804
|
+
var DEFAULT_PROVIDER_SETTINGS = {
|
|
15805
|
+
openai: {
|
|
15806
|
+
api_key_env: "OPENAI_API_KEY",
|
|
15807
|
+
default_model: "gpt-5.2"
|
|
15808
|
+
},
|
|
15809
|
+
anthropic: {
|
|
15810
|
+
api_key_env: "ANTHROPIC_API_KEY",
|
|
15811
|
+
default_model: "claude-sonnet-4-6"
|
|
15812
|
+
},
|
|
15813
|
+
deepseek: {
|
|
15814
|
+
api_key_env: "DEEPSEEK_API_KEY",
|
|
15815
|
+
default_model: "deepseek-chat"
|
|
15816
|
+
}
|
|
15817
|
+
};
|
|
15818
|
+
var PROVIDER_CAPABILITIES = {
|
|
15819
|
+
openai: {
|
|
15820
|
+
text_generation: true,
|
|
15821
|
+
structured_output: true,
|
|
15822
|
+
tool_usage: true,
|
|
15823
|
+
tool_streaming: true,
|
|
15824
|
+
image_input: true,
|
|
15825
|
+
native_web_search: true,
|
|
15826
|
+
reasoning: true,
|
|
15827
|
+
embeddings: true
|
|
15828
|
+
},
|
|
15829
|
+
anthropic: {
|
|
15830
|
+
text_generation: true,
|
|
15831
|
+
structured_output: true,
|
|
15832
|
+
tool_usage: true,
|
|
15833
|
+
tool_streaming: true,
|
|
15834
|
+
image_input: true,
|
|
15835
|
+
native_web_search: false,
|
|
15836
|
+
reasoning: true,
|
|
15837
|
+
embeddings: false
|
|
15838
|
+
},
|
|
15839
|
+
deepseek: {
|
|
15840
|
+
text_generation: true,
|
|
15841
|
+
structured_output: true,
|
|
15842
|
+
tool_usage: true,
|
|
15843
|
+
tool_streaming: true,
|
|
15844
|
+
image_input: false,
|
|
15845
|
+
native_web_search: false,
|
|
15846
|
+
reasoning: true,
|
|
15847
|
+
embeddings: false
|
|
15848
|
+
}
|
|
15849
|
+
};
|
|
15850
|
+
var BUILTIN_ALIASES = {
|
|
15851
|
+
default: "openai:gpt-5.2",
|
|
15852
|
+
fast: "openai:gpt-5-mini",
|
|
15853
|
+
reasoning: "anthropic:claude-opus-4-6",
|
|
15854
|
+
sonnet: "anthropic:claude-sonnet-4-6",
|
|
15855
|
+
deepseek: "deepseek:deepseek-chat",
|
|
15856
|
+
"deepseek-reasoning": "deepseek:deepseek-reasoner"
|
|
15857
|
+
};
|
|
15858
|
+
function providerConfig(config2) {
|
|
15859
|
+
return config2.providers ?? {};
|
|
15860
|
+
}
|
|
15861
|
+
function providerSettings(config2, provider) {
|
|
15862
|
+
const configured = providerConfig(config2)[provider] ?? {};
|
|
15863
|
+
return {
|
|
15864
|
+
...DEFAULT_PROVIDER_SETTINGS[provider],
|
|
15865
|
+
...configured
|
|
15866
|
+
};
|
|
15867
|
+
}
|
|
15868
|
+
function modelAliases(config2) {
|
|
15869
|
+
const configured = providerConfig(config2);
|
|
15870
|
+
return {
|
|
15871
|
+
...BUILTIN_ALIASES,
|
|
15872
|
+
...configured.default_model ? { default: configured.default_model } : {},
|
|
15873
|
+
...configured.aliases ?? {}
|
|
15874
|
+
};
|
|
15875
|
+
}
|
|
15876
|
+
function parseModelRef(modelRef) {
|
|
15877
|
+
const [provider, ...rest] = modelRef.split(":");
|
|
15878
|
+
const model = rest.join(":");
|
|
15879
|
+
if (provider !== "openai" && provider !== "anthropic" && provider !== "deepseek") {
|
|
15880
|
+
throw new Error(`Unsupported AI provider: ${provider}`);
|
|
15881
|
+
}
|
|
15882
|
+
if (!model)
|
|
15883
|
+
throw new Error(`Invalid model ref: ${modelRef}. Expected provider:model.`);
|
|
15884
|
+
return { provider, model };
|
|
15885
|
+
}
|
|
15886
|
+
function resolveModelRef(aliasOrRef, config2) {
|
|
15887
|
+
const aliases = modelAliases(config2);
|
|
15888
|
+
return aliases[aliasOrRef] ?? aliasOrRef;
|
|
15889
|
+
}
|
|
15890
|
+
function listModelRegistry(config2) {
|
|
15891
|
+
const aliases = modelAliases(config2);
|
|
15892
|
+
return Object.entries(aliases).map(([alias, modelRef]) => {
|
|
15893
|
+
const parsed = parseModelRef(modelRef);
|
|
15894
|
+
return {
|
|
15895
|
+
alias,
|
|
15896
|
+
model_ref: modelRef,
|
|
15897
|
+
provider: parsed.provider,
|
|
15898
|
+
model: parsed.model,
|
|
15899
|
+
default: alias === "default",
|
|
15900
|
+
capabilities: PROVIDER_CAPABILITIES[parsed.provider]
|
|
15901
|
+
};
|
|
15902
|
+
});
|
|
15903
|
+
}
|
|
15904
|
+
function providerCredentialStatus(config2, env = process.env) {
|
|
15905
|
+
return Object.keys(DEFAULT_PROVIDER_SETTINGS).map((provider) => {
|
|
15906
|
+
const settings = providerSettings(config2, provider);
|
|
15907
|
+
const configured = Boolean(env[settings.api_key_env]);
|
|
15908
|
+
return {
|
|
15909
|
+
provider,
|
|
15910
|
+
api_key_env: settings.api_key_env,
|
|
15911
|
+
configured,
|
|
15912
|
+
source: configured ? "env" : "missing",
|
|
15913
|
+
base_url: settings.base_url ?? null,
|
|
15914
|
+
default_model: settings.default_model
|
|
15915
|
+
};
|
|
15916
|
+
});
|
|
15917
|
+
}
|
|
15918
|
+
function providerStatus(config2, env = process.env) {
|
|
15919
|
+
return {
|
|
15920
|
+
default_model: resolveModelRef("default", config2),
|
|
15921
|
+
providers: providerCredentialStatus(config2, env),
|
|
15922
|
+
models: listModelRegistry(config2)
|
|
15923
|
+
};
|
|
15924
|
+
}
|
|
15925
|
+
|
|
15926
|
+
// src/storage-contract.ts
|
|
15927
|
+
import { createHash as createHash5, randomUUID as randomUUID4 } from "crypto";
|
|
15928
|
+
var GENERATED_ARTIFACTS = [
|
|
15929
|
+
{
|
|
15930
|
+
kind: "schema",
|
|
15931
|
+
prefix: "schemas/",
|
|
15932
|
+
description: "Machine-readable agent schemas and source rules."
|
|
15933
|
+
},
|
|
15934
|
+
{
|
|
15935
|
+
kind: "index",
|
|
15936
|
+
prefix: "indexes/",
|
|
15937
|
+
description: "Small orientation indexes and future shard manifests."
|
|
15938
|
+
},
|
|
15939
|
+
{
|
|
15940
|
+
kind: "log",
|
|
15941
|
+
prefix: "logs/",
|
|
15942
|
+
description: "Append-only JSONL run and wiki-maintenance log partitions."
|
|
15943
|
+
},
|
|
15944
|
+
{
|
|
15945
|
+
kind: "run",
|
|
15946
|
+
prefix: "runs/",
|
|
15947
|
+
description: "Prompt/tool/cost ledgers and generated output records."
|
|
15948
|
+
},
|
|
15949
|
+
{
|
|
15950
|
+
kind: "wiki_page",
|
|
15951
|
+
prefix: "wiki/",
|
|
15952
|
+
description: "Generated cited Markdown pages, not raw source files."
|
|
15953
|
+
},
|
|
15954
|
+
{
|
|
15955
|
+
kind: "export",
|
|
15956
|
+
prefix: "exports/",
|
|
15957
|
+
description: "Portable exports and snapshots of derived knowledge state."
|
|
15958
|
+
}
|
|
15959
|
+
];
|
|
15960
|
+
function hashArtifactBody(body) {
|
|
15961
|
+
const bytes = typeof body === "string" ? Buffer.from(body) : Buffer.from(body);
|
|
15962
|
+
return {
|
|
15963
|
+
hash: `sha256:${createHash5("sha256").update(bytes).digest("hex")}`,
|
|
15964
|
+
size_bytes: bytes.byteLength
|
|
15965
|
+
};
|
|
15966
|
+
}
|
|
15967
|
+
function artifactKindForKey(key) {
|
|
15968
|
+
const match = GENERATED_ARTIFACTS.find((entry) => key.startsWith(entry.prefix));
|
|
15969
|
+
return match?.kind ?? "artifact";
|
|
15970
|
+
}
|
|
15971
|
+
function resolveStorageContract(config2, workspace, scope = "global") {
|
|
15972
|
+
const validation = validateStorageConfig(config2, workspace);
|
|
15973
|
+
const s3 = config2.storage.s3 ?? null;
|
|
15974
|
+
const prefix = s3?.prefix?.replace(/^\/+|\/+$/g, "") ?? "";
|
|
15975
|
+
const s3UriPrefix = s3 ? `s3://${s3.bucket}/${prefix ? `${prefix}/` : ""}` : "";
|
|
15976
|
+
return {
|
|
15977
|
+
scope,
|
|
15978
|
+
mode: config2.mode,
|
|
15979
|
+
storage_type: config2.storage.type,
|
|
15980
|
+
workspace_home: workspace.home,
|
|
15981
|
+
local_layout: {
|
|
15982
|
+
app_path: HASNA_KNOWLEDGE_APP_PATH,
|
|
15983
|
+
config_path: workspace.configPath,
|
|
15984
|
+
json_store_path: workspace.jsonStorePath,
|
|
15985
|
+
knowledge_db_path: workspace.knowledgeDbPath,
|
|
15986
|
+
directories: {
|
|
15987
|
+
artifacts: workspace.artifactsDir,
|
|
15988
|
+
cache: workspace.cacheDir,
|
|
15989
|
+
exports: workspace.exportsDir,
|
|
15990
|
+
indexes: workspace.indexesDir,
|
|
15991
|
+
logs: workspace.logsDir,
|
|
15992
|
+
runs: workspace.runsDir,
|
|
15993
|
+
schemas: workspace.schemasDir,
|
|
15994
|
+
wiki: workspace.wikiDir
|
|
15995
|
+
}
|
|
15996
|
+
},
|
|
15997
|
+
artifact_store: {
|
|
15998
|
+
type: config2.storage.type,
|
|
15999
|
+
artifacts_root: config2.storage.artifacts_root,
|
|
16000
|
+
uri_prefix: config2.storage.type === "s3" ? s3UriPrefix : `file://${workspace.artifactsDir}/`,
|
|
16001
|
+
s3: s3 ? {
|
|
16002
|
+
bucket: s3.bucket,
|
|
16003
|
+
prefix,
|
|
16004
|
+
region: s3.region ?? null,
|
|
16005
|
+
profile: s3.profile ?? null,
|
|
16006
|
+
server_side_encryption: s3.server_side_encryption ?? null,
|
|
16007
|
+
kms_key_configured: Boolean(s3.kms_key_id)
|
|
16008
|
+
} : null
|
|
16009
|
+
},
|
|
16010
|
+
source_ownership: {
|
|
16011
|
+
owner: "open-files",
|
|
16012
|
+
preferred_ref: config2.sources.preferred_ref,
|
|
16013
|
+
allowed_schemes: config2.sources.allowed_schemes,
|
|
16014
|
+
raw_source_bytes_stored_in_open_knowledge: false,
|
|
16015
|
+
stores: [
|
|
16016
|
+
"source refs",
|
|
16017
|
+
"source revisions and hashes",
|
|
16018
|
+
"citation spans",
|
|
16019
|
+
"redacted extracted chunks",
|
|
16020
|
+
"embeddings",
|
|
16021
|
+
"generated wiki artifacts",
|
|
16022
|
+
"indexes",
|
|
16023
|
+
"run ledgers"
|
|
16024
|
+
],
|
|
16025
|
+
does_not_store: [
|
|
16026
|
+
"raw open-files bytes",
|
|
16027
|
+
"S3 object credentials",
|
|
16028
|
+
"connector secrets",
|
|
16029
|
+
"hosted tenant ownership state"
|
|
16030
|
+
]
|
|
16031
|
+
},
|
|
16032
|
+
generated_artifacts: GENERATED_ARTIFACTS,
|
|
16033
|
+
scalability: {
|
|
16034
|
+
catalog: "knowledge.db tracks sources, revisions, chunks, citations, indexes, runs, and storage_objects.",
|
|
16035
|
+
indexes: "Indexes are cataloged DB rows plus sharded artifacts, not one giant index.md.",
|
|
16036
|
+
logs: "Logs use dated JSONL partitions under logs/yyyy/mm/dd.jsonl.",
|
|
16037
|
+
markdown: "Markdown pages are the readable wiki layer over DB/object-store state."
|
|
16038
|
+
},
|
|
16039
|
+
warnings: validation.warnings
|
|
16040
|
+
};
|
|
16041
|
+
}
|
|
16042
|
+
function validateStorageConfig(config2, workspace) {
|
|
16043
|
+
const errors3 = [];
|
|
16044
|
+
const warnings = [];
|
|
16045
|
+
if (!workspace.home.endsWith(HASNA_KNOWLEDGE_APP_PATH)) {
|
|
16046
|
+
warnings.push(`Workspace home does not end with ${HASNA_KNOWLEDGE_APP_PATH}: ${workspace.home}`);
|
|
16047
|
+
}
|
|
16048
|
+
if (config2.storage.type === "s3") {
|
|
16049
|
+
if (!config2.storage.s3?.bucket)
|
|
16050
|
+
errors3.push("storage.s3.bucket is required when storage.type is s3.");
|
|
16051
|
+
if (!config2.storage.s3?.prefix)
|
|
16052
|
+
warnings.push("storage.s3.prefix is empty; generated knowledge artifacts will be written at the bucket root.");
|
|
16053
|
+
if (config2.mode === "local")
|
|
16054
|
+
warnings.push("storage.type is s3 while mode is local; this is valid for BYO S3, but hosted wrappers should set mode to hosted.");
|
|
16055
|
+
}
|
|
16056
|
+
if (config2.storage.type === "local" && config2.storage.s3) {
|
|
16057
|
+
warnings.push("storage.s3 is configured but ignored while storage.type is local.");
|
|
16058
|
+
}
|
|
16059
|
+
if (config2.sources.preferred_ref !== "open-files") {
|
|
16060
|
+
warnings.push("sources.preferred_ref should stay open-files for durable company knowledge.");
|
|
16061
|
+
}
|
|
16062
|
+
if (!config2.sources.allowed_schemes.includes("open-files")) {
|
|
16063
|
+
errors3.push("sources.allowed_schemes must include open-files.");
|
|
16064
|
+
}
|
|
16065
|
+
return {
|
|
16066
|
+
ok: errors3.length === 0,
|
|
16067
|
+
errors: errors3,
|
|
16068
|
+
warnings
|
|
16069
|
+
};
|
|
16070
|
+
}
|
|
16071
|
+
function recordStorageObjects(db, objects, now = new Date) {
|
|
16072
|
+
const timestamp = now.toISOString();
|
|
16073
|
+
const statement = db.prepare(`
|
|
16074
|
+
INSERT INTO storage_objects (
|
|
16075
|
+
id, artifact_uri, kind, content_type, hash, size_bytes, metadata_json, created_at, updated_at
|
|
16076
|
+
)
|
|
16077
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
16078
|
+
ON CONFLICT(artifact_uri) DO UPDATE SET
|
|
16079
|
+
kind = excluded.kind,
|
|
16080
|
+
content_type = excluded.content_type,
|
|
16081
|
+
hash = excluded.hash,
|
|
16082
|
+
size_bytes = excluded.size_bytes,
|
|
16083
|
+
metadata_json = excluded.metadata_json,
|
|
16084
|
+
updated_at = excluded.updated_at
|
|
16085
|
+
`);
|
|
16086
|
+
const insert = db.transaction((entries) => {
|
|
16087
|
+
for (const entry of entries) {
|
|
16088
|
+
statement.run(randomUUID4(), entry.uri, entry.kind, entry.content_type ?? null, entry.hash ?? null, entry.size_bytes ?? null, JSON.stringify({
|
|
16089
|
+
key: entry.key,
|
|
16090
|
+
...entry.metadata ?? {}
|
|
16091
|
+
}), timestamp, timestamp);
|
|
16092
|
+
}
|
|
16093
|
+
});
|
|
16094
|
+
insert(objects);
|
|
16095
|
+
}
|
|
16096
|
+
|
|
15776
16097
|
// src/wiki-layout.ts
|
|
15777
16098
|
function todayParts(now) {
|
|
15778
16099
|
const year = String(now.getUTCFullYear());
|
|
@@ -15847,19 +16168,29 @@ async function initializeWikiLayout(store, now = new Date) {
|
|
|
15847
16168
|
root_index_key: rootIndexKey,
|
|
15848
16169
|
wiki_readme_key: wikiReadmeKey
|
|
15849
16170
|
};
|
|
15850
|
-
const
|
|
15851
|
-
|
|
15852
|
-
|
|
15853
|
-
|
|
15854
|
-
|
|
15855
|
-
`, content_type: "application/x-ndjson" }
|
|
16171
|
+
const entries = [
|
|
16172
|
+
{ key: schemaKey, body: agentSchemaTemplate(), content_type: "text/markdown" },
|
|
16173
|
+
{ key: rootIndexKey, body: rootIndexTemplate(), content_type: "text/markdown" },
|
|
16174
|
+
{ key: wikiReadmeKey, body: wikiReadmeTemplate(), content_type: "text/markdown" },
|
|
16175
|
+
{ key: logKey, body: `${JSON.stringify(event)}
|
|
16176
|
+
`, content_type: "application/x-ndjson" }
|
|
15856
16177
|
];
|
|
15857
|
-
await Promise.all(
|
|
16178
|
+
const artifacts = await Promise.all(entries.map(async (entry) => {
|
|
16179
|
+
const result = await store.put(entry);
|
|
16180
|
+
return {
|
|
16181
|
+
key: result.key,
|
|
16182
|
+
uri: result.uri,
|
|
16183
|
+
kind: artifactKindForKey(entry.key),
|
|
16184
|
+
content_type: entry.content_type,
|
|
16185
|
+
...hashArtifactBody(entry.body)
|
|
16186
|
+
};
|
|
16187
|
+
}));
|
|
15858
16188
|
return {
|
|
15859
16189
|
schema_key: schemaKey,
|
|
15860
16190
|
root_index_key: rootIndexKey,
|
|
15861
16191
|
wiki_readme_key: wikiReadmeKey,
|
|
15862
16192
|
log_key: logKey,
|
|
16193
|
+
artifacts,
|
|
15863
16194
|
written: [schemaKey, rootIndexKey, wikiReadmeKey, logKey]
|
|
15864
16195
|
};
|
|
15865
16196
|
}
|
|
@@ -15899,6 +16230,12 @@ class KnowledgeService {
|
|
|
15899
16230
|
artifactStore() {
|
|
15900
16231
|
return createArtifactStore(this.config(), this.ensureWorkspace());
|
|
15901
16232
|
}
|
|
16233
|
+
storageContract() {
|
|
16234
|
+
return resolveStorageContract(this.config(), this.ensureWorkspace(), this.scope);
|
|
16235
|
+
}
|
|
16236
|
+
validateStorage() {
|
|
16237
|
+
return validateStorageConfig(this.config(), this.ensureWorkspace());
|
|
16238
|
+
}
|
|
15902
16239
|
paths() {
|
|
15903
16240
|
const workspace = this.ensureWorkspace();
|
|
15904
16241
|
return {
|
|
@@ -15927,7 +16264,16 @@ class KnowledgeService {
|
|
|
15927
16264
|
return getKnowledgeDbStats(workspace.knowledgeDbPath);
|
|
15928
16265
|
}
|
|
15929
16266
|
async initWiki() {
|
|
15930
|
-
|
|
16267
|
+
const workspace = this.ensureWorkspace();
|
|
16268
|
+
migrateKnowledgeDb(workspace.knowledgeDbPath);
|
|
16269
|
+
const result = await initializeWikiLayout(this.artifactStore());
|
|
16270
|
+
const db = openKnowledgeDb(workspace.knowledgeDbPath);
|
|
16271
|
+
try {
|
|
16272
|
+
recordStorageObjects(db, result.artifacts);
|
|
16273
|
+
} finally {
|
|
16274
|
+
db.close();
|
|
16275
|
+
}
|
|
16276
|
+
return result;
|
|
15931
16277
|
}
|
|
15932
16278
|
async ingestManifest(input) {
|
|
15933
16279
|
const workspace = this.ensureWorkspace();
|
|
@@ -15967,6 +16313,12 @@ class KnowledgeService {
|
|
|
15967
16313
|
safetyPolicy: this.safetyPolicy()
|
|
15968
16314
|
});
|
|
15969
16315
|
}
|
|
16316
|
+
providerStatus(env = process.env) {
|
|
16317
|
+
return providerStatus(this.config(), env);
|
|
16318
|
+
}
|
|
16319
|
+
modelRegistry() {
|
|
16320
|
+
return listModelRegistry(this.config());
|
|
16321
|
+
}
|
|
15970
16322
|
}
|
|
15971
16323
|
function createKnowledgeService(options = {}) {
|
|
15972
16324
|
return new KnowledgeService(options);
|
|
@@ -16032,6 +16384,17 @@ function buildServer() {
|
|
|
16032
16384
|
}, async ({ scope }) => {
|
|
16033
16385
|
return jsonText(createKnowledgeService({ scope }).paths());
|
|
16034
16386
|
});
|
|
16387
|
+
registerTool(server, "ok_storage_status", "Knowledge storage status", "Inspect local/S3 artifact storage, source ownership, and scalability contract", {
|
|
16388
|
+
scope: scopeField
|
|
16389
|
+
}, async ({ scope }) => {
|
|
16390
|
+
const service = createKnowledgeService({ scope });
|
|
16391
|
+
const validation = service.validateStorage();
|
|
16392
|
+
return jsonText({
|
|
16393
|
+
ok: validation.ok,
|
|
16394
|
+
...service.storageContract(),
|
|
16395
|
+
validation
|
|
16396
|
+
});
|
|
16397
|
+
});
|
|
16035
16398
|
registerTool(server, "ok_parse_source_ref", "Parse source reference", "Parse and validate an open-files, S3, file, or web source ref", {
|
|
16036
16399
|
uri: exports_external.string().describe("Source reference URI")
|
|
16037
16400
|
}, async ({ uri }) => {
|
|
@@ -16058,6 +16421,18 @@ function buildServer() {
|
|
|
16058
16421
|
return errorText(error48 instanceof Error ? error48.message : String(error48));
|
|
16059
16422
|
}
|
|
16060
16423
|
});
|
|
16424
|
+
registerTool(server, "ok_provider_status", "AI provider status", "Inspect configured AI SDK providers, model aliases, and BYOK credential availability", {
|
|
16425
|
+
scope: scopeField
|
|
16426
|
+
}, async ({ scope }) => {
|
|
16427
|
+
const service = createKnowledgeService({ scope });
|
|
16428
|
+
return jsonText({ ok: true, ...service.providerStatus() });
|
|
16429
|
+
});
|
|
16430
|
+
registerTool(server, "ok_provider_models", "AI provider models", "List AI SDK model aliases and capability metadata", {
|
|
16431
|
+
scope: scopeField
|
|
16432
|
+
}, async ({ scope }) => {
|
|
16433
|
+
const service = createKnowledgeService({ scope });
|
|
16434
|
+
return jsonText({ ok: true, models: service.modelRegistry() });
|
|
16435
|
+
});
|
|
16061
16436
|
registerTool(server, "ok_add", "Add a knowledge item", "Add a new item to the knowledge store", {
|
|
16062
16437
|
title: exports_external.string().describe("Item title"),
|
|
16063
16438
|
content: exports_external.string().describe("Item content/body"),
|