@synap-core/workspace-templates 0.9.3 → 0.9.4
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 +42 -33
- package/dist/index.js +142 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/crm.yaml +66 -0
- package/src/templates.ts +60 -0
- package/src/types.ts +47 -37
- package/src/validate-onboarding.test.ts +88 -0
- package/src/validate.ts +122 -10
package/dist/index.d.ts
CHANGED
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
* YAML source files validate against these types.
|
|
6
6
|
* Every consumer (browser, backend, CLI, marketplace) imports from here.
|
|
7
7
|
*/
|
|
8
|
-
type TemplateAppKind =
|
|
8
|
+
type TemplateAppKind = "native" | "stored" | "url";
|
|
9
9
|
interface TemplateAppRef {
|
|
10
10
|
kind: TemplateAppKind;
|
|
11
11
|
/** For kind='native': the appId registered in the browser manifest */
|
|
12
12
|
appId?: string;
|
|
13
13
|
/** For kind='native': how the platform renders the app */
|
|
14
|
-
rendererType?:
|
|
14
|
+
rendererType?: "native" | "external" | "iframe-srcdoc";
|
|
15
15
|
/** For kind='stored': the cell key registered in the cell registry */
|
|
16
16
|
cellKey?: string;
|
|
17
17
|
/** For kind='stored': default props passed to the cell */
|
|
@@ -25,25 +25,34 @@ type WorkspaceJsonValue = string | number | boolean | null | WorkspaceJsonValue[
|
|
|
25
25
|
[key: string]: WorkspaceJsonValue;
|
|
26
26
|
};
|
|
27
27
|
type WorkspacePrimarySurface = {
|
|
28
|
-
kind:
|
|
28
|
+
kind: "app";
|
|
29
29
|
appId: string;
|
|
30
|
-
rendererType:
|
|
30
|
+
rendererType: "native";
|
|
31
31
|
title?: string;
|
|
32
32
|
props?: Record<string, WorkspaceJsonValue>;
|
|
33
33
|
} | {
|
|
34
|
-
kind:
|
|
34
|
+
kind: "app";
|
|
35
35
|
appId: string;
|
|
36
|
-
rendererType:
|
|
36
|
+
rendererType: "external";
|
|
37
37
|
url: string;
|
|
38
38
|
title?: string;
|
|
39
39
|
props?: Record<string, WorkspaceJsonValue>;
|
|
40
40
|
} | {
|
|
41
|
-
kind:
|
|
41
|
+
kind: "cell";
|
|
42
42
|
cellKey: string;
|
|
43
43
|
title?: string;
|
|
44
44
|
props?: Record<string, WorkspaceJsonValue>;
|
|
45
45
|
} | {
|
|
46
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Ordinary website. Unlike an external `app`, this has no trusted app
|
|
48
|
+
* identity and is always opened through the Browser's unprivileged URL
|
|
49
|
+
* surface.
|
|
50
|
+
*/
|
|
51
|
+
kind: "url";
|
|
52
|
+
url: string;
|
|
53
|
+
title?: string;
|
|
54
|
+
} | {
|
|
55
|
+
kind: "view";
|
|
47
56
|
viewId: string;
|
|
48
57
|
title?: string;
|
|
49
58
|
};
|
|
@@ -53,12 +62,12 @@ type WorkspacePrimarySurface = {
|
|
|
53
62
|
* slug. The pod resolves this to `WorkspacePrimarySurface` before persistence.
|
|
54
63
|
*/
|
|
55
64
|
type WorkspacePrimarySurfaceViewReference = {
|
|
56
|
-
kind:
|
|
65
|
+
kind: "view";
|
|
57
66
|
viewName: string;
|
|
58
67
|
viewSlug?: string;
|
|
59
68
|
title?: string;
|
|
60
69
|
} | {
|
|
61
|
-
kind:
|
|
70
|
+
kind: "view";
|
|
62
71
|
viewName?: string;
|
|
63
72
|
viewSlug: string;
|
|
64
73
|
title?: string;
|
|
@@ -102,11 +111,11 @@ interface TemplateProfile {
|
|
|
102
111
|
* `entity_scope` of an existing profile (a divergence is reported as an
|
|
103
112
|
* advisory `scopeConflict` and the live row is left alone).
|
|
104
113
|
*/
|
|
105
|
-
entityScope?:
|
|
114
|
+
entityScope?: "pod" | "workspace";
|
|
106
115
|
/** Semantic slug for system profiles. */
|
|
107
116
|
semanticSlug?: string;
|
|
108
117
|
/** Kind vs role profile. Omit for default ('kind'). */
|
|
109
|
-
profileKind?:
|
|
118
|
+
profileKind?: "kind" | "role";
|
|
110
119
|
/** For profileKind='role': which base-kind profile slugs this role can attach to. */
|
|
111
120
|
applicableKinds?: string[];
|
|
112
121
|
properties: TemplateProperty[];
|
|
@@ -159,21 +168,21 @@ interface TemplateDisplayTemplate {
|
|
|
159
168
|
config: Record<string, unknown>;
|
|
160
169
|
}
|
|
161
170
|
interface TemplateSidebarSurface {
|
|
162
|
-
kind:
|
|
171
|
+
kind: "cell" | "view" | "entity" | "document" | "channel" | "app" | "url";
|
|
163
172
|
cellKey?: string;
|
|
164
173
|
viewId?: string;
|
|
165
174
|
viewName?: string;
|
|
166
175
|
appId?: string;
|
|
167
176
|
url?: string;
|
|
168
|
-
rendererType?:
|
|
169
|
-
placement?:
|
|
170
|
-
displayMode?:
|
|
177
|
+
rendererType?: "native" | "external" | "iframe-srcdoc";
|
|
178
|
+
placement?: "main" | "side" | "floating" | "modal" | "popover" | "embed";
|
|
179
|
+
displayMode?: "compact" | "medium" | "full";
|
|
171
180
|
props?: Record<string, unknown>;
|
|
172
181
|
title?: string;
|
|
173
182
|
meta?: Record<string, unknown>;
|
|
174
183
|
}
|
|
175
184
|
interface TemplateSidebarItem {
|
|
176
|
-
kind:
|
|
185
|
+
kind: "app" | "view" | "profile" | "external" | "cell";
|
|
177
186
|
appId?: string;
|
|
178
187
|
viewName?: string;
|
|
179
188
|
viewId?: string;
|
|
@@ -204,7 +213,7 @@ interface TemplateLayoutConfig {
|
|
|
204
213
|
sidebarItems?: TemplateSidebarItem[];
|
|
205
214
|
}
|
|
206
215
|
interface TemplateBentoBlock {
|
|
207
|
-
kind:
|
|
216
|
+
kind: "view" | "widget";
|
|
208
217
|
viewName?: string;
|
|
209
218
|
widgetType?: string;
|
|
210
219
|
pos: {
|
|
@@ -222,7 +231,7 @@ interface TemplateProfileEntityBentoTemplates {
|
|
|
222
231
|
};
|
|
223
232
|
}
|
|
224
233
|
/** Mirrors `WorkspaceSourceRole` in synap-backend `schema/workspaces.ts`. */
|
|
225
|
-
type TemplateSourceRole =
|
|
234
|
+
type TemplateSourceRole = "provider" | "consumer" | "provider-consumer";
|
|
226
235
|
/** Mirrors `WorkspaceDefaultSource` in synap-backend `schema/workspaces.ts`. */
|
|
227
236
|
interface TemplateDefaultSource {
|
|
228
237
|
/** The workspace id that supplies this capability domain. */
|
|
@@ -240,7 +249,7 @@ interface TemplateOnboardingCollectTarget {
|
|
|
240
249
|
/** Human description of what to capture for this target. */
|
|
241
250
|
what: string;
|
|
242
251
|
/** Roughly how many to expect — guides interview depth. */
|
|
243
|
-
cardinality?:
|
|
252
|
+
cardinality?: "one" | "few" | "several";
|
|
244
253
|
/** Key fields the agent should make sure to fill. */
|
|
245
254
|
keyFields?: string[];
|
|
246
255
|
}
|
|
@@ -280,7 +289,7 @@ interface TemplateOnboarding {
|
|
|
280
289
|
}
|
|
281
290
|
interface TemplatePlaybookParam {
|
|
282
291
|
name: string;
|
|
283
|
-
type:
|
|
292
|
+
type: "text" | "number" | "entity" | "choice" | "boolean";
|
|
284
293
|
label?: string;
|
|
285
294
|
required?: boolean;
|
|
286
295
|
defaultValue?: unknown;
|
|
@@ -290,12 +299,12 @@ interface TemplatePlaybook {
|
|
|
290
299
|
description?: string;
|
|
291
300
|
goalTemplate: string;
|
|
292
301
|
params?: TemplatePlaybookParam[];
|
|
293
|
-
executor?:
|
|
294
|
-
inputStrategy?:
|
|
302
|
+
executor?: "is-agent" | "external-agent" | "hybrid";
|
|
303
|
+
inputStrategy?: "none" | "static" | "rotating" | "query";
|
|
295
304
|
channelSpec?: {
|
|
296
|
-
type:
|
|
305
|
+
type: "GROUP" | "AGENT_COLLAB" | "THREAD";
|
|
297
306
|
members?: string[];
|
|
298
|
-
aiReactionMode?:
|
|
307
|
+
aiReactionMode?: "on_mention" | "always" | "never";
|
|
299
308
|
};
|
|
300
309
|
/**
|
|
301
310
|
* A cron schedule that maintains ONE backing cron automation (a single
|
|
@@ -327,29 +336,29 @@ interface TemplatePlaybook {
|
|
|
327
336
|
profileSlug: string;
|
|
328
337
|
filter?: Record<string, unknown>;
|
|
329
338
|
};
|
|
330
|
-
status?:
|
|
339
|
+
status?: "draft" | "active" | "paused";
|
|
331
340
|
}
|
|
332
341
|
interface TemplateAutomation {
|
|
333
342
|
/** Stable local key (unused on the wire today — reserved for dedup/refs). */
|
|
334
343
|
key?: string;
|
|
335
344
|
name: string;
|
|
336
345
|
description?: string;
|
|
337
|
-
triggerType:
|
|
346
|
+
triggerType: "event" | "cron" | "webhook" | "manual";
|
|
338
347
|
triggerConfig?: Record<string, unknown>;
|
|
339
348
|
flow: {
|
|
340
349
|
nodes: unknown[];
|
|
341
350
|
edges: unknown[];
|
|
342
351
|
precondition?: string;
|
|
343
352
|
};
|
|
344
|
-
status?:
|
|
353
|
+
status?: "draft" | "active" | "paused";
|
|
345
354
|
}
|
|
346
355
|
interface TemplateActionPlacement {
|
|
347
356
|
/** Entity kind whose detail surface this action appears on. */
|
|
348
357
|
profileSlug: string;
|
|
349
358
|
/** Where the action renders. Only "entity-detail" is consumed today. */
|
|
350
|
-
surface:
|
|
359
|
+
surface: "entity-detail" | string;
|
|
351
360
|
/** What the action does when clicked. */
|
|
352
|
-
kind:
|
|
361
|
+
kind: "capability" | "playbook" | "automation";
|
|
353
362
|
/**
|
|
354
363
|
* Reference to the thing to run: a capability verb key, or a playbook /
|
|
355
364
|
* automation NAME (resolved to a row id at apply time).
|
|
@@ -381,7 +390,7 @@ interface TemplateIntegration {
|
|
|
381
390
|
/** Override default params with {{var}} interpolation. */
|
|
382
391
|
params?: Record<string, string>;
|
|
383
392
|
/** "apply" (create+update) or "install" (create only). */
|
|
384
|
-
mode?:
|
|
393
|
+
mode?: "apply" | "install";
|
|
385
394
|
}
|
|
386
395
|
interface TemplateExtendsRef {
|
|
387
396
|
source: string;
|
|
@@ -465,7 +474,7 @@ interface TemplateDependency {
|
|
|
465
474
|
* - `'require'` (default): the dependency must be present on the pod as its
|
|
466
475
|
* own artifact, but this template does not merge into it.
|
|
467
476
|
*/
|
|
468
|
-
relation?:
|
|
477
|
+
relation?: "compose" | "require";
|
|
469
478
|
/**
|
|
470
479
|
* Short human reason shown in the install-time "this also installs…" prompt.
|
|
471
480
|
*/
|
|
@@ -502,7 +511,7 @@ interface WorkspaceYaml {
|
|
|
502
511
|
* Workspace classifier — consumed by createFromDefinition (settings.workspaceType,
|
|
503
512
|
* creator-role + agent-workspace behavior). Mirrors the backend union.
|
|
504
513
|
*/
|
|
505
|
-
workspaceType?:
|
|
514
|
+
workspaceType?: "personal" | "agent" | "project" | "operational";
|
|
506
515
|
visibility?: string;
|
|
507
516
|
/** Capabilities this workspace provides (e.g. "brand.library", "content.pipeline") */
|
|
508
517
|
capabilities?: string[];
|
package/dist/index.js
CHANGED
|
@@ -10658,6 +10658,66 @@ var DATA = {
|
|
|
10658
10658
|
"type": "linked_to_deal"
|
|
10659
10659
|
}
|
|
10660
10660
|
],
|
|
10661
|
+
"onboarding": {
|
|
10662
|
+
"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",
|
|
10663
|
+
"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",
|
|
10664
|
+
"expertise": {
|
|
10665
|
+
"starters": [
|
|
10666
|
+
"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",
|
|
10667
|
+
"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",
|
|
10668
|
+
'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'
|
|
10669
|
+
],
|
|
10670
|
+
"blindSpots": [
|
|
10671
|
+
"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",
|
|
10672
|
+
"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",
|
|
10673
|
+
"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"
|
|
10674
|
+
],
|
|
10675
|
+
"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"
|
|
10676
|
+
},
|
|
10677
|
+
"collect": [
|
|
10678
|
+
{
|
|
10679
|
+
"profileSlug": "company",
|
|
10680
|
+
"what": "The real prospect organisations behind current conversations.",
|
|
10681
|
+
"cardinality": "few",
|
|
10682
|
+
"keyFields": [
|
|
10683
|
+
"website",
|
|
10684
|
+
"industry",
|
|
10685
|
+
"companySize",
|
|
10686
|
+
"source",
|
|
10687
|
+
"nextStep"
|
|
10688
|
+
]
|
|
10689
|
+
},
|
|
10690
|
+
{
|
|
10691
|
+
"profileSlug": "person",
|
|
10692
|
+
"what": "The known stakeholders for those organisations and opportunities.",
|
|
10693
|
+
"cardinality": "few",
|
|
10694
|
+
"keyFields": [
|
|
10695
|
+
"email",
|
|
10696
|
+
"jobTitle",
|
|
10697
|
+
"company",
|
|
10698
|
+
"source",
|
|
10699
|
+
"nextFollowUpDate"
|
|
10700
|
+
]
|
|
10701
|
+
},
|
|
10702
|
+
{
|
|
10703
|
+
"profileSlug": "deal",
|
|
10704
|
+
"what": "The live opportunities worth actively moving through the pipeline.",
|
|
10705
|
+
"cardinality": "few",
|
|
10706
|
+
"keyFields": [
|
|
10707
|
+
"dealStage",
|
|
10708
|
+
"value",
|
|
10709
|
+
"closeDate",
|
|
10710
|
+
"nextStep"
|
|
10711
|
+
]
|
|
10712
|
+
}
|
|
10713
|
+
],
|
|
10714
|
+
"openingQuestions": [
|
|
10715
|
+
"Which three to five opportunities are you actively trying to move right now?",
|
|
10716
|
+
"For the most important one, which company and real stakeholder are involved?",
|
|
10717
|
+
"What has the buyer actually done so far, and what is the next dated action?"
|
|
10718
|
+
],
|
|
10719
|
+
"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"
|
|
10720
|
+
},
|
|
10661
10721
|
"playbooks": [
|
|
10662
10722
|
{
|
|
10663
10723
|
"name": "Enrich Lead/Company",
|
|
@@ -29398,7 +29458,8 @@ function validateTemplate(tpl, opts = {}) {
|
|
|
29398
29458
|
if (!tpl.meta) add("meta-required", "meta", "Missing required field: meta");
|
|
29399
29459
|
else {
|
|
29400
29460
|
for (const f of REQUIRED_META_FIELDS) {
|
|
29401
|
-
if (!tpl.meta[f])
|
|
29461
|
+
if (!tpl.meta[f])
|
|
29462
|
+
add("meta-field-required", `meta.${f}`, `meta.${f} is required`);
|
|
29402
29463
|
}
|
|
29403
29464
|
}
|
|
29404
29465
|
if (!tpl.workspace)
|
|
@@ -29539,7 +29600,85 @@ function validateTemplate(tpl, opts = {}) {
|
|
|
29539
29600
|
);
|
|
29540
29601
|
}
|
|
29541
29602
|
}
|
|
29542
|
-
|
|
29603
|
+
const onboarding = tpl.onboarding;
|
|
29604
|
+
if (onboarding) {
|
|
29605
|
+
if (typeof onboarding.goal !== "string" || onboarding.goal.trim() === "") {
|
|
29606
|
+
add(
|
|
29607
|
+
"onboarding-goal-empty",
|
|
29608
|
+
"onboarding.goal",
|
|
29609
|
+
"onboarding.goal must be a non-empty string"
|
|
29610
|
+
);
|
|
29611
|
+
}
|
|
29612
|
+
if (typeof onboarding.framing !== "string" || onboarding.framing.trim() === "") {
|
|
29613
|
+
add(
|
|
29614
|
+
"onboarding-framing-empty",
|
|
29615
|
+
"onboarding.framing",
|
|
29616
|
+
"onboarding.framing must be a non-empty string"
|
|
29617
|
+
);
|
|
29618
|
+
}
|
|
29619
|
+
if (!Array.isArray(onboarding.collect) || onboarding.collect.length === 0) {
|
|
29620
|
+
add(
|
|
29621
|
+
"onboarding-collect-empty",
|
|
29622
|
+
"onboarding.collect",
|
|
29623
|
+
"onboarding.collect must contain at least one structured target"
|
|
29624
|
+
);
|
|
29625
|
+
}
|
|
29626
|
+
const expertise = onboarding.expertise;
|
|
29627
|
+
for (const [field, values] of [
|
|
29628
|
+
["starters", expertise?.starters],
|
|
29629
|
+
["blindSpots", expertise?.blindSpots]
|
|
29630
|
+
]) {
|
|
29631
|
+
if (values !== void 0 && (!Array.isArray(values) || values.some(
|
|
29632
|
+
(value) => typeof value !== "string" || value.trim() === ""
|
|
29633
|
+
))) {
|
|
29634
|
+
add(
|
|
29635
|
+
"onboarding-expertise-invalid",
|
|
29636
|
+
`onboarding.expertise.${field}`,
|
|
29637
|
+
`onboarding.expertise.${field} must contain only non-empty strings`
|
|
29638
|
+
);
|
|
29639
|
+
}
|
|
29640
|
+
}
|
|
29641
|
+
if (expertise?.bar !== void 0 && (typeof expertise.bar !== "string" || expertise.bar.trim() === "")) {
|
|
29642
|
+
add(
|
|
29643
|
+
"onboarding-expertise-invalid",
|
|
29644
|
+
"onboarding.expertise.bar",
|
|
29645
|
+
"onboarding.expertise.bar must be a non-empty string"
|
|
29646
|
+
);
|
|
29647
|
+
}
|
|
29648
|
+
if (onboarding.openingQuestions !== void 0 && (!Array.isArray(onboarding.openingQuestions) || onboarding.openingQuestions.some(
|
|
29649
|
+
(question) => typeof question !== "string" || question.trim() === ""
|
|
29650
|
+
))) {
|
|
29651
|
+
add(
|
|
29652
|
+
"onboarding-opening-questions-invalid",
|
|
29653
|
+
"onboarding.openingQuestions",
|
|
29654
|
+
"onboarding.openingQuestions must contain only non-empty strings"
|
|
29655
|
+
);
|
|
29656
|
+
}
|
|
29657
|
+
if (onboarding.doneWhen !== void 0 && (typeof onboarding.doneWhen !== "string" || onboarding.doneWhen.trim() === "")) {
|
|
29658
|
+
add(
|
|
29659
|
+
"onboarding-done-when-invalid",
|
|
29660
|
+
"onboarding.doneWhen",
|
|
29661
|
+
"onboarding.doneWhen must be a non-empty string"
|
|
29662
|
+
);
|
|
29663
|
+
}
|
|
29664
|
+
}
|
|
29665
|
+
for (const c of onboarding?.collect || []) {
|
|
29666
|
+
if (typeof c.what !== "string" || c.what.trim() === "") {
|
|
29667
|
+
add(
|
|
29668
|
+
"onboarding-collect-what-empty",
|
|
29669
|
+
`onboarding.collect.${c.profileSlug}.what`,
|
|
29670
|
+
`onboarding.collect target "${c.profileSlug}" must describe what to capture`
|
|
29671
|
+
);
|
|
29672
|
+
}
|
|
29673
|
+
if (c.keyFields !== void 0 && (!Array.isArray(c.keyFields) || c.keyFields.some(
|
|
29674
|
+
(field) => typeof field !== "string" || field.trim() === ""
|
|
29675
|
+
))) {
|
|
29676
|
+
add(
|
|
29677
|
+
"onboarding-collect-key-fields-invalid",
|
|
29678
|
+
`onboarding.collect.${c.profileSlug}.keyFields`,
|
|
29679
|
+
`onboarding.collect target "${c.profileSlug}" keyFields must contain only non-empty strings`
|
|
29680
|
+
);
|
|
29681
|
+
}
|
|
29543
29682
|
if (!known(c.profileSlug))
|
|
29544
29683
|
add(
|
|
29545
29684
|
"onboarding-ref-unknown",
|
|
@@ -29572,9 +29711,7 @@ function validateTemplate(tpl, opts = {}) {
|
|
|
29572
29711
|
);
|
|
29573
29712
|
const flow = a.flow ?? a.flowDefinition;
|
|
29574
29713
|
if (flow != null) {
|
|
29575
|
-
const {
|
|
29576
|
-
errors: flowErrors
|
|
29577
|
-
} = validateFlowDefinition(flow);
|
|
29714
|
+
const { errors: flowErrors } = validateFlowDefinition(flow);
|
|
29578
29715
|
for (const fe of flowErrors) {
|
|
29579
29716
|
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
29717
|
add(`automation-flow-${fe.code}`, loc, `${loc}: ${fe.message}`);
|