@starascendin/lifeos-mcp 0.7.67 → 0.7.69
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 +18 -15
- package/dist/build-info.d.ts +2 -2
- package/dist/build-info.js +2 -2
- package/dist/index.js +297 -387
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,17 +24,20 @@ npx @starascendin/lifeos-mcp --url <your-convex-url> --user-id <your-user-id> --
|
|
|
24
24
|
| User ID | `--user-id`, `-i` | `LIFEOS_USER_ID` | Your LifeOS user ID |
|
|
25
25
|
| API Key | `--api-key`, `-k` | `LIFEOS_API_KEY` | Your API key for authentication |
|
|
26
26
|
|
|
27
|
-
### Optional
|
|
27
|
+
### Optional FalkorDB Sidecar Parameters
|
|
28
28
|
|
|
29
|
-
These are required only for the
|
|
29
|
+
These are required only for the Falkor graph tools.
|
|
30
30
|
|
|
31
31
|
| Env Variable | Description |
|
|
32
32
|
|--------------|-------------|
|
|
33
|
-
| `
|
|
34
|
-
| `
|
|
35
|
-
| `
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
33
|
+
| `FALKOR_BROWSER_ENDPOINT` | FalkorDB Browser HTTP endpoint, defaults to `https://falkordb.apps.rjlabs.dev` |
|
|
34
|
+
| `FALKOR_GRAPH` | Falkor graph name, defaults to `lifeos_ppv` |
|
|
35
|
+
| `FALKOR_PASS` | Falkor password, used to mint short-lived browser tokens |
|
|
36
|
+
| `FALKOR_TOKEN` / `FALKOR_PAT` | Optional pre-provisioned Falkor browser token |
|
|
37
|
+
| `FALKOR_USER` | Falkor username, defaults to `default` |
|
|
38
|
+
| `FALKOR_HOST` | Browser backend Redis host, defaults to `localhost` |
|
|
39
|
+
| `FALKOR_PORT` | Browser backend Redis port, defaults to `6379` |
|
|
40
|
+
| `FALKOR_TLS` | Browser backend Redis TLS flag, defaults to `false` |
|
|
38
41
|
|
|
39
42
|
### Getting Your Credentials
|
|
40
43
|
|
|
@@ -190,16 +193,16 @@ Add to your `.mcp.json` (project root or `~/.claude/mcp.json`):
|
|
|
190
193
|
- **refresh_unified_graph_cache** - Recompute inferred graph links and refresh the cached projection
|
|
191
194
|
- **upsert_unified_graph_link / delete_unified_graph_link** - Create or remove manual links between graph nodes
|
|
192
195
|
|
|
193
|
-
###
|
|
194
|
-
- **
|
|
195
|
-
- **
|
|
196
|
-
- **
|
|
197
|
-
- **
|
|
196
|
+
### FalkorDB Sidecar Graph
|
|
197
|
+
- **falkor_graph_schema** - Inspect the Falkor graph contract, configured graph, allowed labels, relationship types, and examples
|
|
198
|
+
- **falkor_graph_query** - Run guarded read-only Cypher against the Falkor PPV sidecar
|
|
199
|
+
- **falkor_graph_link** - Create/update an agent-owned `AGENT_LINK` relationship without mutating Convex-synced nodes or inferred PPV relationships
|
|
200
|
+
- **falkor_graph_unlink** - Remove a relationship previously created by `falkor_graph_link`
|
|
198
201
|
|
|
199
|
-
|
|
202
|
+
Falkor graph rules:
|
|
200
203
|
- Convex remains canonical for entity data.
|
|
201
|
-
-
|
|
202
|
-
- Agents may query graph data and write only agent-owned
|
|
204
|
+
- FalkorDB is a graph query/read sidecar for the PPV experiment.
|
|
205
|
+
- Agents may query graph data and write only agent-owned `AGENT_LINK` relationships.
|
|
203
206
|
- Every agent-created link must include a reason and confidence.
|
|
204
207
|
- Raw read queries should include `LIMIT`.
|
|
205
208
|
|
package/dist/build-info.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.7.
|
|
2
|
-
export declare const BUILD_TIME = "2026-05-
|
|
1
|
+
export declare const VERSION = "0.7.69";
|
|
2
|
+
export declare const BUILD_TIME = "2026-05-17T20:11:00.340Z";
|
package/dist/build-info.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// AUTO-GENERATED — do not edit. Regenerated on every build.
|
|
2
|
-
export const VERSION = "0.7.
|
|
3
|
-
export const BUILD_TIME = "2026-05-
|
|
2
|
+
export const VERSION = "0.7.69";
|
|
3
|
+
export const BUILD_TIME = "2026-05-17T20:11:00.340Z";
|
package/dist/index.js
CHANGED
|
@@ -60,7 +60,7 @@ program
|
|
|
60
60
|
});
|
|
61
61
|
program
|
|
62
62
|
.command("ppv <action>")
|
|
63
|
-
.description("Manage PPV as JSON. Actions: workspace, graph, vision-graph, seed, vision, activate-vision, set-active-vision, identity, pillar
|
|
63
|
+
.description("Manage PPV as JSON. Actions: workspace, graph, vision-graph, seed, vision, activate-vision, set-active-vision, identity, pillar.")
|
|
64
64
|
.option("--file <file>", "JSON payload file for mutating actions. workspace, graph, vision-graph, and seed do not require a file.")
|
|
65
65
|
.action((action, commandOptions) => {
|
|
66
66
|
directCliCommand = {
|
|
@@ -685,6 +685,19 @@ const TOOLS = [
|
|
|
685
685
|
type: "string",
|
|
686
686
|
description: "Updated goals (optional)",
|
|
687
687
|
},
|
|
688
|
+
whatWentWell: {
|
|
689
|
+
type: "string",
|
|
690
|
+
description: "Retrospective notes on what went well (optional)",
|
|
691
|
+
},
|
|
692
|
+
whatCouldImprove: {
|
|
693
|
+
type: "string",
|
|
694
|
+
description: "Retrospective notes on what could improve (optional)",
|
|
695
|
+
},
|
|
696
|
+
actionItems: {
|
|
697
|
+
type: "array",
|
|
698
|
+
items: { type: "string" },
|
|
699
|
+
description: "Retrospective action items (optional)",
|
|
700
|
+
},
|
|
688
701
|
},
|
|
689
702
|
required: ["cycleId"],
|
|
690
703
|
},
|
|
@@ -4542,7 +4555,7 @@ const TOOLS = [
|
|
|
4542
4555
|
// PPV v1 tools
|
|
4543
4556
|
{
|
|
4544
4557
|
name: "get_ppv_workspace",
|
|
4545
|
-
description: "Get the PPV workspace for the selected vision, plus all owned visions, active-vision context, identity, pillars, linked projects
|
|
4558
|
+
description: "Get the PPV workspace for the selected vision, plus all owned visions, active-vision context, identity, pillars, and linked projects.",
|
|
4546
4559
|
inputSchema: {
|
|
4547
4560
|
type: "object",
|
|
4548
4561
|
properties: {
|
|
@@ -4553,7 +4566,7 @@ const TOOLS = [
|
|
|
4553
4566
|
},
|
|
4554
4567
|
{
|
|
4555
4568
|
name: "get_active_vision_graph",
|
|
4556
|
-
description: "Best default graph read for PPV questions. Get the unified active-vision graph linking PPV vision, identity, pillars,
|
|
4569
|
+
description: "Best default graph read for PPV questions. Get the unified active-vision graph linking PPV vision, identity, pillars, related projects/issues, and recent voice memos.",
|
|
4557
4570
|
inputSchema: {
|
|
4558
4571
|
type: "object",
|
|
4559
4572
|
properties: {
|
|
@@ -4730,52 +4743,52 @@ const TOOLS = [
|
|
|
4730
4743
|
},
|
|
4731
4744
|
},
|
|
4732
4745
|
{
|
|
4733
|
-
name: "
|
|
4734
|
-
description: "Inspect the
|
|
4746
|
+
name: "falkor_graph_schema",
|
|
4747
|
+
description: "Inspect the FalkorDB sidecar graph contract for agents: configured graph, mirrored PPV labels, safe Cypher rules, relationship types, and examples. Use before raw Falkor Cypher graph work.",
|
|
4735
4748
|
inputSchema: {
|
|
4736
4749
|
type: "object",
|
|
4737
4750
|
properties: {},
|
|
4738
4751
|
},
|
|
4739
4752
|
},
|
|
4740
4753
|
{
|
|
4741
|
-
name: "
|
|
4742
|
-
description: "Run guarded read-only
|
|
4754
|
+
name: "falkor_graph_query",
|
|
4755
|
+
description: "Run guarded read-only Cypher against the LifeOS FalkorDB graph sidecar. Allowed statements are MATCH, WITH, RETURN, EXPLAIN, and PROFILE query shapes only. MATCH queries should include LIMIT unless doing count aggregation. Do not use this for Convex-canonical writes.",
|
|
4743
4756
|
inputSchema: {
|
|
4744
4757
|
type: "object",
|
|
4745
4758
|
properties: {
|
|
4746
4759
|
query: {
|
|
4747
4760
|
type: "string",
|
|
4748
|
-
description: "Read-only
|
|
4761
|
+
description: "Read-only Cypher. MATCH queries should include LIMIT unless they are aggregate count queries.",
|
|
4749
4762
|
},
|
|
4750
4763
|
maxRows: {
|
|
4751
4764
|
type: "number",
|
|
4752
|
-
description: "Maximum rows returned
|
|
4765
|
+
description: "Maximum rows returned after execution. Default 50, max 200.",
|
|
4753
4766
|
},
|
|
4754
4767
|
},
|
|
4755
4768
|
required: ["query"],
|
|
4756
4769
|
},
|
|
4757
4770
|
},
|
|
4758
4771
|
{
|
|
4759
|
-
name: "
|
|
4760
|
-
description: "Create or update an agent-owned
|
|
4772
|
+
name: "falkor_graph_link",
|
|
4773
|
+
description: "Create or update an agent-owned AGENT_LINK relationship in FalkorDB between two existing LifeOS graph records. This never mutates Convex-synced nodes or inferred PPV relationships.",
|
|
4761
4774
|
inputSchema: {
|
|
4762
4775
|
type: "object",
|
|
4763
4776
|
properties: {
|
|
4764
|
-
|
|
4777
|
+
fromLabel: {
|
|
4765
4778
|
type: "string",
|
|
4766
|
-
description: "Source
|
|
4779
|
+
description: "Source Falkor node label, e.g. PpvPillar.",
|
|
4767
4780
|
},
|
|
4768
4781
|
fromId: {
|
|
4769
4782
|
type: "string",
|
|
4770
|
-
description: "Source record id
|
|
4783
|
+
description: "Source Convex record id stored in node.convexId.",
|
|
4771
4784
|
},
|
|
4772
|
-
|
|
4785
|
+
toLabel: {
|
|
4773
4786
|
type: "string",
|
|
4774
|
-
description: "Target
|
|
4787
|
+
description: "Target Falkor node label, e.g. PpvProject.",
|
|
4775
4788
|
},
|
|
4776
4789
|
toId: {
|
|
4777
4790
|
type: "string",
|
|
4778
|
-
description: "Target record id
|
|
4791
|
+
description: "Target Convex record id stored in node.convexId.",
|
|
4779
4792
|
},
|
|
4780
4793
|
kind: {
|
|
4781
4794
|
type: "string",
|
|
@@ -4799,31 +4812,31 @@ const TOOLS = [
|
|
|
4799
4812
|
additionalProperties: true,
|
|
4800
4813
|
},
|
|
4801
4814
|
},
|
|
4802
|
-
required: ["
|
|
4815
|
+
required: ["fromLabel", "fromId", "toLabel", "toId", "kind", "reason"],
|
|
4803
4816
|
},
|
|
4804
4817
|
},
|
|
4805
4818
|
{
|
|
4806
|
-
name: "
|
|
4807
|
-
description: "Remove an agent-owned
|
|
4819
|
+
name: "falkor_graph_unlink",
|
|
4820
|
+
description: "Remove an agent-owned FalkorDB AGENT_LINK relationship previously created by falkor_graph_link.",
|
|
4808
4821
|
inputSchema: {
|
|
4809
4822
|
type: "object",
|
|
4810
4823
|
properties: {
|
|
4811
|
-
|
|
4824
|
+
fromLabel: { type: "string", description: "Source Falkor node label." },
|
|
4812
4825
|
fromId: {
|
|
4813
4826
|
type: "string",
|
|
4814
|
-
description: "Source record id
|
|
4827
|
+
description: "Source Convex record id stored in node.convexId.",
|
|
4815
4828
|
},
|
|
4816
|
-
|
|
4829
|
+
toLabel: { type: "string", description: "Target Falkor node label." },
|
|
4817
4830
|
toId: {
|
|
4818
4831
|
type: "string",
|
|
4819
|
-
description: "Target record id
|
|
4832
|
+
description: "Target Convex record id stored in node.convexId.",
|
|
4820
4833
|
},
|
|
4821
4834
|
kind: {
|
|
4822
4835
|
type: "string",
|
|
4823
4836
|
description: "Relationship kind used when linking.",
|
|
4824
4837
|
},
|
|
4825
4838
|
},
|
|
4826
|
-
required: ["
|
|
4839
|
+
required: ["fromLabel", "fromId", "toLabel", "toId", "kind"],
|
|
4827
4840
|
},
|
|
4828
4841
|
},
|
|
4829
4842
|
{
|
|
@@ -4914,7 +4927,7 @@ const TOOLS = [
|
|
|
4914
4927
|
},
|
|
4915
4928
|
{
|
|
4916
4929
|
name: "delete_ppv_vision",
|
|
4917
|
-
description: "Delete a PPV vision and cascade-delete its identity
|
|
4930
|
+
description: "Delete a PPV vision and cascade-delete its identity and pillars.",
|
|
4918
4931
|
inputSchema: {
|
|
4919
4932
|
type: "object",
|
|
4920
4933
|
properties: {
|
|
@@ -5017,143 +5030,6 @@ const TOOLS = [
|
|
|
5017
5030
|
required: ["pillarId"],
|
|
5018
5031
|
},
|
|
5019
5032
|
},
|
|
5020
|
-
{
|
|
5021
|
-
name: "create_ppv_weekly_action",
|
|
5022
|
-
description: "Create an identity-aligned PPV weekly action.",
|
|
5023
|
-
inputSchema: {
|
|
5024
|
-
type: "object",
|
|
5025
|
-
properties: {
|
|
5026
|
-
userId: { type: "string", description: "Override user ID" },
|
|
5027
|
-
visionId: { type: "string", description: "PPV vision ID" },
|
|
5028
|
-
pillarId: { type: "string", description: "PPV pillar ID" },
|
|
5029
|
-
projectId: {
|
|
5030
|
-
type: "string",
|
|
5031
|
-
description: "Existing LifeOS project ID",
|
|
5032
|
-
},
|
|
5033
|
-
category: { type: "string", description: "Action category" },
|
|
5034
|
-
action: { type: "string", description: "Small action to take" },
|
|
5035
|
-
status: {
|
|
5036
|
-
type: "string",
|
|
5037
|
-
enum: ["pending", "in_progress", "completed", "skipped"],
|
|
5038
|
-
description: "Action status",
|
|
5039
|
-
},
|
|
5040
|
-
scheduledDate: { type: "string", description: "ISO scheduled date" },
|
|
5041
|
-
week: { type: "string", description: "ISO week label" },
|
|
5042
|
-
},
|
|
5043
|
-
required: ["visionId", "category", "action"],
|
|
5044
|
-
},
|
|
5045
|
-
},
|
|
5046
|
-
{
|
|
5047
|
-
name: "update_ppv_weekly_action",
|
|
5048
|
-
description: "Update a PPV weekly action.",
|
|
5049
|
-
inputSchema: {
|
|
5050
|
-
type: "object",
|
|
5051
|
-
properties: {
|
|
5052
|
-
userId: { type: "string", description: "Override user ID" },
|
|
5053
|
-
actionId: { type: "string", description: "PPV weekly action ID" },
|
|
5054
|
-
pillarId: { type: "string", description: "PPV pillar ID" },
|
|
5055
|
-
projectId: {
|
|
5056
|
-
type: "string",
|
|
5057
|
-
description: "Existing LifeOS project ID",
|
|
5058
|
-
},
|
|
5059
|
-
category: { type: "string", description: "Updated category" },
|
|
5060
|
-
action: { type: "string", description: "Updated action" },
|
|
5061
|
-
status: {
|
|
5062
|
-
type: "string",
|
|
5063
|
-
enum: ["pending", "in_progress", "completed", "skipped"],
|
|
5064
|
-
description: "Updated status",
|
|
5065
|
-
},
|
|
5066
|
-
scheduledDate: { type: "string", description: "ISO scheduled date" },
|
|
5067
|
-
week: { type: "string", description: "ISO week label" },
|
|
5068
|
-
},
|
|
5069
|
-
required: ["actionId"],
|
|
5070
|
-
},
|
|
5071
|
-
},
|
|
5072
|
-
{
|
|
5073
|
-
name: "delete_ppv_weekly_action",
|
|
5074
|
-
description: "Delete a PPV weekly action.",
|
|
5075
|
-
inputSchema: {
|
|
5076
|
-
type: "object",
|
|
5077
|
-
properties: {
|
|
5078
|
-
userId: { type: "string", description: "Override user ID" },
|
|
5079
|
-
actionId: { type: "string", description: "PPV weekly action ID" },
|
|
5080
|
-
},
|
|
5081
|
-
required: ["actionId"],
|
|
5082
|
-
},
|
|
5083
|
-
},
|
|
5084
|
-
{
|
|
5085
|
-
name: "create_ppv_reflection",
|
|
5086
|
-
description: "Create a PPV weekly reflection.",
|
|
5087
|
-
inputSchema: {
|
|
5088
|
-
type: "object",
|
|
5089
|
-
properties: {
|
|
5090
|
-
userId: { type: "string", description: "Override user ID" },
|
|
5091
|
-
visionId: { type: "string", description: "PPV vision ID" },
|
|
5092
|
-
week: { type: "string", description: "ISO week label" },
|
|
5093
|
-
questions: {
|
|
5094
|
-
type: "array",
|
|
5095
|
-
items: {
|
|
5096
|
-
type: "object",
|
|
5097
|
-
properties: {
|
|
5098
|
-
question: { type: "string" },
|
|
5099
|
-
answer: { type: "string" },
|
|
5100
|
-
},
|
|
5101
|
-
required: ["question", "answer"],
|
|
5102
|
-
},
|
|
5103
|
-
description: "Reflection questions and answers",
|
|
5104
|
-
},
|
|
5105
|
-
alignmentScore: { type: "number", description: "Optional score" },
|
|
5106
|
-
},
|
|
5107
|
-
required: ["visionId", "week", "questions"],
|
|
5108
|
-
},
|
|
5109
|
-
},
|
|
5110
|
-
{
|
|
5111
|
-
name: "delete_ppv_reflection",
|
|
5112
|
-
description: "Delete a PPV reflection.",
|
|
5113
|
-
inputSchema: {
|
|
5114
|
-
type: "object",
|
|
5115
|
-
properties: {
|
|
5116
|
-
userId: { type: "string", description: "Override user ID" },
|
|
5117
|
-
reflectionId: { type: "string", description: "PPV reflection ID" },
|
|
5118
|
-
},
|
|
5119
|
-
required: ["reflectionId"],
|
|
5120
|
-
},
|
|
5121
|
-
},
|
|
5122
|
-
{
|
|
5123
|
-
name: "create_ppv_adjustment",
|
|
5124
|
-
description: "Create a PPV adjustment from reflection or lived evidence.",
|
|
5125
|
-
inputSchema: {
|
|
5126
|
-
type: "object",
|
|
5127
|
-
properties: {
|
|
5128
|
-
userId: { type: "string", description: "Override user ID" },
|
|
5129
|
-
visionId: { type: "string", description: "PPV vision ID" },
|
|
5130
|
-
sourceReflectionId: {
|
|
5131
|
-
type: "string",
|
|
5132
|
-
description: "Source PPV reflection ID",
|
|
5133
|
-
},
|
|
5134
|
-
observation: { type: "string", description: "What was observed" },
|
|
5135
|
-
decision: { type: "string", description: "What will change" },
|
|
5136
|
-
affectedComponents: {
|
|
5137
|
-
type: "array",
|
|
5138
|
-
items: { type: "string" },
|
|
5139
|
-
description: "Components affected by the adjustment",
|
|
5140
|
-
},
|
|
5141
|
-
},
|
|
5142
|
-
required: ["visionId", "observation", "decision", "affectedComponents"],
|
|
5143
|
-
},
|
|
5144
|
-
},
|
|
5145
|
-
{
|
|
5146
|
-
name: "delete_ppv_adjustment",
|
|
5147
|
-
description: "Delete a PPV adjustment.",
|
|
5148
|
-
inputSchema: {
|
|
5149
|
-
type: "object",
|
|
5150
|
-
properties: {
|
|
5151
|
-
userId: { type: "string", description: "Override user ID" },
|
|
5152
|
-
adjustmentId: { type: "string", description: "PPV adjustment ID" },
|
|
5153
|
-
},
|
|
5154
|
-
required: ["adjustmentId"],
|
|
5155
|
-
},
|
|
5156
|
-
},
|
|
5157
5033
|
// MCP Server Info
|
|
5158
5034
|
{
|
|
5159
5035
|
name: "get_version",
|
|
@@ -5233,22 +5109,22 @@ const PROMPTS = [
|
|
|
5233
5109
|
},
|
|
5234
5110
|
{
|
|
5235
5111
|
name: "ppv",
|
|
5236
|
-
description: "Manage the PPV life design system: vision, identity, pillars,
|
|
5112
|
+
description: "Manage the PPV life design system: vision, identity, pillars, and projects.",
|
|
5237
5113
|
arguments: [
|
|
5238
5114
|
{
|
|
5239
5115
|
name: "intent",
|
|
5240
|
-
description: "What to do with PPV, e.g. create vision,
|
|
5116
|
+
description: "What to do with PPV, e.g. create vision, update identity, add pillar, link project.",
|
|
5241
5117
|
required: false,
|
|
5242
5118
|
},
|
|
5243
5119
|
],
|
|
5244
5120
|
},
|
|
5245
5121
|
{
|
|
5246
|
-
name: "
|
|
5247
|
-
description: "Use the
|
|
5122
|
+
name: "falkor-graph",
|
|
5123
|
+
description: "Use the FalkorDB sidecar graph: query PPV graph relationships with Cypher and create controlled agent-owned graph links.",
|
|
5248
5124
|
arguments: [
|
|
5249
5125
|
{
|
|
5250
5126
|
name: "intent",
|
|
5251
|
-
description: "What to do with the
|
|
5127
|
+
description: "What to do with the Falkor graph, e.g. inspect PPV-project links or add an agent-owned PPV relationship.",
|
|
5252
5128
|
required: false,
|
|
5253
5129
|
},
|
|
5254
5130
|
],
|
|
@@ -5614,7 +5490,7 @@ Do not ask for confirmation; the user intends this planning workflow to mutate L
|
|
|
5614
5490
|
${intentClause}
|
|
5615
5491
|
|
|
5616
5492
|
Use the LifeOS MCP PPV tools:
|
|
5617
|
-
1. Call get_ppv_workspace first to inspect all owned visions, the selected/current vision, identity, pillars, linked projects,
|
|
5493
|
+
1. Call get_ppv_workspace first to inspect all owned visions, the selected/current vision, identity, pillars, linked projects, and available LifeOS projects.
|
|
5618
5494
|
2. If there is no vision and the user wants the example, call seed_ppv_beijing_workspace.
|
|
5619
5495
|
3. For vision changes, call upsert_ppv_vision.
|
|
5620
5496
|
4. If the user wants to change a vision lifecycle without editing content, call set_ppv_vision_status.
|
|
@@ -5623,8 +5499,6 @@ Use the LifeOS MCP PPV tools:
|
|
|
5623
5499
|
7. If the user wants to permanently remove a vision and its PPV subcomponents, call delete_ppv_vision.
|
|
5624
5500
|
8. For identity work, call upsert_ppv_identity.
|
|
5625
5501
|
9. For pillars, call create_ppv_pillar or update_ppv_pillar. Link only existing LifeOS project IDs from get_ppv_workspace.projects.
|
|
5626
|
-
10. For execution, call create_ppv_weekly_action or update_ppv_weekly_action.
|
|
5627
|
-
11. For learning loops, call create_ppv_reflection and create_ppv_adjustment.
|
|
5628
5502
|
|
|
5629
5503
|
Keep the system simple:
|
|
5630
5504
|
- Vision is directional and experiential, not a task list.
|
|
@@ -5632,38 +5506,37 @@ Keep the system simple:
|
|
|
5632
5506
|
- Pillars are ongoing systems.
|
|
5633
5507
|
- Projects are existing LifeOS projects.
|
|
5634
5508
|
- Weekly actions should be small, concrete, and identity-aligned.
|
|
5635
|
-
- Reflections and adjustments should update behavior based on evidence.
|
|
5636
5509
|
|
|
5637
5510
|
After mutating, report exactly what changed and any IDs needed for future updates.`,
|
|
5638
5511
|
},
|
|
5639
5512
|
},
|
|
5640
5513
|
];
|
|
5641
5514
|
},
|
|
5642
|
-
"
|
|
5515
|
+
"falkor-graph": (args) => {
|
|
5643
5516
|
const intentClause = args.intent
|
|
5644
5517
|
? `User intent: ${args.intent}`
|
|
5645
|
-
: "Start by inspecting the
|
|
5518
|
+
: "Start by inspecting the Falkor graph schema and current agent links.";
|
|
5646
5519
|
return [
|
|
5647
5520
|
{
|
|
5648
5521
|
role: "user",
|
|
5649
5522
|
content: {
|
|
5650
5523
|
type: "text",
|
|
5651
|
-
text: `Use the LifeOS
|
|
5524
|
+
text: `Use the LifeOS FalkorDB sidecar graph.
|
|
5652
5525
|
|
|
5653
5526
|
${intentClause}
|
|
5654
5527
|
|
|
5655
5528
|
Use these MCP tools:
|
|
5656
|
-
1. Call
|
|
5657
|
-
2. Use
|
|
5658
|
-
3. Use
|
|
5659
|
-
4. Use
|
|
5529
|
+
1. Call falkor_graph_schema first to inspect the allowed graph contract and examples.
|
|
5530
|
+
2. Use falkor_graph_query for read-only Cypher. MATCH/WITH queries must include LIMIT unless doing count aggregation.
|
|
5531
|
+
3. Use falkor_graph_link when you need to create a relationship between existing Falkor nodes. This writes only AGENT_LINK relationships.
|
|
5532
|
+
4. Use falkor_graph_unlink only to remove relationships previously created by falkor_graph_link.
|
|
5660
5533
|
|
|
5661
5534
|
Rules:
|
|
5662
|
-
- Convex remains canonical for entity data. Do not use
|
|
5663
|
-
-
|
|
5535
|
+
- Convex remains canonical for entity data. Do not use FalkorDB to edit projects or PPV records.
|
|
5536
|
+
- FalkorDB is the graph sidecar.
|
|
5664
5537
|
- Always include a concrete reason and confidence when linking.
|
|
5665
|
-
- Prefer precise
|
|
5666
|
-
- Keep queries scoped with LIMIT and return concise findings plus the
|
|
5538
|
+
- Prefer precise node labels and convexId values from prior reads; do not invent ids.
|
|
5539
|
+
- Keep queries scoped with LIMIT and return concise findings plus the Cypher you used when useful.`,
|
|
5667
5540
|
},
|
|
5668
5541
|
},
|
|
5669
5542
|
];
|
|
@@ -6550,11 +6423,14 @@ Keep it concise and direct. This is an accountability report, not a feel-good su
|
|
|
6550
6423
|
const CONVEX_URL = options.url || process.env.CONVEX_URL;
|
|
6551
6424
|
const API_KEY = options.apiKey || process.env.LIFEOS_API_KEY;
|
|
6552
6425
|
const USER_ID = options.userId || process.env.LIFEOS_USER_ID;
|
|
6553
|
-
const
|
|
6554
|
-
const
|
|
6555
|
-
const
|
|
6556
|
-
const
|
|
6557
|
-
const
|
|
6426
|
+
const FALKOR_BROWSER_ENDPOINT = process.env.FALKOR_BROWSER_ENDPOINT || "https://falkordb.apps.rjlabs.dev";
|
|
6427
|
+
const FALKOR_GRAPH = process.env.FALKOR_GRAPH || "lifeos_ppv";
|
|
6428
|
+
const FALKOR_USER = process.env.FALKOR_USER || "default";
|
|
6429
|
+
const FALKOR_PASS = process.env.FALKOR_PASS;
|
|
6430
|
+
const FALKOR_TOKEN = process.env.FALKOR_TOKEN || process.env.FALKOR_PAT;
|
|
6431
|
+
const FALKOR_HOST = process.env.FALKOR_HOST || "localhost";
|
|
6432
|
+
const FALKOR_PORT = process.env.FALKOR_PORT || "6379";
|
|
6433
|
+
const FALKOR_TLS = process.env.FALKOR_TLS || "false";
|
|
6558
6434
|
// Validate required configuration
|
|
6559
6435
|
const missingConfig = [];
|
|
6560
6436
|
if (!CONVEX_URL)
|
|
@@ -6666,13 +6542,6 @@ async function runDirectCliCommand(command) {
|
|
|
6666
6542
|
pillar: "create_ppv_pillar",
|
|
6667
6543
|
"update-pillar": "update_ppv_pillar",
|
|
6668
6544
|
"delete-pillar": "delete_ppv_pillar",
|
|
6669
|
-
action: "create_ppv_weekly_action",
|
|
6670
|
-
"update-action": "update_ppv_weekly_action",
|
|
6671
|
-
"delete-action": "delete_ppv_weekly_action",
|
|
6672
|
-
reflection: "create_ppv_reflection",
|
|
6673
|
-
"delete-reflection": "delete_ppv_reflection",
|
|
6674
|
-
adjustment: "create_ppv_adjustment",
|
|
6675
|
-
"delete-adjustment": "delete_ppv_adjustment",
|
|
6676
6545
|
};
|
|
6677
6546
|
const tool = actionToTool[command.action];
|
|
6678
6547
|
if (!tool) {
|
|
@@ -6708,55 +6577,6 @@ async function callConvexJsonEndpoint(path, body) {
|
|
|
6708
6577
|
}
|
|
6709
6578
|
return await response.json();
|
|
6710
6579
|
}
|
|
6711
|
-
const SURREAL_AGENT_LINK_TABLE = "lifeos_agent_links";
|
|
6712
|
-
const SURREAL_TABLE_PREFIXES = ["lifeos_", "life_", "ppv1_"];
|
|
6713
|
-
const SURREAL_RELATION_TABLES = [
|
|
6714
|
-
"ppv_has_identity",
|
|
6715
|
-
"ppv_has_pillar",
|
|
6716
|
-
"ppv_has_action",
|
|
6717
|
-
"ppv_has_reflection",
|
|
6718
|
-
"ppv_has_adjustment",
|
|
6719
|
-
"ppv_pillar_drives_action",
|
|
6720
|
-
"ppv_pillar_supports_project",
|
|
6721
|
-
"ppv_project_executes_action",
|
|
6722
|
-
"ppv_reflection_inspires_adjustment",
|
|
6723
|
-
SURREAL_AGENT_LINK_TABLE,
|
|
6724
|
-
];
|
|
6725
|
-
const SURREAL_MUTATING_KEYWORDS = [
|
|
6726
|
-
"ALTER",
|
|
6727
|
-
"CREATE",
|
|
6728
|
-
"DEFINE",
|
|
6729
|
-
"DELETE",
|
|
6730
|
-
"INSERT",
|
|
6731
|
-
"KILL",
|
|
6732
|
-
"LIVE",
|
|
6733
|
-
"REBUILD",
|
|
6734
|
-
"RELATE",
|
|
6735
|
-
"REMOVE",
|
|
6736
|
-
"SLEEP",
|
|
6737
|
-
"THROW",
|
|
6738
|
-
"UPDATE",
|
|
6739
|
-
"UPSERT",
|
|
6740
|
-
];
|
|
6741
|
-
function requireSurrealConfig() {
|
|
6742
|
-
const missing = [];
|
|
6743
|
-
if (!SURREAL_ENDPOINT)
|
|
6744
|
-
missing.push("SURREAL_ENDPOINT");
|
|
6745
|
-
if (!SURREAL_USER)
|
|
6746
|
-
missing.push("SURREAL_USER");
|
|
6747
|
-
if (!SURREAL_PASS)
|
|
6748
|
-
missing.push("SURREAL_PASS");
|
|
6749
|
-
if (missing.length > 0) {
|
|
6750
|
-
throw new Error(`Missing SurrealDB configuration: ${missing.join(", ")}. Set SURREAL_ENDPOINT, SURREAL_USER, SURREAL_PASS, SURREAL_NS, and SURREAL_DB in the agent runtime.`);
|
|
6751
|
-
}
|
|
6752
|
-
return {
|
|
6753
|
-
endpoint: SURREAL_ENDPOINT,
|
|
6754
|
-
user: SURREAL_USER,
|
|
6755
|
-
pass: SURREAL_PASS,
|
|
6756
|
-
ns: SURREAL_NS,
|
|
6757
|
-
db: SURREAL_DB,
|
|
6758
|
-
};
|
|
6759
|
-
}
|
|
6760
6580
|
function stripSqlCommentsAndStrings(sql) {
|
|
6761
6581
|
let output = "";
|
|
6762
6582
|
let index = 0;
|
|
@@ -6809,74 +6629,11 @@ function stripSqlCommentsAndStrings(sql) {
|
|
|
6809
6629
|
}
|
|
6810
6630
|
return output;
|
|
6811
6631
|
}
|
|
6812
|
-
function splitSqlStatements(sql) {
|
|
6813
|
-
return stripSqlCommentsAndStrings(sql)
|
|
6814
|
-
.split(";")
|
|
6815
|
-
.map((statement) => statement.trim())
|
|
6816
|
-
.filter(Boolean);
|
|
6817
|
-
}
|
|
6818
|
-
function assertReadOnlySurrealSql(sql) {
|
|
6819
|
-
const statements = splitSqlStatements(sql);
|
|
6820
|
-
if (statements.length === 0) {
|
|
6821
|
-
throw new Error("SurrealQL query is empty.");
|
|
6822
|
-
}
|
|
6823
|
-
if (statements.length > 5) {
|
|
6824
|
-
throw new Error("SurrealQL query is too broad. Use 5 statements or fewer.");
|
|
6825
|
-
}
|
|
6826
|
-
for (const statement of statements) {
|
|
6827
|
-
const upper = statement.toUpperCase();
|
|
6828
|
-
const startsReadOnly = upper.startsWith("SELECT ") ||
|
|
6829
|
-
upper.startsWith("INFO ") ||
|
|
6830
|
-
upper.startsWith("EXPLAIN ");
|
|
6831
|
-
if (!startsReadOnly) {
|
|
6832
|
-
throw new Error("surreal_graph_query only allows SELECT, INFO, and EXPLAIN statements. Use surreal_graph_link for controlled relationship writes.");
|
|
6833
|
-
}
|
|
6834
|
-
for (const keyword of SURREAL_MUTATING_KEYWORDS) {
|
|
6835
|
-
if (new RegExp(`\\b${keyword}\\b`, "i").test(statement)) {
|
|
6836
|
-
throw new Error(`surreal_graph_query blocked mutating keyword ${keyword}. Use guarded graph write tools instead.`);
|
|
6837
|
-
}
|
|
6838
|
-
}
|
|
6839
|
-
const isAggregateCount = /\bCOUNT\s*\(/i.test(statement);
|
|
6840
|
-
if (upper.startsWith("SELECT ") &&
|
|
6841
|
-
!/\bLIMIT\b/i.test(statement) &&
|
|
6842
|
-
!isAggregateCount) {
|
|
6843
|
-
throw new Error("SELECT queries must include LIMIT unless they are aggregate count queries.");
|
|
6844
|
-
}
|
|
6845
|
-
}
|
|
6846
|
-
}
|
|
6847
6632
|
function clampMaxRows(value) {
|
|
6848
6633
|
if (typeof value !== "number" || !Number.isFinite(value))
|
|
6849
6634
|
return 50;
|
|
6850
6635
|
return Math.max(1, Math.min(200, Math.floor(value)));
|
|
6851
6636
|
}
|
|
6852
|
-
function capSurrealResults(results, maxRows) {
|
|
6853
|
-
return results.map((entry) => {
|
|
6854
|
-
if (!Array.isArray(entry.result) || entry.result.length <= maxRows) {
|
|
6855
|
-
return entry;
|
|
6856
|
-
}
|
|
6857
|
-
return {
|
|
6858
|
-
...entry,
|
|
6859
|
-
result: entry.result.slice(0, maxRows),
|
|
6860
|
-
detail: `${entry.result.length - maxRows} additional rows omitted by lifeos-mcp maxRows=${maxRows}`,
|
|
6861
|
-
};
|
|
6862
|
-
});
|
|
6863
|
-
}
|
|
6864
|
-
function assertSurrealIdentifier(value, label) {
|
|
6865
|
-
if (typeof value !== "string" || !/^[A-Za-z][A-Za-z0-9_]*$/.test(value)) {
|
|
6866
|
-
throw new Error(`${label} must be a simple Surreal identifier.`);
|
|
6867
|
-
}
|
|
6868
|
-
return value;
|
|
6869
|
-
}
|
|
6870
|
-
function assertLifeOsNodeTable(value, label) {
|
|
6871
|
-
const table = assertSurrealIdentifier(value, label);
|
|
6872
|
-
if (!SURREAL_TABLE_PREFIXES.some((prefix) => table.startsWith(prefix))) {
|
|
6873
|
-
throw new Error(`${label} must start with one of: ${SURREAL_TABLE_PREFIXES.join(", ")}`);
|
|
6874
|
-
}
|
|
6875
|
-
if (SURREAL_RELATION_TABLES.includes(table)) {
|
|
6876
|
-
throw new Error(`${label} must be an entity table, not a relation table.`);
|
|
6877
|
-
}
|
|
6878
|
-
return table;
|
|
6879
|
-
}
|
|
6880
6637
|
function assertRecordId(value, label) {
|
|
6881
6638
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
6882
6639
|
throw new Error(`${label} is required.`);
|
|
@@ -6892,79 +6649,237 @@ function assertRelationKind(value) {
|
|
|
6892
6649
|
}
|
|
6893
6650
|
return value;
|
|
6894
6651
|
}
|
|
6895
|
-
|
|
6896
|
-
|
|
6652
|
+
const FALKOR_AGENT_RELATIONSHIP = "AGENT_LINK";
|
|
6653
|
+
const FALKOR_NODE_LABELS = [
|
|
6654
|
+
"PpvVision",
|
|
6655
|
+
"PpvIdentity",
|
|
6656
|
+
"PpvPillar",
|
|
6657
|
+
"PpvProject",
|
|
6658
|
+
];
|
|
6659
|
+
const FALKOR_INFERRED_RELATIONSHIPS = [
|
|
6660
|
+
"HAS_IDENTITY",
|
|
6661
|
+
"HAS_PILLAR",
|
|
6662
|
+
"PILLAR_SUPPORTS_PROJECT",
|
|
6663
|
+
];
|
|
6664
|
+
const FALKOR_MUTATING_KEYWORDS = [
|
|
6665
|
+
"CALL",
|
|
6666
|
+
"CREATE",
|
|
6667
|
+
"DELETE",
|
|
6668
|
+
"DROP",
|
|
6669
|
+
"FOREACH",
|
|
6670
|
+
"LOAD",
|
|
6671
|
+
"MERGE",
|
|
6672
|
+
"REMOVE",
|
|
6673
|
+
"SET",
|
|
6674
|
+
];
|
|
6675
|
+
function requireFalkorConfig() {
|
|
6676
|
+
if (!FALKOR_TOKEN && !FALKOR_PASS) {
|
|
6677
|
+
throw new Error("Missing FalkorDB configuration: set FALKOR_TOKEN/FALKOR_PAT or FALKOR_PASS in the agent runtime.");
|
|
6678
|
+
}
|
|
6679
|
+
return {
|
|
6680
|
+
endpoint: FALKOR_BROWSER_ENDPOINT,
|
|
6681
|
+
graph: FALKOR_GRAPH,
|
|
6682
|
+
username: FALKOR_USER,
|
|
6683
|
+
password: FALKOR_PASS,
|
|
6684
|
+
token: FALKOR_TOKEN,
|
|
6685
|
+
host: FALKOR_HOST,
|
|
6686
|
+
port: FALKOR_PORT,
|
|
6687
|
+
tls: FALKOR_TLS,
|
|
6688
|
+
};
|
|
6689
|
+
}
|
|
6690
|
+
function sleep(ms) {
|
|
6691
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6897
6692
|
}
|
|
6898
|
-
function
|
|
6899
|
-
return
|
|
6693
|
+
function cypherString(value) {
|
|
6694
|
+
return `'${String(value).replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
6900
6695
|
}
|
|
6901
|
-
function
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
.
|
|
6696
|
+
function cypherValue(value) {
|
|
6697
|
+
if (value === null || value === undefined)
|
|
6698
|
+
return "NULL";
|
|
6699
|
+
if (typeof value === "number")
|
|
6700
|
+
return Number.isFinite(value) ? String(value) : "NULL";
|
|
6701
|
+
if (typeof value === "boolean")
|
|
6702
|
+
return value ? "true" : "false";
|
|
6703
|
+
if (typeof value === "string")
|
|
6704
|
+
return cypherString(value);
|
|
6705
|
+
if (Array.isArray(value))
|
|
6706
|
+
return `[${value.map(cypherValue).join(", ")}]`;
|
|
6707
|
+
return cypherString(JSON.stringify(value));
|
|
6708
|
+
}
|
|
6709
|
+
function cypherProps(props) {
|
|
6710
|
+
return `{${Object.entries(props)
|
|
6711
|
+
.filter(([, value]) => value !== undefined)
|
|
6712
|
+
.map(([key, value]) => `${key}: ${cypherValue(value)}`)
|
|
6713
|
+
.join(", ")}}`;
|
|
6906
6714
|
}
|
|
6907
|
-
async function
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6715
|
+
async function requestFalkorToken(config, attempt) {
|
|
6716
|
+
if (!config.password) {
|
|
6717
|
+
throw new Error("FALKOR_PASS is required when FALKOR_TOKEN is not set.");
|
|
6718
|
+
}
|
|
6719
|
+
const tokenName = [
|
|
6720
|
+
"lifeos-mcp",
|
|
6721
|
+
attempt === 0 ? "graph" : "retry",
|
|
6722
|
+
Date.now(),
|
|
6723
|
+
Math.random().toString(36).slice(2, 10),
|
|
6724
|
+
].join("-");
|
|
6725
|
+
const response = await fetch(`${config.endpoint.replace(/\/$/, "")}/api/auth/tokens/credentials`, {
|
|
6911
6726
|
method: "POST",
|
|
6727
|
+
headers: { "Content-Type": "application/json" },
|
|
6728
|
+
body: JSON.stringify({
|
|
6729
|
+
username: config.username,
|
|
6730
|
+
password: config.password,
|
|
6731
|
+
host: config.host,
|
|
6732
|
+
port: config.port,
|
|
6733
|
+
tls: config.tls,
|
|
6734
|
+
name: tokenName,
|
|
6735
|
+
ttlSeconds: 3600,
|
|
6736
|
+
}),
|
|
6737
|
+
});
|
|
6738
|
+
const text = await response.text();
|
|
6739
|
+
if (!response.ok) {
|
|
6740
|
+
throw new Error(`FalkorDB auth failed (${response.status}): ${text}`);
|
|
6741
|
+
}
|
|
6742
|
+
const parsed = JSON.parse(text);
|
|
6743
|
+
if (!parsed.token) {
|
|
6744
|
+
throw new Error(`FalkorDB auth returned no token: ${text}`);
|
|
6745
|
+
}
|
|
6746
|
+
return parsed.token;
|
|
6747
|
+
}
|
|
6748
|
+
async function getFalkorToken(config) {
|
|
6749
|
+
if (config.token)
|
|
6750
|
+
return config.token;
|
|
6751
|
+
let lastError;
|
|
6752
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
6753
|
+
try {
|
|
6754
|
+
return await requestFalkorToken(config, attempt);
|
|
6755
|
+
}
|
|
6756
|
+
catch (error) {
|
|
6757
|
+
lastError = error;
|
|
6758
|
+
if (attempt < 2)
|
|
6759
|
+
await sleep(250 * (attempt + 1));
|
|
6760
|
+
}
|
|
6761
|
+
}
|
|
6762
|
+
throw lastError;
|
|
6763
|
+
}
|
|
6764
|
+
function parseFalkorEventStream(text) {
|
|
6765
|
+
const data = text
|
|
6766
|
+
.split(/\r?\n/)
|
|
6767
|
+
.filter((line) => line.startsWith("data:"))
|
|
6768
|
+
.map((line) => line.slice(5).trimStart())
|
|
6769
|
+
.join("\n")
|
|
6770
|
+
.trim();
|
|
6771
|
+
if (!data)
|
|
6772
|
+
return { data: [], metadata: [] };
|
|
6773
|
+
return JSON.parse(data);
|
|
6774
|
+
}
|
|
6775
|
+
async function runFalkorQuery(query) {
|
|
6776
|
+
const config = requireFalkorConfig();
|
|
6777
|
+
const token = await getFalkorToken(config);
|
|
6778
|
+
const url = `${config.endpoint.replace(/\/$/, "")}/api/graph/${encodeURIComponent(config.graph)}?query=${encodeURIComponent(query)}&timeout=30000`;
|
|
6779
|
+
const response = await fetch(url, {
|
|
6780
|
+
method: "GET",
|
|
6912
6781
|
headers: {
|
|
6913
|
-
Authorization: `
|
|
6914
|
-
|
|
6915
|
-
Accept: "application/json",
|
|
6916
|
-
"Surreal-NS": cfg.ns,
|
|
6917
|
-
"Surreal-DB": cfg.db,
|
|
6782
|
+
Authorization: `Bearer ${token}`,
|
|
6783
|
+
Accept: "text/event-stream",
|
|
6918
6784
|
},
|
|
6919
|
-
body: sql,
|
|
6920
6785
|
});
|
|
6921
6786
|
const text = await response.text();
|
|
6922
6787
|
if (!response.ok) {
|
|
6923
|
-
throw new Error(`
|
|
6788
|
+
throw new Error(`FalkorDB query failed (${response.status}): ${text}`);
|
|
6924
6789
|
}
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6790
|
+
return parseFalkorEventStream(text);
|
|
6791
|
+
}
|
|
6792
|
+
function assertReadOnlyFalkorCypher(query) {
|
|
6793
|
+
const stripped = stripSqlCommentsAndStrings(query).trim();
|
|
6794
|
+
if (!stripped)
|
|
6795
|
+
throw new Error("FalkorDB Cypher query is empty.");
|
|
6796
|
+
if (stripped.includes(";")) {
|
|
6797
|
+
throw new Error("falkor_graph_query allows one Cypher statement at a time.");
|
|
6798
|
+
}
|
|
6799
|
+
const upper = stripped.toUpperCase();
|
|
6800
|
+
const startsReadOnly = upper.startsWith("MATCH ") ||
|
|
6801
|
+
upper.startsWith("WITH ") ||
|
|
6802
|
+
upper.startsWith("RETURN ") ||
|
|
6803
|
+
upper.startsWith("EXPLAIN ") ||
|
|
6804
|
+
upper.startsWith("PROFILE ");
|
|
6805
|
+
if (!startsReadOnly) {
|
|
6806
|
+
throw new Error("falkor_graph_query only allows read-only Cypher. Start with MATCH, WITH, RETURN, EXPLAIN, or PROFILE.");
|
|
6807
|
+
}
|
|
6808
|
+
for (const keyword of FALKOR_MUTATING_KEYWORDS) {
|
|
6809
|
+
if (new RegExp(`\\b${keyword}\\b`, "i").test(stripped)) {
|
|
6810
|
+
throw new Error(`falkor_graph_query blocked mutating keyword ${keyword}. Use falkor_graph_link for guarded relationship writes.`);
|
|
6811
|
+
}
|
|
6812
|
+
}
|
|
6813
|
+
const isAggregateCount = /\bCOUNT\s*\(/i.test(stripped);
|
|
6814
|
+
if ((upper.startsWith("MATCH ") || upper.startsWith("WITH ")) &&
|
|
6815
|
+
!/\bLIMIT\b/i.test(stripped) &&
|
|
6816
|
+
!isAggregateCount) {
|
|
6817
|
+
throw new Error("MATCH/WITH queries must include LIMIT unless they are aggregate count queries.");
|
|
6818
|
+
}
|
|
6819
|
+
}
|
|
6820
|
+
function capFalkorResult(result, maxRows) {
|
|
6821
|
+
const rows = result.data ?? [];
|
|
6822
|
+
if (rows.length <= maxRows)
|
|
6823
|
+
return result;
|
|
6824
|
+
return {
|
|
6825
|
+
...result,
|
|
6826
|
+
data: rows.slice(0, maxRows),
|
|
6827
|
+
detail: `${rows.length - maxRows} additional rows omitted by lifeos-mcp maxRows=${maxRows}`,
|
|
6828
|
+
};
|
|
6829
|
+
}
|
|
6830
|
+
function assertFalkorIdentifier(value, label) {
|
|
6831
|
+
if (typeof value !== "string" || !/^[A-Za-z][A-Za-z0-9_]*$/.test(value)) {
|
|
6832
|
+
throw new Error(`${label} must be a simple Falkor identifier.`);
|
|
6833
|
+
}
|
|
6834
|
+
return value;
|
|
6835
|
+
}
|
|
6836
|
+
function assertFalkorLabel(value, label) {
|
|
6837
|
+
const nodeLabel = assertFalkorIdentifier(value, label);
|
|
6838
|
+
if (!FALKOR_NODE_LABELS.includes(nodeLabel)) {
|
|
6839
|
+
throw new Error(`${label} must be one of: ${FALKOR_NODE_LABELS.join(", ")}`);
|
|
6929
6840
|
}
|
|
6930
|
-
return
|
|
6841
|
+
return nodeLabel;
|
|
6931
6842
|
}
|
|
6932
|
-
function
|
|
6843
|
+
function falkorLinkId(parts) {
|
|
6844
|
+
return [parts.kind, parts.fromLabel, parts.fromId, parts.toLabel, parts.toId]
|
|
6845
|
+
.join("__")
|
|
6846
|
+
.replace(/[^A-Za-z0-9_:-]/g, "_")
|
|
6847
|
+
.slice(0, 512);
|
|
6848
|
+
}
|
|
6849
|
+
function falkorGraphSchema() {
|
|
6933
6850
|
return {
|
|
6934
|
-
configured: Boolean(
|
|
6935
|
-
endpointConfigured: Boolean(
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
agentWritableRelationTable: SURREAL_AGENT_LINK_TABLE,
|
|
6851
|
+
configured: Boolean(FALKOR_TOKEN || FALKOR_PASS),
|
|
6852
|
+
endpointConfigured: Boolean(FALKOR_BROWSER_ENDPOINT),
|
|
6853
|
+
graph: FALKOR_GRAPH,
|
|
6854
|
+
nodeLabels: FALKOR_NODE_LABELS,
|
|
6855
|
+
inferredRelationships: FALKOR_INFERRED_RELATIONSHIPS,
|
|
6856
|
+
agentWritableRelationshipType: FALKOR_AGENT_RELATIONSHIP,
|
|
6941
6857
|
rules: [
|
|
6942
|
-
"Use
|
|
6943
|
-
"
|
|
6944
|
-
"Use
|
|
6945
|
-
"Do not mutate Convex-synced
|
|
6858
|
+
"Use falkor_graph_query for read-only Cypher only.",
|
|
6859
|
+
"MATCH/WITH queries must include LIMIT unless they are aggregate count queries.",
|
|
6860
|
+
"Use falkor_graph_link for agent-created relationships; it writes only AGENT_LINK edges.",
|
|
6861
|
+
"Do not mutate Convex-synced nodes or inferred PPV relationships from FalkorDB. Convex remains canonical.",
|
|
6946
6862
|
"Every agent link must include a reason and confidence.",
|
|
6947
6863
|
],
|
|
6948
6864
|
examples: {
|
|
6949
|
-
|
|
6950
|
-
projectInboundLinks: "
|
|
6951
|
-
agentLinks: "
|
|
6865
|
+
ppvOverview: "MATCH (v:PpvVision)-[:HAS_PILLAR]->(p:PpvPillar)-[:PILLAR_SUPPORTS_PROJECT]->(project:PpvProject) RETURN v.convexId, v.title, p.convexId, p.title, project.convexId, project.title LIMIT 25",
|
|
6866
|
+
projectInboundLinks: "MATCH (pillar:PpvPillar)-[:PILLAR_SUPPORTS_PROJECT]->(project:PpvProject {convexId: 'PROJECT_ID'}) RETURN pillar.convexId, pillar.title, project.title LIMIT 10",
|
|
6867
|
+
agentLinks: "MATCH (a)-[r:AGENT_LINK]->(b) RETURN a.convexId, type(r), r.kind, r.reason, r.confidence, b.convexId LIMIT 25",
|
|
6952
6868
|
},
|
|
6953
6869
|
};
|
|
6954
6870
|
}
|
|
6955
|
-
async function
|
|
6871
|
+
async function falkorGraphQuery(args) {
|
|
6956
6872
|
const query = args.query;
|
|
6957
6873
|
if (typeof query !== "string") {
|
|
6958
6874
|
throw new Error("query is required.");
|
|
6959
6875
|
}
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
return capSurrealResults(results, clampMaxRows(args.maxRows));
|
|
6876
|
+
assertReadOnlyFalkorCypher(query);
|
|
6877
|
+
return capFalkorResult(await runFalkorQuery(query), clampMaxRows(args.maxRows));
|
|
6963
6878
|
}
|
|
6964
|
-
async function
|
|
6965
|
-
const
|
|
6879
|
+
async function falkorGraphLink(args) {
|
|
6880
|
+
const fromLabel = assertFalkorLabel(args.fromLabel, "fromLabel");
|
|
6966
6881
|
const fromId = assertRecordId(args.fromId, "fromId");
|
|
6967
|
-
const
|
|
6882
|
+
const toLabel = assertFalkorLabel(args.toLabel, "toLabel");
|
|
6968
6883
|
const toId = assertRecordId(args.toId, "toId");
|
|
6969
6884
|
const kind = assertRelationKind(args.kind);
|
|
6970
6885
|
const reason = assertRecordId(args.reason, "reason");
|
|
@@ -6980,8 +6895,9 @@ async function surrealGraphLink(args) {
|
|
|
6980
6895
|
? args.metadata
|
|
6981
6896
|
: {};
|
|
6982
6897
|
const now = new Date().toISOString();
|
|
6983
|
-
const linkId =
|
|
6984
|
-
const
|
|
6898
|
+
const linkId = falkorLinkId({ fromLabel, fromId, toLabel, toId, kind });
|
|
6899
|
+
const props = cypherProps({
|
|
6900
|
+
linkId,
|
|
6985
6901
|
kind,
|
|
6986
6902
|
reason,
|
|
6987
6903
|
confidence,
|
|
@@ -6989,41 +6905,35 @@ async function surrealGraphLink(args) {
|
|
|
6989
6905
|
sourceSystem: "agent",
|
|
6990
6906
|
sourceUserId: USER_ID,
|
|
6991
6907
|
userId: USER_ID,
|
|
6992
|
-
|
|
6993
|
-
targetTable: toTable,
|
|
6994
|
-
metadata,
|
|
6908
|
+
metadataJson: JSON.stringify(metadata),
|
|
6995
6909
|
createdAt: now,
|
|
6996
6910
|
updatedAt: now,
|
|
6997
|
-
};
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
`DEFINE INDEX IF NOT EXISTS ${SURREAL_AGENT_LINK_TABLE}_kind ON TABLE ${SURREAL_AGENT_LINK_TABLE} COLUMNS kind;`,
|
|
7001
|
-
`DEFINE INDEX IF NOT EXISTS ${SURREAL_AGENT_LINK_TABLE}_createdBy ON TABLE ${SURREAL_AGENT_LINK_TABLE} COLUMNS createdBy;`,
|
|
7002
|
-
`DELETE ${SURREAL_AGENT_LINK_TABLE}:\`${escapeSurrealRecordId(linkId)}\`;`,
|
|
7003
|
-
`RELATE ${surrealThing(fromTable, fromId)}->${SURREAL_AGENT_LINK_TABLE}:\`${escapeSurrealRecordId(linkId)}\`->${surrealThing(toTable, toId)} CONTENT ${JSON.stringify(content)};`,
|
|
7004
|
-
].join("\n");
|
|
7005
|
-
const results = await runSurrealSql(sql);
|
|
6911
|
+
});
|
|
6912
|
+
await runFalkorQuery(`MATCH (a:${fromLabel} {convexId: ${cypherString(fromId)}})-[r:${FALKOR_AGENT_RELATIONSHIP} {linkId: ${cypherString(linkId)}}]->(b:${toLabel} {convexId: ${cypherString(toId)}}) DELETE r`);
|
|
6913
|
+
const result = await runFalkorQuery(`MATCH (a:${fromLabel} {convexId: ${cypherString(fromId)}}), (b:${toLabel} {convexId: ${cypherString(toId)}}) CREATE (a)-[r:${FALKOR_AGENT_RELATIONSHIP} ${props}]->(b) RETURN r.linkId AS linkId, r.kind AS kind, r.reason AS reason, r.confidence AS confidence`);
|
|
7006
6914
|
return {
|
|
7007
6915
|
linked: true,
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
6916
|
+
relationType: FALKOR_AGENT_RELATIONSHIP,
|
|
6917
|
+
relationId: linkId,
|
|
6918
|
+
from: `${fromLabel}:${fromId}`,
|
|
6919
|
+
to: `${toLabel}:${toId}`,
|
|
7011
6920
|
kind,
|
|
7012
|
-
|
|
6921
|
+
result,
|
|
7013
6922
|
};
|
|
7014
6923
|
}
|
|
7015
|
-
async function
|
|
7016
|
-
const
|
|
6924
|
+
async function falkorGraphUnlink(args) {
|
|
6925
|
+
const fromLabel = assertFalkorLabel(args.fromLabel, "fromLabel");
|
|
7017
6926
|
const fromId = assertRecordId(args.fromId, "fromId");
|
|
7018
|
-
const
|
|
6927
|
+
const toLabel = assertFalkorLabel(args.toLabel, "toLabel");
|
|
7019
6928
|
const toId = assertRecordId(args.toId, "toId");
|
|
7020
6929
|
const kind = assertRelationKind(args.kind);
|
|
7021
|
-
const linkId =
|
|
7022
|
-
const
|
|
6930
|
+
const linkId = falkorLinkId({ fromLabel, fromId, toLabel, toId, kind });
|
|
6931
|
+
const result = await runFalkorQuery(`MATCH (a:${fromLabel} {convexId: ${cypherString(fromId)}})-[r:${FALKOR_AGENT_RELATIONSHIP} {linkId: ${cypherString(linkId)}}]->(b:${toLabel} {convexId: ${cypherString(toId)}}) DELETE r`);
|
|
7023
6932
|
return {
|
|
7024
6933
|
unlinked: true,
|
|
7025
|
-
|
|
7026
|
-
|
|
6934
|
+
relationType: FALKOR_AGENT_RELATIONSHIP,
|
|
6935
|
+
relationId: linkId,
|
|
6936
|
+
result,
|
|
7027
6937
|
};
|
|
7028
6938
|
}
|
|
7029
6939
|
const STATIC_RESOURCES = [
|
|
@@ -7408,18 +7318,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
7408
7318
|
],
|
|
7409
7319
|
};
|
|
7410
7320
|
}
|
|
7411
|
-
if (name === "
|
|
7321
|
+
if (name === "falkor_graph_schema") {
|
|
7412
7322
|
return {
|
|
7413
7323
|
content: [
|
|
7414
7324
|
{
|
|
7415
7325
|
type: "text",
|
|
7416
|
-
text: JSON.stringify(
|
|
7326
|
+
text: JSON.stringify(falkorGraphSchema(), null, 2),
|
|
7417
7327
|
},
|
|
7418
7328
|
],
|
|
7419
7329
|
};
|
|
7420
7330
|
}
|
|
7421
|
-
if (name === "
|
|
7422
|
-
const result = await
|
|
7331
|
+
if (name === "falkor_graph_query") {
|
|
7332
|
+
const result = await falkorGraphQuery(args || {});
|
|
7423
7333
|
return {
|
|
7424
7334
|
content: [
|
|
7425
7335
|
{
|
|
@@ -7429,8 +7339,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
7429
7339
|
],
|
|
7430
7340
|
};
|
|
7431
7341
|
}
|
|
7432
|
-
if (name === "
|
|
7433
|
-
const result = await
|
|
7342
|
+
if (name === "falkor_graph_link") {
|
|
7343
|
+
const result = await falkorGraphLink(args || {});
|
|
7434
7344
|
return {
|
|
7435
7345
|
content: [
|
|
7436
7346
|
{
|
|
@@ -7440,8 +7350,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
7440
7350
|
],
|
|
7441
7351
|
};
|
|
7442
7352
|
}
|
|
7443
|
-
if (name === "
|
|
7444
|
-
const result = await
|
|
7353
|
+
if (name === "falkor_graph_unlink") {
|
|
7354
|
+
const result = await falkorGraphUnlink(args || {});
|
|
7445
7355
|
return {
|
|
7446
7356
|
content: [
|
|
7447
7357
|
{
|
package/package.json
CHANGED