@synap-core/workspace-templates 0.9.3 → 0.10.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.
- package/dist/index.d.ts +137 -44
- package/dist/index.js +1816 -128
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__fixtures__/default-relation-defs.json +208 -0
- package/src/__fixtures__/report-automation-flow.json +393 -0
- package/src/agent-fleet.yaml +6 -1
- package/src/base.template.test.ts +160 -0
- package/src/base.yaml +839 -0
- package/src/business-developer.template.test.ts +79 -10
- package/src/business-developer.yaml +61 -39
- package/src/concept-guard.test.ts +47 -31
- package/src/concept-guard.ts +23 -19
- package/src/content-os.yaml +22 -0
- package/src/corpus-flow-validity.test.ts +130 -0
- package/src/crm.yaml +125 -8
- package/src/define.primary-surface.test.ts +2 -4
- package/src/define.ts +28 -0
- package/src/ecosystem.yaml +14 -0
- package/src/enterprise-os.yaml +1 -1
- package/src/foundation.yaml +8 -0
- package/src/grants.yaml +8 -0
- package/src/life-os.yaml +6 -1
- package/src/operations.yaml +270 -0
- package/src/outreach-comms.yaml +196 -0
- package/src/project-management.yaml +6 -1
- package/src/templates.ts +1717 -371
- package/src/types.ts +113 -37
- package/src/validate-flow.ts +186 -7
- package/src/validate-onboarding.test.ts +88 -0
- package/src/validate.ts +148 -12
package/dist/index.js
CHANGED
|
@@ -598,7 +598,7 @@ var DATA = {
|
|
|
598
598
|
"displayName": "Person",
|
|
599
599
|
"icon": "user",
|
|
600
600
|
"color": "#0891B2",
|
|
601
|
-
"
|
|
601
|
+
"entityScope": "pod",
|
|
602
602
|
"description": "Team member or agent owner",
|
|
603
603
|
"properties": [
|
|
604
604
|
{
|
|
@@ -2736,6 +2736,853 @@ var DATA = {
|
|
|
2736
2736
|
"doneWhen": "The live agents exist with their model/provider, the providers are captured with budgets, key skills are catalogued, and agents are linked to active projects."
|
|
2737
2737
|
}
|
|
2738
2738
|
},
|
|
2739
|
+
"base": {
|
|
2740
|
+
"meta": {
|
|
2741
|
+
"slug": "base",
|
|
2742
|
+
"domain": "core",
|
|
2743
|
+
"name": "Base",
|
|
2744
|
+
"description": "Universal operational defaults every workspace inherits \u2014 the report automation, the default relation vocabulary, and the default command pack.",
|
|
2745
|
+
"icon": "layers",
|
|
2746
|
+
"color": "#6B7280",
|
|
2747
|
+
"tags": [
|
|
2748
|
+
"core",
|
|
2749
|
+
"defaults",
|
|
2750
|
+
"operational"
|
|
2751
|
+
],
|
|
2752
|
+
"isPublic": true
|
|
2753
|
+
},
|
|
2754
|
+
"workspace": {
|
|
2755
|
+
"name": "Base",
|
|
2756
|
+
"description": "The pod-wide operational defaults \u2014 carried as config so one reconcile door seeds them into every workspace instead of three hardcoded seeders.",
|
|
2757
|
+
"proposalId": "base-v1",
|
|
2758
|
+
"visibility": "pod_visible"
|
|
2759
|
+
},
|
|
2760
|
+
"profiles": [],
|
|
2761
|
+
"relationDefs": [
|
|
2762
|
+
{
|
|
2763
|
+
"slug": "assigned_to",
|
|
2764
|
+
"displayName": "Assigned To",
|
|
2765
|
+
"description": "Person assigned to task/project",
|
|
2766
|
+
"isDirectional": true,
|
|
2767
|
+
"uiHints": {
|
|
2768
|
+
"category": "workflow"
|
|
2769
|
+
}
|
|
2770
|
+
},
|
|
2771
|
+
{
|
|
2772
|
+
"slug": "blocks",
|
|
2773
|
+
"displayName": "Blocks",
|
|
2774
|
+
"description": "Prevents progress on another task",
|
|
2775
|
+
"isDirectional": true,
|
|
2776
|
+
"uiHints": {
|
|
2777
|
+
"category": "workflow"
|
|
2778
|
+
}
|
|
2779
|
+
},
|
|
2780
|
+
{
|
|
2781
|
+
"slug": "depends_on",
|
|
2782
|
+
"displayName": "Depends On",
|
|
2783
|
+
"description": "Requires completion of another task",
|
|
2784
|
+
"isDirectional": true,
|
|
2785
|
+
"uiHints": {
|
|
2786
|
+
"category": "workflow"
|
|
2787
|
+
}
|
|
2788
|
+
},
|
|
2789
|
+
{
|
|
2790
|
+
"slug": "relates_to",
|
|
2791
|
+
"displayName": "Relates To",
|
|
2792
|
+
"description": "General relationship between entities",
|
|
2793
|
+
"isDirectional": false,
|
|
2794
|
+
"uiHints": {
|
|
2795
|
+
"category": "reference"
|
|
2796
|
+
}
|
|
2797
|
+
},
|
|
2798
|
+
{
|
|
2799
|
+
"slug": "mentions",
|
|
2800
|
+
"displayName": "Mentions",
|
|
2801
|
+
"description": "Referenced in content",
|
|
2802
|
+
"isDirectional": true,
|
|
2803
|
+
"uiHints": {
|
|
2804
|
+
"category": "reference"
|
|
2805
|
+
}
|
|
2806
|
+
},
|
|
2807
|
+
{
|
|
2808
|
+
"slug": "links_to",
|
|
2809
|
+
"displayName": "Links To",
|
|
2810
|
+
"description": "Hyperlink or reference",
|
|
2811
|
+
"isDirectional": true,
|
|
2812
|
+
"uiHints": {
|
|
2813
|
+
"category": "reference"
|
|
2814
|
+
}
|
|
2815
|
+
},
|
|
2816
|
+
{
|
|
2817
|
+
"slug": "parent_of",
|
|
2818
|
+
"displayName": "Parent Of",
|
|
2819
|
+
"description": "Hierarchical parent relationship",
|
|
2820
|
+
"isDirectional": true,
|
|
2821
|
+
"uiHints": {
|
|
2822
|
+
"category": "hierarchy"
|
|
2823
|
+
}
|
|
2824
|
+
},
|
|
2825
|
+
{
|
|
2826
|
+
"slug": "tagged_with",
|
|
2827
|
+
"displayName": "Tagged With",
|
|
2828
|
+
"description": "Categorization tag",
|
|
2829
|
+
"isDirectional": true,
|
|
2830
|
+
"uiHints": {
|
|
2831
|
+
"category": "reference"
|
|
2832
|
+
}
|
|
2833
|
+
},
|
|
2834
|
+
{
|
|
2835
|
+
"slug": "created_by",
|
|
2836
|
+
"displayName": "Created By",
|
|
2837
|
+
"description": "Author or creator",
|
|
2838
|
+
"isDirectional": true,
|
|
2839
|
+
"uiHints": {
|
|
2840
|
+
"category": "social"
|
|
2841
|
+
}
|
|
2842
|
+
},
|
|
2843
|
+
{
|
|
2844
|
+
"slug": "attended_by",
|
|
2845
|
+
"displayName": "Attended By",
|
|
2846
|
+
"description": "Participant in event",
|
|
2847
|
+
"isDirectional": true,
|
|
2848
|
+
"uiHints": {
|
|
2849
|
+
"category": "social"
|
|
2850
|
+
}
|
|
2851
|
+
},
|
|
2852
|
+
{
|
|
2853
|
+
"slug": "belongs_to_project",
|
|
2854
|
+
"displayName": "Belongs To Project",
|
|
2855
|
+
"description": "Project membership",
|
|
2856
|
+
"isDirectional": true,
|
|
2857
|
+
"uiHints": {
|
|
2858
|
+
"category": "hierarchy"
|
|
2859
|
+
}
|
|
2860
|
+
},
|
|
2861
|
+
{
|
|
2862
|
+
"slug": "founder_brand_of",
|
|
2863
|
+
"displayName": "Founder Brand Of",
|
|
2864
|
+
"description": "A personal-brand project is the founder brand of a company project \u2014 links a person's brand to the company it expresses",
|
|
2865
|
+
"isDirectional": true,
|
|
2866
|
+
"uiHints": {
|
|
2867
|
+
"category": "hierarchy",
|
|
2868
|
+
"inverseLabel": "Expression of"
|
|
2869
|
+
}
|
|
2870
|
+
},
|
|
2871
|
+
{
|
|
2872
|
+
"slug": "references",
|
|
2873
|
+
"displayName": "References",
|
|
2874
|
+
"description": "Cites or refers to",
|
|
2875
|
+
"isDirectional": true,
|
|
2876
|
+
"uiHints": {
|
|
2877
|
+
"category": "reference"
|
|
2878
|
+
}
|
|
2879
|
+
},
|
|
2880
|
+
{
|
|
2881
|
+
"slug": "works_at",
|
|
2882
|
+
"displayName": "Works At",
|
|
2883
|
+
"description": "Person works at an organization",
|
|
2884
|
+
"isDirectional": true,
|
|
2885
|
+
"uiHints": {
|
|
2886
|
+
"category": "social",
|
|
2887
|
+
"inverseLabel": "Employs"
|
|
2888
|
+
}
|
|
2889
|
+
},
|
|
2890
|
+
{
|
|
2891
|
+
"slug": "deal_for",
|
|
2892
|
+
"displayName": "Deal For",
|
|
2893
|
+
"description": "Sales deal associated with a contact",
|
|
2894
|
+
"isDirectional": true,
|
|
2895
|
+
"uiHints": {
|
|
2896
|
+
"category": "workflow",
|
|
2897
|
+
"inverseLabel": "Has Deal"
|
|
2898
|
+
}
|
|
2899
|
+
},
|
|
2900
|
+
{
|
|
2901
|
+
"slug": "advances",
|
|
2902
|
+
"displayName": "Advances",
|
|
2903
|
+
"description": "advances the company's Foundation mission/vision goal",
|
|
2904
|
+
"isDirectional": true,
|
|
2905
|
+
"uiHints": {
|
|
2906
|
+
"category": "workflow",
|
|
2907
|
+
"inverseLabel": "Advanced by"
|
|
2908
|
+
}
|
|
2909
|
+
},
|
|
2910
|
+
{
|
|
2911
|
+
"slug": "met_at",
|
|
2912
|
+
"displayName": "Met at",
|
|
2913
|
+
"description": "Person met at an event or gathering",
|
|
2914
|
+
"isDirectional": true,
|
|
2915
|
+
"uiHints": {
|
|
2916
|
+
"category": "social",
|
|
2917
|
+
"inverseLabel": "Attendees"
|
|
2918
|
+
}
|
|
2919
|
+
},
|
|
2920
|
+
{
|
|
2921
|
+
"slug": "works_on",
|
|
2922
|
+
"displayName": "Works on",
|
|
2923
|
+
"description": "Person contributes to a project",
|
|
2924
|
+
"isDirectional": true,
|
|
2925
|
+
"uiHints": {
|
|
2926
|
+
"category": "workflow",
|
|
2927
|
+
"inverseLabel": "Contributors"
|
|
2928
|
+
}
|
|
2929
|
+
},
|
|
2930
|
+
{
|
|
2931
|
+
"slug": "has_skill",
|
|
2932
|
+
"displayName": "Has skill",
|
|
2933
|
+
"description": "Person possesses a skill or expertise",
|
|
2934
|
+
"isDirectional": true,
|
|
2935
|
+
"uiHints": {
|
|
2936
|
+
"category": "reference",
|
|
2937
|
+
"inverseLabel": "Skilled people"
|
|
2938
|
+
}
|
|
2939
|
+
},
|
|
2940
|
+
{
|
|
2941
|
+
"slug": "affiliated_with",
|
|
2942
|
+
"displayName": "Affiliated with",
|
|
2943
|
+
"description": "Person affiliated with an organization",
|
|
2944
|
+
"isDirectional": true,
|
|
2945
|
+
"uiHints": {
|
|
2946
|
+
"category": "social",
|
|
2947
|
+
"inverseLabel": "Members"
|
|
2948
|
+
}
|
|
2949
|
+
},
|
|
2950
|
+
{
|
|
2951
|
+
"slug": "knows",
|
|
2952
|
+
"displayName": "Knows",
|
|
2953
|
+
"description": "Two people know each other",
|
|
2954
|
+
"isDirectional": false,
|
|
2955
|
+
"uiHints": {
|
|
2956
|
+
"category": "social"
|
|
2957
|
+
}
|
|
2958
|
+
},
|
|
2959
|
+
{
|
|
2960
|
+
"slug": "discussed_with",
|
|
2961
|
+
"displayName": "Discussed with",
|
|
2962
|
+
"description": "Had a discussion or conversation with",
|
|
2963
|
+
"isDirectional": false,
|
|
2964
|
+
"uiHints": {
|
|
2965
|
+
"category": "social"
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
],
|
|
2969
|
+
"commands": [
|
|
2970
|
+
{
|
|
2971
|
+
"title": "Create View from Description",
|
|
2972
|
+
"promptTemplate": "You are helping the user create a new view from a natural language description.\n\n**User's goal:** @{arg:goal}\n\nCreate a proposal for a new view that matches this description. Include:\n- View type (table, kanban, list, etc.)\n- Filters, sorts, and scope\n- Layout configuration\n\nOutput your proposal clearly so it can be turned into a Synap view.",
|
|
2973
|
+
"derivedInputs": [
|
|
2974
|
+
{
|
|
2975
|
+
"name": "goal",
|
|
2976
|
+
"label": "Goal",
|
|
2977
|
+
"type": "text"
|
|
2978
|
+
}
|
|
2979
|
+
],
|
|
2980
|
+
"outputMode": "view",
|
|
2981
|
+
"canCreateViews": true,
|
|
2982
|
+
"permissionsProfile": "propose_writes"
|
|
2983
|
+
},
|
|
2984
|
+
{
|
|
2985
|
+
"title": "Extract Entities from Text",
|
|
2986
|
+
"promptTemplate": "You are helping the user extract entities and relationships from text.\n\n**User's text or selection:**\n\n@{context:text}\n\nAnalyze this content and propose:\n1. New entities to create (Notes, Tasks, People, Companies, etc.) with their properties\n2. Relationships between entities (blocks, related_to, parent_of, etc.)\n\nOutput proposals that the user can review and approve. Do not create anything directly\u2014always propose.",
|
|
2987
|
+
"derivedInputs": [],
|
|
2988
|
+
"outputMode": "proposal",
|
|
2989
|
+
"canCreateViews": false,
|
|
2990
|
+
"permissionsProfile": "propose_writes"
|
|
2991
|
+
},
|
|
2992
|
+
{
|
|
2993
|
+
"title": "Summarize Selection",
|
|
2994
|
+
"promptTemplate": "You are helping the user get a concise summary of their selection.\n\n**User's text or selection:**\n\n@{context:text}\n\nProvide a clear, structured summary. Highlight key points, dates, and actionable items if relevant. Keep it concise but comprehensive.",
|
|
2995
|
+
"derivedInputs": [],
|
|
2996
|
+
"outputMode": "text",
|
|
2997
|
+
"canCreateViews": false,
|
|
2998
|
+
"permissionsProfile": "read_only"
|
|
2999
|
+
},
|
|
3000
|
+
{
|
|
3001
|
+
"title": "Analyze Entities",
|
|
3002
|
+
"promptTemplate": "You are helping the user analyze selected entities.\n\n**Current entity context:**\n\n@{context:entity}\n\nAnalyze this entity and provide:\n- Overview and key properties\n- Insights, gaps, or opportunities\n- Suggestions for relationships or next steps\n\nBe concise and actionable.",
|
|
3003
|
+
"derivedInputs": [],
|
|
3004
|
+
"outputMode": "text",
|
|
3005
|
+
"canCreateViews": false,
|
|
3006
|
+
"permissionsProfile": "read_only"
|
|
3007
|
+
},
|
|
3008
|
+
{
|
|
3009
|
+
"title": "Create Task from Text",
|
|
3010
|
+
"promptTemplate": "You are helping the user turn text into tasks.\n\n**User's text (meeting notes, pasted content, or selection):**\n\n@{context:text}\n\nExtract actionable tasks. For each task propose:\n- Title\n- Optional: due date, priority, assignee, status\n- Link to source if applicable\n\nOutput proposals only\u2014do not create entities directly. The user will review and approve.",
|
|
3011
|
+
"derivedInputs": [],
|
|
3012
|
+
"outputMode": "proposal",
|
|
3013
|
+
"canCreateViews": false,
|
|
3014
|
+
"permissionsProfile": "propose_writes"
|
|
3015
|
+
},
|
|
3016
|
+
{
|
|
3017
|
+
"title": "Create Event from Text",
|
|
3018
|
+
"promptTemplate": "You are helping the user turn text into events.\n\n**User's text (meeting notes, calendar copy, or selection):**\n\n@{context:text}\n\nExtract events with dates/times. For each event propose:\n- Title\n- Start time and end time\n- Optional: description, tags\n\nOutput proposals only\u2014do not create entities directly. The user will review and approve.",
|
|
3019
|
+
"derivedInputs": [],
|
|
3020
|
+
"outputMode": "proposal",
|
|
3021
|
+
"canCreateViews": false,
|
|
3022
|
+
"permissionsProfile": "propose_writes"
|
|
3023
|
+
}
|
|
3024
|
+
],
|
|
3025
|
+
"automations": [
|
|
3026
|
+
{
|
|
3027
|
+
"name": "Generate report",
|
|
3028
|
+
"description": `Gather this workspace's state, interpret it over three AI rounds, and write a report entity. Run it as-is for a general report, or pass a prompt (e.g. "focus on our clients") to steer what it is about.`,
|
|
3029
|
+
"triggerType": "manual",
|
|
3030
|
+
"triggerConfig": {},
|
|
3031
|
+
"status": "active",
|
|
3032
|
+
"flow": {
|
|
3033
|
+
"nodes": [
|
|
3034
|
+
{
|
|
3035
|
+
"id": "trigger",
|
|
3036
|
+
"type": "trigger",
|
|
3037
|
+
"position": {
|
|
3038
|
+
"x": 0,
|
|
3039
|
+
"y": 200
|
|
3040
|
+
},
|
|
3041
|
+
"data": {
|
|
3042
|
+
"triggerType": "manual",
|
|
3043
|
+
"label": "Run report",
|
|
3044
|
+
"config": {}
|
|
3045
|
+
}
|
|
3046
|
+
},
|
|
3047
|
+
{
|
|
3048
|
+
"id": "now",
|
|
3049
|
+
"type": "compute",
|
|
3050
|
+
"position": {
|
|
3051
|
+
"x": 200,
|
|
3052
|
+
"y": 200
|
|
3053
|
+
},
|
|
3054
|
+
"data": {
|
|
3055
|
+
"label": "Timestamp",
|
|
3056
|
+
"operation": "now"
|
|
3057
|
+
}
|
|
3058
|
+
},
|
|
3059
|
+
{
|
|
3060
|
+
"id": "gather-tasks",
|
|
3061
|
+
"type": "query",
|
|
3062
|
+
"position": {
|
|
3063
|
+
"x": 420,
|
|
3064
|
+
"y": 20
|
|
3065
|
+
},
|
|
3066
|
+
"data": {
|
|
3067
|
+
"label": "Tasks",
|
|
3068
|
+
"profileSlug": "task",
|
|
3069
|
+
"filter": '{"updatedAt": {"$gte": "{{trigger.payload.since}}"}}',
|
|
3070
|
+
"orderBy": "updatedAt",
|
|
3071
|
+
"orderDir": "desc",
|
|
3072
|
+
"limit": 25,
|
|
3073
|
+
"errorHandling": {
|
|
3074
|
+
"continueOnError": true
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
},
|
|
3078
|
+
{
|
|
3079
|
+
"id": "gather-notes",
|
|
3080
|
+
"type": "query",
|
|
3081
|
+
"position": {
|
|
3082
|
+
"x": 420,
|
|
3083
|
+
"y": 140
|
|
3084
|
+
},
|
|
3085
|
+
"data": {
|
|
3086
|
+
"label": "Notes",
|
|
3087
|
+
"profileSlug": "note",
|
|
3088
|
+
"filter": '{"updatedAt": {"$gte": "{{trigger.payload.since}}"}}',
|
|
3089
|
+
"orderBy": "updatedAt",
|
|
3090
|
+
"orderDir": "desc",
|
|
3091
|
+
"limit": 15,
|
|
3092
|
+
"errorHandling": {
|
|
3093
|
+
"continueOnError": true
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
},
|
|
3097
|
+
{
|
|
3098
|
+
"id": "gather-people",
|
|
3099
|
+
"type": "query",
|
|
3100
|
+
"position": {
|
|
3101
|
+
"x": 420,
|
|
3102
|
+
"y": 260
|
|
3103
|
+
},
|
|
3104
|
+
"data": {
|
|
3105
|
+
"label": "People",
|
|
3106
|
+
"profileSlug": "person",
|
|
3107
|
+
"filter": '{"updatedAt": {"$gte": "{{trigger.payload.since}}"}}',
|
|
3108
|
+
"orderBy": "updatedAt",
|
|
3109
|
+
"orderDir": "desc",
|
|
3110
|
+
"limit": 15,
|
|
3111
|
+
"errorHandling": {
|
|
3112
|
+
"continueOnError": true
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
3115
|
+
},
|
|
3116
|
+
{
|
|
3117
|
+
"id": "gather-companies",
|
|
3118
|
+
"type": "query",
|
|
3119
|
+
"position": {
|
|
3120
|
+
"x": 420,
|
|
3121
|
+
"y": 380
|
|
3122
|
+
},
|
|
3123
|
+
"data": {
|
|
3124
|
+
"label": "Companies",
|
|
3125
|
+
"profileSlug": "company",
|
|
3126
|
+
"filter": '{"updatedAt": {"$gte": "{{trigger.payload.since}}"}}',
|
|
3127
|
+
"orderBy": "updatedAt",
|
|
3128
|
+
"orderDir": "desc",
|
|
3129
|
+
"limit": 15,
|
|
3130
|
+
"errorHandling": {
|
|
3131
|
+
"continueOnError": true
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
},
|
|
3135
|
+
{
|
|
3136
|
+
"id": "project-tasks",
|
|
3137
|
+
"type": "transform",
|
|
3138
|
+
"position": {
|
|
3139
|
+
"x": 540,
|
|
3140
|
+
"y": 20
|
|
3141
|
+
},
|
|
3142
|
+
"data": {
|
|
3143
|
+
"label": "Project tasks",
|
|
3144
|
+
"expression": "{{steps.gather-tasks.output.entities}} | map: {{item.id}} \xB7 {{item.title}} \xB7 status={{item.properties.status}} \xB7 priority={{item.properties.priority}} \xB7 due={{item.properties.dueDate}} \xB7 updated={{item.updatedAt}}",
|
|
3145
|
+
"errorHandling": {
|
|
3146
|
+
"continueOnError": true
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3149
|
+
},
|
|
3150
|
+
{
|
|
3151
|
+
"id": "project-notes",
|
|
3152
|
+
"type": "transform",
|
|
3153
|
+
"position": {
|
|
3154
|
+
"x": 540,
|
|
3155
|
+
"y": 140
|
|
3156
|
+
},
|
|
3157
|
+
"data": {
|
|
3158
|
+
"label": "Project notes",
|
|
3159
|
+
"expression": "{{steps.gather-notes.output.entities}} | map: {{item.id}} \xB7 {{item.title}} \xB7 updated={{item.updatedAt}}",
|
|
3160
|
+
"errorHandling": {
|
|
3161
|
+
"continueOnError": true
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
},
|
|
3165
|
+
{
|
|
3166
|
+
"id": "project-people",
|
|
3167
|
+
"type": "transform",
|
|
3168
|
+
"position": {
|
|
3169
|
+
"x": 540,
|
|
3170
|
+
"y": 260
|
|
3171
|
+
},
|
|
3172
|
+
"data": {
|
|
3173
|
+
"label": "Project people",
|
|
3174
|
+
"expression": "{{steps.gather-people.output.entities}} | map: {{item.id}} \xB7 {{item.title}} \xB7 email={{item.properties.email}} \xB7 lastInteraction={{item.properties.lastInteractionAt}} \xB7 updated={{item.updatedAt}}",
|
|
3175
|
+
"errorHandling": {
|
|
3176
|
+
"continueOnError": true
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
3179
|
+
},
|
|
3180
|
+
{
|
|
3181
|
+
"id": "project-companies",
|
|
3182
|
+
"type": "transform",
|
|
3183
|
+
"position": {
|
|
3184
|
+
"x": 540,
|
|
3185
|
+
"y": 380
|
|
3186
|
+
},
|
|
3187
|
+
"data": {
|
|
3188
|
+
"label": "Project companies",
|
|
3189
|
+
"expression": "{{steps.gather-companies.output.entities}} | map: {{item.id}} \xB7 {{item.title}} \xB7 industry={{item.properties.industry}} \xB7 location={{item.properties.location}} \xB7 updated={{item.updatedAt}}",
|
|
3190
|
+
"errorHandling": {
|
|
3191
|
+
"continueOnError": true
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
},
|
|
3195
|
+
{
|
|
3196
|
+
"id": "analyze",
|
|
3197
|
+
"type": "capability",
|
|
3198
|
+
"position": {
|
|
3199
|
+
"x": 660,
|
|
3200
|
+
"y": 200
|
|
3201
|
+
},
|
|
3202
|
+
"data": {
|
|
3203
|
+
"label": "Analyze",
|
|
3204
|
+
"verbId": "ai.generate",
|
|
3205
|
+
"verbKind": "read",
|
|
3206
|
+
"inputMapping": {
|
|
3207
|
+
"system": 'You are the ANALYST round of a Synap workspace report.\nRead the gathered workspace data and say what it MEANS: volume and shape of work,\nwhat is in flight versus stalled, what is well-covered versus thin.\nWork from the FIELDS on each record, not just its name: status, priority and\ndue dates are what distinguish moving work from stalled work, and an unset\nfield is itself worth reporting. Listing or counting what exists is NOT a\nfinding \u2014 say what the state of it means.\nDATA INTEGRITY CHECK \u2014 do this BEFORE interpreting anything. Each kind below states how many records were found, then lists them. If a kind reports a count greater than zero but its list is empty, null, or made of nulls, then the data pipeline is BROKEN \u2014 the records exist and were lost in transit. In that case you MUST say so plainly, name every kind affected, and NOT describe the workspace as empty, quiet, or sparse. Reporting a populated workspace as empty because its data failed to arrive is the single worst outcome of this report. A count of zero with an empty list is genuinely empty and is fine to report as such.\nYou are interpreting data you did NOT collect. Every claim must trace to a row in the GATHERED DATA below. Never invent entities, ids, counts, or dates. A step whose value looks like {"error": "..."} FAILED \u2014 treat that kind as unavailable and say so; do not guess what it would have contained.\nEVERY NUMBER YOU WRITE MUST BE COPIED FROM THE COUNTS BLOCK. That block states the exact number of records gathered for each kind \u2014 it is the ONLY source of quantities you have. Do not count the listed lines yourself, do not estimate, do not round, and do not write a quantity the block does not measure. Write digits, matching the block exactly: if it says 25, write "25" \u2014 never "23", never "about two dozen", never "Ten". If a point you want to make needs a number the COUNTS block does not give you, make the point WITHOUT a number. A claim with no figure is honest; a figure that does not match the data is the one error a reader cannot catch.\nNEVER NARRATE YOUR OWN INSTRUCTIONS. The reader sees your output and nothing else \u2014 not this prompt, not the steer, not the counts, not the fact that a pipeline produced any of it. Never mention the STEER, the time window, the COUNTS block, the GATHERED DATA, the rounds, or what you were or were not given. Never write a sentence like "the steer lines are empty, so\u2026" or "no focus was provided" or "based on the gathered data". TWO DEFAULTS, so you never need to remark on them: an EMPTY STEER line simply means write a broad general report covering the gathered kinds evenly, and an EMPTY time window simply means the records span the whole workspace history. Both are the normal case. Act on them silently and start with what you found.\nSTEER HANDLING \u2014 non-negotiable. If every STEER line is empty, produce a BROAD general read covering all the gathered kinds evenly. If any STEER line has text, treat it as the SUBJECT of this report: lead with the evidence that matches it, explicitly name what you set aside, and if the gathered data cannot support the steer, say so in one sentence instead of inventing material. Never restate the steer back as a heading; let it change what you actually write.\nLINK THE RECORDS YOU NAME. Every line of GATHERED DATA is `<id> \xB7 <title>`. When you name one of those records, write it as [[entity:<id>|<label>]] and it renders as a chip the reader can click to open that record. Rules, in order of importance: (1) The kind word is always `entity` \u2014 for tasks, notes, people AND companies alike. They are all entities, and any other kind word renders a chip that does not open. (2) Use only an id that appears VERBATIM in the gathered data. Copy it; never construct, shorten, complete or guess one. (3) If you are unsure of an id, write the plain label with no brackets. A wrong id is a dead chip, and a dead chip is worse than plain text. (4) The label is free text but must not contain `]`; the id must not contain `:` or `|`. A marker breaking either rule renders as raw literal text. (5) EVERY record you name in prose MUST carry a chip on its FIRST mention \u2014 this is what makes a named record a live object the reader can open rather than a string. Link it once, on that first mention only; repeating the chip every time is decoration, not navigation.\nOutput PLAIN PROSE \u2014 short paragraphs and bullets, with [[entity:\u2026]] chips\ninline wherever you name a record. No markdown headings, no ::: directive\nblocks, no code fences. Under 300 words. Another round will format this.',
|
|
3208
|
+
"prompt": 'STEER:\n- Prompt: {{trigger.payload.prompt}}\n- Focus: {{trigger.payload.focus}}\n- Project lens id: {{trigger.payload.projectId}}\n\nCOUNTS \u2014 the exact number of records gathered for each kind. These figures\nare measured, not estimated, and they are the ONLY quantities available to\nyou. Each line also gives the cap the gather was limited to.\n- Tasks: {{steps.gather-tasks.output.count}} gathered (cap 25)\n- Notes: {{steps.gather-notes.output.count}} gathered (cap 15)\n- People: {{steps.gather-people.output.count}} gathered (cap 15)\n- Companies: {{steps.gather-companies.output.count}} gathered (cap 15)\nREADING A COUNT AGAINST ITS CAP: a count BELOW the cap means every record of\nthat kind is listed below \u2014 the set is COMPLETE and you may describe it as\nsuch. A count EQUAL to the cap means the gather stopped at the cap and more\nrecords exist that you cannot see \u2014 describe that kind as "at least N" and\nnever as complete, and never claim something does not exist merely because\nit is absent from the list.\n\nTime window: only records updated on or after {{trigger.payload.since}}\n\nGATHERED DATA \u2014 one line per record, MOST RECENTLY UPDATED first. Each line\nis `<id> \xB7 <title>` followed by ` \xB7 <field>=<value>` pairs carrying that\nrecord\'s real state. An EMPTY value means the field is not set on that\nrecord \u2014 that is a fact you may report (an unset status or a missing due\ndate is a finding), and never a value to guess at. Recent does not mean\nimportant.\nTasks \u2014 {{steps.gather-tasks.output.count}} found: {{steps.project-tasks.output.result}}\nNotes \u2014 {{steps.gather-notes.output.count}} found: {{steps.project-notes.output.result}}\nPeople \u2014 {{steps.gather-people.output.count}} found: {{steps.project-people.output.result}}\nCompanies \u2014 {{steps.gather-companies.output.count}} found: {{steps.project-companies.output.result}}',
|
|
3209
|
+
"maxTokens": "700"
|
|
3210
|
+
},
|
|
3211
|
+
"errorHandling": {
|
|
3212
|
+
"continueOnError": true
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
},
|
|
3216
|
+
{
|
|
3217
|
+
"id": "relate",
|
|
3218
|
+
"type": "capability",
|
|
3219
|
+
"position": {
|
|
3220
|
+
"x": 900,
|
|
3221
|
+
"y": 200
|
|
3222
|
+
},
|
|
3223
|
+
"data": {
|
|
3224
|
+
"label": "Patterns",
|
|
3225
|
+
"verbId": "ai.generate",
|
|
3226
|
+
"verbKind": "read",
|
|
3227
|
+
"inputMapping": {
|
|
3228
|
+
"system": 'You are the PATTERNS round of a Synap workspace report.\nYou receive the raw gathered data AND the analyst\'s read of it. Your job is what\nthe analyst did NOT do: surface RELATIONSHIPS and things worth attention \u2014\nclusters, repeated themes, people or companies that recur across kinds, gaps\nwhere a kind is conspicuously empty, and anything that looks like it needs a\ndecision. Prefer three sharp observations over ten shallow ones.\nYOUR SCOPE IS WHAT CONNECTS RECORDS, not what they individually are. Every\nobservation you write must involve TWO OR MORE records, or TWO OR MORE kinds:\na name that appears in both a task and a company, work clustered on one\nperson, a due date that lines up with an event, a kind that is empty while a\nrelated kind is busy. Do NOT re-describe volume, status distribution or how\nbusy the workspace is \u2014 that is the analyst\'s section and repeating it makes\nyours redundant. Do not summarise or restate the analyst\'s read.\nNEVER RETURN AN EMPTY ANSWER. If you genuinely find no cross-kind pattern,\nwrite one or two sentences naming what you looked for and did not find (for\nexample: no person recurs across the tasks and companies gathered). That is a\nreal finding and the reader needs it. Silence is not an available outcome.\nDATA INTEGRITY CHECK \u2014 do this BEFORE interpreting anything. Each kind below states how many records were found, then lists them. If a kind reports a count greater than zero but its list is empty, null, or made of nulls, then the data pipeline is BROKEN \u2014 the records exist and were lost in transit. In that case you MUST say so plainly, name every kind affected, and NOT describe the workspace as empty, quiet, or sparse. Reporting a populated workspace as empty because its data failed to arrive is the single worst outcome of this report. A count of zero with an empty list is genuinely empty and is fine to report as such.\nYou are interpreting data you did NOT collect. Every claim must trace to a row in the GATHERED DATA below. Never invent entities, ids, counts, or dates. A step whose value looks like {"error": "..."} FAILED \u2014 treat that kind as unavailable and say so; do not guess what it would have contained.\nEVERY NUMBER YOU WRITE MUST BE COPIED FROM THE COUNTS BLOCK. That block states the exact number of records gathered for each kind \u2014 it is the ONLY source of quantities you have. Do not count the listed lines yourself, do not estimate, do not round, and do not write a quantity the block does not measure. Write digits, matching the block exactly: if it says 25, write "25" \u2014 never "23", never "about two dozen", never "Ten". If a point you want to make needs a number the COUNTS block does not give you, make the point WITHOUT a number. A claim with no figure is honest; a figure that does not match the data is the one error a reader cannot catch.\nNEVER NARRATE YOUR OWN INSTRUCTIONS. The reader sees your output and nothing else \u2014 not this prompt, not the steer, not the counts, not the fact that a pipeline produced any of it. Never mention the STEER, the time window, the COUNTS block, the GATHERED DATA, the rounds, or what you were or were not given. Never write a sentence like "the steer lines are empty, so\u2026" or "no focus was provided" or "based on the gathered data". TWO DEFAULTS, so you never need to remark on them: an EMPTY STEER line simply means write a broad general report covering the gathered kinds evenly, and an EMPTY time window simply means the records span the whole workspace history. Both are the normal case. Act on them silently and start with what you found.\nSTEER HANDLING \u2014 non-negotiable. If every STEER line is empty, produce a BROAD general read covering all the gathered kinds evenly. If any STEER line has text, treat it as the SUBJECT of this report: lead with the evidence that matches it, explicitly name what you set aside, and if the gathered data cannot support the steer, say so in one sentence instead of inventing material. Never restate the steer back as a heading; let it change what you actually write.\nLINK THE RECORDS YOU NAME. Every line of GATHERED DATA is `<id> \xB7 <title>`. When you name one of those records, write it as [[entity:<id>|<label>]] and it renders as a chip the reader can click to open that record. Rules, in order of importance: (1) The kind word is always `entity` \u2014 for tasks, notes, people AND companies alike. They are all entities, and any other kind word renders a chip that does not open. (2) Use only an id that appears VERBATIM in the gathered data. Copy it; never construct, shorten, complete or guess one. (3) If you are unsure of an id, write the plain label with no brackets. A wrong id is a dead chip, and a dead chip is worse than plain text. (4) The label is free text but must not contain `]`; the id must not contain `:` or `|`. A marker breaking either rule renders as raw literal text. (5) EVERY record you name in prose MUST carry a chip on its FIRST mention \u2014 this is what makes a named record a live object the reader can open rather than a string. Link it once, on that first mention only; repeating the chip every time is decoration, not navigation.\nOutput PLAIN PROSE, under 250 words, with [[entity:\u2026]] chips inline. No\nheadings, no ::: directive blocks, no code fences.',
|
|
3229
|
+
"prompt": "STEER:\n- Prompt: {{trigger.payload.prompt}}\n- Focus: {{trigger.payload.focus}}\n- Project lens id: {{trigger.payload.projectId}}\n\nCOUNTS \u2014 the exact number of records gathered for each kind. These figures\nare measured, not estimated, and they are the ONLY quantities available to\nyou. Each line also gives the cap the gather was limited to.\n- Tasks: {{steps.gather-tasks.output.count}} gathered (cap 25)\n- Notes: {{steps.gather-notes.output.count}} gathered (cap 15)\n- People: {{steps.gather-people.output.count}} gathered (cap 15)\n- Companies: {{steps.gather-companies.output.count}} gathered (cap 15)\nREADING A COUNT AGAINST ITS CAP: a count BELOW the cap means every record of\nthat kind is listed below \u2014 the set is COMPLETE and you may describe it as\nsuch. A count EQUAL to the cap means the gather stopped at the cap and more\nrecords exist that you cannot see \u2014 describe that kind as \"at least N\" and\nnever as complete, and never claim something does not exist merely because\nit is absent from the list.\n\nTime window: only records updated on or after {{trigger.payload.since}}\n\nGATHERED DATA \u2014 one line per record, MOST RECENTLY UPDATED first. Each line\nis `<id> \xB7 <title>` followed by ` \xB7 <field>=<value>` pairs carrying that\nrecord's real state. An EMPTY value means the field is not set on that\nrecord \u2014 that is a fact you may report (an unset status or a missing due\ndate is a finding), and never a value to guess at. Recent does not mean\nimportant.\nTasks \u2014 {{steps.gather-tasks.output.count}} found: {{steps.project-tasks.output.result}}\nNotes \u2014 {{steps.gather-notes.output.count}} found: {{steps.project-notes.output.result}}\nPeople \u2014 {{steps.gather-people.output.count}} found: {{steps.project-people.output.result}}\nCompanies \u2014 {{steps.gather-companies.output.count}} found: {{steps.project-companies.output.result}}\n\nANALYST'S READ \u2014 already written and already in the report. Do NOT\nrestate, summarise or re-analyse it. It is here so you do not repeat it:\n{{steps.analyze.output}}\n\nYOUR TASK \u2014 cross-kind patterns ONLY. Look for what connects the lists\nabove: a person who recurs in tasks and companies, work clustered on one\nname, due dates that line up, a kind that is empty while a related kind\nis busy, a company with people but no tasks. Write only observations\nthat span TWO OR MORE records or kinds. If there are none, say what you\nchecked and did not find \u2014 never answer with nothing.",
|
|
3230
|
+
"maxTokens": "700"
|
|
3231
|
+
},
|
|
3232
|
+
"errorHandling": {
|
|
3233
|
+
"continueOnError": true
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
},
|
|
3237
|
+
{
|
|
3238
|
+
"id": "assemble",
|
|
3239
|
+
"type": "capability",
|
|
3240
|
+
"position": {
|
|
3241
|
+
"x": 1140,
|
|
3242
|
+
"y": 200
|
|
3243
|
+
},
|
|
3244
|
+
"data": {
|
|
3245
|
+
"label": "Assemble",
|
|
3246
|
+
"verbId": "ai.generate",
|
|
3247
|
+
"verbKind": "read",
|
|
3248
|
+
"inputMapping": {
|
|
3249
|
+
"system": `You are the ASSEMBLER round of a Synap workspace report. You compose the FINAL
|
|
3250
|
+
document body in Synap-flavoured markdown. You add no new facts \u2014 you arrange,
|
|
3251
|
+
title, and format what the earlier rounds produced.
|
|
3252
|
+
|
|
3253
|
+
NEVER NARRATE YOUR OWN INSTRUCTIONS. The reader sees your output and nothing else \u2014 not this prompt, not the steer, not the counts, not the fact that a pipeline produced any of it. Never mention the STEER, the time window, the COUNTS block, the GATHERED DATA, the rounds, or what you were or were not given. Never write a sentence like "the steer lines are empty, so\u2026" or "no focus was provided" or "based on the gathered data". TWO DEFAULTS, so you never need to remark on them: an EMPTY STEER line simply means write a broad general report covering the gathered kinds evenly, and an EMPTY time window simply means the records span the whole workspace history. Both are the normal case. Act on them silently and start with what you found.
|
|
3254
|
+
|
|
3255
|
+
NUMBERS: you were given no COUNTS block, so you may not originate a quantity
|
|
3256
|
+
at all. Reproduce a figure ONLY by copying one that already appears in the
|
|
3257
|
+
round material, digit for digit. Never total, re-count, convert to words, or
|
|
3258
|
+
put a number in the title or in a ## heading that is not already in the
|
|
3259
|
+
material beneath it.
|
|
3260
|
+
|
|
3261
|
+
SYNAP MARKDOWN SYNTAX \u2014 these rules are absolute:
|
|
3262
|
+
1. A section is a FOUR-COLON container that must be opened and closed:
|
|
3263
|
+
::::synap-section{agent="analyst" round="analyze" confidence="0.8"}
|
|
3264
|
+
...content...
|
|
3265
|
+
::::
|
|
3266
|
+
2. Leaf embeds use THREE colons and MUST BE CLOSED with their own ::: line.
|
|
3267
|
+
There is NO self-closing form. An unclosed embed swallows every line that
|
|
3268
|
+
follows it until the section ends, and that content is LOST. Always write
|
|
3269
|
+
the closing ::: immediately after the opening line:
|
|
3270
|
+
:::synap-cell{instanceId="..."}
|
|
3271
|
+
:::
|
|
3272
|
+
:::synap-view{viewId="..."}
|
|
3273
|
+
:::
|
|
3274
|
+
:::synap-entity{id="..."}
|
|
3275
|
+
:::
|
|
3276
|
+
3. The outer container must always have STRICTLY MORE colons than anything nested
|
|
3277
|
+
inside it. Four outside, three inside. If you ever nest a four-colon block in a
|
|
3278
|
+
four-colon block the section closes early and the rest of the report leaks out.
|
|
3279
|
+
4. ATTRIBUTE VALUES MUST BE QUOTED. An UNQUOTED value ends at the first } it
|
|
3280
|
+
contains, which silently truncates the attribute \u2014 so always quote. Use
|
|
3281
|
+
DOUBLE quotes for a plain value, and SINGLE quotes when the value is itself
|
|
3282
|
+
a JSON object, so the JSON's own double quotes survive:
|
|
3283
|
+
:::synap-cell{cellKey="chart-pie" cellProps='{"profileSlug":"task","groupBy":"status"}'}
|
|
3284
|
+
:::
|
|
3285
|
+
A single-quoted value may contain both " and } \u2014 those are only special to
|
|
3286
|
+
an unquoted value. Write the JSON compact, on one line, with no trailing
|
|
3287
|
+
comma; malformed JSON renders the cell with empty config.
|
|
3288
|
+
5. The ONLY section attributes that survive are: id, agent, round, skills,
|
|
3289
|
+
confidence, stepRunId, nodeId, status. Anything else is silently dropped.
|
|
3290
|
+
6. confidence is a 0-1 ratio written as a decimal string, e.g. confidence="0.8".
|
|
3291
|
+
Never a percentage.
|
|
3292
|
+
7. CHIPS ARE CONTENT \u2014 COPY THEM THROUGH VERBATIM. The round material you were
|
|
3293
|
+
given already contains [[entity:<id>|<label>]] markers. Reproduce every one
|
|
3294
|
+
EXACTLY as written: same brackets, same id, same label. Do NOT tidy them into
|
|
3295
|
+
plain text, do NOT rewrite or re-case an id, do NOT convert them to markdown
|
|
3296
|
+
links. You are told you add no new FACTS; a chip is not a fact, it is the
|
|
3297
|
+
earlier round's finding made navigable, and stripping it removes the only
|
|
3298
|
+
clickable thing in the report while leaving the prose looking fine.
|
|
3299
|
+
You may add a chip ONLY by copying a marker that already appears in the
|
|
3300
|
+
material. Never invent an id, and never use a kind word other than \`entity\` \u2014
|
|
3301
|
+
this report is READ-ONLY prose, and either mistake produces a dead chip.
|
|
3302
|
+
8. NEVER EMIT :::synap-view, AND NEVER EMIT :::synap-cell WITH AN instanceId.
|
|
3303
|
+
Both need an id you have not been given and cannot obtain, and a made-up
|
|
3304
|
+
reference produces a permanent broken block in a document a human reads.
|
|
3305
|
+
9. YOU MAY EMIT A CHART :::synap-cell \u2014 from this allowlist ONLY, configured
|
|
3306
|
+
through cellProps. These cells fetch their own data at render time, so they
|
|
3307
|
+
need no id, and they make the report a live surface instead of a text file:
|
|
3308
|
+
- :::synap-cell{cellKey="stat-card" cellProps='{"profileSlug":"<kind>","aggregation":"count","timePeriod":"week","label":"<Kind> created this week"}'}
|
|
3309
|
+
- :::synap-cell{cellKey="chart-pie" cellProps='{"profileSlug":"<kind>","groupBy":"<property>","label":"<the finding this shows>"}'}
|
|
3310
|
+
- :::synap-cell{cellKey="chart-bar" cellProps='{"profileSlug":"<kind>","mode":"category","groupBy":"<property>","label":"<the finding this shows>"}'}
|
|
3311
|
+
Rules, all mandatory:
|
|
3312
|
+
(a) profileSlug must be one of the kinds gathered for THIS report: task,
|
|
3313
|
+
note, person, company. A chart over any other kind is a claim this
|
|
3314
|
+
report cannot back \u2014 the data was never read.
|
|
3315
|
+
(b) groupBy / field must name a property that plausibly exists on that
|
|
3316
|
+
kind. If you are not confident one does, use the stat-card line above
|
|
3317
|
+
EXACTLY as written \u2014 profileSlug, aggregation, timePeriod AND a label
|
|
3318
|
+
naming the window. Never drop timePeriod and never label a windowed
|
|
3319
|
+
count with a bare kind name: the card counts records CREATED in that
|
|
3320
|
+
window, so "Tasks" over a weekly count is a false statement about
|
|
3321
|
+
the workspace.
|
|
3322
|
+
(c) Close EVERY embed with its own ::: line, per rule 2. An unclosed embed
|
|
3323
|
+
swallows the rest of the section.
|
|
3324
|
+
(d) EDITORIAL RULE \u2014 embed to SHOW what a sentence cannot: a distribution,
|
|
3325
|
+
a ratio, a count. Never to decorate. At most 2-3 in the whole report,
|
|
3326
|
+
placed inside the section whose prose they support. A chart that merely
|
|
3327
|
+
restates the sentence beside it is noise, and noise is worse than
|
|
3328
|
+
nothing here. If no gathered kind has a distribution worth showing,
|
|
3329
|
+
emit no cells at all \u2014 that is a correct report, not a lesser one.
|
|
3330
|
+
(e) AT MOST ONE cell per section, and NEVER at the top of a section.
|
|
3331
|
+
Place it immediately AFTER the sentence making the claim it evidences,
|
|
3332
|
+
so the reader has the claim before the picture. A chart that opens a
|
|
3333
|
+
section makes the reader decode a graphic to find out what the section
|
|
3334
|
+
is about, which is the delay the ## heading and the claim sentence
|
|
3335
|
+
exist to remove.
|
|
3336
|
+
(f) The label must state the FINDING, not the dimension. Write
|
|
3337
|
+
label:"Half the open tasks are blocked", never label:"Distribution",
|
|
3338
|
+
"Status" or "Tasks by status". Always set label explicitly: with none,
|
|
3339
|
+
a pie chart titles itself "Distribution" and a bar chart "Breakdown" \u2014
|
|
3340
|
+
words true of every such chart ever drawn, which tell the reader
|
|
3341
|
+
nothing they cannot already see in the shapes.
|
|
3342
|
+
(g) A CHART IS A LIVE VIEW, THE PROSE IS A SNAPSHOT. These cells run
|
|
3343
|
+
their own query when the report is OPENED, which may be long after
|
|
3344
|
+
this text was written \u2014 so a sentence quoting a count and a chart
|
|
3345
|
+
measuring the same thing WILL eventually disagree, and the reader
|
|
3346
|
+
cannot tell which is stale. Never write a specific number in a
|
|
3347
|
+
sentence that sits beside a chart of the same data: let the chart
|
|
3348
|
+
carry the number and let the sentence carry the claim
|
|
3349
|
+
("most open tasks are stalled at review", not "12 of 19 are stalled").
|
|
3350
|
+
And if the sentence already gives the number the chart would show,
|
|
3351
|
+
DROP THE CHART \u2014 one of them is redundant, and the sentence is the one
|
|
3352
|
+
that cannot go stale against itself.
|
|
3353
|
+
Everything else this report needs is ::::synap-section containers plus
|
|
3354
|
+
[[entity:\u2026]] chips inline.
|
|
3355
|
+
|
|
3356
|
+
10. EVERY ::::synap-section MUST OPEN WITH A ## CLAIM HEADING \u2014 its first
|
|
3357
|
+
line, before any prose. The heading states WHAT THE SECTION FOUND, not
|
|
3358
|
+
what produced it: "## Half the open tasks are blocked", never
|
|
3359
|
+
"## Analysis", "## Analyze round" or "## Findings". Under 10 words, no
|
|
3360
|
+
trailing period, no markdown or [[entity:\u2026]] chip inside the heading
|
|
3361
|
+
itself \u2014 put the chip in the prose beneath it. Write exactly one ## per
|
|
3362
|
+
section. This heading is the section's title everywhere it is read: the
|
|
3363
|
+
slide deck sets it as the slide headline (with no heading it falls back
|
|
3364
|
+
to the round and agent names, which the slide already shows underneath),
|
|
3365
|
+
and the scrolling reader has no section titles at all without it. A
|
|
3366
|
+
section whose heading merely names the round tells the reader nothing
|
|
3367
|
+
they cannot already see.
|
|
3368
|
+
|
|
3369
|
+
STRUCTURE: one ::::synap-section per round, in order \u2014 analyze, then relate.
|
|
3370
|
+
Open with a single "# " title line ABOVE the first section, and open each
|
|
3371
|
+
section with its own ## claim heading per rule 10.
|
|
3372
|
+
|
|
3373
|
+
STEER HANDLING: if the STEER lines are empty, title the report generically
|
|
3374
|
+
("Workspace report \u2014 <period>") and keep the rounds in their given order. If any
|
|
3375
|
+
STEER line has text, name that subject in the title and lead each section with
|
|
3376
|
+
the material that answers it. Never add facts the rounds did not give you.
|
|
3377
|
+
|
|
3378
|
+
MISSING ROUNDS: if a round's material looks like {"error": "..."} or is empty,
|
|
3379
|
+
you MUST still emit its section, with status="failed", containing one visible
|
|
3380
|
+
sentence naming what is missing. NEVER silently produce a shorter report \u2014 a
|
|
3381
|
+
reader must be able to see that a round did not run. A failed section is NOT
|
|
3382
|
+
exempt from rule 10: give it a ## heading that states what is missing
|
|
3383
|
+
("## The patterns round produced nothing"), because it still occupies a slide
|
|
3384
|
+
and still needs a headline.
|
|
3385
|
+
|
|
3386
|
+
WORKED EXAMPLE of correct output:
|
|
3387
|
+
# Workspace report \u2014 July 2026
|
|
3388
|
+
|
|
3389
|
+
::::synap-section{agent="analyst" round="analyze" confidence="0.8"}
|
|
3390
|
+
## Most open tasks are stalled at review
|
|
3391
|
+
|
|
3392
|
+
Open tasks are piling up untouched since the sprint opened, and the great
|
|
3393
|
+
majority are sitting in one status \u2014 the oldest is
|
|
3394
|
+
[[entity:3f2a91c4-7b10-4e55-9c02-8ad1f6e4b2d7|Migrate the billing worker]].
|
|
3395
|
+
Notes are thin.
|
|
3396
|
+
:::synap-cell{cellKey="chart-pie" cellProps='{"profileSlug":"task","groupBy":"status","label":"Review is where open tasks accumulate"}'}
|
|
3397
|
+
:::
|
|
3398
|
+
::::
|
|
3399
|
+
|
|
3400
|
+
::::synap-section{agent="analyst" round="relate" status="failed" confidence="0.2"}
|
|
3401
|
+
## The patterns round produced nothing
|
|
3402
|
+
|
|
3403
|
+
This section is missing: the patterns round failed and produced no material.
|
|
3404
|
+
::::
|
|
3405
|
+
|
|
3406
|
+
Be CONCISE \u2014 your entire output must fit in 2000 tokens. Emit ONLY the markdown
|
|
3407
|
+
document. No preamble, no explanation, no code fences around the whole thing.`,
|
|
3408
|
+
"prompt": "STEER:\n- Prompt: {{trigger.payload.prompt}}\n- Focus: {{trigger.payload.focus}}\n- Project lens id: {{trigger.payload.projectId}}\n\nReport generated at: {{steps.now.output.result}}\n\nANALYZE ROUND MATERIAL:\n{{steps.analyze.output}}\n\nRELATE ROUND MATERIAL:\n{{steps.relate.output}}",
|
|
3409
|
+
"maxTokens": "2000"
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
},
|
|
3413
|
+
{
|
|
3414
|
+
"id": "summarize",
|
|
3415
|
+
"type": "capability",
|
|
3416
|
+
"position": {
|
|
3417
|
+
"x": 1380,
|
|
3418
|
+
"y": 200
|
|
3419
|
+
},
|
|
3420
|
+
"data": {
|
|
3421
|
+
"label": "One-line summary",
|
|
3422
|
+
"verbId": "ai.generate",
|
|
3423
|
+
"verbKind": "read",
|
|
3424
|
+
"inputMapping": {
|
|
3425
|
+
"system": 'Write ONE plain sentence, under 200 characters, stating what this report found.\nNo markdown, no directives, no quotes, no leading label. Just the sentence.\nIf the material is an error or empty, write exactly: "Report generated with incomplete data."',
|
|
3426
|
+
"prompt": "Material to summarize (may be an error object if the round failed):\n{{steps.analyze.output}}",
|
|
3427
|
+
"maxTokens": "500"
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
},
|
|
3431
|
+
{
|
|
3432
|
+
"id": "guard-assembled",
|
|
3433
|
+
"type": "guard",
|
|
3434
|
+
"position": {
|
|
3435
|
+
"x": 1620,
|
|
3436
|
+
"y": 200
|
|
3437
|
+
},
|
|
3438
|
+
"data": {
|
|
3439
|
+
"label": "Body assembled",
|
|
3440
|
+
"checks": [
|
|
3441
|
+
{
|
|
3442
|
+
"path": "steps.assemble.output",
|
|
3443
|
+
"minLength": 200,
|
|
3444
|
+
"message": "The assembler produced no usable body (under 200 characters) \u2014 refusing to write an empty report."
|
|
3445
|
+
},
|
|
3446
|
+
{
|
|
3447
|
+
"path": "steps.assemble.output.error",
|
|
3448
|
+
"exists": false,
|
|
3449
|
+
"message": "The assembler round failed \u2014 refusing to write a report without a body."
|
|
3450
|
+
}
|
|
3451
|
+
]
|
|
3452
|
+
}
|
|
3453
|
+
},
|
|
3454
|
+
{
|
|
3455
|
+
"id": "create-report",
|
|
3456
|
+
"type": "output",
|
|
3457
|
+
"position": {
|
|
3458
|
+
"x": 1860,
|
|
3459
|
+
"y": 200
|
|
3460
|
+
},
|
|
3461
|
+
"data": {
|
|
3462
|
+
"label": "Create report",
|
|
3463
|
+
"outputType": "entity_create",
|
|
3464
|
+
"config": {
|
|
3465
|
+
"profileSlug": "report",
|
|
3466
|
+
"title": "Workspace report \u2014 {{steps.now.output.result}}",
|
|
3467
|
+
"properties": {
|
|
3468
|
+
"reportPeriod": "{{steps.now.output.result}}",
|
|
3469
|
+
"generatedAt": "{{steps.now.output.result}}",
|
|
3470
|
+
"reportStatus": "ready",
|
|
3471
|
+
"summary": "{{steps.summarize.output}}",
|
|
3472
|
+
"reportSources": [
|
|
3473
|
+
"Tasks",
|
|
3474
|
+
"Notes",
|
|
3475
|
+
"People",
|
|
3476
|
+
"Companies"
|
|
3477
|
+
]
|
|
3478
|
+
},
|
|
3479
|
+
"body": "{{steps.assemble.output}}",
|
|
3480
|
+
"systemData": {
|
|
3481
|
+
"renderer": {
|
|
3482
|
+
"kind": "cell",
|
|
3483
|
+
"cellKey": "{{trigger.payload.renderer}}"
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
],
|
|
3490
|
+
"edges": [
|
|
3491
|
+
{
|
|
3492
|
+
"id": "e-trigger-now",
|
|
3493
|
+
"source": "trigger",
|
|
3494
|
+
"target": "now"
|
|
3495
|
+
},
|
|
3496
|
+
{
|
|
3497
|
+
"id": "e-now-tasks",
|
|
3498
|
+
"source": "now",
|
|
3499
|
+
"target": "gather-tasks"
|
|
3500
|
+
},
|
|
3501
|
+
{
|
|
3502
|
+
"id": "e-now-notes",
|
|
3503
|
+
"source": "now",
|
|
3504
|
+
"target": "gather-notes"
|
|
3505
|
+
},
|
|
3506
|
+
{
|
|
3507
|
+
"id": "e-now-people",
|
|
3508
|
+
"source": "now",
|
|
3509
|
+
"target": "gather-people"
|
|
3510
|
+
},
|
|
3511
|
+
{
|
|
3512
|
+
"id": "e-now-companies",
|
|
3513
|
+
"source": "now",
|
|
3514
|
+
"target": "gather-companies"
|
|
3515
|
+
},
|
|
3516
|
+
{
|
|
3517
|
+
"id": "e-tasks-project",
|
|
3518
|
+
"source": "gather-tasks",
|
|
3519
|
+
"target": "project-tasks"
|
|
3520
|
+
},
|
|
3521
|
+
{
|
|
3522
|
+
"id": "e-notes-project",
|
|
3523
|
+
"source": "gather-notes",
|
|
3524
|
+
"target": "project-notes"
|
|
3525
|
+
},
|
|
3526
|
+
{
|
|
3527
|
+
"id": "e-people-project",
|
|
3528
|
+
"source": "gather-people",
|
|
3529
|
+
"target": "project-people"
|
|
3530
|
+
},
|
|
3531
|
+
{
|
|
3532
|
+
"id": "e-companies-project",
|
|
3533
|
+
"source": "gather-companies",
|
|
3534
|
+
"target": "project-companies"
|
|
3535
|
+
},
|
|
3536
|
+
{
|
|
3537
|
+
"id": "e-tasks-analyze",
|
|
3538
|
+
"source": "project-tasks",
|
|
3539
|
+
"target": "analyze"
|
|
3540
|
+
},
|
|
3541
|
+
{
|
|
3542
|
+
"id": "e-notes-analyze",
|
|
3543
|
+
"source": "project-notes",
|
|
3544
|
+
"target": "analyze"
|
|
3545
|
+
},
|
|
3546
|
+
{
|
|
3547
|
+
"id": "e-people-analyze",
|
|
3548
|
+
"source": "project-people",
|
|
3549
|
+
"target": "analyze"
|
|
3550
|
+
},
|
|
3551
|
+
{
|
|
3552
|
+
"id": "e-companies-analyze",
|
|
3553
|
+
"source": "project-companies",
|
|
3554
|
+
"target": "analyze"
|
|
3555
|
+
},
|
|
3556
|
+
{
|
|
3557
|
+
"id": "e-analyze-relate",
|
|
3558
|
+
"source": "analyze",
|
|
3559
|
+
"target": "relate"
|
|
3560
|
+
},
|
|
3561
|
+
{
|
|
3562
|
+
"id": "e-relate-assemble",
|
|
3563
|
+
"source": "relate",
|
|
3564
|
+
"target": "assemble"
|
|
3565
|
+
},
|
|
3566
|
+
{
|
|
3567
|
+
"id": "e-assemble-summarize",
|
|
3568
|
+
"source": "assemble",
|
|
3569
|
+
"target": "summarize"
|
|
3570
|
+
},
|
|
3571
|
+
{
|
|
3572
|
+
"id": "e-summarize-guard",
|
|
3573
|
+
"source": "summarize",
|
|
3574
|
+
"target": "guard-assembled"
|
|
3575
|
+
},
|
|
3576
|
+
{
|
|
3577
|
+
"id": "e-guard-create",
|
|
3578
|
+
"source": "guard-assembled",
|
|
3579
|
+
"target": "create-report"
|
|
3580
|
+
}
|
|
3581
|
+
]
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
]
|
|
3585
|
+
},
|
|
2739
3586
|
"blockchain-ecosystem": {
|
|
2740
3587
|
"meta": {
|
|
2741
3588
|
"slug": "blockchain-ecosystem",
|
|
@@ -6796,17 +7643,16 @@ var DATA = {
|
|
|
6796
7643
|
},
|
|
6797
7644
|
"profiles": [
|
|
6798
7645
|
{
|
|
6799
|
-
"slug": "
|
|
6800
|
-
"displayName": "
|
|
6801
|
-
"icon": "user-
|
|
6802
|
-
"color": "
|
|
6803
|
-
"description": "
|
|
6804
|
-
"scope": "
|
|
7646
|
+
"slug": "lead",
|
|
7647
|
+
"displayName": "Lead",
|
|
7648
|
+
"icon": "user-plus",
|
|
7649
|
+
"color": "yellow",
|
|
7650
|
+
"description": "An early-pipeline prospect \u2014 not yet a client, being qualified or nurtured.",
|
|
7651
|
+
"scope": "shared",
|
|
6805
7652
|
"profileKind": "role",
|
|
6806
|
-
"semanticSlug": "crm.lead",
|
|
6807
7653
|
"applicableKinds": [
|
|
6808
|
-
"
|
|
6809
|
-
"
|
|
7654
|
+
"company",
|
|
7655
|
+
"person"
|
|
6810
7656
|
],
|
|
6811
7657
|
"properties": [
|
|
6812
7658
|
{
|
|
@@ -6903,17 +7749,16 @@ var DATA = {
|
|
|
6903
7749
|
]
|
|
6904
7750
|
},
|
|
6905
7751
|
{
|
|
6906
|
-
"slug": "
|
|
6907
|
-
"displayName": "
|
|
6908
|
-
"icon": "
|
|
7752
|
+
"slug": "client",
|
|
7753
|
+
"displayName": "Client",
|
|
7754
|
+
"icon": "briefcase",
|
|
6909
7755
|
"color": "emerald",
|
|
6910
|
-
"description": "
|
|
6911
|
-
"scope": "
|
|
7756
|
+
"description": "An active commercial relationship \u2014 created when a deal is won.",
|
|
7757
|
+
"scope": "shared",
|
|
6912
7758
|
"profileKind": "role",
|
|
6913
|
-
"semanticSlug": "crm.client",
|
|
6914
7759
|
"applicableKinds": [
|
|
6915
|
-
"
|
|
6916
|
-
"
|
|
7760
|
+
"company",
|
|
7761
|
+
"person"
|
|
6917
7762
|
],
|
|
6918
7763
|
"properties": [
|
|
6919
7764
|
{
|
|
@@ -7278,7 +8123,7 @@ var DATA = {
|
|
|
7278
8123
|
"description": "Research and qualify one CRM lead, then make the next action explicit without creating a commercial proposal automatically.",
|
|
7279
8124
|
"goalTemplate": "Work on CRM lead ({lead}). Verify and enrich only factual information that can be supported, assess fit, and update its CRM lifecycle stage, ICP score, qualification notes, outreach channels, and next action. Do not create a deal simply because the lead is qualified. If a commercial proposal is warranted, explain why and ask for or propose that next step separately.",
|
|
7280
8125
|
"subjectProfile": {
|
|
7281
|
-
"profileSlug": "
|
|
8126
|
+
"profileSlug": "lead"
|
|
7282
8127
|
},
|
|
7283
8128
|
"params": [
|
|
7284
8129
|
{
|
|
@@ -7381,7 +8226,7 @@ var DATA = {
|
|
|
7381
8226
|
},
|
|
7382
8227
|
{
|
|
7383
8228
|
"path": "steps.read-deal.output.entity.properties.sourcedByUserId",
|
|
7384
|
-
"
|
|
8229
|
+
"minLength": 1,
|
|
7385
8230
|
"message": "Set the business developer who sourced this proposal before handing it off."
|
|
7386
8231
|
},
|
|
7387
8232
|
{
|
|
@@ -7451,7 +8296,7 @@ var DATA = {
|
|
|
7451
8296
|
"checks": [
|
|
7452
8297
|
{
|
|
7453
8298
|
"path": "steps.read-party.output.entity.facetSlugs",
|
|
7454
|
-
"arrayIncludes": "
|
|
8299
|
+
"arrayIncludes": "lead",
|
|
7455
8300
|
"message": "The commercial counterparty must still be a CRM lead before it becomes a CRM client."
|
|
7456
8301
|
}
|
|
7457
8302
|
]
|
|
@@ -7714,7 +8559,7 @@ var DATA = {
|
|
|
7714
8559
|
"outputType": "facet_attach",
|
|
7715
8560
|
"config": {
|
|
7716
8561
|
"entityId": "{{steps.read-party.output.entity.id}}",
|
|
7717
|
-
"facetSlug": "
|
|
8562
|
+
"facetSlug": "client",
|
|
7718
8563
|
"properties": {
|
|
7719
8564
|
"becameClientAt": "{{steps.handoff-time.output.result}}",
|
|
7720
8565
|
"handoffStatus": "handed_off"
|
|
@@ -7734,7 +8579,7 @@ var DATA = {
|
|
|
7734
8579
|
"outputType": "facet_update",
|
|
7735
8580
|
"config": {
|
|
7736
8581
|
"entityId": "{{steps.read-party.output.entity.id}}",
|
|
7737
|
-
"facetSlug": "
|
|
8582
|
+
"facetSlug": "lead",
|
|
7738
8583
|
"properties": {
|
|
7739
8584
|
"lifecycleStage": "converted",
|
|
7740
8585
|
"convertedAt": "{{steps.handoff-time.output.result}}"
|
|
@@ -7877,7 +8722,7 @@ var DATA = {
|
|
|
7877
8722
|
"label": "Qualify lead",
|
|
7878
8723
|
"when": {
|
|
7879
8724
|
"requiredFacetSlugs": [
|
|
7880
|
-
"
|
|
8725
|
+
"lead"
|
|
7881
8726
|
]
|
|
7882
8727
|
}
|
|
7883
8728
|
},
|
|
@@ -7889,7 +8734,7 @@ var DATA = {
|
|
|
7889
8734
|
"label": "Qualify lead",
|
|
7890
8735
|
"when": {
|
|
7891
8736
|
"requiredFacetSlugs": [
|
|
7892
|
-
"
|
|
8737
|
+
"lead"
|
|
7893
8738
|
]
|
|
7894
8739
|
}
|
|
7895
8740
|
},
|
|
@@ -9211,7 +10056,8 @@ var DATA = {
|
|
|
9211
10056
|
"scopeProfileSlug": "post",
|
|
9212
10057
|
"description": "Your editorial calendar \u2014 every post on the day it publishes.",
|
|
9213
10058
|
"config": {
|
|
9214
|
-
"dateField": "properties.publish-date"
|
|
10059
|
+
"dateField": "properties.publish-date",
|
|
10060
|
+
"focusKind": "post"
|
|
9215
10061
|
}
|
|
9216
10062
|
},
|
|
9217
10063
|
{
|
|
@@ -9356,6 +10202,82 @@ var DATA = {
|
|
|
9356
10202
|
}
|
|
9357
10203
|
]
|
|
9358
10204
|
},
|
|
10205
|
+
"profileEntityBentoTemplates": {
|
|
10206
|
+
"series": {
|
|
10207
|
+
"blocks": [
|
|
10208
|
+
{
|
|
10209
|
+
"id": "series-eb-header",
|
|
10210
|
+
"kind": "widget",
|
|
10211
|
+
"widgetType": "entity-header",
|
|
10212
|
+
"pos": {
|
|
10213
|
+
"x": 0,
|
|
10214
|
+
"y": 0,
|
|
10215
|
+
"w": 12,
|
|
10216
|
+
"h": 2
|
|
10217
|
+
}
|
|
10218
|
+
},
|
|
10219
|
+
{
|
|
10220
|
+
"id": "series-eb-props",
|
|
10221
|
+
"kind": "widget",
|
|
10222
|
+
"widgetType": "entity-properties",
|
|
10223
|
+
"pos": {
|
|
10224
|
+
"x": 0,
|
|
10225
|
+
"y": 2,
|
|
10226
|
+
"w": 5,
|
|
10227
|
+
"h": 8
|
|
10228
|
+
}
|
|
10229
|
+
},
|
|
10230
|
+
{
|
|
10231
|
+
"id": "series-eb-curriculum",
|
|
10232
|
+
"kind": "widget",
|
|
10233
|
+
"widgetType": "series-curriculum",
|
|
10234
|
+
"pos": {
|
|
10235
|
+
"x": 5,
|
|
10236
|
+
"y": 2,
|
|
10237
|
+
"w": 7,
|
|
10238
|
+
"h": 8
|
|
10239
|
+
}
|
|
10240
|
+
}
|
|
10241
|
+
]
|
|
10242
|
+
},
|
|
10243
|
+
"pillar": {
|
|
10244
|
+
"blocks": [
|
|
10245
|
+
{
|
|
10246
|
+
"id": "pillar-eb-header",
|
|
10247
|
+
"kind": "widget",
|
|
10248
|
+
"widgetType": "entity-header",
|
|
10249
|
+
"pos": {
|
|
10250
|
+
"x": 0,
|
|
10251
|
+
"y": 0,
|
|
10252
|
+
"w": 12,
|
|
10253
|
+
"h": 2
|
|
10254
|
+
}
|
|
10255
|
+
},
|
|
10256
|
+
{
|
|
10257
|
+
"id": "pillar-eb-props",
|
|
10258
|
+
"kind": "widget",
|
|
10259
|
+
"widgetType": "entity-properties",
|
|
10260
|
+
"pos": {
|
|
10261
|
+
"x": 0,
|
|
10262
|
+
"y": 2,
|
|
10263
|
+
"w": 5,
|
|
10264
|
+
"h": 8
|
|
10265
|
+
}
|
|
10266
|
+
},
|
|
10267
|
+
{
|
|
10268
|
+
"id": "pillar-eb-chapter",
|
|
10269
|
+
"kind": "widget",
|
|
10270
|
+
"widgetType": "pillar-chapter",
|
|
10271
|
+
"pos": {
|
|
10272
|
+
"x": 5,
|
|
10273
|
+
"y": 2,
|
|
10274
|
+
"w": 7,
|
|
10275
|
+
"h": 8
|
|
10276
|
+
}
|
|
10277
|
+
}
|
|
10278
|
+
]
|
|
10279
|
+
}
|
|
10280
|
+
},
|
|
9359
10281
|
"entityLinks": [
|
|
9360
10282
|
{
|
|
9361
10283
|
"sourceProfileSlug": "post",
|
|
@@ -10085,11 +11007,12 @@ var DATA = {
|
|
|
10085
11007
|
],
|
|
10086
11008
|
"isPublic": true
|
|
10087
11009
|
},
|
|
10088
|
-
"
|
|
10089
|
-
"
|
|
10090
|
-
|
|
10091
|
-
|
|
10092
|
-
|
|
11010
|
+
"layout": {
|
|
11011
|
+
"primarySurface": {
|
|
11012
|
+
"kind": "url",
|
|
11013
|
+
"url": "https://crm.synap.live",
|
|
11014
|
+
"title": "CRM"
|
|
11015
|
+
}
|
|
10093
11016
|
},
|
|
10094
11017
|
"dependencies": [
|
|
10095
11018
|
{
|
|
@@ -10126,6 +11049,7 @@ var DATA = {
|
|
|
10126
11049
|
"icon": "user",
|
|
10127
11050
|
"color": "amber",
|
|
10128
11051
|
"description": "A prospect, stakeholder, or contact.",
|
|
11052
|
+
"entityScope": "pod",
|
|
10129
11053
|
"properties": [
|
|
10130
11054
|
{
|
|
10131
11055
|
"slug": "email",
|
|
@@ -10211,7 +11135,7 @@ var DATA = {
|
|
|
10211
11135
|
"icon": "building-2",
|
|
10212
11136
|
"color": "#10B981",
|
|
10213
11137
|
"description": "An organisation being qualified or sold to.",
|
|
10214
|
-
"
|
|
11138
|
+
"entityScope": "pod",
|
|
10215
11139
|
"properties": [
|
|
10216
11140
|
{
|
|
10217
11141
|
"slug": "contextSummary",
|
|
@@ -10481,6 +11405,18 @@ var DATA = {
|
|
|
10481
11405
|
"at_risk",
|
|
10482
11406
|
"critical"
|
|
10483
11407
|
]
|
|
11408
|
+
},
|
|
11409
|
+
{
|
|
11410
|
+
"slug": "driveLink",
|
|
11411
|
+
"label": "Drive folder",
|
|
11412
|
+
"valueType": "string",
|
|
11413
|
+
"inputType": "url"
|
|
11414
|
+
},
|
|
11415
|
+
{
|
|
11416
|
+
"slug": "driveFolderId",
|
|
11417
|
+
"label": "Drive folder ID",
|
|
11418
|
+
"valueType": "string",
|
|
11419
|
+
"inputType": "text"
|
|
10484
11420
|
}
|
|
10485
11421
|
]
|
|
10486
11422
|
},
|
|
@@ -10656,8 +11592,92 @@ var DATA = {
|
|
|
10656
11592
|
"sourceProfileSlug": "company",
|
|
10657
11593
|
"targetProfileSlug": "deal",
|
|
10658
11594
|
"type": "linked_to_deal"
|
|
11595
|
+
},
|
|
11596
|
+
{
|
|
11597
|
+
"sourceProfileSlug": "person",
|
|
11598
|
+
"targetProfileSlug": "deal",
|
|
11599
|
+
"type": "client_on_deal",
|
|
11600
|
+
"label": "Client side"
|
|
11601
|
+
},
|
|
11602
|
+
{
|
|
11603
|
+
"sourceProfileSlug": "company",
|
|
11604
|
+
"targetProfileSlug": "deal",
|
|
11605
|
+
"type": "client_on_deal",
|
|
11606
|
+
"label": "Client side"
|
|
11607
|
+
},
|
|
11608
|
+
{
|
|
11609
|
+
"sourceProfileSlug": "person",
|
|
11610
|
+
"targetProfileSlug": "deal",
|
|
11611
|
+
"type": "partner_on_deal",
|
|
11612
|
+
"label": "Partner side"
|
|
11613
|
+
},
|
|
11614
|
+
{
|
|
11615
|
+
"sourceProfileSlug": "company",
|
|
11616
|
+
"targetProfileSlug": "deal",
|
|
11617
|
+
"type": "partner_on_deal",
|
|
11618
|
+
"label": "Partner side"
|
|
10659
11619
|
}
|
|
10660
11620
|
],
|
|
11621
|
+
"onboarding": {
|
|
11622
|
+
"goal": "Turn the user's real commercial motion into a usable pipeline \u2014 the right companies and people, their active opportunities, and an explicit next step for every deal \u2014 so CRM becomes the place sales work moves forward.\n",
|
|
11623
|
+
"framing": "THE REVENUE OPERATOR: Act as a pragmatic sales lead setting up a CRM people will actually maintain. Start from the user's existing motion, vocabulary, and active conversations. Resolve before creating so imported or Pod-wide people are reused rather than duplicated. Push for a concrete next step and date; never invent revenue, probability, or contact details.\n",
|
|
11624
|
+
"expertise": {
|
|
11625
|
+
"starters": [
|
|
11626
|
+
"A useful first pipeline is deliberately small. Start with the few stages that change how the team acts \u2014 contacted, qualifying, proposal, negotiating, won, and lost \u2014 and keep the template's default stages unless the user's real process needs a different view.\n",
|
|
11627
|
+
"Populate the workspace with live work, not a perfect historical archive: propose the three to five opportunities the user is actively trying to move, then add the companies and people needed to understand those deals.\n",
|
|
11628
|
+
'Every active deal needs one observable next step with a date. If the next step is "follow up" or "keep warm", help turn it into a specific action such as "send the revised proposal Thursday" before capturing it.\n'
|
|
11629
|
+
],
|
|
11630
|
+
"blindSpots": [
|
|
11631
|
+
"A deal without a real person is usually an account-shaped hope. If an opportunity has no stakeholder, surface the gap instead of fabricating a placeholder contact.\n",
|
|
11632
|
+
"Pipelines become fiction when stages describe confidence rather than an event the buyer completed. Ask what evidence moves a deal into its current stage, especially around proposal and negotiation.\n",
|
|
11633
|
+
"More fields do not create better sales hygiene. Prioritize stage, value when known, expected close date, and next step; leave unverifiable fields empty and offer enrichment separately.\n"
|
|
11634
|
+
],
|
|
11635
|
+
"bar": "A sales lead can open the pipeline and immediately see the few live opportunities, the real company and stakeholder behind each one, its evidence-based stage, and the dated next action \u2014 with no duplicate people and no invented commercial data.\n"
|
|
11636
|
+
},
|
|
11637
|
+
"collect": [
|
|
11638
|
+
{
|
|
11639
|
+
"profileSlug": "company",
|
|
11640
|
+
"what": "The real prospect organisations behind current conversations.",
|
|
11641
|
+
"cardinality": "few",
|
|
11642
|
+
"keyFields": [
|
|
11643
|
+
"website",
|
|
11644
|
+
"industry",
|
|
11645
|
+
"companySize",
|
|
11646
|
+
"source",
|
|
11647
|
+
"nextStep"
|
|
11648
|
+
]
|
|
11649
|
+
},
|
|
11650
|
+
{
|
|
11651
|
+
"profileSlug": "person",
|
|
11652
|
+
"what": "The known stakeholders for those organisations and opportunities.",
|
|
11653
|
+
"cardinality": "few",
|
|
11654
|
+
"keyFields": [
|
|
11655
|
+
"email",
|
|
11656
|
+
"jobTitle",
|
|
11657
|
+
"company",
|
|
11658
|
+
"source",
|
|
11659
|
+
"nextFollowUpDate"
|
|
11660
|
+
]
|
|
11661
|
+
},
|
|
11662
|
+
{
|
|
11663
|
+
"profileSlug": "deal",
|
|
11664
|
+
"what": "The live opportunities worth actively moving through the pipeline.",
|
|
11665
|
+
"cardinality": "few",
|
|
11666
|
+
"keyFields": [
|
|
11667
|
+
"dealStage",
|
|
11668
|
+
"value",
|
|
11669
|
+
"closeDate",
|
|
11670
|
+
"nextStep"
|
|
11671
|
+
]
|
|
11672
|
+
}
|
|
11673
|
+
],
|
|
11674
|
+
"openingQuestions": [
|
|
11675
|
+
"Which three to five opportunities are you actively trying to move right now?",
|
|
11676
|
+
"For the most important one, which company and real stakeholder are involved?",
|
|
11677
|
+
"What has the buyer actually done so far, and what is the next dated action?"
|
|
11678
|
+
],
|
|
11679
|
+
"doneWhen": "At least one real opportunity is linked to its company or stakeholder and has an evidence-based stage plus a concrete next step; the user can see how to add or import the rest without duplicating existing people.\n"
|
|
11680
|
+
},
|
|
10661
11681
|
"playbooks": [
|
|
10662
11682
|
{
|
|
10663
11683
|
"name": "Enrich Lead/Company",
|
|
@@ -12464,6 +13484,7 @@ var DATA = {
|
|
|
12464
13484
|
"icon": "user",
|
|
12465
13485
|
"color": "amber",
|
|
12466
13486
|
"description": "A person in the ecosystem \u2014 a founder, operator, or contact.",
|
|
13487
|
+
"entityScope": "pod",
|
|
12467
13488
|
"properties": [
|
|
12468
13489
|
{
|
|
12469
13490
|
"slug": "email",
|
|
@@ -12485,6 +13506,7 @@ var DATA = {
|
|
|
12485
13506
|
"icon": "building-2",
|
|
12486
13507
|
"color": "#10B981",
|
|
12487
13508
|
"description": "A company in the ecosystem \u2014 a player you operate around.",
|
|
13509
|
+
"entityScope": "pod",
|
|
12488
13510
|
"properties": [
|
|
12489
13511
|
{
|
|
12490
13512
|
"slug": "website",
|
|
@@ -13213,7 +14235,7 @@ var DATA = {
|
|
|
13213
14235
|
"slug": "business-developer",
|
|
13214
14236
|
"kind": "workspace",
|
|
13215
14237
|
"relation": "require",
|
|
13216
|
-
"reason": "The lead-first sales motion (
|
|
14238
|
+
"reason": "The lead-first sales motion (the business-development overlay on the pod-wide lead/client roles, commercial proposals, commission handoff) that the CRM app runs on. Composes onto the CRM workspace above \u2014 without it the CRM lens has the bare lead/client roles with no lifecycle or handoff fields and the app's pipeline is empty."
|
|
13217
14239
|
},
|
|
13218
14240
|
{
|
|
13219
14241
|
"slug": "content-os",
|
|
@@ -14697,6 +15719,18 @@ var DATA = {
|
|
|
14697
15719
|
"at_risk",
|
|
14698
15720
|
"critical"
|
|
14699
15721
|
]
|
|
15722
|
+
},
|
|
15723
|
+
{
|
|
15724
|
+
"slug": "driveLink",
|
|
15725
|
+
"label": "Drive folder",
|
|
15726
|
+
"valueType": "string",
|
|
15727
|
+
"inputType": "url"
|
|
15728
|
+
},
|
|
15729
|
+
{
|
|
15730
|
+
"slug": "driveFolderId",
|
|
15731
|
+
"label": "Drive folder ID",
|
|
15732
|
+
"valueType": "string",
|
|
15733
|
+
"inputType": "text"
|
|
14700
15734
|
}
|
|
14701
15735
|
]
|
|
14702
15736
|
},
|
|
@@ -16143,12 +17177,24 @@ var DATA = {
|
|
|
16143
17177
|
"slug": "healthScore",
|
|
16144
17178
|
"label": "Health",
|
|
16145
17179
|
"valueType": "string",
|
|
16146
|
-
"inputType": "select",
|
|
16147
|
-
"enumValues": [
|
|
16148
|
-
"healthy",
|
|
16149
|
-
"at_risk",
|
|
16150
|
-
"critical"
|
|
16151
|
-
]
|
|
17180
|
+
"inputType": "select",
|
|
17181
|
+
"enumValues": [
|
|
17182
|
+
"healthy",
|
|
17183
|
+
"at_risk",
|
|
17184
|
+
"critical"
|
|
17185
|
+
]
|
|
17186
|
+
},
|
|
17187
|
+
{
|
|
17188
|
+
"slug": "driveLink",
|
|
17189
|
+
"label": "Drive folder",
|
|
17190
|
+
"valueType": "string",
|
|
17191
|
+
"inputType": "url"
|
|
17192
|
+
},
|
|
17193
|
+
{
|
|
17194
|
+
"slug": "driveFolderId",
|
|
17195
|
+
"label": "Drive folder ID",
|
|
17196
|
+
"valueType": "string",
|
|
17197
|
+
"inputType": "text"
|
|
16152
17198
|
}
|
|
16153
17199
|
]
|
|
16154
17200
|
},
|
|
@@ -19477,7 +20523,7 @@ var DATA = {
|
|
|
19477
20523
|
"icon": "User",
|
|
19478
20524
|
"color": "#06B6D4",
|
|
19479
20525
|
"description": "People in your intellectual and professional world.",
|
|
19480
|
-
"
|
|
20526
|
+
"entityScope": "pod",
|
|
19481
20527
|
"properties": [
|
|
19482
20528
|
{
|
|
19483
20529
|
"slug": "name",
|
|
@@ -23828,6 +24874,24 @@ Pick the SINGLE best provider for this need, or none if nothing fits well. Respo
|
|
|
23828
24874
|
"targetProfileSlug": "company",
|
|
23829
24875
|
"type": "briefing_about_client",
|
|
23830
24876
|
"label": "Client"
|
|
24877
|
+
},
|
|
24878
|
+
{
|
|
24879
|
+
"sourceProfileSlug": "engagement",
|
|
24880
|
+
"targetProfileSlug": "company",
|
|
24881
|
+
"type": "engagement_for_client",
|
|
24882
|
+
"label": "Client"
|
|
24883
|
+
},
|
|
24884
|
+
{
|
|
24885
|
+
"sourceProfileSlug": "engagement",
|
|
24886
|
+
"targetProfileSlug": "person",
|
|
24887
|
+
"type": "engagement_for_client",
|
|
24888
|
+
"label": "Client"
|
|
24889
|
+
},
|
|
24890
|
+
{
|
|
24891
|
+
"sourceProfileSlug": "contract",
|
|
24892
|
+
"targetProfileSlug": "engagement",
|
|
24893
|
+
"type": "contract_for_engagement",
|
|
24894
|
+
"label": "Engagement"
|
|
23831
24895
|
}
|
|
23832
24896
|
],
|
|
23833
24897
|
"views": [
|
|
@@ -24058,140 +25122,422 @@ Recent conversation (oldest first, JSON): {{steps.read-comms.output.messages}}`
|
|
|
24058
25122
|
"type": "condition",
|
|
24059
25123
|
"position": {
|
|
24060
25124
|
"x": 0,
|
|
24061
|
-
"y": 600
|
|
25125
|
+
"y": 600
|
|
25126
|
+
},
|
|
25127
|
+
"data": {
|
|
25128
|
+
"label": "Only continue when there's real signal",
|
|
25129
|
+
"expression": "steps.digest.output.hasSignal === true"
|
|
25130
|
+
}
|
|
25131
|
+
},
|
|
25132
|
+
{
|
|
25133
|
+
"id": "write-briefing",
|
|
25134
|
+
"type": "output",
|
|
25135
|
+
"position": {
|
|
25136
|
+
"x": 0,
|
|
25137
|
+
"y": 720
|
|
25138
|
+
},
|
|
25139
|
+
"data": {
|
|
25140
|
+
"label": "Save the client briefing",
|
|
25141
|
+
"outputType": "entity_create",
|
|
25142
|
+
"config": {
|
|
25143
|
+
"profileSlug": "client-briefing",
|
|
25144
|
+
"title": "Briefing \u2014 {{loop.item.title}}",
|
|
25145
|
+
"dedupeBy": "briefing-key",
|
|
25146
|
+
"properties": {
|
|
25147
|
+
"briefing-key": "{{loop.item.id}}::{{trigger.payload.scheduledAt}}",
|
|
25148
|
+
"briefing-client": "{{loop.item.id}}",
|
|
25149
|
+
"briefing-date": "{{trigger.payload.scheduledAt}}",
|
|
25150
|
+
"briefing-headline": "{{steps.digest.output.headline}}",
|
|
25151
|
+
"briefing-summary": "{{steps.digest.output.summary}}",
|
|
25152
|
+
"briefing-risks": "{{steps.digest.output.risks}}",
|
|
25153
|
+
"briefing-next-actions": "{{steps.digest.output.nextActions}}",
|
|
25154
|
+
"briefing-message-count": "{{steps.read-comms.output.count}}"
|
|
25155
|
+
}
|
|
25156
|
+
}
|
|
25157
|
+
}
|
|
25158
|
+
},
|
|
25159
|
+
{
|
|
25160
|
+
"id": "link-briefing",
|
|
25161
|
+
"type": "output",
|
|
25162
|
+
"position": {
|
|
25163
|
+
"x": 0,
|
|
25164
|
+
"y": 840
|
|
25165
|
+
},
|
|
25166
|
+
"data": {
|
|
25167
|
+
"label": "Link briefing \u2192 client",
|
|
25168
|
+
"outputType": "relation_create",
|
|
25169
|
+
"config": {
|
|
25170
|
+
"fromEntityId": "{{steps.write-briefing.output.entityId}}",
|
|
25171
|
+
"toEntityId": "{{loop.item.id}}",
|
|
25172
|
+
"relationType": "briefing_about_client"
|
|
25173
|
+
}
|
|
25174
|
+
}
|
|
25175
|
+
},
|
|
25176
|
+
{
|
|
25177
|
+
"id": "resolve-team",
|
|
25178
|
+
"type": "capability",
|
|
25179
|
+
"position": {
|
|
25180
|
+
"x": 0,
|
|
25181
|
+
"y": 960
|
|
25182
|
+
},
|
|
25183
|
+
"data": {
|
|
25184
|
+
"label": "Resolve the client's TEAM channel (FIREWALL)",
|
|
25185
|
+
"verbId": "channel.resolve",
|
|
25186
|
+
"inputMapping": {
|
|
25187
|
+
"contextObjectType": "entity",
|
|
25188
|
+
"contextObjectId": "{{loop.item.id}}",
|
|
25189
|
+
"branchPurpose": "team"
|
|
25190
|
+
}
|
|
25191
|
+
}
|
|
25192
|
+
},
|
|
25193
|
+
{
|
|
25194
|
+
"id": "post-recap",
|
|
25195
|
+
"type": "output",
|
|
25196
|
+
"position": {
|
|
25197
|
+
"x": 0,
|
|
25198
|
+
"y": 1080
|
|
25199
|
+
},
|
|
25200
|
+
"data": {
|
|
25201
|
+
"label": "Post the recap to the TEAM channel (FIREWALL)",
|
|
25202
|
+
"outputType": "channel_message",
|
|
25203
|
+
"config": {
|
|
25204
|
+
"channelId": "{{steps.resolve-team.output.channelId}}",
|
|
25205
|
+
"content": "**{{loop.item.title}} \u2014 daily briefing**\n{{steps.digest.output.headline}}\n{{steps.digest.output.summary}}\n**Risks:** {{steps.digest.output.risks}}\n**Next:** {{steps.digest.output.nextActions}}",
|
|
25206
|
+
"metadata": {
|
|
25207
|
+
"automationMessage": true,
|
|
25208
|
+
"kind": "client-briefing"
|
|
25209
|
+
}
|
|
25210
|
+
}
|
|
25211
|
+
}
|
|
25212
|
+
}
|
|
25213
|
+
],
|
|
25214
|
+
"edges": [
|
|
25215
|
+
{
|
|
25216
|
+
"id": "e-trigger-list",
|
|
25217
|
+
"source": "trigger",
|
|
25218
|
+
"target": "list-clients"
|
|
25219
|
+
},
|
|
25220
|
+
{
|
|
25221
|
+
"id": "e-list-loop",
|
|
25222
|
+
"source": "list-clients",
|
|
25223
|
+
"target": "clients-loop"
|
|
25224
|
+
},
|
|
25225
|
+
{
|
|
25226
|
+
"id": "e-loop-read",
|
|
25227
|
+
"source": "clients-loop",
|
|
25228
|
+
"target": "read-comms"
|
|
25229
|
+
},
|
|
25230
|
+
{
|
|
25231
|
+
"id": "e-read-digest",
|
|
25232
|
+
"source": "read-comms",
|
|
25233
|
+
"target": "digest"
|
|
25234
|
+
},
|
|
25235
|
+
{
|
|
25236
|
+
"id": "e-digest-gate",
|
|
25237
|
+
"source": "digest",
|
|
25238
|
+
"target": "signal-gate"
|
|
25239
|
+
},
|
|
25240
|
+
{
|
|
25241
|
+
"id": "e-gate-write",
|
|
25242
|
+
"source": "signal-gate",
|
|
25243
|
+
"target": "write-briefing"
|
|
25244
|
+
},
|
|
25245
|
+
{
|
|
25246
|
+
"id": "e-write-link",
|
|
25247
|
+
"source": "write-briefing",
|
|
25248
|
+
"target": "link-briefing"
|
|
25249
|
+
},
|
|
25250
|
+
{
|
|
25251
|
+
"id": "e-link-resolve",
|
|
25252
|
+
"source": "link-briefing",
|
|
25253
|
+
"target": "resolve-team"
|
|
25254
|
+
},
|
|
25255
|
+
{
|
|
25256
|
+
"id": "e-resolve-post",
|
|
25257
|
+
"source": "resolve-team",
|
|
25258
|
+
"target": "post-recap"
|
|
25259
|
+
}
|
|
25260
|
+
]
|
|
25261
|
+
}
|
|
25262
|
+
},
|
|
25263
|
+
{
|
|
25264
|
+
"key": "operations-crm-handoff-pickup",
|
|
25265
|
+
"name": "Pick up handed-off clients",
|
|
25266
|
+
"description": "Daily. Finds pod-wide clients the CRM marked ready or handed off that have no engagement yet, and opens the delivery shell for them \u2014 a proposed engagement plus a draft contract, both linked to the client. Deduped per client, so it is safe to re-run.",
|
|
25267
|
+
"triggerType": "cron",
|
|
25268
|
+
"triggerConfig": {
|
|
25269
|
+
"expression": "0 7 * * *"
|
|
25270
|
+
},
|
|
25271
|
+
"status": "active",
|
|
25272
|
+
"metadata": {
|
|
25273
|
+
"templateKey": "operations-crm-handoff-pickup",
|
|
25274
|
+
"requiresPodWideEntityQueryLens": true
|
|
25275
|
+
},
|
|
25276
|
+
"flow": {
|
|
25277
|
+
"nodes": [
|
|
25278
|
+
{
|
|
25279
|
+
"id": "trigger",
|
|
25280
|
+
"type": "trigger",
|
|
25281
|
+
"position": {
|
|
25282
|
+
"x": 0,
|
|
25283
|
+
"y": 0
|
|
25284
|
+
},
|
|
25285
|
+
"data": {
|
|
25286
|
+
"label": "Daily at 07:00",
|
|
25287
|
+
"triggerType": "cron",
|
|
25288
|
+
"config": {
|
|
25289
|
+
"expression": "0 7 * * *"
|
|
25290
|
+
}
|
|
25291
|
+
}
|
|
25292
|
+
},
|
|
25293
|
+
{
|
|
25294
|
+
"id": "list-clients",
|
|
25295
|
+
"type": "capability",
|
|
25296
|
+
"position": {
|
|
25297
|
+
"x": 0,
|
|
25298
|
+
"y": 120
|
|
25299
|
+
},
|
|
25300
|
+
"data": {
|
|
25301
|
+
"label": "List clients (pod-wide `client` role)",
|
|
25302
|
+
"verbId": "entity.query",
|
|
25303
|
+
"inputMapping": {
|
|
25304
|
+
"profileSlug": "client",
|
|
25305
|
+
"scope": "pod",
|
|
25306
|
+
"limit": "100"
|
|
25307
|
+
}
|
|
25308
|
+
}
|
|
25309
|
+
},
|
|
25310
|
+
{
|
|
25311
|
+
"id": "handoff-loop",
|
|
25312
|
+
"type": "loop",
|
|
25313
|
+
"position": {
|
|
25314
|
+
"x": 0,
|
|
25315
|
+
"y": 240
|
|
25316
|
+
},
|
|
25317
|
+
"data": {
|
|
25318
|
+
"label": "For each client",
|
|
25319
|
+
"iteratorExpression": "steps.list-clients.output.entities",
|
|
25320
|
+
"itemVariable": "client"
|
|
25321
|
+
}
|
|
25322
|
+
},
|
|
25323
|
+
{
|
|
25324
|
+
"id": "read-client-facets",
|
|
25325
|
+
"type": "capability",
|
|
25326
|
+
"position": {
|
|
25327
|
+
"x": 0,
|
|
25328
|
+
"y": 360
|
|
25329
|
+
},
|
|
25330
|
+
"data": {
|
|
25331
|
+
"label": "Read this party's role facets",
|
|
25332
|
+
"verbId": "entity_facet.list",
|
|
25333
|
+
"inputMapping": {
|
|
25334
|
+
"entityId": "{{loop.item.id}}"
|
|
25335
|
+
}
|
|
25336
|
+
}
|
|
25337
|
+
},
|
|
25338
|
+
{
|
|
25339
|
+
"id": "client-handoff-status",
|
|
25340
|
+
"type": "transform",
|
|
25341
|
+
"position": {
|
|
25342
|
+
"x": 0,
|
|
25343
|
+
"y": 480
|
|
25344
|
+
},
|
|
25345
|
+
"data": {
|
|
25346
|
+
"label": "Read the client facet's handoff status",
|
|
25347
|
+
"expression": "{{steps.read-client-facets.output.facets}} | filter:item.profileSlug === client | map:{{item.properties.handoffStatus}}"
|
|
25348
|
+
}
|
|
25349
|
+
},
|
|
25350
|
+
{
|
|
25351
|
+
"id": "handoff-gate",
|
|
25352
|
+
"type": "condition",
|
|
25353
|
+
"position": {
|
|
25354
|
+
"x": 0,
|
|
25355
|
+
"y": 600
|
|
25356
|
+
},
|
|
25357
|
+
"data": {
|
|
25358
|
+
"label": "Only clients the CRM marked ready or handed off",
|
|
25359
|
+
"expression": "steps.client-handoff-status.output.result contains-any ready,handed_off"
|
|
25360
|
+
}
|
|
25361
|
+
},
|
|
25362
|
+
{
|
|
25363
|
+
"id": "existing-engagements",
|
|
25364
|
+
"type": "query",
|
|
25365
|
+
"position": {
|
|
25366
|
+
"x": 0,
|
|
25367
|
+
"y": 720
|
|
25368
|
+
},
|
|
25369
|
+
"data": {
|
|
25370
|
+
"label": "Does this client already have an engagement?",
|
|
25371
|
+
"profileSlug": "engagement",
|
|
25372
|
+
"filter": '{"engagement-client": "{{loop.item.id}}"}',
|
|
25373
|
+
"limit": 1
|
|
25374
|
+
}
|
|
25375
|
+
},
|
|
25376
|
+
{
|
|
25377
|
+
"id": "engagement-gate",
|
|
25378
|
+
"type": "condition",
|
|
25379
|
+
"position": {
|
|
25380
|
+
"x": 0,
|
|
25381
|
+
"y": 840
|
|
25382
|
+
},
|
|
25383
|
+
"data": {
|
|
25384
|
+
"label": "Only clients with no engagement yet",
|
|
25385
|
+
"expression": "steps.existing-engagements.output.count === 0"
|
|
25386
|
+
}
|
|
25387
|
+
},
|
|
25388
|
+
{
|
|
25389
|
+
"id": "handoff-time",
|
|
25390
|
+
"type": "compute",
|
|
25391
|
+
"position": {
|
|
25392
|
+
"x": 0,
|
|
25393
|
+
"y": 900
|
|
24062
25394
|
},
|
|
24063
25395
|
"data": {
|
|
24064
|
-
"label": "
|
|
24065
|
-
"
|
|
25396
|
+
"label": "Record handoff time",
|
|
25397
|
+
"operation": "now"
|
|
24066
25398
|
}
|
|
24067
25399
|
},
|
|
24068
25400
|
{
|
|
24069
|
-
"id": "
|
|
25401
|
+
"id": "create-engagement",
|
|
24070
25402
|
"type": "output",
|
|
24071
25403
|
"position": {
|
|
24072
25404
|
"x": 0,
|
|
24073
|
-
"y":
|
|
25405
|
+
"y": 960
|
|
24074
25406
|
},
|
|
24075
25407
|
"data": {
|
|
24076
|
-
"label": "
|
|
25408
|
+
"label": "Open a proposed engagement",
|
|
24077
25409
|
"outputType": "entity_create",
|
|
24078
25410
|
"config": {
|
|
24079
|
-
"profileSlug": "
|
|
24080
|
-
"title": "
|
|
24081
|
-
"dedupeBy": "
|
|
25411
|
+
"profileSlug": "engagement",
|
|
25412
|
+
"title": "Engagement \u2014 {{loop.item.title}}",
|
|
25413
|
+
"dedupeBy": "engagement-client",
|
|
24082
25414
|
"properties": {
|
|
24083
|
-
"
|
|
24084
|
-
"
|
|
24085
|
-
"
|
|
24086
|
-
"
|
|
24087
|
-
"briefing-summary": "{{steps.digest.output.summary}}",
|
|
24088
|
-
"briefing-risks": "{{steps.digest.output.risks}}",
|
|
24089
|
-
"briefing-next-actions": "{{steps.digest.output.nextActions}}",
|
|
24090
|
-
"briefing-message-count": "{{steps.read-comms.output.count}}"
|
|
25415
|
+
"engagement-status": "proposed",
|
|
25416
|
+
"engagement-client": "{{loop.item.id}}",
|
|
25417
|
+
"engagement-start": "{{steps.handoff-time.output.result}}",
|
|
25418
|
+
"engagement-notes": "Opened automatically from the CRM handoff. Confirm scope, dates, and owner before activating."
|
|
24091
25419
|
}
|
|
24092
25420
|
}
|
|
24093
25421
|
}
|
|
24094
25422
|
},
|
|
24095
25423
|
{
|
|
24096
|
-
"id": "link-
|
|
25424
|
+
"id": "link-engagement",
|
|
24097
25425
|
"type": "output",
|
|
24098
25426
|
"position": {
|
|
24099
25427
|
"x": 0,
|
|
24100
|
-
"y":
|
|
25428
|
+
"y": 1080
|
|
24101
25429
|
},
|
|
24102
25430
|
"data": {
|
|
24103
|
-
"label": "Link
|
|
25431
|
+
"label": "Link engagement \u2192 client",
|
|
24104
25432
|
"outputType": "relation_create",
|
|
24105
25433
|
"config": {
|
|
24106
|
-
"fromEntityId": "{{steps.
|
|
25434
|
+
"fromEntityId": "{{steps.create-engagement.output.entityId}}",
|
|
24107
25435
|
"toEntityId": "{{loop.item.id}}",
|
|
24108
|
-
"relationType": "
|
|
25436
|
+
"relationType": "engagement_for_client"
|
|
24109
25437
|
}
|
|
24110
25438
|
}
|
|
24111
25439
|
},
|
|
24112
25440
|
{
|
|
24113
|
-
"id": "
|
|
24114
|
-
"type": "
|
|
25441
|
+
"id": "create-contract",
|
|
25442
|
+
"type": "output",
|
|
24115
25443
|
"position": {
|
|
24116
25444
|
"x": 0,
|
|
24117
|
-
"y":
|
|
25445
|
+
"y": 1200
|
|
24118
25446
|
},
|
|
24119
25447
|
"data": {
|
|
24120
|
-
"label": "
|
|
24121
|
-
"
|
|
24122
|
-
"
|
|
24123
|
-
"
|
|
24124
|
-
"
|
|
24125
|
-
"
|
|
25448
|
+
"label": "Open a draft contract",
|
|
25449
|
+
"outputType": "entity_create",
|
|
25450
|
+
"config": {
|
|
25451
|
+
"profileSlug": "contract",
|
|
25452
|
+
"title": "Contract \u2014 {{loop.item.title}}",
|
|
25453
|
+
"dedupeBy": "contract-engagement",
|
|
25454
|
+
"properties": {
|
|
25455
|
+
"contract-status": "draft",
|
|
25456
|
+
"contract-engagement": "{{steps.create-engagement.output.entityId}}",
|
|
25457
|
+
"start-date": "{{steps.handoff-time.output.result}}",
|
|
25458
|
+
"contract-notes": "Drafted automatically from the CRM handoff. Attach the signed document and set the term."
|
|
25459
|
+
}
|
|
24126
25460
|
}
|
|
24127
25461
|
}
|
|
24128
25462
|
},
|
|
24129
25463
|
{
|
|
24130
|
-
"id": "
|
|
25464
|
+
"id": "link-contract",
|
|
24131
25465
|
"type": "output",
|
|
24132
25466
|
"position": {
|
|
24133
25467
|
"x": 0,
|
|
24134
|
-
"y":
|
|
25468
|
+
"y": 1320
|
|
24135
25469
|
},
|
|
24136
25470
|
"data": {
|
|
24137
|
-
"label": "
|
|
24138
|
-
"outputType": "
|
|
25471
|
+
"label": "Link contract \u2192 engagement",
|
|
25472
|
+
"outputType": "relation_create",
|
|
24139
25473
|
"config": {
|
|
24140
|
-
"
|
|
24141
|
-
"
|
|
24142
|
-
"
|
|
24143
|
-
"automationMessage": true,
|
|
24144
|
-
"kind": "client-briefing"
|
|
24145
|
-
}
|
|
25474
|
+
"fromEntityId": "{{steps.create-contract.output.entityId}}",
|
|
25475
|
+
"toEntityId": "{{steps.create-engagement.output.entityId}}",
|
|
25476
|
+
"relationType": "contract_for_engagement"
|
|
24146
25477
|
}
|
|
24147
25478
|
}
|
|
24148
25479
|
}
|
|
24149
25480
|
],
|
|
24150
25481
|
"edges": [
|
|
24151
25482
|
{
|
|
24152
|
-
"id": "
|
|
25483
|
+
"id": "h-trigger-list",
|
|
24153
25484
|
"source": "trigger",
|
|
24154
25485
|
"target": "list-clients"
|
|
24155
25486
|
},
|
|
24156
25487
|
{
|
|
24157
|
-
"id": "
|
|
25488
|
+
"id": "h-list-time",
|
|
24158
25489
|
"source": "list-clients",
|
|
24159
|
-
"target": "
|
|
25490
|
+
"target": "handoff-time"
|
|
24160
25491
|
},
|
|
24161
25492
|
{
|
|
24162
|
-
"id": "
|
|
24163
|
-
"source": "
|
|
24164
|
-
"target": "
|
|
25493
|
+
"id": "h-time-loop",
|
|
25494
|
+
"source": "handoff-time",
|
|
25495
|
+
"target": "handoff-loop"
|
|
24165
25496
|
},
|
|
24166
25497
|
{
|
|
24167
|
-
"id": "
|
|
24168
|
-
"source": "
|
|
24169
|
-
"target": "
|
|
25498
|
+
"id": "h-loop-facets",
|
|
25499
|
+
"source": "handoff-loop",
|
|
25500
|
+
"target": "read-client-facets"
|
|
24170
25501
|
},
|
|
24171
25502
|
{
|
|
24172
|
-
"id": "
|
|
24173
|
-
"source": "
|
|
24174
|
-
"target": "
|
|
25503
|
+
"id": "h-facets-status",
|
|
25504
|
+
"source": "read-client-facets",
|
|
25505
|
+
"target": "client-handoff-status"
|
|
24175
25506
|
},
|
|
24176
25507
|
{
|
|
24177
|
-
"id": "
|
|
24178
|
-
"source": "
|
|
24179
|
-
"target": "
|
|
25508
|
+
"id": "h-status-gate",
|
|
25509
|
+
"source": "client-handoff-status",
|
|
25510
|
+
"target": "handoff-gate"
|
|
24180
25511
|
},
|
|
24181
25512
|
{
|
|
24182
|
-
"id": "
|
|
24183
|
-
"source": "
|
|
24184
|
-
"target": "
|
|
25513
|
+
"id": "h-gate-existing",
|
|
25514
|
+
"source": "handoff-gate",
|
|
25515
|
+
"target": "existing-engagements"
|
|
24185
25516
|
},
|
|
24186
25517
|
{
|
|
24187
|
-
"id": "
|
|
24188
|
-
"source": "
|
|
24189
|
-
"target": "
|
|
25518
|
+
"id": "h-existing-gate",
|
|
25519
|
+
"source": "existing-engagements",
|
|
25520
|
+
"target": "engagement-gate"
|
|
24190
25521
|
},
|
|
24191
25522
|
{
|
|
24192
|
-
"id": "
|
|
24193
|
-
"source": "
|
|
24194
|
-
"target": "
|
|
25523
|
+
"id": "h-gate-engagement",
|
|
25524
|
+
"source": "engagement-gate",
|
|
25525
|
+
"target": "create-engagement"
|
|
25526
|
+
},
|
|
25527
|
+
{
|
|
25528
|
+
"id": "h-engagement-link",
|
|
25529
|
+
"source": "create-engagement",
|
|
25530
|
+
"target": "link-engagement"
|
|
25531
|
+
},
|
|
25532
|
+
{
|
|
25533
|
+
"id": "h-link-contract",
|
|
25534
|
+
"source": "link-engagement",
|
|
25535
|
+
"target": "create-contract"
|
|
25536
|
+
},
|
|
25537
|
+
{
|
|
25538
|
+
"id": "h-contract-link",
|
|
25539
|
+
"source": "create-contract",
|
|
25540
|
+
"target": "link-contract"
|
|
24195
25541
|
}
|
|
24196
25542
|
]
|
|
24197
25543
|
}
|
|
@@ -24505,6 +25851,164 @@ Recent conversation (oldest first, JSON): {{steps.read-comms.output.messages}}`
|
|
|
24505
25851
|
"status": "active"
|
|
24506
25852
|
}
|
|
24507
25853
|
],
|
|
25854
|
+
"automations": [
|
|
25855
|
+
{
|
|
25856
|
+
"key": "linkedin-conversation-ingest",
|
|
25857
|
+
"name": "LinkedIn conversation ingest",
|
|
25858
|
+
"description": "Pull LinkedIn conversations via Unipile and record each thread as an external channel + its messages through channel.ingest (idempotent, so re-runs are safe). Cron = low-frequency reconciliation; manual trigger = on-demand backfill.",
|
|
25859
|
+
"triggerType": "cron",
|
|
25860
|
+
"triggerConfig": {
|
|
25861
|
+
"expression": "0 6 * * *"
|
|
25862
|
+
},
|
|
25863
|
+
"status": "draft",
|
|
25864
|
+
"flow": {
|
|
25865
|
+
"nodes": [
|
|
25866
|
+
{
|
|
25867
|
+
"id": "trigger",
|
|
25868
|
+
"type": "trigger",
|
|
25869
|
+
"position": {
|
|
25870
|
+
"x": 0,
|
|
25871
|
+
"y": 0
|
|
25872
|
+
},
|
|
25873
|
+
"data": {
|
|
25874
|
+
"label": "Daily 06:00 reconciliation (also fired manually for backfill)",
|
|
25875
|
+
"triggerType": "cron",
|
|
25876
|
+
"config": {
|
|
25877
|
+
"expression": "0 6 * * *"
|
|
25878
|
+
}
|
|
25879
|
+
}
|
|
25880
|
+
},
|
|
25881
|
+
{
|
|
25882
|
+
"id": "list-accounts",
|
|
25883
|
+
"type": "capability",
|
|
25884
|
+
"position": {
|
|
25885
|
+
"x": 0,
|
|
25886
|
+
"y": 120
|
|
25887
|
+
},
|
|
25888
|
+
"data": {
|
|
25889
|
+
"label": "Resolve the connected Unipile account",
|
|
25890
|
+
"verbId": "unipile_list_accounts"
|
|
25891
|
+
}
|
|
25892
|
+
},
|
|
25893
|
+
{
|
|
25894
|
+
"id": "filter-linkedin-account",
|
|
25895
|
+
"type": "transform",
|
|
25896
|
+
"position": {
|
|
25897
|
+
"x": 0,
|
|
25898
|
+
"y": 180
|
|
25899
|
+
},
|
|
25900
|
+
"data": {
|
|
25901
|
+
"label": "Keep only LinkedIn Unipile accounts",
|
|
25902
|
+
"expression": "{{steps.list-accounts.output.accounts}} | filter:item.type == 'LINKEDIN'"
|
|
25903
|
+
}
|
|
25904
|
+
},
|
|
25905
|
+
{
|
|
25906
|
+
"id": "list-conversations",
|
|
25907
|
+
"type": "capability",
|
|
25908
|
+
"position": {
|
|
25909
|
+
"x": 0,
|
|
25910
|
+
"y": 240
|
|
25911
|
+
},
|
|
25912
|
+
"data": {
|
|
25913
|
+
"label": "List LinkedIn conversations for the account",
|
|
25914
|
+
"verbId": "unipile_list_conversations",
|
|
25915
|
+
"inputMapping": {
|
|
25916
|
+
"accountId": "{{steps.filter-linkedin-account.output.result.0.id}}"
|
|
25917
|
+
}
|
|
25918
|
+
}
|
|
25919
|
+
},
|
|
25920
|
+
{
|
|
25921
|
+
"id": "conversations-loop",
|
|
25922
|
+
"type": "loop",
|
|
25923
|
+
"position": {
|
|
25924
|
+
"x": 0,
|
|
25925
|
+
"y": 360
|
|
25926
|
+
},
|
|
25927
|
+
"data": {
|
|
25928
|
+
"label": "For each conversation",
|
|
25929
|
+
"iteratorExpression": "steps.list-conversations.output.conversations",
|
|
25930
|
+
"itemVariable": "conversation"
|
|
25931
|
+
}
|
|
25932
|
+
},
|
|
25933
|
+
{
|
|
25934
|
+
"id": "list-messages",
|
|
25935
|
+
"type": "capability",
|
|
25936
|
+
"position": {
|
|
25937
|
+
"x": 0,
|
|
25938
|
+
"y": 480
|
|
25939
|
+
},
|
|
25940
|
+
"data": {
|
|
25941
|
+
"label": "List this thread's messages",
|
|
25942
|
+
"verbId": "unipile_list_messages",
|
|
25943
|
+
"inputMapping": {
|
|
25944
|
+
"threadId": "{{loop.item.id}}",
|
|
25945
|
+
"accountId": "{{steps.filter-linkedin-account.output.result.0.id}}"
|
|
25946
|
+
}
|
|
25947
|
+
}
|
|
25948
|
+
},
|
|
25949
|
+
{
|
|
25950
|
+
"id": "ingest",
|
|
25951
|
+
"type": "capability",
|
|
25952
|
+
"position": {
|
|
25953
|
+
"x": 0,
|
|
25954
|
+
"y": 600
|
|
25955
|
+
},
|
|
25956
|
+
"data": {
|
|
25957
|
+
"label": "Ingest thread + messages (idempotent, generic)",
|
|
25958
|
+
"verbId": "channel.ingest",
|
|
25959
|
+
"inputMapping": {
|
|
25960
|
+
"provider": "linkedin",
|
|
25961
|
+
"externalId": "{{loop.item.id}}",
|
|
25962
|
+
"title": "{{loop.item.name}}",
|
|
25963
|
+
"accountExternalId": "{{steps.filter-linkedin-account.output.result.0.id}}",
|
|
25964
|
+
"workspaceId": "499328b7-fd35-4143-b1bc-67b9e45d9b4b",
|
|
25965
|
+
"suppressSideEffects": true,
|
|
25966
|
+
"messages": "{{steps.list-messages.output.messages}}",
|
|
25967
|
+
"messageMap": {
|
|
25968
|
+
"text": "text",
|
|
25969
|
+
"id": "id",
|
|
25970
|
+
"sentAt": "timestamp",
|
|
25971
|
+
"isOutbound": "is_sender"
|
|
25972
|
+
}
|
|
25973
|
+
}
|
|
25974
|
+
}
|
|
25975
|
+
}
|
|
25976
|
+
],
|
|
25977
|
+
"edges": [
|
|
25978
|
+
{
|
|
25979
|
+
"id": "e-trigger-accounts",
|
|
25980
|
+
"source": "trigger",
|
|
25981
|
+
"target": "list-accounts"
|
|
25982
|
+
},
|
|
25983
|
+
{
|
|
25984
|
+
"id": "e-accounts-filter",
|
|
25985
|
+
"source": "list-accounts",
|
|
25986
|
+
"target": "filter-linkedin-account"
|
|
25987
|
+
},
|
|
25988
|
+
{
|
|
25989
|
+
"id": "e-filter-convos",
|
|
25990
|
+
"source": "filter-linkedin-account",
|
|
25991
|
+
"target": "list-conversations"
|
|
25992
|
+
},
|
|
25993
|
+
{
|
|
25994
|
+
"id": "e-convos-loop",
|
|
25995
|
+
"source": "list-conversations",
|
|
25996
|
+
"target": "conversations-loop"
|
|
25997
|
+
},
|
|
25998
|
+
{
|
|
25999
|
+
"id": "e-loop-messages",
|
|
26000
|
+
"source": "conversations-loop",
|
|
26001
|
+
"target": "list-messages"
|
|
26002
|
+
},
|
|
26003
|
+
{
|
|
26004
|
+
"id": "e-messages-ingest",
|
|
26005
|
+
"source": "list-messages",
|
|
26006
|
+
"target": "ingest"
|
|
26007
|
+
}
|
|
26008
|
+
]
|
|
26009
|
+
}
|
|
26010
|
+
}
|
|
26011
|
+
],
|
|
24508
26012
|
"integrations": [
|
|
24509
26013
|
{
|
|
24510
26014
|
"templateKey": "unipile-linkedin"
|
|
@@ -25070,7 +26574,7 @@ Recent conversation (oldest first, JSON): {{steps.read-comms.output.messages}}`
|
|
|
25070
26574
|
"icon": "user",
|
|
25071
26575
|
"color": "#0891B2",
|
|
25072
26576
|
"description": "Team member or stakeholder",
|
|
25073
|
-
"
|
|
26577
|
+
"entityScope": "pod",
|
|
25074
26578
|
"properties": [
|
|
25075
26579
|
{
|
|
25076
26580
|
"slug": "person-role",
|
|
@@ -28829,17 +30333,7 @@ function layerTemplateGraph(input) {
|
|
|
28829
30333
|
}
|
|
28830
30334
|
|
|
28831
30335
|
// src/concept-guard.ts
|
|
28832
|
-
var DECLARED_DIVERGENCES = {
|
|
28833
|
-
// Two-tier lead vocabulary, documented at business-developer.yaml:1-16.
|
|
28834
|
-
// Foundation's `lead` is a SHARED pod-wide role: being a lead is true
|
|
28835
|
-
// everywhere. `crm-lead` is a WORKSPACE-scoped role so a person can be a lead
|
|
28836
|
-
// inside one CRM workspace without becoming a lead across the whole pod. The
|
|
28837
|
-
// tiers are intentionally separate rows; `semanticSlug: crm.lead` records the
|
|
28838
|
-
// kinship so the two are discoverable as one concept via getBySemanticSlug.
|
|
28839
|
-
"crm-lead|lead": "business-developer: workspace-tier lead, deliberately NOT pod-wide foundation `lead` (two-tier CRM design)",
|
|
28840
|
-
// Same reasoning for the post-sale side of the same lifecycle.
|
|
28841
|
-
"client|crm-client": "business-developer: workspace-tier client, deliberately NOT pod-wide foundation `client` (two-tier CRM design)"
|
|
28842
|
-
};
|
|
30336
|
+
var DECLARED_DIVERGENCES = {};
|
|
28843
30337
|
function effectiveSemanticSlug(profile) {
|
|
28844
30338
|
return profile.semanticSlug ?? profile.slug;
|
|
28845
30339
|
}
|
|
@@ -28858,7 +30352,7 @@ function declaredValue(profile, field) {
|
|
|
28858
30352
|
function pairKey(a, b) {
|
|
28859
30353
|
return [a, b].sort().join("|");
|
|
28860
30354
|
}
|
|
28861
|
-
function findConceptViolations(templates = WORKSPACE_TEMPLATES) {
|
|
30355
|
+
function findConceptViolations(templates = WORKSPACE_TEMPLATES, declaredDivergences = DECLARED_DIVERGENCES) {
|
|
28862
30356
|
const violations = [];
|
|
28863
30357
|
const all = [];
|
|
28864
30358
|
for (const [templateSlug, template] of Object.entries(templates)) {
|
|
@@ -28878,7 +30372,7 @@ function findConceptViolations(templates = WORKSPACE_TEMPLATES) {
|
|
|
28878
30372
|
const term = conceptTerm(effectiveSemanticSlug(a.profile));
|
|
28879
30373
|
if (term !== conceptTerm(effectiveSemanticSlug(b.profile))) continue;
|
|
28880
30374
|
const key = pairKey(a.profile.slug, b.profile.slug);
|
|
28881
|
-
if (key in
|
|
30375
|
+
if (key in declaredDivergences) {
|
|
28882
30376
|
usedAllowlistKeys.add(key);
|
|
28883
30377
|
continue;
|
|
28884
30378
|
}
|
|
@@ -28917,7 +30411,7 @@ function findConceptViolations(templates = WORKSPACE_TEMPLATES) {
|
|
|
28917
30411
|
}
|
|
28918
30412
|
}
|
|
28919
30413
|
}
|
|
28920
|
-
for (const key of Object.keys(
|
|
30414
|
+
for (const key of Object.keys(declaredDivergences)) {
|
|
28921
30415
|
if (usedAllowlistKeys.has(key)) continue;
|
|
28922
30416
|
violations.push({
|
|
28923
30417
|
rule: "stale-allowlist",
|
|
@@ -28997,6 +30491,8 @@ var FLOW_NODE_TYPES = [
|
|
|
28997
30491
|
"select",
|
|
28998
30492
|
"claim",
|
|
28999
30493
|
"messages_query",
|
|
30494
|
+
"runs_query",
|
|
30495
|
+
"proposals_query",
|
|
29000
30496
|
"switch",
|
|
29001
30497
|
"skill",
|
|
29002
30498
|
"capability",
|
|
@@ -29021,7 +30517,24 @@ var CHANNEL_MESSAGE_CHANNEL_TYPES = [
|
|
|
29021
30517
|
"proactive",
|
|
29022
30518
|
"subjectEntity"
|
|
29023
30519
|
];
|
|
30520
|
+
var LOOP_BODY_NODE_TYPES = [
|
|
30521
|
+
"command",
|
|
30522
|
+
"output",
|
|
30523
|
+
"playbook_run",
|
|
30524
|
+
"messages_query",
|
|
30525
|
+
"runs_query",
|
|
30526
|
+
"proposals_query",
|
|
30527
|
+
"query",
|
|
30528
|
+
"fetch",
|
|
30529
|
+
"transform",
|
|
30530
|
+
"condition",
|
|
30531
|
+
"skill",
|
|
30532
|
+
"capability"
|
|
30533
|
+
];
|
|
30534
|
+
var LOOP_PROVIDED_BINDINGS = ["item", "index"];
|
|
29024
30535
|
var NODE_TYPE_SET = new Set(FLOW_NODE_TYPES);
|
|
30536
|
+
var LOOP_BODY_TYPE_SET = new Set(LOOP_BODY_NODE_TYPES);
|
|
30537
|
+
var LOOP_BINDING_SET = new Set(LOOP_PROVIDED_BINDINGS);
|
|
29025
30538
|
var OUTPUT_TYPE_SET = new Set(FLOW_OUTPUT_TYPES);
|
|
29026
30539
|
var CHANNEL_TYPE_SET = new Set(CHANNEL_MESSAGE_CHANNEL_TYPES);
|
|
29027
30540
|
function isRecord(v) {
|
|
@@ -29263,6 +30776,40 @@ function validateFlowDefinition(flow, resolvers) {
|
|
|
29263
30776
|
}
|
|
29264
30777
|
}
|
|
29265
30778
|
}
|
|
30779
|
+
const loopBodyNodeIds = /* @__PURE__ */ new Set();
|
|
30780
|
+
for (const node of nodes) {
|
|
30781
|
+
if (!nonEmptyString(node.id) || node.type !== "loop") continue;
|
|
30782
|
+
for (const id of collectLoopBody(node.id, nodesById, edges)) {
|
|
30783
|
+
loopBodyNodeIds.add(id);
|
|
30784
|
+
}
|
|
30785
|
+
}
|
|
30786
|
+
for (const node of nodes) {
|
|
30787
|
+
if (!nonEmptyString(node.id)) continue;
|
|
30788
|
+
const refs = collectLoopBindingRefs(node.data);
|
|
30789
|
+
if (refs.length === 0) continue;
|
|
30790
|
+
if (!loopBodyNodeIds.has(node.id)) {
|
|
30791
|
+
errors.push({
|
|
30792
|
+
nodeId: node.id,
|
|
30793
|
+
code: "loop_ref_outside_loop_body",
|
|
30794
|
+
message: `Node "${node.id}" references ${refs.map((r) => `\`{{loop.${r}...}}\``).join(
|
|
30795
|
+
", "
|
|
30796
|
+
)} but is not inside a loop body, so there is no item to bind \u2014 it would run once with an empty value. Connect it downstream of a loop node with no switch/delay/nested-loop/sub_automation node in between.`
|
|
30797
|
+
});
|
|
30798
|
+
continue;
|
|
30799
|
+
}
|
|
30800
|
+
const unknown = refs.filter((r) => !LOOP_BINDING_SET.has(r));
|
|
30801
|
+
if (unknown.length > 0) {
|
|
30802
|
+
errors.push({
|
|
30803
|
+
nodeId: node.id,
|
|
30804
|
+
code: "loop_ref_unknown_path",
|
|
30805
|
+
message: `Node "${node.id}" references ${unknown.map((r) => `\`{{loop.${r}}}\``).join(
|
|
30806
|
+
", "
|
|
30807
|
+
)}, which a loop does not provide. A loop binds only: ${LOOP_PROVIDED_BINDINGS.map(
|
|
30808
|
+
(b) => `\`loop.${b}\``
|
|
30809
|
+
).join(", ")}.`
|
|
30810
|
+
});
|
|
30811
|
+
}
|
|
30812
|
+
}
|
|
29266
30813
|
const cyclic = detectCycle(nodeIds, edges);
|
|
29267
30814
|
if (cyclic.length > 0) {
|
|
29268
30815
|
errors.push({
|
|
@@ -29274,6 +30821,53 @@ function validateFlowDefinition(flow, resolvers) {
|
|
|
29274
30821
|
}
|
|
29275
30822
|
return { valid: errors.length === 0, errors };
|
|
29276
30823
|
}
|
|
30824
|
+
function collectLoopBody(loopNodeId, nodesById, edges) {
|
|
30825
|
+
const body = /* @__PURE__ */ new Set();
|
|
30826
|
+
const stack = [];
|
|
30827
|
+
for (const edge of edges) {
|
|
30828
|
+
if (edge.source === loopNodeId && nonEmptyString(edge.target))
|
|
30829
|
+
stack.push(edge.target);
|
|
30830
|
+
}
|
|
30831
|
+
while (stack.length > 0) {
|
|
30832
|
+
const id = stack.pop();
|
|
30833
|
+
if (body.has(id)) continue;
|
|
30834
|
+
const node = nodesById.get(id);
|
|
30835
|
+
if (!node || !nonEmptyString(node.type) || !LOOP_BODY_TYPE_SET.has(node.type))
|
|
30836
|
+
continue;
|
|
30837
|
+
body.add(id);
|
|
30838
|
+
for (const edge of edges) {
|
|
30839
|
+
if (edge.source === id && nonEmptyString(edge.target))
|
|
30840
|
+
stack.push(edge.target);
|
|
30841
|
+
}
|
|
30842
|
+
}
|
|
30843
|
+
return body;
|
|
30844
|
+
}
|
|
30845
|
+
function collectLoopBindingRefs(data) {
|
|
30846
|
+
const found = /* @__PURE__ */ new Set();
|
|
30847
|
+
const walk = (v) => {
|
|
30848
|
+
if (typeof v === "string") {
|
|
30849
|
+
const head = v.split("|")[0];
|
|
30850
|
+
const re = /\{\{\s*loop\.([A-Za-z0-9_$]+)/g;
|
|
30851
|
+
let m;
|
|
30852
|
+
let matched = false;
|
|
30853
|
+
while ((m = re.exec(head)) !== null) {
|
|
30854
|
+
found.add(m[1]);
|
|
30855
|
+
matched = true;
|
|
30856
|
+
}
|
|
30857
|
+
if (!matched && /\{\{\s*loop\./.test(head)) found.add("");
|
|
30858
|
+
return;
|
|
30859
|
+
}
|
|
30860
|
+
if (Array.isArray(v)) {
|
|
30861
|
+
for (const x of v) walk(x);
|
|
30862
|
+
return;
|
|
30863
|
+
}
|
|
30864
|
+
if (isRecord(v)) {
|
|
30865
|
+
for (const x of Object.values(v)) walk(x);
|
|
30866
|
+
}
|
|
30867
|
+
};
|
|
30868
|
+
walk(data);
|
|
30869
|
+
return [...found];
|
|
30870
|
+
}
|
|
29277
30871
|
function detectCycle(nodeIds, edges) {
|
|
29278
30872
|
const inDegree = /* @__PURE__ */ new Map();
|
|
29279
30873
|
const adjacency = /* @__PURE__ */ new Map();
|
|
@@ -29344,7 +30938,12 @@ var SYSTEM_PROFILES = /* @__PURE__ */ new Set([
|
|
|
29344
30938
|
"question",
|
|
29345
30939
|
"research",
|
|
29346
30940
|
"knowledge",
|
|
29347
|
-
"user_observation"
|
|
30941
|
+
"user_observation",
|
|
30942
|
+
// Generated artifact — a workspace-scoped pod system profile
|
|
30943
|
+
// (`ensure-system-profiles.ts`) written by the `base` report automation. It
|
|
30944
|
+
// was simply missing from this hand-maintained list; the flow's
|
|
30945
|
+
// `create-report` node references it, so referential integrity needs it here.
|
|
30946
|
+
"report"
|
|
29348
30947
|
]);
|
|
29349
30948
|
function isPodWideProfile(p) {
|
|
29350
30949
|
return String(p.scope || "").toLowerCase() === "shared";
|
|
@@ -29398,7 +30997,8 @@ function validateTemplate(tpl, opts = {}) {
|
|
|
29398
30997
|
if (!tpl.meta) add("meta-required", "meta", "Missing required field: meta");
|
|
29399
30998
|
else {
|
|
29400
30999
|
for (const f of REQUIRED_META_FIELDS) {
|
|
29401
|
-
if (!tpl.meta[f])
|
|
31000
|
+
if (!tpl.meta[f])
|
|
31001
|
+
add("meta-field-required", `meta.${f}`, `meta.${f} is required`);
|
|
29402
31002
|
}
|
|
29403
31003
|
}
|
|
29404
31004
|
if (!tpl.workspace)
|
|
@@ -29413,8 +31013,13 @@ function validateTemplate(tpl, opts = {}) {
|
|
|
29413
31013
|
);
|
|
29414
31014
|
}
|
|
29415
31015
|
}
|
|
29416
|
-
|
|
29417
|
-
|
|
31016
|
+
const carriesOperationalDefaults = tpl.relationDefs && tpl.relationDefs.length > 0 || tpl.commands && tpl.commands.length > 0 || tpl.automations && tpl.automations.length > 0;
|
|
31017
|
+
if ((!tpl.profiles || tpl.profiles.length === 0) && !carriesOperationalDefaults) {
|
|
31018
|
+
add(
|
|
31019
|
+
"profiles-required",
|
|
31020
|
+
"profiles",
|
|
31021
|
+
"At least one profile is required (or operational defaults: relationDefs / commands / automations)"
|
|
31022
|
+
);
|
|
29418
31023
|
}
|
|
29419
31024
|
if (tpl.app && tpl.layout && Object.prototype.hasOwnProperty.call(tpl.layout, "primarySurface")) {
|
|
29420
31025
|
add(
|
|
@@ -29539,7 +31144,85 @@ function validateTemplate(tpl, opts = {}) {
|
|
|
29539
31144
|
);
|
|
29540
31145
|
}
|
|
29541
31146
|
}
|
|
29542
|
-
|
|
31147
|
+
const onboarding = tpl.onboarding;
|
|
31148
|
+
if (onboarding) {
|
|
31149
|
+
if (typeof onboarding.goal !== "string" || onboarding.goal.trim() === "") {
|
|
31150
|
+
add(
|
|
31151
|
+
"onboarding-goal-empty",
|
|
31152
|
+
"onboarding.goal",
|
|
31153
|
+
"onboarding.goal must be a non-empty string"
|
|
31154
|
+
);
|
|
31155
|
+
}
|
|
31156
|
+
if (typeof onboarding.framing !== "string" || onboarding.framing.trim() === "") {
|
|
31157
|
+
add(
|
|
31158
|
+
"onboarding-framing-empty",
|
|
31159
|
+
"onboarding.framing",
|
|
31160
|
+
"onboarding.framing must be a non-empty string"
|
|
31161
|
+
);
|
|
31162
|
+
}
|
|
31163
|
+
if (!Array.isArray(onboarding.collect) || onboarding.collect.length === 0) {
|
|
31164
|
+
add(
|
|
31165
|
+
"onboarding-collect-empty",
|
|
31166
|
+
"onboarding.collect",
|
|
31167
|
+
"onboarding.collect must contain at least one structured target"
|
|
31168
|
+
);
|
|
31169
|
+
}
|
|
31170
|
+
const expertise = onboarding.expertise;
|
|
31171
|
+
for (const [field, values] of [
|
|
31172
|
+
["starters", expertise?.starters],
|
|
31173
|
+
["blindSpots", expertise?.blindSpots]
|
|
31174
|
+
]) {
|
|
31175
|
+
if (values !== void 0 && (!Array.isArray(values) || values.some(
|
|
31176
|
+
(value) => typeof value !== "string" || value.trim() === ""
|
|
31177
|
+
))) {
|
|
31178
|
+
add(
|
|
31179
|
+
"onboarding-expertise-invalid",
|
|
31180
|
+
`onboarding.expertise.${field}`,
|
|
31181
|
+
`onboarding.expertise.${field} must contain only non-empty strings`
|
|
31182
|
+
);
|
|
31183
|
+
}
|
|
31184
|
+
}
|
|
31185
|
+
if (expertise?.bar !== void 0 && (typeof expertise.bar !== "string" || expertise.bar.trim() === "")) {
|
|
31186
|
+
add(
|
|
31187
|
+
"onboarding-expertise-invalid",
|
|
31188
|
+
"onboarding.expertise.bar",
|
|
31189
|
+
"onboarding.expertise.bar must be a non-empty string"
|
|
31190
|
+
);
|
|
31191
|
+
}
|
|
31192
|
+
if (onboarding.openingQuestions !== void 0 && (!Array.isArray(onboarding.openingQuestions) || onboarding.openingQuestions.some(
|
|
31193
|
+
(question) => typeof question !== "string" || question.trim() === ""
|
|
31194
|
+
))) {
|
|
31195
|
+
add(
|
|
31196
|
+
"onboarding-opening-questions-invalid",
|
|
31197
|
+
"onboarding.openingQuestions",
|
|
31198
|
+
"onboarding.openingQuestions must contain only non-empty strings"
|
|
31199
|
+
);
|
|
31200
|
+
}
|
|
31201
|
+
if (onboarding.doneWhen !== void 0 && (typeof onboarding.doneWhen !== "string" || onboarding.doneWhen.trim() === "")) {
|
|
31202
|
+
add(
|
|
31203
|
+
"onboarding-done-when-invalid",
|
|
31204
|
+
"onboarding.doneWhen",
|
|
31205
|
+
"onboarding.doneWhen must be a non-empty string"
|
|
31206
|
+
);
|
|
31207
|
+
}
|
|
31208
|
+
}
|
|
31209
|
+
for (const c of onboarding?.collect || []) {
|
|
31210
|
+
if (typeof c.what !== "string" || c.what.trim() === "") {
|
|
31211
|
+
add(
|
|
31212
|
+
"onboarding-collect-what-empty",
|
|
31213
|
+
`onboarding.collect.${c.profileSlug}.what`,
|
|
31214
|
+
`onboarding.collect target "${c.profileSlug}" must describe what to capture`
|
|
31215
|
+
);
|
|
31216
|
+
}
|
|
31217
|
+
if (c.keyFields !== void 0 && (!Array.isArray(c.keyFields) || c.keyFields.some(
|
|
31218
|
+
(field) => typeof field !== "string" || field.trim() === ""
|
|
31219
|
+
))) {
|
|
31220
|
+
add(
|
|
31221
|
+
"onboarding-collect-key-fields-invalid",
|
|
31222
|
+
`onboarding.collect.${c.profileSlug}.keyFields`,
|
|
31223
|
+
`onboarding.collect target "${c.profileSlug}" keyFields must contain only non-empty strings`
|
|
31224
|
+
);
|
|
31225
|
+
}
|
|
29543
31226
|
if (!known(c.profileSlug))
|
|
29544
31227
|
add(
|
|
29545
31228
|
"onboarding-ref-unknown",
|
|
@@ -29572,9 +31255,7 @@ function validateTemplate(tpl, opts = {}) {
|
|
|
29572
31255
|
);
|
|
29573
31256
|
const flow = a.flow ?? a.flowDefinition;
|
|
29574
31257
|
if (flow != null) {
|
|
29575
|
-
const {
|
|
29576
|
-
errors: flowErrors
|
|
29577
|
-
} = validateFlowDefinition(flow);
|
|
31258
|
+
const { errors: flowErrors } = validateFlowDefinition(flow);
|
|
29578
31259
|
for (const fe of flowErrors) {
|
|
29579
31260
|
const loc = fe.nodeId ? `automation "${a.name}".flow.node "${fe.nodeId}"` : fe.edgeId ? `automation "${a.name}".flow.edge "${fe.edgeId}"` : `automation "${a.name}".flow`;
|
|
29580
31261
|
add(`automation-flow-${fe.code}`, loc, `${loc}: ${fe.message}`);
|
|
@@ -29901,6 +31582,11 @@ function toWorkspaceDefinition(slugOrYaml, nameOverride) {
|
|
|
29901
31582
|
seedEntities: tpl.seedEntities ?? [],
|
|
29902
31583
|
suggestedRelations: tpl.suggestedRelations,
|
|
29903
31584
|
displayTemplates: tpl.displayTemplates,
|
|
31585
|
+
// Relation-type vocabulary + intelligence commands — carried verbatim so the
|
|
31586
|
+
// one reconcile door can seed the universal defaults (the `base` template)
|
|
31587
|
+
// that three hardcoded seeders used to. Authored form IS the wire form.
|
|
31588
|
+
relationDefs: tpl.relationDefs,
|
|
31589
|
+
commands: tpl.commands,
|
|
29904
31590
|
layoutConfig: normalizeTemplateLayout(tpl.layout, tpl.app),
|
|
29905
31591
|
bentoViewName: tpl.bento?.viewName,
|
|
29906
31592
|
bentoLayout: bento.bentoLayout,
|
|
@@ -29971,6 +31657,8 @@ function toPackageDefinition(slugOrYaml) {
|
|
|
29971
31657
|
suggestedRelations: tpl.suggestedRelations,
|
|
29972
31658
|
displayTemplates: tpl.displayTemplates,
|
|
29973
31659
|
entityLinks: tpl.entityLinks,
|
|
31660
|
+
relationDefs: tpl.relationDefs,
|
|
31661
|
+
commands: tpl.commands,
|
|
29974
31662
|
bentoLayout: bento.bentoLayout,
|
|
29975
31663
|
bentoViewBlocks: bento.bentoViewBlocks,
|
|
29976
31664
|
bentoViewName: tpl.bento?.viewName,
|