@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 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 = 'native' | 'stored' | 'url';
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?: 'native' | 'external' | 'iframe-srcdoc';
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: 'app';
28
+ kind: "app";
29
29
  appId: string;
30
- rendererType: 'native';
30
+ rendererType: "native";
31
31
  title?: string;
32
32
  props?: Record<string, WorkspaceJsonValue>;
33
33
  } | {
34
- kind: 'app';
34
+ kind: "app";
35
35
  appId: string;
36
- rendererType: 'external';
36
+ rendererType: "external";
37
37
  url: string;
38
38
  title?: string;
39
39
  props?: Record<string, WorkspaceJsonValue>;
40
40
  } | {
41
- kind: 'cell';
41
+ kind: "cell";
42
42
  cellKey: string;
43
43
  title?: string;
44
44
  props?: Record<string, WorkspaceJsonValue>;
45
45
  } | {
46
- kind: 'view';
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: 'view';
65
+ kind: "view";
57
66
  viewName: string;
58
67
  viewSlug?: string;
59
68
  title?: string;
60
69
  } | {
61
- kind: 'view';
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?: 'pod' | 'workspace';
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?: 'kind' | 'role';
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: 'cell' | 'view' | 'entity' | 'document' | 'channel' | 'app' | 'url';
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?: 'native' | 'external' | 'iframe-srcdoc';
169
- placement?: 'main' | 'side' | 'floating' | 'modal' | 'popover' | 'embed';
170
- displayMode?: 'compact' | 'medium' | 'full';
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: 'app' | 'view' | 'profile' | 'external' | 'cell';
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: 'view' | 'widget';
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 = 'provider' | 'consumer' | 'provider-consumer';
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?: 'one' | 'few' | 'several';
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: 'text' | 'number' | 'entity' | 'choice' | 'boolean';
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?: 'is-agent' | 'external-agent' | 'hybrid';
294
- inputStrategy?: 'none' | 'static' | 'rotating' | 'query';
302
+ executor?: "is-agent" | "external-agent" | "hybrid";
303
+ inputStrategy?: "none" | "static" | "rotating" | "query";
295
304
  channelSpec?: {
296
- type: 'GROUP' | 'AGENT_COLLAB' | 'THREAD';
305
+ type: "GROUP" | "AGENT_COLLAB" | "THREAD";
297
306
  members?: string[];
298
- aiReactionMode?: 'on_mention' | 'always' | 'never';
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?: 'draft' | 'active' | 'paused';
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: 'event' | 'cron' | 'webhook' | 'manual';
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?: 'draft' | 'active' | 'paused';
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: 'entity-detail' | string;
359
+ surface: "entity-detail" | string;
351
360
  /** What the action does when clicked. */
352
- kind: 'capability' | 'playbook' | 'automation';
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?: 'apply' | 'install';
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?: 'compose' | 'require';
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?: 'personal' | 'agent' | 'project' | 'operational';
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]) add("meta-field-required", `meta.${f}`, `meta.${f} is required`);
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
- for (const c of tpl.onboarding?.collect || []) {
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}`);