fuli-context 0.4.0 → 0.7.2
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.en.md +5 -0
- package/README.md +411 -173
- package/README.zh-CN.md +448 -0
- package/compose.graphiti.yml +6 -6
- package/dist/web/assets/AboutPage-DXh7Fwxt.css +1 -0
- package/dist/web/assets/AboutPage-DfnZo7Lo.js +1 -0
- package/dist/web/assets/ConnectionsPage-BloaSKmt.js +1 -0
- package/dist/web/assets/ConnectionsPage-CBHAdZXT.css +1 -0
- package/dist/web/assets/KnowledgeInspector-Bjhdu2mf.css +1 -0
- package/dist/web/assets/KnowledgeInspector-QvNpv0_k.js +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-Cx1w1jb2.css +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-DKq1jVD5.js +1 -0
- package/dist/web/assets/KnowledgePage-BWL0jT01.js +1 -0
- package/dist/web/assets/KnowledgeWorkspace-Czq6aZ7b.js +4 -0
- package/dist/web/assets/OverviewPage-5sJoO1Gh.js +1 -0
- package/dist/web/assets/{PersonalProfilePage-S8ZcG2LU.css → PersonalProfilePage-CdmEzyvZ.css} +1 -1
- package/dist/web/assets/PersonalProfilePage-tVChM9ik.js +1 -0
- package/dist/web/assets/PersonalProjectsPage-BPFrup4V.js +1 -0
- package/dist/web/assets/PublicProjectsPage-BPJSeREN.js +1 -0
- package/dist/web/assets/ReviewPage-B7EN817i.js +1 -0
- package/dist/web/assets/SearchableSelect-ClLN0rIb.js +1 -0
- package/dist/web/assets/SettingsPage-C6ouU0sR.css +1 -0
- package/dist/web/assets/SettingsPage-Chd_B3eH.js +1 -0
- package/dist/web/assets/_plugin-vue_export-helper-BDNMzG2s.js +1 -0
- package/dist/web/assets/console-CbDsKrsO.js +1 -0
- package/dist/web/assets/fuli-logo-Bu9WBuQe.js +1 -0
- package/dist/web/assets/fuli-logo-ER1wQ8Kk.png +0 -0
- package/dist/web/assets/i18n-BQcns1PH.js +5 -0
- package/dist/web/assets/identity-B8K4VYg6.js +1 -0
- package/dist/web/assets/index-CB8Xmsc2.js +2 -0
- package/dist/web/assets/index-CZeuPh-U.css +1 -0
- package/dist/web/assets/model-eF52GgPF.js +1 -0
- package/dist/web/assets/paths-hwz-Ac1g.js +1 -0
- package/dist/web/assets/useMinimumLoadingDisplay-BpCyBeko.css +1 -0
- package/dist/web/assets/useMinimumLoadingDisplay-wCgGDmHg.js +1 -0
- package/dist/web/assets/vue-router-BsEp_XwI.js +1 -0
- package/dist/web/index.html +9 -6
- package/docs/external-knowledge-architecture.md +279 -0
- package/examples/external-knowledge/markdown-folder.mjs +235 -0
- package/graph-provider/fuli_graph/app.py +57 -1
- package/graph-provider/fuli_graph/graph_models.py +3 -0
- package/graph-provider/fuli_graph/graph_query.py +62 -16
- package/graph-provider/fuli_graph/knowledge_review.py +612 -0
- package/graph-provider/fuli_graph/knowledge_review_models.py +127 -0
- package/graph-provider/fuli_graph/knowledge_search.py +51 -4
- package/graph-provider/fuli_graph/models.py +19 -0
- package/graph-provider/fuli_graph/runtime.py +15 -0
- package/graph-provider/fuli_graph/store_knowledge.py +2 -1
- package/graph-provider/pyproject.toml +1 -1
- package/npm-shrinkwrap.json +5 -407
- package/package.json +7 -4
- package/skills/capturing-session-knowledge/SKILL.md +24 -4
- package/skills/flreview/SKILL.md +73 -0
- package/skills/flreview/agents/openai.yaml +6 -0
- package/src/agent-tools/graph-definitions.js +79 -4
- package/src/agent-tools/graph-handlers.js +12 -0
- package/src/cli/command-registry.js +3 -3
- package/src/cli/local-runtime-command.js +60 -23
- package/src/cli/local-runtime-options.js +16 -6
- package/src/cli/setup-agent-selection.js +12 -12
- package/src/cli/setup-command.js +41 -18
- package/src/cli/shell-argument.js +10 -0
- package/src/cli/uninstall-command.js +17 -15
- package/src/cli/update-command.js +38 -30
- package/src/external-knowledge/catalog.js +61 -0
- package/src/external-knowledge/conflict-policy.js +58 -0
- package/src/external-knowledge/connected-search.js +166 -0
- package/src/external-knowledge/connectors/custom.js +133 -0
- package/src/external-knowledge/connectors/feishu.js +461 -0
- package/src/external-knowledge/connectors/mcp.js +438 -0
- package/src/external-knowledge/connectors/notion.js +307 -0
- package/src/external-knowledge/connectors/retrieval-api.js +222 -0
- package/src/external-knowledge/document-mapping.js +151 -0
- package/src/external-knowledge/graph-projection.js +17 -0
- package/src/external-knowledge/index.js +8 -0
- package/src/external-knowledge/registry.js +91 -0
- package/src/external-knowledge/runtime.js +68 -0
- package/src/external-knowledge/safe-diagnostic.js +14 -0
- package/src/external-knowledge/service.js +811 -0
- package/src/graphiti/agent-knowledge-workflows.js +7 -2
- package/src/graphiti/federated-application.js +66 -41
- package/src/graphiti/knowledge-item-role.js +21 -0
- package/src/graphiti/knowledge-review.js +43 -0
- package/src/graphiti/project-profile-mapping.js +32 -0
- package/src/graphiti/provider-client.js +22 -1
- package/src/graphiti/source-marker.js +54 -3
- package/src/graphiti/task-knowledge-recall.js +250 -0
- package/src/http/api-router.js +23 -108
- package/src/http/external-knowledge-api-router.js +104 -0
- package/src/http/graph-api-router.js +12 -1
- package/src/http/request-policy.js +38 -3
- package/src/local-runtime/lifecycle.js +37 -16
- package/src/mcp/create-mcp-server.js +1 -0
- package/src/mcp/instructions.js +7 -7
- package/src/mcp/tool-annotations.js +6 -0
- package/src/mcp/tool-result.js +1 -0
- package/src/server/application-lifecycle.js +2 -11
- package/src/server/blocked-ports.js +11 -0
- package/src/server/lan-access.js +52 -0
- package/src/server.js +103 -25
- package/src/setup/agent-installation-status.js +2 -1
- package/src/setup/agents.js +2 -1
- package/src/setup/claude-code-config.js +2 -1
- package/src/setup/codex-bootstrap.js +12 -7
- package/src/setup/container-runtime.js +25 -21
- package/src/setup/graph-runtime.js +219 -58
- package/src/setup/node-runtime.js +3 -3
- package/src/setup/options.js +2 -4
- package/src/setup/paths.js +18 -3
- package/src/setup/review-skill-adapters.js +49 -0
- package/src/setup/setup.js +26 -2
- package/src/setup/uninstall.js +4 -2
- package/src/system/resource-monitor.js +317 -0
- package/src/system/runtime-settings.js +131 -0
- package/src/system/system-service.js +119 -0
- package/dist/web/assets/ConnectionsPage-CCKWm-6p.js +0 -1
- package/dist/web/assets/KnowledgeInspector-BZkB2wJn.js +0 -1
- package/dist/web/assets/KnowledgeInspector-CrGYxEob.css +0 -1
- package/dist/web/assets/KnowledgeOrganizerPage-CJCNI2Wd.css +0 -1
- package/dist/web/assets/KnowledgeOrganizerPage-PXH-TlGt.js +0 -1
- package/dist/web/assets/KnowledgePage-CNQXd15y.js +0 -1
- package/dist/web/assets/KnowledgeWorkspace-CXPE4GCD.js +0 -4
- package/dist/web/assets/OverviewPage-ul7g7HXh.js +0 -1
- package/dist/web/assets/PersonalProfilePage-CC40Pw2L.js +0 -1
- package/dist/web/assets/PersonalProjectsPage-CPIEcShD.js +0 -1
- package/dist/web/assets/PublicProjectsPage-C700sR7z.js +0 -1
- package/dist/web/assets/ReviewPage-7MDDUDgA.js +0 -1
- package/dist/web/assets/console-B_Ena2DS.js +0 -5
- package/dist/web/assets/fuli-logo-DGuyqchQ.png +0 -0
- package/dist/web/assets/identity-CjBwYSMX.js +0 -1
- package/dist/web/assets/index-BQRkOwPx.js +0 -2
- package/dist/web/assets/index-CpLDx3of.css +0 -1
- package/dist/web/assets/model-C-5RVIJ2.js +0 -1
- package/dist/web/assets/paths-BkULqmGI.js +0 -1
- package/dist/web/assets/vue-router-DnD3hUm7.js +0 -1
- package/src/agent/candidate-read-service.js +0 -53
- package/src/agent/context-pack-service.js +0 -83
- package/src/agent/public-read-policy.js +0 -39
- package/src/agent/public-read-service.js +0 -69
- package/src/agent/read-projection.js +0 -107
- package/src/agent/search-service.js +0 -61
- package/src/agent/space-boundary.js +0 -33
- package/src/app/bootstrap.js +0 -8
- package/src/app/create-application.js +0 -143
- package/src/app/state-service.js +0 -38
- package/src/candidates.js +0 -78
- package/src/classifier.js +0 -63
- package/src/cli/command-arguments.js +0 -14
- package/src/context-pack.js +0 -206
- package/src/extractor.js +0 -77
- package/src/fact-history.js +0 -39
- package/src/facts/fact-service.js +0 -89
- package/src/http/bounded-json-array.js +0 -19
- package/src/http/lens-route.js +0 -50
- package/src/ingestion.js +0 -161
- package/src/lens/lens-correction-service.js +0 -81
- package/src/lens/lens-fact-writer.js +0 -85
- package/src/lens/lens-input.js +0 -62
- package/src/lens/lens-query-projection.js +0 -87
- package/src/lens/lens-query-ranking.js +0 -62
- package/src/lens/lens-query.js +0 -143
- package/src/lens/lens-resource-projection.js +0 -93
- package/src/lens/lens-resource-selection.js +0 -45
- package/src/lens/lens-resource-service.js +0 -103
- package/src/lens/lens-search-query.js +0 -122
- package/src/lens/lens-service.js +0 -183
- package/src/models.js +0 -44
- package/src/observer.js +0 -83
- package/src/project-rules.js +0 -33
- package/src/publication/outbox-service.js +0 -46
- package/src/publication/publication-envelope-schema.js +0 -75
- package/src/publication/publication-envelope.js +0 -37
- package/src/publication/publication-policy.js +0 -82
- package/src/publication/publication-service.js +0 -48
- package/src/router.js +0 -58
- package/src/runtime-options.js +0 -138
- package/src/security/episode-visibility.js +0 -14
- package/src/security/fact-visibility.js +0 -15
- package/src/security/source-metadata-policy.js +0 -8
- package/src/storage/episode-evidence-preview.js +0 -100
- package/src/storage/fact-search-order.js +0 -18
- package/src/storage/file/episode-evidence.js +0 -37
- package/src/storage/file/episode-index.js +0 -59
- package/src/storage/file/fact-search.js +0 -60
- package/src/storage/file-store.js +0 -437
- package/src/storage/migrate.js +0 -128
- package/src/storage/migrations/001-initial.sql +0 -83
- package/src/storage/migrations/002-storage-integrity.sql +0 -113
- package/src/storage/record-validation.js +0 -46
- package/src/storage/snapshot-defaults.js +0 -54
- package/src/storage/sqlite/candidates-repository.js +0 -84
- package/src/storage/sqlite/episode-evidence-repository.js +0 -154
- package/src/storage/sqlite/episode-privacy-sql.js +0 -33
- package/src/storage/sqlite/episodes-repository.js +0 -52
- package/src/storage/sqlite/fact-search-repository.js +0 -162
- package/src/storage/sqlite/facts-repository.js +0 -146
- package/src/storage/sqlite/imports-repository.js +0 -39
- package/src/storage/sqlite/mapper.js +0 -84
- package/src/storage/sqlite/open-database.js +0 -15
- package/src/storage/sqlite/outbox-repository.js +0 -103
- package/src/storage/sqlite/snapshot-repository.js +0 -69
- package/src/storage/sqlite/spaces-repository.js +0 -52
- package/src/storage/sqlite/subscriptions-repository.js +0 -54
- package/src/storage/sqlite-store.js +0 -159
- package/src/storage/stable-json.js +0 -24
- package/src/storage/store-port.js +0 -20
- package/src/store.js +0 -2
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { nowIso, FactScope, FactStatus, isCurrentFact } from '../models.js';
|
|
2
|
-
import { ApplicationError, ApplicationErrorCode } from '../app/application-error.js';
|
|
3
|
-
import { assertStorePort } from '../storage/store-port.js';
|
|
4
|
-
import { writePersonalFact } from './lens-fact-writer.js';
|
|
5
|
-
import { assertSafeLensTexts, requirePersonalSpace } from './lens-input.js';
|
|
6
|
-
|
|
7
|
-
const ACTIONS = new Set(['replace', 'reject', 'deprecate']);
|
|
8
|
-
const CORRECTABLE_STATUSES = new Set([
|
|
9
|
-
FactStatus.CONFIRMED,
|
|
10
|
-
FactStatus.OBSERVED,
|
|
11
|
-
FactStatus.SUGGESTED
|
|
12
|
-
]);
|
|
13
|
-
const IMMEDIATE = { mode: 'immediate' };
|
|
14
|
-
|
|
15
|
-
export class LensCorrectionService {
|
|
16
|
-
constructor(store) {
|
|
17
|
-
assertStorePort(store);
|
|
18
|
-
this.store = store;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
correctUserFact({ personalSpaceId, factId, action, value, sourceText, sourceKind = 'correction' }) {
|
|
22
|
-
if (!ACTIONS.has(action)) {
|
|
23
|
-
throw new ApplicationError(ApplicationErrorCode.VALIDATION, `Unknown correction action: ${action}`);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
requirePersonalSpace(this.store, personalSpaceId);
|
|
27
|
-
assertSafeLensTexts([sourceText]);
|
|
28
|
-
if (action === 'replace') assertSafeLensTexts([value]);
|
|
29
|
-
|
|
30
|
-
const original = this.#requireCurrentFact(personalSpaceId, factId);
|
|
31
|
-
if (action === 'replace') assertSafeLensTexts([original.subject, original.predicate]);
|
|
32
|
-
|
|
33
|
-
return this.store.transaction(() => {
|
|
34
|
-
requirePersonalSpace(this.store, personalSpaceId);
|
|
35
|
-
const current = this.#requireCurrentFact(personalSpaceId, factId);
|
|
36
|
-
assertSafeLensTexts([sourceText]);
|
|
37
|
-
if (action === 'replace') assertSafeLensTexts([current.subject, current.predicate, value]);
|
|
38
|
-
|
|
39
|
-
const episode = this.store.addEpisode(
|
|
40
|
-
personalSpaceId,
|
|
41
|
-
sourceKind,
|
|
42
|
-
sourceText,
|
|
43
|
-
null,
|
|
44
|
-
{ kind: 'lens_correction', factId: current.id, action }
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
if (action === 'replace') {
|
|
48
|
-
const fact = writePersonalFact(this.store, {
|
|
49
|
-
spaceId: personalSpaceId,
|
|
50
|
-
subject: current.subject,
|
|
51
|
-
predicate: current.predicate,
|
|
52
|
-
object: value,
|
|
53
|
-
sourceEpisodeId: episode.id,
|
|
54
|
-
status: FactStatus.CONFIRMED,
|
|
55
|
-
sensitivity: current.sensitivity,
|
|
56
|
-
confidence: current.confidence,
|
|
57
|
-
scope: FactScope.PERSONAL
|
|
58
|
-
});
|
|
59
|
-
this.store.invalidateFact(current.id, fact.id);
|
|
60
|
-
return { episode, fact, replacedFact: this.store.getFact(current.id) };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const fact = this.store.updateFact(current.id, {
|
|
64
|
-
status: action === 'reject' ? FactStatus.REJECTED : FactStatus.DEPRECATED,
|
|
65
|
-
invalidAt: nowIso()
|
|
66
|
-
});
|
|
67
|
-
return { episode, fact };
|
|
68
|
-
}, IMMEDIATE);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
#requireCurrentFact(personalSpaceId, factId) {
|
|
72
|
-
const fact = this.store.getFact(factId);
|
|
73
|
-
if (!fact || fact.spaceId !== personalSpaceId || fact.scope !== FactScope.PERSONAL) {
|
|
74
|
-
throw new ApplicationError(ApplicationErrorCode.NOT_FOUND, `Lens fact not found: ${factId}`);
|
|
75
|
-
}
|
|
76
|
-
if (!isCurrentFact(fact) || !CORRECTABLE_STATUSES.has(fact.status)) {
|
|
77
|
-
throw new ApplicationError(ApplicationErrorCode.VALIDATION, `Lens fact is not current: ${factId}`);
|
|
78
|
-
}
|
|
79
|
-
return fact;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { FactScope, FactStatus } from '../models.js';
|
|
2
|
-
|
|
3
|
-
const STATUS_RANK = Object.freeze({
|
|
4
|
-
[FactStatus.SUGGESTED]: 1,
|
|
5
|
-
[FactStatus.OBSERVED]: 2,
|
|
6
|
-
[FactStatus.CONFIRMED]: 3
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
export function writePersonalFact(store, input) {
|
|
10
|
-
const equivalent = equivalentCurrentFacts(store, input);
|
|
11
|
-
const fact = store.addFact({
|
|
12
|
-
spaceId: input.spaceId,
|
|
13
|
-
subject: input.subject,
|
|
14
|
-
predicate: input.predicate,
|
|
15
|
-
object: input.object,
|
|
16
|
-
sourceEpisodeId: input.sourceEpisodeId,
|
|
17
|
-
status: input.status,
|
|
18
|
-
confidence: input.confidence,
|
|
19
|
-
sensitivity: input.sensitivity,
|
|
20
|
-
scope: FactScope.PERSONAL
|
|
21
|
-
});
|
|
22
|
-
const candidates = [...equivalent, fact];
|
|
23
|
-
const winner = candidates.reduce(strongerOrLater);
|
|
24
|
-
for (const candidate of candidates) {
|
|
25
|
-
if (candidate.id !== winner.id) store.invalidateFact(candidate.id, winner.id);
|
|
26
|
-
}
|
|
27
|
-
return store.getFact(fact.id);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function writePersonalFactSpecs(store, input) {
|
|
31
|
-
const facts = [];
|
|
32
|
-
for (const spec of input.specs) {
|
|
33
|
-
if (spec.kind === 'replacement') {
|
|
34
|
-
facts.push(...writeReplacement(store, input, spec));
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
facts.push(writePersonalFact(store, {
|
|
38
|
-
...input,
|
|
39
|
-
subject: spec.subject ?? input.subject,
|
|
40
|
-
predicate: spec.predicate,
|
|
41
|
-
object: spec.object,
|
|
42
|
-
status: FactStatus.CONFIRMED
|
|
43
|
-
}));
|
|
44
|
-
}
|
|
45
|
-
return facts;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function writeReplacement(store, input, spec) {
|
|
49
|
-
const subject = spec.subject ?? input.subject;
|
|
50
|
-
const oldFacts = store.currentFacts(input.spaceId).filter(
|
|
51
|
-
(fact) => fact.subject === subject && fact.object === spec.oldValue
|
|
52
|
-
);
|
|
53
|
-
const predicates = oldFacts.length
|
|
54
|
-
? [...new Set(oldFacts.map((fact) => fact.predicate))]
|
|
55
|
-
: ['has_replacement'];
|
|
56
|
-
return predicates.map((predicate) => {
|
|
57
|
-
const fact = writePersonalFact(store, {
|
|
58
|
-
...input,
|
|
59
|
-
subject,
|
|
60
|
-
predicate,
|
|
61
|
-
object: spec.newValue,
|
|
62
|
-
status: FactStatus.CONFIRMED
|
|
63
|
-
});
|
|
64
|
-
for (const previous of oldFacts.filter((item) => item.predicate === predicate)) {
|
|
65
|
-
store.invalidateFact(previous.id, fact.id);
|
|
66
|
-
}
|
|
67
|
-
return fact;
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function equivalentCurrentFacts(store, input) {
|
|
72
|
-
return store.currentFacts(input.spaceId).filter((fact) =>
|
|
73
|
-
fact.subject === input.subject &&
|
|
74
|
-
fact.predicate === input.predicate &&
|
|
75
|
-
fact.object === input.object
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function strongerOrLater(current, candidate) {
|
|
80
|
-
return rank(candidate.status) >= rank(current.status) ? candidate : current;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function rank(status) {
|
|
84
|
-
return STATUS_RANK[status] ?? 0;
|
|
85
|
-
}
|
package/src/lens/lens-input.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { ApplicationError, ApplicationErrorCode } from '../app/application-error.js';
|
|
2
|
-
import { Sensitivity, SpaceKind } from '../models.js';
|
|
3
|
-
import { detectSensitiveContent } from '../security/sensitive-content.js';
|
|
4
|
-
|
|
5
|
-
export function validateLensWriteInput(
|
|
6
|
-
store,
|
|
7
|
-
{ personalSpaceId, texts, sensitivity, confidence }
|
|
8
|
-
) {
|
|
9
|
-
requirePersonalSpace(store, personalSpaceId);
|
|
10
|
-
assertSafeLensTexts(texts);
|
|
11
|
-
if (!Object.values(Sensitivity).includes(sensitivity)) {
|
|
12
|
-
throw new ApplicationError(
|
|
13
|
-
ApplicationErrorCode.VALIDATION,
|
|
14
|
-
`Invalid sensitivity: ${sensitivity}`
|
|
15
|
-
);
|
|
16
|
-
}
|
|
17
|
-
if (
|
|
18
|
-
typeof confidence !== 'number' ||
|
|
19
|
-
!Number.isFinite(confidence) ||
|
|
20
|
-
confidence < 0 ||
|
|
21
|
-
confidence > 1
|
|
22
|
-
) {
|
|
23
|
-
throw new ApplicationError(
|
|
24
|
-
ApplicationErrorCode.VALIDATION,
|
|
25
|
-
'Confidence must be between 0 and 1'
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function requirePersonalSpace(store, spaceId) {
|
|
31
|
-
const space = store.getSpace(spaceId);
|
|
32
|
-
if (!space) {
|
|
33
|
-
throw new ApplicationError(
|
|
34
|
-
ApplicationErrorCode.NOT_FOUND,
|
|
35
|
-
`Personal space not found: ${spaceId}`
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
if (space.kind !== SpaceKind.PERSONAL) {
|
|
39
|
-
throw new ApplicationError(
|
|
40
|
-
ApplicationErrorCode.VALIDATION,
|
|
41
|
-
`Space is not personal: ${spaceId}`
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
return space;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function assertSafeLensTexts(texts) {
|
|
48
|
-
for (const text of texts) {
|
|
49
|
-
if (typeof text !== 'string' || text.length === 0) {
|
|
50
|
-
throw new ApplicationError(
|
|
51
|
-
ApplicationErrorCode.VALIDATION,
|
|
52
|
-
'Lens text must be a nonempty string'
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
if (detectSensitiveContent(text).restricted) {
|
|
56
|
-
throw new ApplicationError(
|
|
57
|
-
ApplicationErrorCode.VALIDATION,
|
|
58
|
-
'Sensitive content is not allowed in Personal Lens'
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { FactScope, FactStatus, isCurrentFact } from '../models.js';
|
|
2
|
-
import { isRestrictedEpisode } from '../security/episode-visibility.js';
|
|
3
|
-
import { isRestrictedFact } from '../security/fact-visibility.js';
|
|
4
|
-
|
|
5
|
-
export function isInjectableFact(
|
|
6
|
-
fact,
|
|
7
|
-
{ includeObserved, includeSuggested, includeRestricted }
|
|
8
|
-
) {
|
|
9
|
-
if (fact.invalidAt) return false;
|
|
10
|
-
if (fact.status === FactStatus.CONFIRMED) return isVisibleFact(fact, includeRestricted);
|
|
11
|
-
if (fact.status === FactStatus.OBSERVED && includeObserved) {
|
|
12
|
-
return isVisibleFact(fact, includeRestricted);
|
|
13
|
-
}
|
|
14
|
-
if (fact.status === FactStatus.SUGGESTED && includeSuggested) {
|
|
15
|
-
return isVisibleFact(fact, includeRestricted);
|
|
16
|
-
}
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function isSearchableFact(fact, { includeHistorical, includeRestricted }) {
|
|
21
|
-
return (includeHistorical || isCurrentFact(fact)) && isVisibleFact(fact, includeRestricted);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function buildCorrectionIndex(groups, personalSpaceId, includeRestricted) {
|
|
25
|
-
const index = new Map();
|
|
26
|
-
for (const group of groups) {
|
|
27
|
-
if (typeof group.factId !== 'string') continue;
|
|
28
|
-
const episodes = group.episodes.filter((episode) =>
|
|
29
|
-
episode.spaceId === personalSpaceId &&
|
|
30
|
-
episode.metadata?.kind === 'lens_correction' &&
|
|
31
|
-
episode.metadata?.factId === group.factId &&
|
|
32
|
-
(includeRestricted || !isRestrictedEpisode(episode))
|
|
33
|
-
);
|
|
34
|
-
index.set(group.factId, {
|
|
35
|
-
episodes,
|
|
36
|
-
truncated: group.truncated === true || episodes.length < group.episodes.length
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
return index;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function projectSearchFact(
|
|
43
|
-
store,
|
|
44
|
-
fact,
|
|
45
|
-
evidenceEpisode,
|
|
46
|
-
personalSpaceId,
|
|
47
|
-
correctionIndex,
|
|
48
|
-
includeRestricted
|
|
49
|
-
) {
|
|
50
|
-
const sourceEpisode = safePersonalEpisode(
|
|
51
|
-
evidenceEpisode,
|
|
52
|
-
personalSpaceId,
|
|
53
|
-
includeRestricted
|
|
54
|
-
);
|
|
55
|
-
const replacement = fact.replacedByFactId ? store.getFact(fact.replacedByFactId) : null;
|
|
56
|
-
const replacementFact = replacement?.spaceId === personalSpaceId &&
|
|
57
|
-
replacement.scope === FactScope.PERSONAL &&
|
|
58
|
-
isVisibleFact(replacement, includeRestricted)
|
|
59
|
-
? replacement
|
|
60
|
-
: null;
|
|
61
|
-
return {
|
|
62
|
-
fact: projectSearchFactDto(fact, sourceEpisode, replacementFact),
|
|
63
|
-
current: isCurrentFact(fact),
|
|
64
|
-
sourceEpisode,
|
|
65
|
-
replacementFact,
|
|
66
|
-
correctionEpisodes: correctionIndex.get(fact.id)?.episodes ?? [],
|
|
67
|
-
correctionEpisodesTruncated: correctionIndex.get(fact.id)?.truncated ?? false
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function projectSearchFactDto(fact, sourceEpisode, replacementFact) {
|
|
72
|
-
return {
|
|
73
|
-
...fact,
|
|
74
|
-
sourceEpisodeId: sourceEpisode?.id ?? null,
|
|
75
|
-
replacedByFactId: replacementFact?.id ?? null
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function isVisibleFact(fact, includeRestricted) {
|
|
80
|
-
return includeRestricted === true || !isRestrictedFact(fact);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function safePersonalEpisode(episode, personalSpaceId, includeRestricted) {
|
|
84
|
-
if (!episode || episode.spaceId !== personalSpaceId) return null;
|
|
85
|
-
if (!includeRestricted && isRestrictedEpisode(episode)) return null;
|
|
86
|
-
return episode;
|
|
87
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { FactStatus } from '../models.js';
|
|
2
|
-
|
|
3
|
-
export { compareSearchFacts } from '../storage/fact-search-order.js';
|
|
4
|
-
|
|
5
|
-
export const BOUNDARY_PREDICATES = Object.freeze([
|
|
6
|
-
'has_boundary',
|
|
7
|
-
'has_access_boundary',
|
|
8
|
-
'does_not_want',
|
|
9
|
-
'avoids',
|
|
10
|
-
'forbids'
|
|
11
|
-
]);
|
|
12
|
-
export const BOUNDARY_PREFIXES = Object.freeze([
|
|
13
|
-
'boundary_',
|
|
14
|
-
'does_not_',
|
|
15
|
-
'avoids_',
|
|
16
|
-
'forbids_',
|
|
17
|
-
'must_not_'
|
|
18
|
-
]);
|
|
19
|
-
|
|
20
|
-
const STATUS_RANK = Object.freeze({
|
|
21
|
-
[FactStatus.SUGGESTED]: 1,
|
|
22
|
-
[FactStatus.OBSERVED]: 2,
|
|
23
|
-
[FactStatus.CONFIRMED]: 3
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
export function taskTokens(task) {
|
|
27
|
-
return new Set(normalizedTokens(task));
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function compareLensFacts(left, right, terms) {
|
|
31
|
-
return compareNumber(isBoundary(right), isBoundary(left)) ||
|
|
32
|
-
compareNumber(termMatches(right, terms), termMatches(left, terms)) ||
|
|
33
|
-
compareNumber(STATUS_RANK[right.status] ?? 0, STATUS_RANK[left.status] ?? 0) ||
|
|
34
|
-
compareText(right.validAt, left.validAt) ||
|
|
35
|
-
compareText(left.id, right.id);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function termMatches(fact, terms) {
|
|
39
|
-
const factTokens = new Set(normalizedTokens(`${fact.subject} ${fact.predicate} ${fact.object}`));
|
|
40
|
-
let matches = 0;
|
|
41
|
-
for (const term of terms) if (factTokens.has(term)) matches += 1;
|
|
42
|
-
return matches;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function normalizedTokens(text) {
|
|
46
|
-
return String(text).normalize('NFKC').toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function isBoundary(fact) {
|
|
50
|
-
return BOUNDARY_PREDICATES.includes(fact.predicate) ||
|
|
51
|
-
BOUNDARY_PREFIXES.some((prefix) => fact.predicate.startsWith(prefix));
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function compareNumber(left, right) {
|
|
55
|
-
return left - right;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function compareText(left, right) {
|
|
59
|
-
const first = String(left ?? '');
|
|
60
|
-
const second = String(right ?? '');
|
|
61
|
-
return first === second ? 0 : (first < second ? -1 : 1);
|
|
62
|
-
}
|
package/src/lens/lens-query.js
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import { ApplicationError, ApplicationErrorCode } from '../app/application-error.js';
|
|
2
|
-
import { FactScope } from '../models.js';
|
|
3
|
-
import { requirePersonalSpace } from './lens-input.js';
|
|
4
|
-
import {
|
|
5
|
-
BOUNDARY_PREDICATES,
|
|
6
|
-
BOUNDARY_PREFIXES,
|
|
7
|
-
compareLensFacts,
|
|
8
|
-
taskTokens
|
|
9
|
-
} from './lens-query-ranking.js';
|
|
10
|
-
import {
|
|
11
|
-
isInjectableFact,
|
|
12
|
-
projectSearchFact,
|
|
13
|
-
safePersonalEpisode
|
|
14
|
-
} from './lens-query-projection.js';
|
|
15
|
-
import { LensSearchQuery } from './lens-search-query.js';
|
|
16
|
-
|
|
17
|
-
export { BOUNDARY_PREDICATES, BOUNDARY_PREFIXES };
|
|
18
|
-
export {
|
|
19
|
-
DEFAULT_SEARCH_USER_CONTEXT_LIMIT,
|
|
20
|
-
MAX_SEARCH_USER_CONTEXT_LIMIT
|
|
21
|
-
} from './lens-search-query.js';
|
|
22
|
-
|
|
23
|
-
export class LensQuery extends LensSearchQuery {
|
|
24
|
-
getUserLens(input) {
|
|
25
|
-
return this.#getUserLens(input, true);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
#getUserLens({
|
|
29
|
-
personalSpaceId,
|
|
30
|
-
task,
|
|
31
|
-
budget,
|
|
32
|
-
includeObserved = true,
|
|
33
|
-
includeSuggested = false,
|
|
34
|
-
includeRestricted = false
|
|
35
|
-
}, redactRelations) {
|
|
36
|
-
requirePersonalSpace(this.store, personalSpaceId);
|
|
37
|
-
validateTask(task);
|
|
38
|
-
validateBudget(budget);
|
|
39
|
-
|
|
40
|
-
const terms = taskTokens(task);
|
|
41
|
-
const allowRestricted = includeRestricted === true;
|
|
42
|
-
const facts = this.store.currentFacts(personalSpaceId)
|
|
43
|
-
.filter((fact) => fact.spaceId === personalSpaceId && fact.scope === FactScope.PERSONAL)
|
|
44
|
-
.filter((fact) => isInjectableFact(fact, {
|
|
45
|
-
includeObserved,
|
|
46
|
-
includeSuggested,
|
|
47
|
-
includeRestricted: allowRestricted
|
|
48
|
-
}))
|
|
49
|
-
.sort((left, right) => compareLensFacts(left, right, terms));
|
|
50
|
-
const selected = [];
|
|
51
|
-
const lines = [];
|
|
52
|
-
let bytes = 0;
|
|
53
|
-
|
|
54
|
-
for (const fact of facts) {
|
|
55
|
-
const line = formatFactLine(fact);
|
|
56
|
-
const nextBytes = bytes + (lines.length ? 1 : 0) + Buffer.byteLength(line, 'utf8');
|
|
57
|
-
if (nextBytes > budget) continue;
|
|
58
|
-
selected.push(fact);
|
|
59
|
-
lines.push(line);
|
|
60
|
-
bytes = nextBytes;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
personalSpaceId,
|
|
65
|
-
task,
|
|
66
|
-
text: lines.join('\n'),
|
|
67
|
-
facts: redactRelations
|
|
68
|
-
? selected.map((fact) => {
|
|
69
|
-
const source = this.store.episodeEvidencePreview(
|
|
70
|
-
personalSpaceId,
|
|
71
|
-
fact.sourceEpisodeId,
|
|
72
|
-
{ includeRestricted: allowRestricted, maxBodyBytes: 0 }
|
|
73
|
-
);
|
|
74
|
-
return projectSearchFact(
|
|
75
|
-
this.store,
|
|
76
|
-
fact,
|
|
77
|
-
source,
|
|
78
|
-
personalSpaceId,
|
|
79
|
-
new Map(),
|
|
80
|
-
allowRestricted
|
|
81
|
-
).fact;
|
|
82
|
-
})
|
|
83
|
-
: selected,
|
|
84
|
-
estimatedTokens: bytes,
|
|
85
|
-
truncated: selected.length < facts.length
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
getUserLensView(input) {
|
|
90
|
-
return this.store.transaction(() => {
|
|
91
|
-
const lens = this.#getUserLens(input, false);
|
|
92
|
-
const entries = [];
|
|
93
|
-
for (const fact of lens.facts) {
|
|
94
|
-
const source = safePersonalEpisode(
|
|
95
|
-
this.store.episodeEvidencePreview(
|
|
96
|
-
lens.personalSpaceId,
|
|
97
|
-
fact.sourceEpisodeId,
|
|
98
|
-
{ includeRestricted: input.includeRestricted === true, maxBodyBytes: 0 }
|
|
99
|
-
),
|
|
100
|
-
lens.personalSpaceId,
|
|
101
|
-
input.includeRestricted === true
|
|
102
|
-
);
|
|
103
|
-
if (source) entries.push(projectLensViewEntry(fact, source));
|
|
104
|
-
}
|
|
105
|
-
return {
|
|
106
|
-
entries,
|
|
107
|
-
truncated: lens.truncated || entries.length < lens.facts.length
|
|
108
|
-
};
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function validateTask(task) {
|
|
115
|
-
if (typeof task !== 'string') {
|
|
116
|
-
throw new ApplicationError(ApplicationErrorCode.VALIDATION, 'Task must be a string');
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function validateBudget(budget) {
|
|
121
|
-
if (!Number.isInteger(budget) || budget <= 0) {
|
|
122
|
-
throw new ApplicationError(ApplicationErrorCode.VALIDATION, 'Budget must be a positive integer');
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function formatFactLine(fact) {
|
|
127
|
-
return `${fact.subject} ${fact.predicate} ${fact.object} (${fact.status})`;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function projectLensViewEntry(fact, source) {
|
|
131
|
-
return {
|
|
132
|
-
subject: fact.subject,
|
|
133
|
-
predicate: fact.predicate,
|
|
134
|
-
object: fact.object,
|
|
135
|
-
status: fact.status,
|
|
136
|
-
validAt: fact.validAt,
|
|
137
|
-
source: {
|
|
138
|
-
kind: source.sourceKind,
|
|
139
|
-
uri: source.sourceUri,
|
|
140
|
-
createdAt: source.createdAt
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { detectSensitiveContent } from '../security/sensitive-content.js';
|
|
2
|
-
|
|
3
|
-
export function projectCurrentFact(fact) {
|
|
4
|
-
return compactFact(fact);
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function isSafeResourceFact(fact) {
|
|
8
|
-
return compactFact(fact) !== null;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function projectHistoryItem(item) {
|
|
12
|
-
const fact = compactFact(item.fact);
|
|
13
|
-
if (!fact) return null;
|
|
14
|
-
return {
|
|
15
|
-
current: item.current,
|
|
16
|
-
fact,
|
|
17
|
-
source: compactEpisode(item.sourceEpisode),
|
|
18
|
-
replacementFact: compactFact(item.replacementFact),
|
|
19
|
-
correctionEpisodes: item.correctionEpisodes.map(compactCorrection).filter(Boolean)
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function projectSubscription(subscription, space) {
|
|
24
|
-
if (![
|
|
25
|
-
space?.id,
|
|
26
|
-
space?.name,
|
|
27
|
-
space?.kind,
|
|
28
|
-
subscription?.mode,
|
|
29
|
-
subscription?.createdAt
|
|
30
|
-
].every(isSafeResourceText)) return null;
|
|
31
|
-
const compactSpace = { id: space.id, name: space.name, kind: space.kind };
|
|
32
|
-
if (isSafeResourceText(space.description)) {
|
|
33
|
-
compactSpace.description = space.description;
|
|
34
|
-
}
|
|
35
|
-
return {
|
|
36
|
-
space: compactSpace,
|
|
37
|
-
mode: subscription.mode,
|
|
38
|
-
createdAt: subscription.createdAt
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function compactFact(fact) {
|
|
43
|
-
if (!fact || ![
|
|
44
|
-
fact.id,
|
|
45
|
-
fact.subject,
|
|
46
|
-
fact.predicate,
|
|
47
|
-
fact.object,
|
|
48
|
-
fact.status,
|
|
49
|
-
fact.validAt
|
|
50
|
-
].every(isSafeResourceText)) return null;
|
|
51
|
-
if (!Number.isFinite(fact.confidence) || fact.confidence < 0 || fact.confidence > 1) {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
return {
|
|
55
|
-
id: fact.id,
|
|
56
|
-
subject: fact.subject,
|
|
57
|
-
predicate: fact.predicate,
|
|
58
|
-
object: fact.object,
|
|
59
|
-
status: fact.status,
|
|
60
|
-
confidence: fact.confidence,
|
|
61
|
-
validAt: fact.validAt
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function compactEpisode(episode) {
|
|
66
|
-
if (!episode) return null;
|
|
67
|
-
if (![
|
|
68
|
-
episode.id,
|
|
69
|
-
episode.sourceKind,
|
|
70
|
-
episode.createdAt
|
|
71
|
-
].every(isSafeResourceText)) return null;
|
|
72
|
-
if (!isSafeResourceText(episode.sourceUri, { nullable: true })) return null;
|
|
73
|
-
return {
|
|
74
|
-
id: episode.id,
|
|
75
|
-
kind: episode.sourceKind,
|
|
76
|
-
uri: episode.sourceUri,
|
|
77
|
-
createdAt: episode.createdAt
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function compactCorrection(episode) {
|
|
82
|
-
const compact = compactEpisode(episode);
|
|
83
|
-
if (!compact || !isSafeResourceText(episode.metadata?.action)) return null;
|
|
84
|
-
return {
|
|
85
|
-
...compact,
|
|
86
|
-
action: episode.metadata.action
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function isSafeResourceText(value, { nullable = false } = {}) {
|
|
91
|
-
if (value === null || value === undefined) return nullable;
|
|
92
|
-
return typeof value === 'string' && !detectSensitiveContent(value).restricted;
|
|
93
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
const EMPTY_ARRAY_BYTES = 2;
|
|
2
|
-
const HISTORY_ENVELOPE_RESERVE_BYTES = 1024;
|
|
3
|
-
|
|
4
|
-
export function selectCurrentFacts(facts, budgetBytes) {
|
|
5
|
-
const selected = [];
|
|
6
|
-
const lines = [];
|
|
7
|
-
let bytes = 0;
|
|
8
|
-
for (const fact of facts) {
|
|
9
|
-
const line = `${fact.subject} ${fact.predicate} ${fact.object} (${fact.status})`;
|
|
10
|
-
const nextBytes = bytes + (lines.length ? 1 : 0) + Buffer.byteLength(line, 'utf8');
|
|
11
|
-
if (nextBytes > budgetBytes) continue;
|
|
12
|
-
selected.push(fact);
|
|
13
|
-
lines.push(line);
|
|
14
|
-
bytes = nextBytes;
|
|
15
|
-
}
|
|
16
|
-
return {
|
|
17
|
-
facts: selected,
|
|
18
|
-
text: lines.join('\n'),
|
|
19
|
-
truncated: selected.length < facts.length
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function selectHistoryItems(items, { limit, budgetBytes }) {
|
|
24
|
-
const selected = [];
|
|
25
|
-
const itemsBudget = budgetBytes - HISTORY_ENVELOPE_RESERVE_BYTES;
|
|
26
|
-
let itemsBytes = EMPTY_ARRAY_BYTES;
|
|
27
|
-
let truncated = false;
|
|
28
|
-
|
|
29
|
-
for (const item of items) {
|
|
30
|
-
if (selected.length >= limit) {
|
|
31
|
-
truncated = true;
|
|
32
|
-
break;
|
|
33
|
-
}
|
|
34
|
-
const itemBytes = Buffer.byteLength(JSON.stringify(item), 'utf8');
|
|
35
|
-
const nextItemsBytes = itemsBytes + (selected.length ? 1 : 0) + itemBytes;
|
|
36
|
-
if (nextItemsBytes > itemsBudget) {
|
|
37
|
-
truncated = true;
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
selected.push(item);
|
|
41
|
-
itemsBytes = nextItemsBytes;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return { items: selected, itemsBytes, truncated };
|
|
45
|
-
}
|