@synap-core/workspace-templates 0.26.0 → 0.27.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.
@@ -74,7 +74,7 @@
74
74
  {
75
75
  "name": "Lead Outreach",
76
76
  "description": "Flow 1 — Lead acquisition via LinkedIn outreach from an imported list. The agent works the list one lead at a time, each step proposal-gated and operator-reviewed.",
77
- "goalTemplate": "Work a list of imported leads into qualified conversations via LinkedIn. For each lead: (1) find their LinkedIn profile via Unipile profile search; (2) send a connection request (proposal-gated); (3) wait for acceptance (delay / scheduled re-check); (4) on acceptance, use the draft-outreach skill to write a personalized, non-salesy first message from the lead's profile + our agency pitch, and propose it for the operator to review and send; (5) on reply, use qualify-reply to classify interest and recommend the next step — if interested, promote the lead to a deal/client and trigger the client-onboarding flow. Every outbound action stays proposal-gated and reversible.",
77
+ "goalTemplate": "Work a list of imported leads into qualified conversations via LinkedIn. For each lead: (1) find their LinkedIn profile via Unipile profile search; (2) send a connection request (proposal-gated); (3) wait for acceptance (delay / scheduled re-check); (4) on acceptance, use the draft-outreach skill to write a personalized, non-salesy first message from the lead's profile + our agency pitch, and file it through the pod's send door (messaging.send, to = the person's entity id) so the operator reviews it in their channel and the pod sends it once approved; never a tool's own send verb; (5) on reply, use qualify-reply to classify interest and recommend the next step — if interested, promote the lead to a deal/client and trigger the client-onboarding flow. Every outbound action stays proposal-gated and reversible.",
78
78
  "params": [
79
79
  {
80
80
  "name": "leadSource",
@@ -138,8 +138,8 @@
138
138
  },
139
139
  {
140
140
  "name": "Lead Outreach",
141
- "description": "Per lead: draft a personalized first-touch message and propose sending it (proposal-gated — nothing goes out without approval).",
142
- "goalTemplate": "Reach out to the lead \"{leadName}\". Review what we know about them, draft a short, personalized first-touch message tailored to their context, and PROPOSE sending it (connect + message). Do not send anything without explicit approval.",
141
+ "description": "Per lead: draft a personalized first message as a proposed message in the person's channel, through the pod's send door (messaging.send); nothing goes out until a person approves it.",
142
+ "goalTemplate": "Reach out to the lead \"{leadName}\". Review what we know about them in the pod and draft one short, personalized first message tailored to their context. File it through the pod's send door: run messaging.send with to = the person's entity id (find them in the pod first; for a company, the person who decides there) and text = the message, so it waits for review in their channel and goes out only once a person approves it. Never call a tool's own send verb (Unipile, Gmail), never create an outreach touch, a note or a task holding the message, and never send.",
143
143
  "params": [
144
144
  {
145
145
  "name": "leadName",
@@ -85,6 +85,9 @@
85
85
  ],
86
86
  "executionMode": "sync",
87
87
  "timeoutSeconds": 60,
88
+ "metadata": {
89
+ "readOnly": true
90
+ },
88
91
  "parameters": {
89
92
  "accountId": "string"
90
93
  },
@@ -132,6 +135,9 @@
132
135
  ],
133
136
  "executionMode": "sync",
134
137
  "timeoutSeconds": 30,
138
+ "metadata": {
139
+ "readOnly": true
140
+ },
135
141
  "parameters": {
136
142
  "accountId": "string",
137
143
  "cursor": "string?"
@@ -164,6 +170,9 @@
164
170
  ],
165
171
  "executionMode": "sync",
166
172
  "timeoutSeconds": 30,
173
+ "metadata": {
174
+ "readOnly": true
175
+ },
167
176
  "parameters": {
168
177
  "threadId": "string",
169
178
  "accountId": "string"
@@ -281,6 +290,9 @@
281
290
  ],
282
291
  "executionMode": "sync",
283
292
  "timeoutSeconds": 60,
293
+ "metadata": {
294
+ "readOnly": true
295
+ },
284
296
  "parameters": {
285
297
  "keywords": "string",
286
298
  "accountId": "string",
@@ -301,7 +313,8 @@
301
313
  "executionMode": "sync",
302
314
  "timeoutSeconds": 60,
303
315
  "metadata": {
304
- "verbType": "read"
316
+ "verbType": "read",
317
+ "readOnly": true
305
318
  },
306
319
  "parameters": {
307
320
  "fullName": "string",
@@ -321,6 +334,9 @@
321
334
  ],
322
335
  "executionMode": "sync",
323
336
  "timeoutSeconds": 60,
337
+ "metadata": {
338
+ "readOnly": true
339
+ },
324
340
  "parameters": {
325
341
  "identifier": "string",
326
342
  "accountId": "string"
@@ -355,6 +371,9 @@
355
371
  ],
356
372
  "executionMode": "sync",
357
373
  "timeoutSeconds": 30,
374
+ "metadata": {
375
+ "readOnly": true
376
+ },
358
377
  "parameters": {
359
378
  "accountId": "string",
360
379
  "cursor": "string?"
@@ -387,6 +406,9 @@
387
406
  ],
388
407
  "executionMode": "sync",
389
408
  "timeoutSeconds": 30,
409
+ "metadata": {
410
+ "readOnly": true
411
+ },
390
412
  "parameters": {
391
413
  "accountId": "string",
392
414
  "cursor": "string?"
package/dist/index.js CHANGED
@@ -13346,6 +13346,12 @@ var DATA = {
13346
13346
  "valueType": "string",
13347
13347
  "inputType": "text"
13348
13348
  },
13349
+ {
13350
+ "slug": "linkedinUrl",
13351
+ "label": "LinkedIn page",
13352
+ "valueType": "string",
13353
+ "inputType": "url"
13354
+ },
13349
13355
  {
13350
13356
  "slug": "industry",
13351
13357
  "label": "Industry",
@@ -13931,6 +13937,12 @@ var DATA = {
13931
13937
  {
13932
13938
  "key": "no_response",
13933
13939
  "category": "lost"
13940
+ },
13941
+ {
13942
+ "key": "replaced",
13943
+ "label": "Replaced",
13944
+ "category": "lost",
13945
+ "humanOnly": true
13934
13946
  }
13935
13947
  ]
13936
13948
  },
@@ -13969,7 +13981,8 @@ var DATA = {
13969
13981
  "opened",
13970
13982
  "replied",
13971
13983
  "bounced",
13972
- "no_response"
13984
+ "no_response",
13985
+ "replaced"
13973
13986
  ],
13974
13987
  "constraints": {
13975
13988
  "defaultValue": "drafted"
@@ -14954,6 +14967,132 @@ var DATA = {
14954
14967
  }
14955
14968
  ]
14956
14969
  },
14970
+ {
14971
+ "name": "Find the right contact",
14972
+ "description": "Find the person to contact at a company with no contact; read the roles from its audience, look them up on LinkedIn, pick the best one or two with a reason, and file them linked to the company and homed in the CRM; one company or a batch.",
14973
+ "goalTemplate": `Find the right person to contact at the company {company}, for the audience {audience} when one is given. BATCH: when this session also has targets (the companies it works as a batch), do all that follows for each target company, one after the other, and report each one. READ: the company (its name, website, domain and linkedinUrl) and the people the pod already links to it with works_at; when one of them already holds a role the audience names, pick them and search nothing. COMPANY PAGE: when the company has no linkedinUrl, read its website (web.read) and take the LinkedIn company page (linkedin.com/company/...) it links to. Store it as the company's linkedinUrl through the entity door only on a clear match: the company's own website links to it, or its name and its website or domain agree with the company's. Otherwise store nothing and report the page you found. ROLES: read the decision roles the audience names (its persona, segment and traits): the audience given, else the one the company targets (its targets link). When there is no audience or it names no role, look for founder, co-founder, CEO and CTO, in that order. Never invent a role. SEARCH: find the LinkedIn account with unipile_list_accounts (when several are connected, ask the person which one reads). For each role, run linkedin_search_people with keywords "<role> <company name>", then linkedin_get_profile on at most 5 candidates in all, the most likely first. These lookups only read. PICK: the best person for the audience, two at most: a current role at THIS company (in their current position, not a past one or a namesake), the most senior of the roles, the best fit with the audience, and a one-line reason for each. When no candidate passes, pick nobody and say what was searched. FILE, through the pod's doors and its governance: resolve identity first by their LinkedIn URL (synap_resolve_identity with that URL); a strong match is that person, reused, and nothing is created. Otherwise create the person with only their own fields: their name as title, jobTitle (their current title), linkedinUrl, and linkedinProviderId when the profile gives it; never the company's website, domain or email. File a new person and their works_at link to the company as ONE write (synap_capture with the person, the company by existingEntityId and the works_at relation); for a person already in the pod, add the works_at link when it is missing. Home the person in this CRM space: set their homeWorkspaceId to this space with synap_update_entity (on a pod whose entity door has no homeWorkspaceId, file them with this space's workspaceId instead). Then measure their LinkedIn reach through the pod's reach door (POST /channels/linkedin-reach/<personId>), which records it on the person; never set linkedinConnection or linkedinInvitedAt yourself. A write the pod files for review is success: keep going, never retry it, and report it as waiting for review. The company stays the lead: never give the person the lead role, never move the company's lead stage, and never draft or send a message or a connection request. REPORT, for each company: who was picked and why, their reach, whether the company's LinkedIn page was stored, only found or not found, and the next step ("Draft a first message" to the person picked or, when nobody fits, what the person could do instead).`,
14974
+ "subjectProfile": {
14975
+ "profileSlug": "company"
14976
+ },
14977
+ "activators": [
14978
+ {
14979
+ "on": "manual",
14980
+ "mode": "propose"
14981
+ }
14982
+ ],
14983
+ "params": [
14984
+ {
14985
+ "name": "company",
14986
+ "type": "entity",
14987
+ "label": "Company",
14988
+ "required": true
14989
+ },
14990
+ {
14991
+ "name": "audience",
14992
+ "type": "entity",
14993
+ "label": "Audience (ICP) the company came from"
14994
+ }
14995
+ ],
14996
+ "executor": "is-agent",
14997
+ "status": "active",
14998
+ "requiredIntents": [
14999
+ "find_people",
15000
+ "enrich_entity"
15001
+ ],
15002
+ "grants": [
15003
+ "entity.create"
15004
+ ],
15005
+ "stages": [
15006
+ {
15007
+ "key": "read",
15008
+ "name": "Read the company and the roles",
15009
+ "category": "planned",
15010
+ "goal": "Read the company, the people already linked to it and its LinkedIn page, find its page on its website when it has none, and list the roles to look for from the audience (founder, co-founder, CEO and CTO when it names none).",
15011
+ "criteria": [
15012
+ {
15013
+ "key": "contact-roles-from-audience",
15014
+ "statement": "The roles looked for are the ones the audience names, or founder, co-founder, CEO and CTO when there is no audience or it names none; no role was invented.",
15015
+ "check": {
15016
+ "kind": "judge"
15017
+ }
15018
+ },
15019
+ {
15020
+ "key": "contact-page-clear-match",
15021
+ "statement": "The company's linkedinUrl was stored only on a clear match (its own website links to the page, or the name and the website or domain agree); otherwise nothing was stored and the page found was reported.",
15022
+ "check": {
15023
+ "kind": "judge"
15024
+ }
15025
+ }
15026
+ ]
15027
+ },
15028
+ {
15029
+ "key": "find",
15030
+ "name": "Look the people up",
15031
+ "category": "started",
15032
+ "goal": "Search LinkedIn for each role at the company, read at most 5 profiles, and pick the best person (two at most) with a one-line reason each.",
15033
+ "criteria": [
15034
+ {
15035
+ "key": "contact-lookups-capped",
15036
+ "statement": "linkedin_get_profile ran on at most 5 candidates per company, and only lookup verbs were called on LinkedIn.",
15037
+ "check": {
15038
+ "kind": "judge"
15039
+ }
15040
+ },
15041
+ {
15042
+ "key": "contact-current-role",
15043
+ "statement": "Each person picked (two at most) holds a current role at this company and has a one-line reason, or nobody was picked and what was searched is said.",
15044
+ "check": {
15045
+ "kind": "judge"
15046
+ }
15047
+ }
15048
+ ]
15049
+ },
15050
+ {
15051
+ "key": "file",
15052
+ "name": "File the contact",
15053
+ "category": "completed",
15054
+ "goal": "Resolve each person picked by their LinkedIn URL, create them only if new with their own fields, link them works_at the company, home them in this CRM space, measure their reach, and report who, why, their reach and the next step.",
15055
+ "expectedOutputs": [
15056
+ {
15057
+ "kind": "person",
15058
+ "label": "Contact picked",
15059
+ "relationToSubject": "works_at",
15060
+ "optional": true
15061
+ }
15062
+ ],
15063
+ "criteria": [
15064
+ {
15065
+ "key": "contact-identity-first",
15066
+ "statement": "Each person picked was resolved by their LinkedIn URL first; an existing person was reused, and a new one carries only their own fields (never the company's website, domain or email).",
15067
+ "check": {
15068
+ "kind": "judge"
15069
+ }
15070
+ },
15071
+ {
15072
+ "key": "contact-linked-homed",
15073
+ "statement": "Each person picked works_at the company, is homed in this CRM space and had their LinkedIn reach measured through the pod's reach door, or the write is reported as waiting for review.",
15074
+ "check": {
15075
+ "kind": "judge"
15076
+ }
15077
+ },
15078
+ {
15079
+ "key": "contact-company-stays-lead",
15080
+ "statement": "The person got no lead role, the company's lead stage did not move, and no message or connection request was drafted or sent.",
15081
+ "check": {
15082
+ "kind": "judge"
15083
+ }
15084
+ },
15085
+ {
15086
+ "key": "contact-report",
15087
+ "statement": "The report gives, for each company, who was picked and why, their reach, whether its LinkedIn page was stored, only found or not found, and the next step.",
15088
+ "check": {
15089
+ "kind": "judge"
15090
+ }
15091
+ }
15092
+ ]
15093
+ }
15094
+ ]
15095
+ },
14957
15096
  {
14958
15097
  "name": "Follow up on leads in outreach",
14959
15098
  "description": "Each weekday, read the channels of the leads in outreach; summarise a reply and propose the next step, or, when the follow-up is due with no reply, draft the next message in the angle of its outreach campaign as a proposed message for a person to approve; the agent never sends.",
@@ -15160,7 +15299,8 @@ var DATA = {
15160
15299
  "humanOnlyStatuses": [
15161
15300
  "drafted",
15162
15301
  "sent",
15163
- "opened"
15302
+ "opened",
15303
+ "replaced"
15164
15304
  ],
15165
15305
  "activators": [
15166
15306
  {
@@ -32692,6 +32832,17 @@ var vocabularySentencesEn = {
32692
32832
  "vocabulary.templateNoun.work.other": "Work templates",
32693
32833
  "vocabulary.templateNoun.space.one": "Space template",
32694
32834
  "vocabulary.templateNoun.space.other": "Space templates",
32835
+ // `resolvePackageNoun`: what installs, named by what it holds (founder,
32836
+ // 2026-11-05): ONE object (a capability, a playbook, a cell, a kind, an
32837
+ // automation) is a template; several (a space with its kinds and views, a
32838
+ // set of workflows) are a pack.
32839
+ "vocabulary.packageNoun.template.one": "Template",
32840
+ "vocabulary.packageNoun.template.other": "Templates",
32841
+ "vocabulary.packageNoun.pack.one": "Pack",
32842
+ "vocabulary.packageNoun.pack.other": "Packs",
32843
+ // `resolveGrantPermissionSentence`: a permission the person reads as its
32844
+ // act, not as a verb and a subject.
32845
+ "vocabulary.grant.packageInstall": "Add packs and templates to your pod",
32695
32846
  // `resolveNeedsYouItemCount` for a kind no table names: its words are a
32696
32847
  // humanized token, counted with English plural rules.
32697
32848
  "vocabulary.needsYou.count.uncurated": "{n, plural, one {# {noun}} other {# {nouns}}}"