@grackle-ai/plugin-knowledge 0.118.0 → 0.120.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/entity-sync.d.ts +22 -0
  2. package/dist/entity-sync.d.ts.map +1 -0
  3. package/dist/entity-sync.js +116 -0
  4. package/dist/entity-sync.js.map +1 -0
  5. package/dist/knowledge-init.d.ts.map +1 -1
  6. package/dist/knowledge-init.js +12 -1
  7. package/dist/knowledge-init.js.map +1 -1
  8. package/dist/knowledge-plugin.d.ts.map +1 -1
  9. package/dist/knowledge-plugin.js +9 -2
  10. package/dist/knowledge-plugin.js.map +1 -1
  11. package/dist/knowledge-projection-phase.d.ts +39 -0
  12. package/dist/knowledge-projection-phase.d.ts.map +1 -0
  13. package/dist/knowledge-projection-phase.js +154 -0
  14. package/dist/knowledge-projection-phase.js.map +1 -0
  15. package/dist/projection/derive-text.d.ts +21 -0
  16. package/dist/projection/derive-text.d.ts.map +1 -0
  17. package/dist/projection/derive-text.js +40 -0
  18. package/dist/projection/derive-text.js.map +1 -0
  19. package/dist/projection/edge-mappers.d.ts +44 -0
  20. package/dist/projection/edge-mappers.d.ts.map +1 -0
  21. package/dist/projection/edge-mappers.js +95 -0
  22. package/dist/projection/edge-mappers.js.map +1 -0
  23. package/dist/projection/node-mappers.d.ts +42 -0
  24. package/dist/projection/node-mappers.d.ts.map +1 -0
  25. package/dist/projection/node-mappers.js +106 -0
  26. package/dist/projection/node-mappers.js.map +1 -0
  27. package/dist/projection/project-entity.d.ts +54 -0
  28. package/dist/projection/project-entity.d.ts.map +1 -0
  29. package/dist/projection/project-entity.js +164 -0
  30. package/dist/projection/project-entity.js.map +1 -0
  31. package/dist/projection/project-transcript.d.ts +29 -0
  32. package/dist/projection/project-transcript.d.ts.map +1 -0
  33. package/dist/projection/project-transcript.js +90 -0
  34. package/dist/projection/project-transcript.js.map +1 -0
  35. package/dist/projection/rebuild.d.ts +29 -0
  36. package/dist/projection/rebuild.d.ts.map +1 -0
  37. package/dist/projection/rebuild.js +104 -0
  38. package/dist/projection/rebuild.js.map +1 -0
  39. package/dist/proto-converters.d.ts.map +1 -1
  40. package/dist/proto-converters.js +3 -1
  41. package/dist/proto-converters.js.map +1 -1
  42. package/package.json +7 -7
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Pure mappers from Grackle SQL rows to structural knowledge-graph edge specs
3
+ * (#1258). An {@link EdgeSpec} identifies endpoints by `(sourceType, sourceId)`;
4
+ * the projector resolves those to node IDs at write time.
5
+ *
6
+ * Edge rule: an empty/`""` soft foreign-key reference produces NO edge.
7
+ *
8
+ * @module
9
+ */
10
+ import { EDGE_TYPE, REFERENCE_SOURCE, } from "@grackle-ai/knowledge";
11
+ const key = (sourceType, sourceId) => ({
12
+ sourceType,
13
+ sourceId,
14
+ });
15
+ /** Outgoing structural edge types reconciled on a Task node. */
16
+ export const TASK_EDGE_TYPES = [
17
+ EDGE_TYPE.IN_WORKSPACE,
18
+ EDGE_TYPE.PART_OF,
19
+ EDGE_TYPE.DEPENDS_ON,
20
+ ];
21
+ /** Outgoing structural edge types reconciled on a Session node. */
22
+ export const SESSION_EDGE_TYPES = [
23
+ EDGE_TYPE.ATTEMPT_OF,
24
+ EDGE_TYPE.RAN_IN,
25
+ EDGE_TYPE.USED_PERSONA,
26
+ ];
27
+ /** Outgoing structural edge types reconciled on a Workspace node. */
28
+ export const WORKSPACE_EDGE_TYPES = [EDGE_TYPE.LINKED_TO];
29
+ /** Defensively parse the `tasks.dependsOn` JSON-array column. */
30
+ export function parseDependsOn(raw) {
31
+ try {
32
+ const parsed = JSON.parse(raw);
33
+ return Array.isArray(parsed)
34
+ ? parsed.filter((item) => typeof item === "string" && item.length > 0)
35
+ : [];
36
+ }
37
+ catch {
38
+ return [];
39
+ }
40
+ }
41
+ /** Structural edges originating from a Task: IN_WORKSPACE, PART_OF, DEPENDS_ON. */
42
+ export function taskEdges(task) {
43
+ const from = key(REFERENCE_SOURCE.TASK, task.id);
44
+ const edges = [];
45
+ if (task.workspaceId) {
46
+ edges.push({ from, to: key(REFERENCE_SOURCE.WORKSPACE, task.workspaceId), type: EDGE_TYPE.IN_WORKSPACE });
47
+ }
48
+ if (task.parentTaskId) {
49
+ edges.push({ from, to: key(REFERENCE_SOURCE.TASK, task.parentTaskId), type: EDGE_TYPE.PART_OF });
50
+ }
51
+ for (const dependencyId of parseDependsOn(task.dependsOn)) {
52
+ edges.push({ from, to: key(REFERENCE_SOURCE.TASK, dependencyId), type: EDGE_TYPE.DEPENDS_ON });
53
+ }
54
+ return edges;
55
+ }
56
+ /** Outgoing structural edges from a Session: ATTEMPT_OF, RAN_IN, USED_PERSONA. */
57
+ export function sessionEdges(session) {
58
+ const from = key(REFERENCE_SOURCE.SESSION, session.id);
59
+ const edges = [];
60
+ if (session.taskId) {
61
+ edges.push({ from, to: key(REFERENCE_SOURCE.TASK, session.taskId), type: EDGE_TYPE.ATTEMPT_OF });
62
+ }
63
+ if (session.environmentId) {
64
+ edges.push({ from, to: key(REFERENCE_SOURCE.ENVIRONMENT, session.environmentId), type: EDGE_TYPE.RAN_IN });
65
+ }
66
+ if (session.personaId) {
67
+ edges.push({ from, to: key(REFERENCE_SOURCE.PERSONA, session.personaId), type: EDGE_TYPE.USED_PERSONA });
68
+ }
69
+ return edges;
70
+ }
71
+ /**
72
+ * The SPAWNED edge (parent Session → this Session). Originates from the parent,
73
+ * so it is reconciled with the child (parent_session_id is immutable); returned
74
+ * separately from {@link sessionEdges} and upserted (never bulk-removed) so a
75
+ * parent's spawn edges aren't cleared when the parent re-projects.
76
+ */
77
+ export function sessionSpawnEdge(session) {
78
+ if (!session.parentSessionId) {
79
+ return undefined;
80
+ }
81
+ return {
82
+ from: key(REFERENCE_SOURCE.SESSION, session.parentSessionId),
83
+ to: key(REFERENCE_SOURCE.SESSION, session.id),
84
+ type: EDGE_TYPE.SPAWNED,
85
+ };
86
+ }
87
+ /** The LINKED_TO edge for a workspace↔environment link. */
88
+ export function workspaceLinkEdge(workspaceId, environmentId) {
89
+ return {
90
+ from: key(REFERENCE_SOURCE.WORKSPACE, workspaceId),
91
+ to: key(REFERENCE_SOURCE.ENVIRONMENT, environmentId),
92
+ type: EDGE_TYPE.LINKED_TO,
93
+ };
94
+ }
95
+ //# sourceMappingURL=edge-mappers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edge-mappers.js","sourceRoot":"","sources":["../../src/projection/edge-mappers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Pure mappers from Grackle SQL rows to knowledge-graph reference-node upsert
3
+ * inputs (#1258), plus a cheap change-detection hash.
4
+ *
5
+ * Kept pure (no IO) so they are unit-testable without Neo4j.
6
+ *
7
+ * @module
8
+ */
9
+ import { type UpsertReferenceNodeInput } from "@grackle-ai/knowledge";
10
+ import type { TaskRow, WorkspaceRow, SessionRow, PersonaRow, EnvironmentRow } from "@grackle-ai/database";
11
+ /**
12
+ * Stable hash of the projected fields, stored on the node as `projectionHash`
13
+ * so the reconciliation scan can skip rows whose projection is unchanged.
14
+ *
15
+ * Non-cryptographic use (change detection only); SHA-256 is used purely to keep
16
+ * static analysis happy — no weak-algorithm dependence.
17
+ */
18
+ export declare function computeProjectionHash(...parts: unknown[]): string;
19
+ /** Map a Task row to its reference-node upsert input. */
20
+ export declare function taskToNodeInput(task: TaskRow): UpsertReferenceNodeInput;
21
+ /**
22
+ * Map a Workspace row to its reference-node upsert input.
23
+ *
24
+ * @param linkedEnvironmentIds - IDs of the environments linked to this workspace
25
+ * (the `LINKED_TO` edge set). Folded into the projection hash so a link/unlink
26
+ * changes the hash and the reconciliation scan re-projects the workspace,
27
+ * keeping `LINKED_TO` edges converged even if the change's event was missed
28
+ * (e.g. while Neo4j was down).
29
+ */
30
+ export declare function workspaceToNodeInput(workspace: WorkspaceRow, linkedEnvironmentIds?: string[]): UpsertReferenceNodeInput;
31
+ /**
32
+ * Map a Session row to its reference-node upsert input.
33
+ *
34
+ * @param workspaceId - Resolved from the session's task (sessions have no direct
35
+ * workspace column); empty string when the session has no task.
36
+ */
37
+ export declare function sessionToNodeInput(session: SessionRow, workspaceId: string): UpsertReferenceNodeInput;
38
+ /** Map a Persona row to its reference-node upsert input (global scope). */
39
+ export declare function personaToNodeInput(persona: PersonaRow): UpsertReferenceNodeInput;
40
+ /** Map an Environment row to its reference-node upsert input (global scope). */
41
+ export declare function environmentToNodeInput(environment: EnvironmentRow): UpsertReferenceNodeInput;
42
+ //# sourceMappingURL=node-mappers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-mappers.d.ts","sourceRoot":"","sources":["../../src/projection/node-mappers.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAoB,KAAK,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACxF,OAAO,KAAK,EACV,OAAO,EACP,YAAY,EACZ,UAAU,EACV,UAAU,EACV,cAAc,EACf,MAAM,sBAAsB,CAAC;AAS9B;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,KAAK,EAAE,OAAO,EAAE,GAAG,MAAM,CAEjE;AAED,yDAAyD;AACzD,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,wBAAwB,CAoBvE;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,YAAY,EACvB,oBAAoB,GAAE,MAAM,EAAO,GAClC,wBAAwB,CAoB1B;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,UAAU,EACnB,WAAW,EAAE,MAAM,GAClB,wBAAwB,CAqB1B;AAED,2EAA2E;AAC3E,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,UAAU,GAAG,wBAAwB,CAShF;AAED,gFAAgF;AAChF,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,cAAc,GAC1B,wBAAwB,CAY1B"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Pure mappers from Grackle SQL rows to knowledge-graph reference-node upsert
3
+ * inputs (#1258), plus a cheap change-detection hash.
4
+ *
5
+ * Kept pure (no IO) so they are unit-testable without Neo4j.
6
+ *
7
+ * @module
8
+ */
9
+ import { createHash } from "node:crypto";
10
+ import { REFERENCE_SOURCE } from "@grackle-ai/knowledge";
11
+ import { deriveTaskText, deriveWorkspaceText, deriveSessionText, derivePersonaText, deriveEnvironmentText, } from "./derive-text.js";
12
+ /**
13
+ * Stable hash of the projected fields, stored on the node as `projectionHash`
14
+ * so the reconciliation scan can skip rows whose projection is unchanged.
15
+ *
16
+ * Non-cryptographic use (change detection only); SHA-256 is used purely to keep
17
+ * static analysis happy — no weak-algorithm dependence.
18
+ */
19
+ export function computeProjectionHash(...parts) {
20
+ return createHash("sha256").update(JSON.stringify(parts)).digest("hex");
21
+ }
22
+ /** Map a Task row to its reference-node upsert input. */
23
+ export function taskToNodeInput(task) {
24
+ const label = deriveTaskText(task);
25
+ const workspaceId = task.workspaceId ?? "";
26
+ return {
27
+ sourceType: REFERENCE_SOURCE.TASK,
28
+ sourceId: task.id,
29
+ label,
30
+ workspaceId,
31
+ extraProps: {
32
+ status: task.status,
33
+ projectionHash: computeProjectionHash("task", label, workspaceId, task.parentTaskId, task.dependsOn, task.status),
34
+ },
35
+ };
36
+ }
37
+ /**
38
+ * Map a Workspace row to its reference-node upsert input.
39
+ *
40
+ * @param linkedEnvironmentIds - IDs of the environments linked to this workspace
41
+ * (the `LINKED_TO` edge set). Folded into the projection hash so a link/unlink
42
+ * changes the hash and the reconciliation scan re-projects the workspace,
43
+ * keeping `LINKED_TO` edges converged even if the change's event was missed
44
+ * (e.g. while Neo4j was down).
45
+ */
46
+ export function workspaceToNodeInput(workspace, linkedEnvironmentIds = []) {
47
+ const label = deriveWorkspaceText(workspace);
48
+ return {
49
+ sourceType: REFERENCE_SOURCE.WORKSPACE,
50
+ sourceId: workspace.id,
51
+ label,
52
+ // A workspace's own scope is itself.
53
+ workspaceId: workspace.id,
54
+ extraProps: {
55
+ status: workspace.status,
56
+ // Sort so the hash is order-independent (the link store's order is not
57
+ // guaranteed) and only changes when the link *set* actually changes.
58
+ projectionHash: computeProjectionHash("workspace", label, workspace.status, [...linkedEnvironmentIds].sort()),
59
+ },
60
+ };
61
+ }
62
+ /**
63
+ * Map a Session row to its reference-node upsert input.
64
+ *
65
+ * @param workspaceId - Resolved from the session's task (sessions have no direct
66
+ * workspace column); empty string when the session has no task.
67
+ */
68
+ export function sessionToNodeInput(session, workspaceId) {
69
+ const label = deriveSessionText(session);
70
+ return {
71
+ sourceType: REFERENCE_SOURCE.SESSION,
72
+ sourceId: session.id,
73
+ label,
74
+ workspaceId,
75
+ extraProps: {
76
+ status: session.status,
77
+ projectionHash: computeProjectionHash("session", label, workspaceId, session.taskId, session.environmentId, session.personaId, session.parentSessionId, session.status),
78
+ },
79
+ };
80
+ }
81
+ /** Map a Persona row to its reference-node upsert input (global scope). */
82
+ export function personaToNodeInput(persona) {
83
+ const label = derivePersonaText(persona);
84
+ return {
85
+ sourceType: REFERENCE_SOURCE.PERSONA,
86
+ sourceId: persona.id,
87
+ label,
88
+ workspaceId: "",
89
+ extraProps: { projectionHash: computeProjectionHash("persona", label) },
90
+ };
91
+ }
92
+ /** Map an Environment row to its reference-node upsert input (global scope). */
93
+ export function environmentToNodeInput(environment) {
94
+ const label = deriveEnvironmentText(environment);
95
+ return {
96
+ sourceType: REFERENCE_SOURCE.ENVIRONMENT,
97
+ sourceId: environment.id,
98
+ label,
99
+ workspaceId: "",
100
+ extraProps: {
101
+ status: environment.status,
102
+ projectionHash: computeProjectionHash("environment", label, environment.status),
103
+ },
104
+ };
105
+ }
106
+ //# sourceMappingURL=node-mappers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-mappers.js","sourceRoot":"","sources":["../../src/projection/node-mappers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Project a single Grackle entity into the knowledge graph (#1258): upsert its
3
+ * reference node and reconcile its structural edges. Used by both the
4
+ * incremental event subscriber and the full rebuild.
5
+ *
6
+ * @module
7
+ */
8
+ import type { TaskRow, WorkspaceRow, SessionRow, PersonaRow, EnvironmentRow } from "@grackle-ai/database";
9
+ /** Project a Task node + reconcile its IN_WORKSPACE / PART_OF / DEPENDS_ON edges (clearing stale). */
10
+ export declare function projectTask(task: TaskRow): Promise<void>;
11
+ /** Project a Workspace node + reconcile its LINKED_TO edges (clearing stale). */
12
+ export declare function projectWorkspace(workspace: WorkspaceRow): Promise<void>;
13
+ /**
14
+ * Project a Session node + reconcile its ATTEMPT_OF / RAN_IN / USED_PERSONA edges
15
+ * (clearing stale). The incoming SPAWNED edge is handled by {@link linkSessionSpawn}.
16
+ *
17
+ * @param workspaceId - Resolved from the session's task (empty if none).
18
+ */
19
+ export declare function projectSession(session: SessionRow, workspaceId: string): Promise<void>;
20
+ /**
21
+ * Re-apply an entity's outgoing edges with `MERGE` **without** clearing stale ones
22
+ * (additive). Used by the reconciliation scan for rows whose hash is unchanged, so
23
+ * any edge previously dropped (endpoint projected later, or a transient failure) is
24
+ * eventually healed. A no-op if the node is not projected yet (the next pass or a
25
+ * rebuild closes the gap once it exists).
26
+ */
27
+ export declare function reconcileTaskEdges(task: TaskRow): Promise<void>;
28
+ /** Additive re-apply of a Workspace's LINKED_TO edges (see {@link reconcileTaskEdges}). */
29
+ export declare function reconcileWorkspaceEdges(workspace: WorkspaceRow): Promise<void>;
30
+ /** Additive re-apply of a Session's outgoing edges (see {@link reconcileTaskEdges}). */
31
+ export declare function reconcileSessionEdges(session: SessionRow): Promise<void>;
32
+ /**
33
+ * Upsert the incoming SPAWNED edge (parent session → this session), if any.
34
+ *
35
+ * Run as a separate pass *after* all session nodes exist so that ordering
36
+ * (e.g. a child projected before its parent) never permanently drops the edge.
37
+ * Idempotent (MERGE), so it is safe to call every pass.
38
+ */
39
+ export declare function linkSessionSpawn(session: SessionRow): Promise<void>;
40
+ /** Project a Persona node (no outgoing structural edges). */
41
+ export declare function projectPersona(persona: PersonaRow): Promise<void>;
42
+ /** Project an Environment node (no outgoing structural edges). */
43
+ export declare function projectEnvironment(environment: EnvironmentRow): Promise<void>;
44
+ /** Remove a Task node and its edges. */
45
+ export declare function unprojectTask(id: string): Promise<void>;
46
+ /** Remove a Workspace node and its edges. */
47
+ export declare function unprojectWorkspace(id: string): Promise<void>;
48
+ /** Remove a Session node and its edges. */
49
+ export declare function unprojectSession(id: string): Promise<void>;
50
+ /** Remove a Persona node and its edges. */
51
+ export declare function unprojectPersona(id: string): Promise<void>;
52
+ /** Remove an Environment node and its edges. */
53
+ export declare function unprojectEnvironment(id: string): Promise<void>;
54
+ //# sourceMappingURL=project-entity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-entity.d.ts","sourceRoot":"","sources":["../../src/projection/project-entity.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAeH,OAAO,KAAK,EACV,OAAO,EACP,YAAY,EACZ,UAAU,EACV,UAAU,EACV,cAAc,EACf,MAAM,sBAAsB,CAAC;AA0F9B,sGAAsG;AACtG,wBAAsB,WAAW,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAG9D;AAED,iFAAiF;AACjF,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAO7E;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG5F;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAKrE;AAED,2FAA2F;AAC3F,wBAAsB,uBAAuB,CAAC,SAAS,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAKpF;AAED,wFAAwF;AACxF,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAK9E;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAKzE;AAED,6DAA6D;AAC7D,wBAAsB,cAAc,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAEvE;AAED,kEAAkE;AAClE,wBAAsB,kBAAkB,CAAC,WAAW,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAEnF;AAED,wCAAwC;AACxC,wBAAsB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE7D;AAED,6CAA6C;AAC7C,wBAAsB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAElE;AAED,2CAA2C;AAC3C,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEhE;AAED,2CAA2C;AAC3C,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEhE;AAED,gDAAgD;AAChD,wBAAsB,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEpE"}
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Project a single Grackle entity into the knowledge graph (#1258): upsert its
3
+ * reference node and reconcile its structural edges. Used by both the
4
+ * incremental event subscriber and the full rebuild.
5
+ *
6
+ * @module
7
+ */
8
+ import { upsertReferenceNode, upsertEdge, removeOutgoingEdges, findReferenceNodeBySource, getReferenceNodeProps, updateNode, deleteReferenceNodeBySource, REFERENCE_SOURCE, } from "@grackle-ai/knowledge";
9
+ import { workspaceEnvironmentLinkStore } from "@grackle-ai/database";
10
+ import { taskToNodeInput, workspaceToNodeInput, sessionToNodeInput, personaToNodeInput, environmentToNodeInput, } from "./node-mappers.js";
11
+ import { taskEdges, sessionEdges, sessionSpawnEdge, workspaceLinkEdge, TASK_EDGE_TYPES, SESSION_EDGE_TYPES, WORKSPACE_EDGE_TYPES, } from "./edge-mappers.js";
12
+ /**
13
+ * Resolve both endpoints of an edge spec and upsert the edge. Returns `false`
14
+ * (a no-op) if either endpoint node is not projected yet — the next tick or a
15
+ * rebuild will close the gap once both nodes exist.
16
+ */
17
+ async function applyEdge(spec) {
18
+ const [from, to] = await Promise.all([
19
+ findReferenceNodeBySource(spec.from.sourceType, spec.from.sourceId),
20
+ findReferenceNodeBySource(spec.to.sourceType, spec.to.sourceId),
21
+ ]);
22
+ if (!from || !to) {
23
+ return false;
24
+ }
25
+ await upsertEdge(from.id, to.id, spec.type);
26
+ return true;
27
+ }
28
+ /**
29
+ * Upsert an entity reference node and, when its projected text changed, clear the
30
+ * stale embedding so the off-write-path backfill recomputes it.
31
+ *
32
+ * `upsertReferenceNode` sets the embedding only on create, so a re-projection
33
+ * (e.g. a changed task title, persona prompt/model, or workspace scope) would
34
+ * otherwise keep the old vector and leave semantic search stale. We detect the
35
+ * change via the `projectionHash` (compared to the stored one) and reset the
36
+ * embedding to `[]`; new nodes already start empty. Centralized here so every
37
+ * caller (event subscriber, reconciliation scan, rebuild) stays consistent.
38
+ *
39
+ * @returns The (stable) node ID.
40
+ */
41
+ async function upsertEntityNode(input) {
42
+ const newHash = input.extraProps?.projectionHash;
43
+ const existing = await getReferenceNodeProps(input.sourceType, input.sourceId);
44
+ const nodeId = await upsertReferenceNode(input);
45
+ if (existing && newHash !== undefined && existing.projectionHash !== newHash) {
46
+ await updateNode(nodeId, { embedding: [] });
47
+ }
48
+ return nodeId;
49
+ }
50
+ /**
51
+ * Reconcile a node's outgoing structural edges.
52
+ *
53
+ * Always (re-)applies the current edge set with `MERGE` (idempotent + additive),
54
+ * so an edge that was skipped on an earlier pass because an endpoint node did not
55
+ * exist yet is healed on a later pass — even when the node's projection hash is
56
+ * unchanged. `removeOutgoingEdges` (clearing stale edges, e.g. from a changed
57
+ * foreign key) runs **only** when `clearStale` is set, i.e. when the projection
58
+ * actually changed; an unchanged re-apply must never bulk-remove.
59
+ */
60
+ async function reconcileEdges(nodeId, types, specs, clearStale) {
61
+ if (clearStale) {
62
+ await removeOutgoingEdges(nodeId, types);
63
+ }
64
+ for (const spec of specs) {
65
+ await applyEdge(spec);
66
+ }
67
+ }
68
+ /** Resolve a workspace's LINKED_TO edge specs from the junction table. */
69
+ function workspaceLinkEdges(workspaceId) {
70
+ return workspaceEnvironmentLinkStore
71
+ .getLinkedEnvironmentIds(workspaceId)
72
+ .map((environmentId) => workspaceLinkEdge(workspaceId, environmentId));
73
+ }
74
+ /** Project a Task node + reconcile its IN_WORKSPACE / PART_OF / DEPENDS_ON edges (clearing stale). */
75
+ export async function projectTask(task) {
76
+ const nodeId = await upsertEntityNode(taskToNodeInput(task));
77
+ await reconcileEdges(nodeId, TASK_EDGE_TYPES, taskEdges(task), true);
78
+ }
79
+ /** Project a Workspace node + reconcile its LINKED_TO edges (clearing stale). */
80
+ export async function projectWorkspace(workspace) {
81
+ // The link set feeds the projection hash (so link changes trigger re-project)
82
+ // and the LINKED_TO edge reconciliation below.
83
+ const nodeId = await upsertEntityNode(workspaceToNodeInput(workspace, workspaceEnvironmentLinkStore.getLinkedEnvironmentIds(workspace.id)));
84
+ await reconcileEdges(nodeId, WORKSPACE_EDGE_TYPES, workspaceLinkEdges(workspace.id), true);
85
+ }
86
+ /**
87
+ * Project a Session node + reconcile its ATTEMPT_OF / RAN_IN / USED_PERSONA edges
88
+ * (clearing stale). The incoming SPAWNED edge is handled by {@link linkSessionSpawn}.
89
+ *
90
+ * @param workspaceId - Resolved from the session's task (empty if none).
91
+ */
92
+ export async function projectSession(session, workspaceId) {
93
+ const nodeId = await upsertEntityNode(sessionToNodeInput(session, workspaceId));
94
+ await reconcileEdges(nodeId, SESSION_EDGE_TYPES, sessionEdges(session), true);
95
+ }
96
+ /**
97
+ * Re-apply an entity's outgoing edges with `MERGE` **without** clearing stale ones
98
+ * (additive). Used by the reconciliation scan for rows whose hash is unchanged, so
99
+ * any edge previously dropped (endpoint projected later, or a transient failure) is
100
+ * eventually healed. A no-op if the node is not projected yet (the next pass or a
101
+ * rebuild closes the gap once it exists).
102
+ */
103
+ export async function reconcileTaskEdges(task) {
104
+ const node = await findReferenceNodeBySource(REFERENCE_SOURCE.TASK, task.id);
105
+ if (node) {
106
+ await reconcileEdges(node.id, TASK_EDGE_TYPES, taskEdges(task), false);
107
+ }
108
+ }
109
+ /** Additive re-apply of a Workspace's LINKED_TO edges (see {@link reconcileTaskEdges}). */
110
+ export async function reconcileWorkspaceEdges(workspace) {
111
+ const node = await findReferenceNodeBySource(REFERENCE_SOURCE.WORKSPACE, workspace.id);
112
+ if (node) {
113
+ await reconcileEdges(node.id, WORKSPACE_EDGE_TYPES, workspaceLinkEdges(workspace.id), false);
114
+ }
115
+ }
116
+ /** Additive re-apply of a Session's outgoing edges (see {@link reconcileTaskEdges}). */
117
+ export async function reconcileSessionEdges(session) {
118
+ const node = await findReferenceNodeBySource(REFERENCE_SOURCE.SESSION, session.id);
119
+ if (node) {
120
+ await reconcileEdges(node.id, SESSION_EDGE_TYPES, sessionEdges(session), false);
121
+ }
122
+ }
123
+ /**
124
+ * Upsert the incoming SPAWNED edge (parent session → this session), if any.
125
+ *
126
+ * Run as a separate pass *after* all session nodes exist so that ordering
127
+ * (e.g. a child projected before its parent) never permanently drops the edge.
128
+ * Idempotent (MERGE), so it is safe to call every pass.
129
+ */
130
+ export async function linkSessionSpawn(session) {
131
+ const spawn = sessionSpawnEdge(session);
132
+ if (spawn) {
133
+ await applyEdge(spawn);
134
+ }
135
+ }
136
+ /** Project a Persona node (no outgoing structural edges). */
137
+ export async function projectPersona(persona) {
138
+ await upsertEntityNode(personaToNodeInput(persona));
139
+ }
140
+ /** Project an Environment node (no outgoing structural edges). */
141
+ export async function projectEnvironment(environment) {
142
+ await upsertEntityNode(environmentToNodeInput(environment));
143
+ }
144
+ /** Remove a Task node and its edges. */
145
+ export async function unprojectTask(id) {
146
+ await deleteReferenceNodeBySource(REFERENCE_SOURCE.TASK, id);
147
+ }
148
+ /** Remove a Workspace node and its edges. */
149
+ export async function unprojectWorkspace(id) {
150
+ await deleteReferenceNodeBySource(REFERENCE_SOURCE.WORKSPACE, id);
151
+ }
152
+ /** Remove a Session node and its edges. */
153
+ export async function unprojectSession(id) {
154
+ await deleteReferenceNodeBySource(REFERENCE_SOURCE.SESSION, id);
155
+ }
156
+ /** Remove a Persona node and its edges. */
157
+ export async function unprojectPersona(id) {
158
+ await deleteReferenceNodeBySource(REFERENCE_SOURCE.PERSONA, id);
159
+ }
160
+ /** Remove an Environment node and its edges. */
161
+ export async function unprojectEnvironment(id) {
162
+ await deleteReferenceNodeBySource(REFERENCE_SOURCE.ENVIRONMENT, id);
163
+ }
164
+ //# sourceMappingURL=project-entity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-entity.js","sourceRoot":"","sources":["../../src/projection/project-entity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Incrementally project a session's transcript log into TranscriptChunk nodes
3
+ * (#1258).
4
+ *
5
+ * Uses a per-session byte-offset cursor (`logByteOffset`, with `chunkCount`)
6
+ * stored on the Session node so each pass reads + chunks + embeds **only new
7
+ * log content** since the last pass — O(new bytes), never re-reading the whole
8
+ * transcript. New chunks are appended as reference nodes (`transcript_chunk`)
9
+ * with a `PART_OF` edge to the session. The chunk text is re-derivable from the
10
+ * log, so the keystone holds.
11
+ *
12
+ * @module
13
+ */
14
+ import { type Embedder } from "@grackle-ai/knowledge";
15
+ import type { SessionRow } from "@grackle-ai/database";
16
+ /**
17
+ * Project new transcript content for one session.
18
+ *
19
+ * @returns The number of new chunk nodes created this pass.
20
+ */
21
+ export declare function projectSessionTranscript(session: SessionRow, embedder: Embedder): Promise<number>;
22
+ /**
23
+ * Delete all transcript-chunk nodes for a session. Used when a session is
24
+ * pruned (its node's `DETACH DELETE` does not remove the separate chunk nodes).
25
+ *
26
+ * @returns The number of chunk nodes removed.
27
+ */
28
+ export declare function unprojectSessionTranscript(sessionId: string): Promise<number>;
29
+ //# sourceMappingURL=project-transcript.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-transcript.d.ts","sourceRoot":"","sources":["../../src/projection/project-transcript.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EASL,KAAK,QAAQ,EACd,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAKvD;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,UAAU,EACnB,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,MAAM,CAAC,CAiEjB;AAED;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAEnF"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Incrementally project a session's transcript log into TranscriptChunk nodes
3
+ * (#1258).
4
+ *
5
+ * Uses a per-session byte-offset cursor (`logByteOffset`, with `chunkCount`)
6
+ * stored on the Session node so each pass reads + chunks + embeds **only new
7
+ * log content** since the last pass — O(new bytes), never re-reading the whole
8
+ * transcript. New chunks are appended as reference nodes (`transcript_chunk`)
9
+ * with a `PART_OF` edge to the session. The chunk text is re-derivable from the
10
+ * log, so the keystone holds.
11
+ *
12
+ * @module
13
+ */
14
+ import { logWriter } from "@grackle-ai/core";
15
+ import { createTranscriptChunker, ingest, getReferenceNodeProps, upsertReferenceNode, upsertEdge, deleteReferenceNodesByPrefix, REFERENCE_SOURCE, EDGE_TYPE, } from "@grackle-ai/knowledge";
16
+ /** Max characters of chunk text kept in the node `label` (a short preview). */
17
+ const CHUNK_PREVIEW_LENGTH = 120;
18
+ /**
19
+ * Project new transcript content for one session.
20
+ *
21
+ * @returns The number of new chunk nodes created this pass.
22
+ */
23
+ export async function projectSessionTranscript(session, embedder) {
24
+ if (!session.logPath) {
25
+ return 0;
26
+ }
27
+ // The Session node must already be projected (it carries the cursor + scope).
28
+ const sessionProps = await getReferenceNodeProps(REFERENCE_SOURCE.SESSION, session.id);
29
+ if (!sessionProps) {
30
+ return 0;
31
+ }
32
+ const sessionNodeId = sessionProps.id;
33
+ const sessionWorkspaceId = sessionProps.workspaceId ?? "";
34
+ const byteOffset = typeof sessionProps.logByteOffset === "number" ? sessionProps.logByteOffset : 0;
35
+ let chunkCount = typeof sessionProps.chunkCount === "number" ? sessionProps.chunkCount : 0;
36
+ // Read only the bytes appended since the last pass (O(new bytes), not O(log)).
37
+ const { content: newContent, nextOffset } = logWriter.readLogFrom(session.logPath, byteOffset);
38
+ // Truncation/rewrite: the log shrank below our cursor, so `readLogFrom` reset
39
+ // to offset 0. Clear the stale chunk nodes and reset the chunk index so the
40
+ // re-ingestion from the start neither collides with nor strands old chunks.
41
+ const truncated = nextOffset < byteOffset;
42
+ if (truncated) {
43
+ await deleteReferenceNodesByPrefix(REFERENCE_SOURCE.TRANSCRIPT_CHUNK, `${session.id}#`);
44
+ chunkCount = 0;
45
+ }
46
+ let created = 0;
47
+ if (newContent) {
48
+ const embeddedChunks = await ingest(newContent, createTranscriptChunker(), embedder, {
49
+ sessionId: session.id,
50
+ });
51
+ for (const chunk of embeddedChunks) {
52
+ const index = chunkCount + chunk.index;
53
+ const chunkNodeId = await upsertReferenceNode({
54
+ sourceType: REFERENCE_SOURCE.TRANSCRIPT_CHUNK,
55
+ sourceId: `${session.id}#${index}`,
56
+ label: chunk.text.slice(0, CHUNK_PREVIEW_LENGTH),
57
+ content: chunk.text,
58
+ workspaceId: sessionWorkspaceId,
59
+ embedding: chunk.vector,
60
+ });
61
+ await upsertEdge(chunkNodeId, sessionNodeId, EDGE_TYPE.PART_OF);
62
+ }
63
+ chunkCount += embeddedChunks.length;
64
+ created = embeddedChunks.length;
65
+ }
66
+ // Persist the cursor whenever it moved (or a truncation reset it) — even with
67
+ // no new chunks. `readLogFrom` can advance past an over-long line that yielded
68
+ // no complete line this pass; not persisting that would stall the cursor and
69
+ // re-read the same bytes forever.
70
+ if (truncated || nextOffset !== byteOffset) {
71
+ await upsertReferenceNode({
72
+ sourceType: REFERENCE_SOURCE.SESSION,
73
+ sourceId: session.id,
74
+ label: sessionProps.label ?? "",
75
+ workspaceId: sessionWorkspaceId,
76
+ extraProps: { logByteOffset: nextOffset, chunkCount },
77
+ });
78
+ }
79
+ return created;
80
+ }
81
+ /**
82
+ * Delete all transcript-chunk nodes for a session. Used when a session is
83
+ * pruned (its node's `DETACH DELETE` does not remove the separate chunk nodes).
84
+ *
85
+ * @returns The number of chunk nodes removed.
86
+ */
87
+ export async function unprojectSessionTranscript(sessionId) {
88
+ return deleteReferenceNodesByPrefix(REFERENCE_SOURCE.TRANSCRIPT_CHUNK, `${sessionId}#`);
89
+ }
90
+ //# sourceMappingURL=project-transcript.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-transcript.js","sourceRoot":"","sources":["../../src/projection/project-transcript.ts"],"names":[],"mappings":"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"}