bikky 0.4.2 → 0.4.4
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 +64 -37
- package/dist/config.d.ts +15 -1
- package/dist/config.js +116 -20
- package/dist/daemon/capture-policy.d.ts +0 -1
- package/dist/daemon/capture-policy.js +0 -2
- package/dist/daemon/consolidation.d.ts +2 -1
- package/dist/daemon/consolidation.js +32 -15
- package/dist/daemon/entity-typing.js +10 -0
- package/dist/daemon/episode-summary.d.ts +4 -0
- package/dist/daemon/episode-summary.js +39 -8
- package/dist/daemon/extraction.d.ts +2 -2
- package/dist/daemon/extraction.js +65 -22
- package/dist/daemon/loop.js +8 -0
- package/dist/daemon/maintenance-state.d.ts +1 -1
- package/dist/daemon/maintenance-state.js +2 -0
- package/dist/daemon/qdrant.d.ts +32 -10
- package/dist/daemon/qdrant.js +199 -60
- package/dist/daemon/quality-rollups.d.ts +51 -0
- package/dist/daemon/quality-rollups.js +378 -0
- package/dist/daemon/relations.d.ts +3 -3
- package/dist/daemon/relations.js +28 -16
- package/dist/daemon/session-index.d.ts +5 -0
- package/dist/daemon/session-index.js +36 -9
- package/dist/daemon/session-summary.d.ts +3 -0
- package/dist/daemon/session-summary.js +48 -15
- package/dist/daemon/staleness.js +3 -3
- package/dist/daemon/transcript-sources.js +3 -2
- package/dist/daemon/watcher.js +2 -0
- package/dist/daemon/workstream-summary.d.ts +4 -0
- package/dist/daemon/workstream-summary.js +58 -16
- package/dist/install.d.ts +11 -0
- package/dist/install.js +38 -0
- package/dist/lifecycle.js +7 -5
- package/dist/llm/embedding/index.js +2 -1
- package/dist/llm/embedding/providers/openai.js +8 -2
- package/dist/llm/embedding/providers/portkey.js +9 -2
- package/dist/llm/inference/index.js +2 -1
- package/dist/llm/util.d.ts +12 -0
- package/dist/llm/util.js +18 -0
- package/dist/mcp/helpers.d.ts +8 -0
- package/dist/mcp/helpers.js +36 -3
- package/dist/mcp/taxonomy.d.ts +9 -13
- package/dist/mcp/taxonomy.js +59 -42
- package/dist/mcp/tools.js +351 -83
- package/dist/mcp/types.d.ts +35 -0
- package/dist/package-verifier.d.ts +19 -0
- package/dist/package-verifier.js +83 -0
- package/dist/prompts/brief.d.ts +2 -2
- package/dist/prompts/brief.js +0 -1
- package/dist/prompts/extraction.js +9 -11
- package/dist/provenance/origin.d.ts +57 -0
- package/dist/provenance/origin.js +254 -0
- package/dist/routing-context.d.ts +16 -0
- package/dist/routing-context.js +55 -0
- package/dist/status.d.ts +1 -0
- package/dist/status.js +7 -1
- package/docs/config/fully-hosted.md +33 -13
- package/docs/config/hosted-models.md +33 -13
- package/docs/config/hosted-qdrant-local-models.md +1 -0
- package/docs/config/local.md +1 -0
- package/docs/configuration.md +42 -17
- package/package.json +2 -2
package/dist/mcp/types.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Type definitions for the Memory MCP server.
|
|
3
3
|
*/
|
|
4
|
+
import type { OperationOrigin } from "../provenance/origin.js";
|
|
4
5
|
export interface McpToolResult {
|
|
5
6
|
[key: string]: unknown;
|
|
6
7
|
content: Array<{
|
|
@@ -36,6 +37,9 @@ export interface QdrantFilterCondition {
|
|
|
36
37
|
is_empty?: {
|
|
37
38
|
key: string;
|
|
38
39
|
};
|
|
40
|
+
must?: QdrantFilterCondition[];
|
|
41
|
+
should?: QdrantFilterCondition[];
|
|
42
|
+
must_not?: QdrantFilterCondition[];
|
|
39
43
|
}
|
|
40
44
|
export interface QdrantFilter {
|
|
41
45
|
must: QdrantFilterCondition[];
|
|
@@ -67,8 +71,12 @@ export interface FactPayload {
|
|
|
67
71
|
kind?: string;
|
|
68
72
|
layer?: string | null;
|
|
69
73
|
memory_subtype?: string | null;
|
|
74
|
+
origin?: OperationOrigin;
|
|
75
|
+
last_operation_origin?: OperationOrigin;
|
|
76
|
+
/** @deprecated Origin is canonical for new writes. */
|
|
70
77
|
actor_id?: string;
|
|
71
78
|
entities: string[];
|
|
79
|
+
/** @deprecated Origin is canonical for new writes. */
|
|
72
80
|
source?: string;
|
|
73
81
|
confidence: number;
|
|
74
82
|
importance?: number;
|
|
@@ -79,8 +87,16 @@ export interface FactPayload {
|
|
|
79
87
|
verification_count?: number;
|
|
80
88
|
useful_count?: number;
|
|
81
89
|
not_useful_count?: number;
|
|
90
|
+
useful_feedback_count?: number;
|
|
91
|
+
not_useful_feedback_count?: number;
|
|
92
|
+
misleading_count?: number;
|
|
93
|
+
wrong_count?: number;
|
|
94
|
+
irrelevant_count?: number;
|
|
95
|
+
last_useful_at?: string;
|
|
82
96
|
last_used_at?: string;
|
|
83
97
|
last_feedback_at?: string;
|
|
98
|
+
recall_count?: number;
|
|
99
|
+
last_recalled_at?: string;
|
|
84
100
|
superseded_by: string | null;
|
|
85
101
|
superseded_at: string | null;
|
|
86
102
|
created_at: string;
|
|
@@ -131,15 +147,34 @@ export interface FactPayload {
|
|
|
131
147
|
event_session_id?: string;
|
|
132
148
|
recall_query?: string;
|
|
133
149
|
returned_fact_ids?: string[];
|
|
150
|
+
result_count?: number;
|
|
151
|
+
search_scope?: string;
|
|
152
|
+
searched_destinations?: string[];
|
|
153
|
+
failed_destinations?: string[];
|
|
154
|
+
target_fact_id?: string;
|
|
134
155
|
feedback_note?: string;
|
|
156
|
+
feedback_kind?: string;
|
|
135
157
|
outcome?: string;
|
|
136
158
|
outcome_summary?: string;
|
|
159
|
+
scope_type?: string;
|
|
160
|
+
scope_value?: string;
|
|
161
|
+
rollup_type?: string;
|
|
162
|
+
rollup_generated_at?: string;
|
|
163
|
+
rollup_window_start?: string;
|
|
164
|
+
rollup_window_end?: string;
|
|
165
|
+
active_fact_count?: number;
|
|
166
|
+
stale_count?: number;
|
|
167
|
+
low_confidence_count?: number;
|
|
137
168
|
}
|
|
138
169
|
export interface FilterParams {
|
|
139
170
|
category?: string;
|
|
140
171
|
domain?: string;
|
|
141
172
|
kind?: string;
|
|
142
173
|
memory_subtype?: string;
|
|
174
|
+
origin_user_id?: string;
|
|
175
|
+
origin_agent_id?: string;
|
|
176
|
+
origin_interface?: string;
|
|
177
|
+
/** @deprecated Use origin_user_id / origin_agent_id. */
|
|
143
178
|
actor_id?: string;
|
|
144
179
|
entity?: string;
|
|
145
180
|
session_id?: string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface PackageVerifierPattern {
|
|
2
|
+
re: RegExp;
|
|
3
|
+
reason: string;
|
|
4
|
+
}
|
|
5
|
+
export interface PackageVerifierSpec {
|
|
6
|
+
name: string;
|
|
7
|
+
requiredPaths: string[];
|
|
8
|
+
}
|
|
9
|
+
export declare const TEXT_EXTENSIONS: Set<string>;
|
|
10
|
+
export declare const FORBIDDEN_PATH_PATTERNS: PackageVerifierPattern[];
|
|
11
|
+
export declare const FORBIDDEN_CONTENT_PATTERNS: PackageVerifierPattern[];
|
|
12
|
+
export declare function normalizePackPath(file: string): string;
|
|
13
|
+
export declare function findForbiddenPath(file: string, patterns?: ReadonlyArray<PackageVerifierPattern>): PackageVerifierPattern | null;
|
|
14
|
+
export declare function findForbiddenContent(content: string, patterns?: ReadonlyArray<PackageVerifierPattern>): PackageVerifierPattern | null;
|
|
15
|
+
export declare function assertPackageContents(pkg: PackageVerifierSpec, files: string[], patterns?: ReadonlyArray<PackageVerifierPattern>): void;
|
|
16
|
+
export declare function shouldScanTextByMetadata(file: string, size: number, textExtensions?: ReadonlySet<string>): boolean;
|
|
17
|
+
export declare function shouldScanText(file: string): boolean;
|
|
18
|
+
export declare function assertSafeTextContent(pkgName: string, relPath: string, content: string): void;
|
|
19
|
+
//# sourceMappingURL=package-verifier.d.ts.map
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const privateIdentityPathPattern = () => new RegExp([
|
|
4
|
+
["saber", "zrelli", "private"].join("-"),
|
|
5
|
+
["saber", "apate"].join("-"),
|
|
6
|
+
"ap" + "ate",
|
|
7
|
+
].join("|"), "i");
|
|
8
|
+
const privateIdentityContentPattern = () => new RegExp([
|
|
9
|
+
["saber", "zrelli", "private"].join("-"),
|
|
10
|
+
["saber", "apate"].join("-"),
|
|
11
|
+
["apate", "ai"].join("-"),
|
|
12
|
+
["apate", "com"].join("\\."),
|
|
13
|
+
].join("|"), "i");
|
|
14
|
+
export const TEXT_EXTENSIONS = new Set([
|
|
15
|
+
".css",
|
|
16
|
+
".d.ts",
|
|
17
|
+
".html",
|
|
18
|
+
".js",
|
|
19
|
+
".json",
|
|
20
|
+
".md",
|
|
21
|
+
".svg",
|
|
22
|
+
".txt",
|
|
23
|
+
]);
|
|
24
|
+
export const FORBIDDEN_PATH_PATTERNS = [
|
|
25
|
+
{ re: /(^|\/)(tasks|node_modules|\.git)(\/|$)/, reason: "task, node_modules, or git metadata" },
|
|
26
|
+
{ re: /(^|\/)\.env($|\.)/, reason: "environment files" },
|
|
27
|
+
{ re: /(\.test|\.itest)\.(js|d\.ts)$/i, reason: "compiled test artifacts" },
|
|
28
|
+
{ re: /(^|\/)(id_rsa|id_ed25519|.*\.pem|.*\.key)$/i, reason: "private key material" },
|
|
29
|
+
{ re: /(^|\/)Users\/|\/Users\//i, reason: "absolute local user paths" },
|
|
30
|
+
{ re: privateIdentityPathPattern(), reason: "private identity references" },
|
|
31
|
+
];
|
|
32
|
+
export const FORBIDDEN_CONTENT_PATTERNS = [
|
|
33
|
+
{ re: /gh[pousr]_[A-Za-z0-9_]{20,}/, reason: "GitHub token" },
|
|
34
|
+
{ re: /github_pat_[A-Za-z0-9_]{20,}/, reason: "GitHub fine-grained token" },
|
|
35
|
+
{ re: /sk-[A-Za-z0-9]{20,}/, reason: "OpenAI-style API key" },
|
|
36
|
+
{ re: /AKIA[0-9A-Z]{16}/, reason: "AWS access key id" },
|
|
37
|
+
{ re: /AIza[0-9A-Za-z_-]{35}/, reason: "Google API key" },
|
|
38
|
+
{ re: /xox[baprs]-[0-9A-Za-z-]{20,}/, reason: "Slack token" },
|
|
39
|
+
{ re: /-----BEGIN [A-Z ]*PRIVATE KEY-----/, reason: "private key block" },
|
|
40
|
+
{ re: /\/Users\/saber\b/i, reason: "local user path" },
|
|
41
|
+
{ re: privateIdentityContentPattern(), reason: "private identity reference" },
|
|
42
|
+
];
|
|
43
|
+
export function normalizePackPath(file) {
|
|
44
|
+
return file.replace(/^package\//, "");
|
|
45
|
+
}
|
|
46
|
+
export function findForbiddenPath(file, patterns = FORBIDDEN_PATH_PATTERNS) {
|
|
47
|
+
return patterns.find(({ re }) => re.test(file)) ?? null;
|
|
48
|
+
}
|
|
49
|
+
export function findForbiddenContent(content, patterns = FORBIDDEN_CONTENT_PATTERNS) {
|
|
50
|
+
return patterns.find(({ re }) => re.test(content)) ?? null;
|
|
51
|
+
}
|
|
52
|
+
export function assertPackageContents(pkg, files, patterns = FORBIDDEN_PATH_PATTERNS) {
|
|
53
|
+
const fileSet = new Set(files);
|
|
54
|
+
for (const requiredPath of pkg.requiredPaths) {
|
|
55
|
+
if (!fileSet.has(requiredPath)) {
|
|
56
|
+
throw new Error(`${pkg.name} package is missing required file: ${requiredPath}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
for (const file of files) {
|
|
60
|
+
const forbidden = findForbiddenPath(file, patterns);
|
|
61
|
+
if (forbidden) {
|
|
62
|
+
throw new Error(`${pkg.name} package includes forbidden path (${forbidden.reason}): ${file}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export function shouldScanTextByMetadata(file, size, textExtensions = TEXT_EXTENSIONS) {
|
|
67
|
+
if (size > 2_000_000)
|
|
68
|
+
return false;
|
|
69
|
+
const lower = file.toLowerCase();
|
|
70
|
+
if (lower.endsWith(".d.ts"))
|
|
71
|
+
return true;
|
|
72
|
+
return textExtensions.has(path.extname(lower));
|
|
73
|
+
}
|
|
74
|
+
export function shouldScanText(file) {
|
|
75
|
+
return shouldScanTextByMetadata(file, fs.statSync(file).size);
|
|
76
|
+
}
|
|
77
|
+
export function assertSafeTextContent(pkgName, relPath, content) {
|
|
78
|
+
const forbidden = findForbiddenContent(content);
|
|
79
|
+
if (forbidden) {
|
|
80
|
+
throw new Error(`${pkgName} package includes forbidden content (${forbidden.reason}) in ${relPath}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=package-verifier.js.map
|
package/dist/prompts/brief.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { type PromptDescriptor, type RenderedPrompt } from "./index.js";
|
|
8
8
|
export declare const BRIEF_PROMPT_DESCRIPTOR: PromptDescriptor;
|
|
9
|
-
declare const ALLOWED_HEADINGS: readonly ["Engineering", "Product", "
|
|
9
|
+
declare const ALLOWED_HEADINGS: readonly ["Engineering", "Product", "System"];
|
|
10
10
|
export interface BriefInput {
|
|
11
11
|
/** ISO date string injected into the brief header. */
|
|
12
12
|
generatedAt: string;
|
|
@@ -14,6 +14,6 @@ export interface BriefInput {
|
|
|
14
14
|
sections: Partial<Record<(typeof ALLOWED_HEADINGS)[number], string[]>>;
|
|
15
15
|
}
|
|
16
16
|
export declare const briefPrompt: (input: BriefInput) => RenderedPrompt;
|
|
17
|
-
export declare const ALLOWED_BRIEF_HEADINGS: readonly ["Engineering", "Product", "
|
|
17
|
+
export declare const ALLOWED_BRIEF_HEADINGS: readonly ["Engineering", "Product", "System"];
|
|
18
18
|
export {};
|
|
19
19
|
//# sourceMappingURL=brief.d.ts.map
|
package/dist/prompts/brief.js
CHANGED
|
@@ -12,13 +12,18 @@ export const EXTRACTION_PROMPT_DESCRIPTOR = {
|
|
|
12
12
|
const SUBTYPE_REASONING = `## Subtype reasoning (think step-by-step BEFORE picking memory_subtype)
|
|
13
13
|
For each candidate fact, walk these four top-level categories first, then pick the most concrete memory_subtype:
|
|
14
14
|
|
|
15
|
-
ENGINEERING — how the software is built and operated:
|
|
15
|
+
ENGINEERING — how the software is built and operated, including collaboration preferences:
|
|
16
16
|
codebase_map → file paths, symbols, modules, repo structure
|
|
17
17
|
architecture_decision → engineering/system design choice with rationale
|
|
18
18
|
infra_topology → clusters, services, queues, datastores, regions
|
|
19
19
|
access_pattern → roles, permissions, auth flows, approval gates
|
|
20
20
|
operational_procedure → runbook / deploy / rollout / maintenance / incident steps
|
|
21
21
|
troubleshooting_gotcha → stable failure mode, debugging quirk, surprising behaviour
|
|
22
|
+
preference → explicit style, tooling, or interaction preference
|
|
23
|
+
person_profile → durable role, expertise, person/team context
|
|
24
|
+
ownership_note → owner, approver, escalation path, accountability
|
|
25
|
+
working_agreement → collaboration norm or operating rule
|
|
26
|
+
activity_event → explicit actor-action-object event with durable project value
|
|
22
27
|
convention → produced by distillation, not this extraction prompt
|
|
23
28
|
|
|
24
29
|
PRODUCT — what the product should be and how it succeeds:
|
|
@@ -30,13 +35,6 @@ For each candidate fact, walk these four top-level categories first, then pick t
|
|
|
30
35
|
success_metric → KPI, adoption/retention/quality metric, evaluation target
|
|
31
36
|
market_insight → audience, customer/community feedback, competitor/GTM insight
|
|
32
37
|
|
|
33
|
-
HUMAN — durable people, preferences, and collaboration context:
|
|
34
|
-
preference → explicit style, tooling, or interaction preference
|
|
35
|
-
person_profile → durable role, expertise, person/team context
|
|
36
|
-
ownership_note → owner, approver, escalation path, accountability
|
|
37
|
-
working_agreement → collaboration norm or operating rule
|
|
38
|
-
activity_event → explicit actor-action-object event with durable project value
|
|
39
|
-
|
|
40
38
|
Disambiguation rules (apply in order; first match wins):
|
|
41
39
|
R1. If the fact describes a *failure* or *workaround* → troubleshooting_gotcha (NOT operational_procedure).
|
|
42
40
|
R2. If the fact names *files / modules / symbols* → codebase_map (NOT infra_topology).
|
|
@@ -62,7 +60,7 @@ Example 2 — codebase vs infra:
|
|
|
62
60
|
Example 3 — domain_rule vs preference:
|
|
63
61
|
TEXT: "I prefer kebab-case branch names; it's just my style."
|
|
64
62
|
→ memory_subtype: preference (R6: 'I prefer' + 'my style')
|
|
65
|
-
→ subtype_reason: "Personal style →
|
|
63
|
+
→ subtype_reason: "Personal style → Engineering → R6 wins → preference."
|
|
66
64
|
|
|
67
65
|
Example 4 — product vs engineering decision:
|
|
68
66
|
TEXT: "We decided the memory page should show categories and subtype chips directly because a sub-tab layer made the ontology feel confusing."
|
|
@@ -72,7 +70,7 @@ Example 4 — product vs engineering decision:
|
|
|
72
70
|
Example 5 — durable activity event:
|
|
73
71
|
TEXT: "Saber merged PR #85 after approving the subtype UX copy changes."
|
|
74
72
|
→ memory_subtype: activity_event (R7: actor + durable state-changing action + object)
|
|
75
|
-
→ subtype_reason: "Actor-action-object event tied to a PR →
|
|
73
|
+
→ subtype_reason: "Actor-action-object event tied to a PR → Engineering → R7 wins → activity_event."`;
|
|
76
74
|
const SELF_JUDGMENT = `## Self-judgment (think step-by-step BEFORE emitting each fact)
|
|
77
75
|
|
|
78
76
|
For every candidate fact, judge it on three axes. The verifier downstream USES these values, so be honest — bad self-judgment costs the fact its place in memory.
|
|
@@ -218,7 +216,7 @@ If nothing passes the quality gate, return: {"facts": []}`;
|
|
|
218
216
|
const buildSystem = () => {
|
|
219
217
|
return [
|
|
220
218
|
"<role>",
|
|
221
|
-
"You are Bikky's knowledge-extraction agent for open-source coding agents. You read session transcripts and emit durable Engineering, Product,
|
|
219
|
+
"You are Bikky's knowledge-extraction agent for open-source coding agents. You read session transcripts and emit durable Engineering, Product, and System-aligned facts that help a future agent continue useful work.",
|
|
222
220
|
"</role>",
|
|
223
221
|
"",
|
|
224
222
|
"<task>",
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { BikkyConfig } from "../config.js";
|
|
2
|
+
export type OriginIdentityType = "user" | "coding_agent" | "daemon" | "ui" | "api" | "cli" | "docs" | "system" | "unknown";
|
|
3
|
+
export type OriginIdentitySource = "config" | "shell" | "git" | "env" | "hostname";
|
|
4
|
+
export type OriginInterface = "mcp" | "daemon" | "ui" | "api" | "cli" | "system";
|
|
5
|
+
export type OriginAction = "create" | "update" | "delete" | "verify" | "forget" | "review" | "correct" | "reinforce" | "supersede" | "recall" | "aggregate" | "feedback";
|
|
6
|
+
export type OriginMetadataValue = string | number | boolean | null;
|
|
7
|
+
export interface OriginIdentity {
|
|
8
|
+
type: OriginIdentityType;
|
|
9
|
+
id: string | null;
|
|
10
|
+
name: string | null;
|
|
11
|
+
source: OriginIdentitySource;
|
|
12
|
+
}
|
|
13
|
+
export interface OperationOrigin {
|
|
14
|
+
schema_version: 1;
|
|
15
|
+
user: OriginIdentity | null;
|
|
16
|
+
agent: OriginIdentity;
|
|
17
|
+
interface: OriginInterface;
|
|
18
|
+
operation: {
|
|
19
|
+
action: OriginAction;
|
|
20
|
+
tool?: string;
|
|
21
|
+
route?: string;
|
|
22
|
+
subsystem?: string;
|
|
23
|
+
outcome?: string;
|
|
24
|
+
};
|
|
25
|
+
metadata?: Record<string, OriginMetadataValue>;
|
|
26
|
+
}
|
|
27
|
+
export interface ResolveUserIdentityOptions {
|
|
28
|
+
config?: Pick<BikkyConfig, "identity"> | null;
|
|
29
|
+
env?: NodeJS.ProcessEnv;
|
|
30
|
+
cwd?: string;
|
|
31
|
+
hostname?: string;
|
|
32
|
+
shellUsername?: string | null;
|
|
33
|
+
}
|
|
34
|
+
export interface ResolveAgentIdentityOptions {
|
|
35
|
+
type?: OriginIdentityType;
|
|
36
|
+
interface?: OriginInterface;
|
|
37
|
+
env?: NodeJS.ProcessEnv;
|
|
38
|
+
hostname?: string;
|
|
39
|
+
}
|
|
40
|
+
export interface BuildOperationOriginInput extends ResolveUserIdentityOptions {
|
|
41
|
+
interface: OriginInterface;
|
|
42
|
+
action: OriginAction;
|
|
43
|
+
tool?: string;
|
|
44
|
+
route?: string;
|
|
45
|
+
subsystem?: string;
|
|
46
|
+
outcome?: string;
|
|
47
|
+
agentType?: OriginIdentityType;
|
|
48
|
+
metadata?: Record<string, unknown>;
|
|
49
|
+
}
|
|
50
|
+
export declare function normalizeOriginId(value: string | null | undefined): string | null;
|
|
51
|
+
export declare function resolveUserIdentity(options?: ResolveUserIdentityOptions): OriginIdentity;
|
|
52
|
+
export declare function inferUserIdentity(options?: Omit<ResolveUserIdentityOptions, "config">): OriginIdentity;
|
|
53
|
+
export declare function resolveAgentIdentity(options?: ResolveAgentIdentityOptions): OriginIdentity;
|
|
54
|
+
export declare function sanitizeOriginMetadata(metadata: Record<string, unknown> | undefined): Record<string, OriginMetadataValue> | undefined;
|
|
55
|
+
export declare function buildOperationOrigin(input: BuildOperationOriginInput): OperationOrigin;
|
|
56
|
+
export declare function isOperationOrigin(value: unknown): value is OperationOrigin;
|
|
57
|
+
//# sourceMappingURL=origin.d.ts.map
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
const MAX_NAME_LENGTH = 128;
|
|
5
|
+
const MAX_OPERATION_FIELD_LENGTH = 128;
|
|
6
|
+
const MAX_METADATA_KEYS = 32;
|
|
7
|
+
const MAX_METADATA_KEY_LENGTH = 64;
|
|
8
|
+
const MAX_METADATA_STRING_LENGTH = 256;
|
|
9
|
+
const hash = (value) => crypto.createHash("sha256").update(value.trim().toLowerCase()).digest("hex").slice(0, 12);
|
|
10
|
+
const slug = (value) => value.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
|
|
11
|
+
const looksLikeEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim());
|
|
12
|
+
const cleanName = (value) => {
|
|
13
|
+
const trimmed = value?.trim();
|
|
14
|
+
if (!trimmed || looksLikeEmail(trimmed))
|
|
15
|
+
return null;
|
|
16
|
+
return trimmed.slice(0, MAX_NAME_LENGTH);
|
|
17
|
+
};
|
|
18
|
+
const cleanOperationField = (value) => {
|
|
19
|
+
const trimmed = value?.trim();
|
|
20
|
+
return trimmed ? trimmed.slice(0, MAX_OPERATION_FIELD_LENGTH) : undefined;
|
|
21
|
+
};
|
|
22
|
+
export function normalizeOriginId(value) {
|
|
23
|
+
const trimmed = value?.trim();
|
|
24
|
+
if (!trimmed)
|
|
25
|
+
return null;
|
|
26
|
+
if (looksLikeEmail(trimmed))
|
|
27
|
+
return `email:${hash(trimmed)}`;
|
|
28
|
+
const normalized = slug(trimmed);
|
|
29
|
+
return normalized || `id:${hash(trimmed)}`;
|
|
30
|
+
}
|
|
31
|
+
function generatedId(prefix, value) {
|
|
32
|
+
const trimmed = value?.trim();
|
|
33
|
+
if (!trimmed)
|
|
34
|
+
return null;
|
|
35
|
+
if (looksLikeEmail(trimmed))
|
|
36
|
+
return `${prefix}:email:${hash(trimmed)}`;
|
|
37
|
+
const normalized = slug(trimmed);
|
|
38
|
+
return normalized ? `${prefix}:${normalized}` : `${prefix}:${hash(trimmed)}`;
|
|
39
|
+
}
|
|
40
|
+
function configuredIdentity(type, rawId, rawName, source) {
|
|
41
|
+
const name = cleanName(rawName);
|
|
42
|
+
const id = normalizeOriginId(rawId) ?? (name ? generatedId(type === "user" ? "user" : type, name) : null);
|
|
43
|
+
if (!id && !name)
|
|
44
|
+
return null;
|
|
45
|
+
return { type, id, name, source };
|
|
46
|
+
}
|
|
47
|
+
function readGitConfig(key, cwd) {
|
|
48
|
+
try {
|
|
49
|
+
const value = execFileSync("git", ["config", "--get", key], {
|
|
50
|
+
cwd,
|
|
51
|
+
encoding: "utf8",
|
|
52
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
53
|
+
timeout: 1000,
|
|
54
|
+
}).trim();
|
|
55
|
+
return value || undefined;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function gitUserIdentity(cwd) {
|
|
62
|
+
const gitName = readGitConfig("user.name", cwd);
|
|
63
|
+
const gitEmail = readGitConfig("user.email", cwd);
|
|
64
|
+
const nameSlug = slug(gitName ?? "");
|
|
65
|
+
if (gitEmail) {
|
|
66
|
+
return {
|
|
67
|
+
type: "user",
|
|
68
|
+
id: nameSlug ? `git:${nameSlug}:${hash(gitEmail)}` : `git:${hash(gitEmail)}`,
|
|
69
|
+
name: cleanName(gitName),
|
|
70
|
+
source: "git",
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (nameSlug) {
|
|
74
|
+
return {
|
|
75
|
+
type: "user",
|
|
76
|
+
id: `git:${nameSlug}`,
|
|
77
|
+
name: cleanName(gitName),
|
|
78
|
+
source: "git",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
function shellUserName(options) {
|
|
84
|
+
if (Object.prototype.hasOwnProperty.call(options, "shellUsername") && options.shellUsername === null) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const explicit = cleanName(options.shellUsername);
|
|
88
|
+
if (explicit)
|
|
89
|
+
return explicit;
|
|
90
|
+
try {
|
|
91
|
+
const user = os.userInfo().username;
|
|
92
|
+
const cleaned = cleanName(user);
|
|
93
|
+
if (cleaned)
|
|
94
|
+
return cleaned;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// ignore and fall through to env-derived shell names
|
|
98
|
+
}
|
|
99
|
+
const env = options.env ?? process.env;
|
|
100
|
+
return cleanName(env.USER ?? env.LOGNAME ?? env.USERNAME);
|
|
101
|
+
}
|
|
102
|
+
function hostnameValue(hostname) {
|
|
103
|
+
const explicit = hostname?.trim();
|
|
104
|
+
if (explicit)
|
|
105
|
+
return explicit;
|
|
106
|
+
const detected = os.hostname().trim();
|
|
107
|
+
return detected || "unknown-host";
|
|
108
|
+
}
|
|
109
|
+
function hostnameIdentity(type, hostname, prefix) {
|
|
110
|
+
const host = hostnameValue(hostname);
|
|
111
|
+
return {
|
|
112
|
+
type,
|
|
113
|
+
id: generatedId(prefix ?? "host", host),
|
|
114
|
+
name: cleanName(host),
|
|
115
|
+
source: "hostname",
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
export function resolveUserIdentity(options = {}) {
|
|
119
|
+
const env = options.env ?? process.env;
|
|
120
|
+
const fromConfig = configuredIdentity("user", options.config?.identity.user_id, options.config?.identity.user_name, "config");
|
|
121
|
+
if (fromConfig)
|
|
122
|
+
return fromConfig;
|
|
123
|
+
const fromEnv = configuredIdentity("user", env.BIKKY_USER_ID, env.BIKKY_USER_NAME, "env");
|
|
124
|
+
if (fromEnv)
|
|
125
|
+
return fromEnv;
|
|
126
|
+
const fromGit = gitUserIdentity(options.cwd);
|
|
127
|
+
if (fromGit)
|
|
128
|
+
return fromGit;
|
|
129
|
+
const shellName = shellUserName(options);
|
|
130
|
+
if (shellName) {
|
|
131
|
+
return {
|
|
132
|
+
type: "user",
|
|
133
|
+
id: generatedId("shell", shellName),
|
|
134
|
+
name: shellName,
|
|
135
|
+
source: "shell",
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return hostnameIdentity("user", options.hostname);
|
|
139
|
+
}
|
|
140
|
+
export function inferUserIdentity(options = {}) {
|
|
141
|
+
return resolveUserIdentity({ ...options, config: null });
|
|
142
|
+
}
|
|
143
|
+
function defaultAgentType(originInterface) {
|
|
144
|
+
switch (originInterface) {
|
|
145
|
+
case "mcp":
|
|
146
|
+
return "coding_agent";
|
|
147
|
+
case "daemon":
|
|
148
|
+
return "daemon";
|
|
149
|
+
case "ui":
|
|
150
|
+
return "ui";
|
|
151
|
+
case "api":
|
|
152
|
+
return "api";
|
|
153
|
+
case "cli":
|
|
154
|
+
return "cli";
|
|
155
|
+
case "system":
|
|
156
|
+
return "system";
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function defaultAgentName(type, originInterface) {
|
|
160
|
+
if (type === "coding_agent" && originInterface === "mcp")
|
|
161
|
+
return "Bikky MCP client";
|
|
162
|
+
if (type === "daemon")
|
|
163
|
+
return "Bikky daemon";
|
|
164
|
+
if (type === "ui")
|
|
165
|
+
return "Bikky UI";
|
|
166
|
+
if (type === "api")
|
|
167
|
+
return "Bikky API";
|
|
168
|
+
if (type === "cli")
|
|
169
|
+
return "Bikky CLI";
|
|
170
|
+
if (type === "system")
|
|
171
|
+
return "Bikky system";
|
|
172
|
+
if (type === "docs")
|
|
173
|
+
return "Bikky docs importer";
|
|
174
|
+
return "Bikky";
|
|
175
|
+
}
|
|
176
|
+
export function resolveAgentIdentity(options = {}) {
|
|
177
|
+
const env = options.env ?? process.env;
|
|
178
|
+
const type = options.type ?? (options.interface ? defaultAgentType(options.interface) : "unknown");
|
|
179
|
+
const envIdentity = configuredIdentity(type, env.BIKKY_AGENT_ID, env.BIKKY_AGENT_NAME, "env");
|
|
180
|
+
if (envIdentity)
|
|
181
|
+
return envIdentity;
|
|
182
|
+
const host = hostnameValue(options.hostname);
|
|
183
|
+
return {
|
|
184
|
+
type,
|
|
185
|
+
id: generatedId(type, host),
|
|
186
|
+
name: cleanName(`${defaultAgentName(type, options.interface)} on ${host}`),
|
|
187
|
+
source: "hostname",
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
export function sanitizeOriginMetadata(metadata) {
|
|
191
|
+
if (!metadata)
|
|
192
|
+
return undefined;
|
|
193
|
+
const sanitized = {};
|
|
194
|
+
for (const [rawKey, rawValue] of Object.entries(metadata).slice(0, MAX_METADATA_KEYS)) {
|
|
195
|
+
const key = rawKey.trim().replace(/[^a-zA-Z0-9_.-]+/g, "_").slice(0, MAX_METADATA_KEY_LENGTH);
|
|
196
|
+
if (!key)
|
|
197
|
+
continue;
|
|
198
|
+
if (rawValue === null || typeof rawValue === "boolean") {
|
|
199
|
+
sanitized[key] = rawValue;
|
|
200
|
+
}
|
|
201
|
+
else if (typeof rawValue === "number") {
|
|
202
|
+
if (Number.isFinite(rawValue))
|
|
203
|
+
sanitized[key] = rawValue;
|
|
204
|
+
}
|
|
205
|
+
else if (typeof rawValue === "string") {
|
|
206
|
+
sanitized[key] = rawValue.slice(0, MAX_METADATA_STRING_LENGTH);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return Object.keys(sanitized).length > 0 ? sanitized : undefined;
|
|
210
|
+
}
|
|
211
|
+
export function buildOperationOrigin(input) {
|
|
212
|
+
const operation = {
|
|
213
|
+
action: input.action,
|
|
214
|
+
};
|
|
215
|
+
const tool = cleanOperationField(input.tool);
|
|
216
|
+
const route = cleanOperationField(input.route);
|
|
217
|
+
const subsystem = cleanOperationField(input.subsystem);
|
|
218
|
+
const outcome = cleanOperationField(input.outcome);
|
|
219
|
+
if (tool)
|
|
220
|
+
operation.tool = tool;
|
|
221
|
+
if (route)
|
|
222
|
+
operation.route = route;
|
|
223
|
+
if (subsystem)
|
|
224
|
+
operation.subsystem = subsystem;
|
|
225
|
+
if (outcome)
|
|
226
|
+
operation.outcome = outcome;
|
|
227
|
+
const origin = {
|
|
228
|
+
schema_version: 1,
|
|
229
|
+
user: resolveUserIdentity(input),
|
|
230
|
+
agent: resolveAgentIdentity({
|
|
231
|
+
type: input.agentType,
|
|
232
|
+
interface: input.interface,
|
|
233
|
+
env: input.env,
|
|
234
|
+
hostname: input.hostname,
|
|
235
|
+
}),
|
|
236
|
+
interface: input.interface,
|
|
237
|
+
operation,
|
|
238
|
+
};
|
|
239
|
+
const metadata = sanitizeOriginMetadata(input.metadata);
|
|
240
|
+
if (metadata)
|
|
241
|
+
origin.metadata = metadata;
|
|
242
|
+
return origin;
|
|
243
|
+
}
|
|
244
|
+
export function isOperationOrigin(value) {
|
|
245
|
+
if (!value || typeof value !== "object")
|
|
246
|
+
return false;
|
|
247
|
+
const candidate = value;
|
|
248
|
+
return candidate.schema_version === 1
|
|
249
|
+
&& Boolean(candidate.agent)
|
|
250
|
+
&& typeof candidate.interface === "string"
|
|
251
|
+
&& Boolean(candidate.operation)
|
|
252
|
+
&& typeof candidate.operation?.action === "string";
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=origin.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { RoutingInput } from "./routing.js";
|
|
2
|
+
type RoutingMetadata = Record<string, unknown>;
|
|
3
|
+
export interface MemoryRoutingInput {
|
|
4
|
+
destination?: string | null;
|
|
5
|
+
cwd?: string;
|
|
6
|
+
content?: string | null;
|
|
7
|
+
entities?: ReadonlyArray<string>;
|
|
8
|
+
metadata?: RoutingMetadata | null;
|
|
9
|
+
context?: RoutingMetadata | null;
|
|
10
|
+
extraContent?: ReadonlyArray<unknown>;
|
|
11
|
+
}
|
|
12
|
+
export declare const routingText: (values: ReadonlyArray<unknown>) => string;
|
|
13
|
+
export declare const buildMemoryRoutingInput: (input: MemoryRoutingInput) => RoutingInput;
|
|
14
|
+
export declare const mergeRoutingInputs: (base: RoutingInput, override?: RoutingInput) => RoutingInput;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=routing-context.d.ts.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const appendRoutingText = (parts, value) => {
|
|
2
|
+
if (value === null || value === undefined)
|
|
3
|
+
return;
|
|
4
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
5
|
+
const text = String(value).trim();
|
|
6
|
+
if (text)
|
|
7
|
+
parts.push(text);
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
if (Array.isArray(value)) {
|
|
11
|
+
for (const item of value)
|
|
12
|
+
appendRoutingText(parts, item);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (typeof value === "object") {
|
|
16
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
17
|
+
appendRoutingText(parts, key);
|
|
18
|
+
appendRoutingText(parts, nested);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
export const routingText = (values) => {
|
|
23
|
+
const parts = [];
|
|
24
|
+
for (const value of values)
|
|
25
|
+
appendRoutingText(parts, value);
|
|
26
|
+
return Array.from(new Set(parts)).join("\n");
|
|
27
|
+
};
|
|
28
|
+
export const buildMemoryRoutingInput = (input) => {
|
|
29
|
+
const metadata = {
|
|
30
|
+
...(input.metadata ?? {}),
|
|
31
|
+
...(input.context ?? {}),
|
|
32
|
+
};
|
|
33
|
+
return {
|
|
34
|
+
destination: input.destination,
|
|
35
|
+
cwd: input.cwd,
|
|
36
|
+
content: routingText([input.content, input.entities, metadata, ...(input.extraContent ?? [])]),
|
|
37
|
+
entities: input.entities,
|
|
38
|
+
metadata,
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
export const mergeRoutingInputs = (base, override) => {
|
|
42
|
+
if (!override)
|
|
43
|
+
return base;
|
|
44
|
+
return {
|
|
45
|
+
destination: override.destination ?? base.destination,
|
|
46
|
+
cwd: override.cwd ?? base.cwd,
|
|
47
|
+
content: routingText([base.content, override.content]),
|
|
48
|
+
entities: Array.from(new Set([...(base.entities ?? []), ...(override.entities ?? [])])),
|
|
49
|
+
metadata: {
|
|
50
|
+
...(base.metadata ?? {}),
|
|
51
|
+
...(override.metadata ?? {}),
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=routing-context.js.map
|
package/dist/status.d.ts
CHANGED
|
@@ -58,6 +58,7 @@ export interface MaintenanceStatusReport {
|
|
|
58
58
|
state_path: string;
|
|
59
59
|
relation_inference: MaintenanceJobStatus;
|
|
60
60
|
entity_typing: MaintenanceJobStatus;
|
|
61
|
+
memory_quality_rollups: MaintenanceJobStatus;
|
|
61
62
|
}
|
|
62
63
|
export interface UiStatusReport {
|
|
63
64
|
status: DiagnosticState;
|