thinkwork-cli 0.12.16 → 0.13.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.
Files changed (149) hide show
  1. package/dist/cli.js +517 -10403
  2. package/dist/commands/enterprise/templates/deploy-repo/docs/runbook.md +3 -8
  3. package/dist/commands/enterprise/templates/deploy-repo/terraform/main.tf +1 -184
  4. package/dist/drizzle/0050_brain_v0_entity_subtype.sql +2 -2
  5. package/dist/drizzle/0051_brain_v0_tenant_entity_pages.sql +6 -6
  6. package/dist/drizzle/0052_brain_v0_external_refs.sql +2 -2
  7. package/dist/drizzle/0053_brain_v0_unresolved_mentions_subtype.sql +2 -2
  8. package/dist/drizzle/0145_knowledge_graph_thread_ingest.sql +14 -14
  9. package/dist/drizzle/0146_knowledge_graph_source_scope.sql +1 -1
  10. package/dist/drizzle/0157_knowledge_graph_observations_source.sql +2 -2
  11. package/dist/drizzle/0158_wiki_tenant_scope.sql +1 -1
  12. package/dist/drizzle/0165_plugin_premium_entitlements.sql +2 -2
  13. package/dist/drizzle/{0166_company_brain_substrate_contract.sql → 0166_brain_substrate_contract.sql} +9 -9
  14. package/dist/drizzle/{0167_company_brain_artifact_manifest_runtime.sql → 0167_brain_artifact_manifest_runtime.sql} +5 -5
  15. package/dist/drizzle/0168_eval_skill_gate.sql +1 -1
  16. package/dist/drizzle/0183_okf_artifact_manifests.sql +1 -1
  17. package/dist/drizzle/0200_eval_results_execution_tier.sql +40 -0
  18. package/dist/drizzle/0201_brain_dream_runs.sql +72 -0
  19. package/dist/drizzle/0202_backfill_thread_participants_from_senders.sql +110 -0
  20. package/dist/drizzle/0203_threads_mode_override.sql +61 -0
  21. package/dist/drizzle/0204_thread_turns_triggering_message_id.sql +48 -0
  22. package/dist/drizzle/0205_routines_git_python.sql +185 -0
  23. package/dist/drizzle/0206_agent_loop_versions_routine_actions.sql +40 -0
  24. package/dist/drizzle/0207_tenant_credentials_github_repo_kind.sql +52 -0
  25. package/dist/drizzle/0208_knowledge_graph_neutral_identifiers.sql +253 -0
  26. package/dist/drizzle/0209_living_artifacts_schema.sql +109 -0
  27. package/dist/drizzle/0210_think137_automations_additive_schema.sql +113 -0
  28. package/dist/drizzle/0211_backfill_agent_loop_versions_target_spec.sql +120 -0
  29. package/dist/drizzle/0212_fold_webhooks_into_automations.sql +312 -0
  30. package/dist/drizzle/0213_agent_loop_versions_judge_evidence_nullable.sql +63 -0
  31. package/dist/drizzle/0214_think137_drop_judge_evidence_roi.sql +99 -0
  32. package/dist/drizzle/0215_agent_loop_versions_goal_worker_policy_nullable.sql +70 -0
  33. package/dist/drizzle/0216_drop_agent_loop_versions_goal_worker_policy.sql +81 -0
  34. package/dist/drizzle/0217_agents_capability_folder_dispatch.sql +12 -0
  35. package/dist/drizzle/0218_document_plates.sql +30 -0
  36. package/dist/drizzle/0219_document_section_waivers.sql +29 -0
  37. package/dist/drizzle/0220_artifacts_created_by_user.sql +21 -0
  38. package/dist/drizzle/0221_artifact_shares.sql +29 -0
  39. package/dist/drizzle/0221_workflow_interpreter_thin_slice.sql +71 -0
  40. package/dist/drizzle/0222_compliance_drainer_select_audit_events.sql +21 -0
  41. package/dist/drizzle/0223_document_conformance_reports.sql +38 -0
  42. package/dist/drizzle/0224_artifact_refresh_state.sql +11 -0
  43. package/dist/drizzle/0225_think216_agent_loop_convergence.sql +21 -0
  44. package/dist/drizzle/0226_drop_n8n_agent_step_runs.sql +6 -0
  45. package/dist/drizzle/0227_analyst_reader_role.sql +1055 -0
  46. package/dist/drizzle/0228_tenant_credentials_rds_iam_kind.sql +62 -0
  47. package/dist/drizzle/0229_analyst_reader_rds_iam_grant.sql +79 -0
  48. package/dist/drizzle/0230_analyst_rls.sql +1613 -0
  49. package/dist/drizzle/0231_cost_events_cache_write_enforcement_exempt.sql +11 -0
  50. package/dist/plugins/catalog/package.json +0 -1
  51. package/dist/plugins/catalog/src/__tests__/build-catalog.test.ts +0 -1
  52. package/dist/plugins/catalog/src/__tests__/catalog.test.ts +16 -8
  53. package/dist/plugins/catalog/src/__tests__/contracts.test.ts +56 -0
  54. package/dist/plugins/catalog/src/__tests__/plugin-package.test.ts +0 -2
  55. package/dist/plugins/catalog/src/__tests__/plugin-registry.test.ts +0 -5
  56. package/dist/plugins/catalog/src/contracts.ts +112 -0
  57. package/dist/plugins/catalog/src/registry/generated-first-party.ts +0 -6
  58. package/dist/plugins/n8n/README.md +4 -36
  59. package/dist/plugins/n8n/src/deployment/managed-app.ts +0 -17
  60. package/dist/plugins/n8n/src/manifest.ts +0 -5
  61. package/dist/plugins/n8n/terraform/n8n/main.tf +0 -26
  62. package/dist/plugins/n8n/terraform/n8n/outputs.tf +0 -5
  63. package/dist/plugins/n8n/terraform/n8n/variables.tf +0 -6
  64. package/dist/plugins/n8n/test/manifest.test.ts +0 -1
  65. package/dist/plugins/twenty/package.json +5 -0
  66. package/dist/plugins/twenty/scripts/lib/__tests__/load-records.test.ts +232 -0
  67. package/dist/plugins/twenty/scripts/lib/__tests__/mappers.test.ts +489 -0
  68. package/dist/plugins/twenty/scripts/lib/__tests__/members-ensure.test.ts +199 -0
  69. package/dist/plugins/twenty/scripts/lib/__tests__/provision-members-db.test.ts +43 -0
  70. package/dist/plugins/twenty/scripts/lib/__tests__/schema-ensure.test.ts +230 -0
  71. package/dist/plugins/twenty/scripts/lib/__tests__/twenty-client.test.ts +207 -0
  72. package/dist/plugins/twenty/scripts/lib/lastmile-reader.ts +282 -0
  73. package/dist/plugins/twenty/scripts/lib/load-attachments.ts +220 -0
  74. package/dist/plugins/twenty/scripts/lib/load-records.ts +680 -0
  75. package/dist/plugins/twenty/scripts/lib/mappers.ts +559 -0
  76. package/dist/plugins/twenty/scripts/lib/members-ensure.ts +381 -0
  77. package/dist/plugins/twenty/scripts/lib/provision-members-db.ts +191 -0
  78. package/dist/plugins/twenty/scripts/lib/schema-ensure.ts +425 -0
  79. package/dist/plugins/twenty/scripts/lib/twenty-client.ts +246 -0
  80. package/dist/plugins/twenty/scripts/migrate-lastmile.ts +610 -0
  81. package/dist/plugins/twenty/scripts/provision-twenty-members.ts +214 -0
  82. package/dist/plugins/twenty/src/manifest.ts +99 -0
  83. package/dist/plugins/twenty/terraform/twenty/README.md +1 -1
  84. package/dist/plugins/twenty/test/manifest.test.ts +12 -2
  85. package/dist/terraform/examples/greenfield/main.tf +90 -380
  86. package/dist/terraform/examples/greenfield/terraform.tfvars.example +0 -1
  87. package/dist/terraform/modules/app/agentcore-pi/README.md +1 -3
  88. package/dist/terraform/modules/app/agentcore-pi/main.tf +29 -38
  89. package/dist/terraform/modules/app/agentcore-pi/variables.tf +23 -15
  90. package/dist/terraform/modules/app/agentcore-runtime/main.tf +3 -3
  91. package/dist/terraform/modules/app/customer-domain/main.tf +16 -2
  92. package/dist/terraform/modules/app/deployment-control-plane/main.tf +5 -0
  93. package/dist/terraform/modules/app/deployment-control-plane/runner.py +85 -207
  94. package/dist/terraform/modules/app/deployment-control-plane/test_runner_bundle.py +56 -2
  95. package/dist/terraform/modules/app/hindsight-memory/main.tf +52 -4
  96. package/dist/terraform/modules/app/lambda-api/eval-fanout.tf +7 -0
  97. package/dist/terraform/modules/app/lambda-api/handlers.tf +493 -89
  98. package/dist/terraform/modules/app/lambda-api/iam-grouped.tf +215 -96
  99. package/dist/terraform/modules/app/lambda-api/main.tf +24 -5
  100. package/dist/terraform/modules/app/lambda-api/outputs.tf +19 -11
  101. package/dist/terraform/modules/app/lambda-api/variables.tf +94 -67
  102. package/dist/terraform/modules/app/ses-email/main.tf +14 -2
  103. package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/main.tf +493 -0
  104. package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/outputs.tf +34 -0
  105. package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/variables.tf +32 -0
  106. package/dist/terraform/modules/data/aurora-postgres/main.tf +55 -0
  107. package/dist/terraform/modules/data/aurora-postgres/outputs.tf +15 -0
  108. package/dist/terraform/modules/data/s3-buckets/main.tf +26 -0
  109. package/dist/terraform/modules/thinkwork/README.md +0 -1
  110. package/dist/terraform/modules/thinkwork/main.tf +167 -446
  111. package/dist/terraform/modules/thinkwork/outputs.tf +2 -142
  112. package/dist/terraform/modules/thinkwork/variables.tf +55 -309
  113. package/dist/terraform/schema.graphql +4 -0
  114. package/dist/workspace-defaults/files/AGENTS.md +45 -31
  115. package/dist/workspace-defaults/files/CONTEXT.md +48 -4
  116. package/dist/workspace-defaults/files/GUARDRAILS.md +18 -0
  117. package/dist/workspace-defaults/files/MEMORY_GUIDE.md +32 -4
  118. package/dist/workspace-defaults/files/SPACE.md +34 -19
  119. package/dist/workspace-defaults/files/TOOLS.md +30 -0
  120. package/dist/workspace-defaults/files/skills/document-composer/SKILL.md +154 -0
  121. package/dist/workspace-defaults/files/skills/document-composer/references/authoring-rules.md +101 -0
  122. package/package.json +1 -1
  123. package/dist/plugins/company-brain/README.md +0 -37
  124. package/dist/plugins/company-brain/package.json +0 -33
  125. package/dist/plugins/company-brain/runtime/cognee/Dockerfile +0 -11
  126. package/dist/plugins/company-brain/smoke/cognee-managed-app-smoke.mjs +0 -458
  127. package/dist/plugins/company-brain/smoke/cognee-memory-cutover-smoke.mjs +0 -539
  128. package/dist/plugins/company-brain/smoke/company-brain-context-engine-smoke.mjs +0 -312
  129. package/dist/plugins/company-brain/smoke/company-brain-operations-smoke.mjs +0 -449
  130. package/dist/plugins/company-brain/smoke/company-brain-plugin-smoke.mjs +0 -655
  131. package/dist/plugins/company-brain/smoke/hindsight-memory-isolation-smoke.mjs +0 -474
  132. package/dist/plugins/company-brain/src/api/cognee-client.ts +0 -896
  133. package/dist/plugins/company-brain/src/api/cognee-cluster-identity.ts +0 -49
  134. package/dist/plugins/company-brain/src/api/cognee-memory-scope.ts +0 -84
  135. package/dist/plugins/company-brain/src/api/context-engine-provider.ts +0 -888
  136. package/dist/plugins/company-brain/src/api/migration.ts +0 -665
  137. package/dist/plugins/company-brain/src/deployment/cognee-managed-app.ts +0 -396
  138. package/dist/plugins/company-brain/src/index.ts +0 -62
  139. package/dist/plugins/company-brain/src/manifest.ts +0 -112
  140. package/dist/plugins/company-brain/terraform/cognee/README.md +0 -263
  141. package/dist/plugins/company-brain/terraform/cognee/main.tf +0 -766
  142. package/dist/plugins/company-brain/terraform/cognee/outputs.tf +0 -144
  143. package/dist/plugins/company-brain/terraform/cognee/variables.tf +0 -402
  144. package/dist/plugins/company-brain/test/api/cognee-client.test.ts +0 -562
  145. package/dist/plugins/company-brain/test/api/cognee-memory-scope.test.ts +0 -74
  146. package/dist/plugins/company-brain/test/api/context-engine-provider.test.ts +0 -447
  147. package/dist/plugins/company-brain/test/api/migration.test.ts +0 -465
  148. package/dist/plugins/company-brain/test/manifest.test.ts +0 -95
  149. package/dist/plugins/company-brain/tsconfig.json +0 -8
@@ -0,0 +1,11 @@
1
+ -- THINK-245: cache-write token capture + budget-grace flag on cost_events.
2
+ -- Hand-rolled (db:generate blocked by an unrelated interactive rename prompt);
3
+ -- apply with: psql "$DATABASE_URL" -f this file. Idempotent.
4
+ -- creates-column: public.cost_events.cached_write_tokens
5
+ -- creates-column: public.cost_events.enforcement_exempt
6
+
7
+ ALTER TABLE "cost_events"
8
+ ADD COLUMN IF NOT EXISTS "cached_write_tokens" integer;
9
+
10
+ ALTER TABLE "cost_events"
11
+ ADD COLUMN IF NOT EXISTS "enforcement_exempt" boolean NOT NULL DEFAULT false;
@@ -21,7 +21,6 @@
21
21
  "build:catalog": "tsx scripts/generate-plugin-registry.ts --check && tsx scripts/build-catalog.ts"
22
22
  },
23
23
  "dependencies": {
24
- "@thinkwork/plugin-company-brain": "workspace:*",
25
24
  "@thinkwork/plugin-company-data": "workspace:*",
26
25
  "@thinkwork/plugin-company-etl": "workspace:*",
27
26
  "@thinkwork/plugin-email-channel": "workspace:*",
@@ -37,7 +37,6 @@ describe("buildSignedCatalogJson", () => {
37
37
  "n8n",
38
38
  "email-channel",
39
39
  "sendgrid",
40
- "company-brain",
41
40
  "company-data",
42
41
  "company-etl",
43
42
  "twenty",
@@ -13,7 +13,8 @@ import {
13
13
  verifyPluginCatalog,
14
14
  type SignedPluginCatalogDocument,
15
15
  } from "../catalog";
16
- import { companyBrainManifest, lastmileManifest } from "../registry";
16
+ import type { PluginManifest } from "../contracts";
17
+ import { lastmileManifest, n8nManifest } from "../registry";
17
18
 
18
19
  function keyPair() {
19
20
  const pair = generateKeyPairSync("ed25519");
@@ -95,23 +96,30 @@ describe("buildPluginCatalog", () => {
95
96
 
96
97
  it("sorts catalog plugins alphabetically by display name", () => {
97
98
  const catalog = buildPluginCatalog({
98
- manifests: [lastmileManifest, companyBrainManifest],
99
+ manifests: [n8nManifest, lastmileManifest],
99
100
  });
100
101
 
101
102
  expect(catalog.plugins.map((plugin) => plugin.displayName)).toEqual([
102
103
  "LastMile",
103
- "ThinkWork Brain",
104
+ "n8n",
104
105
  ]);
105
106
  });
106
107
 
107
108
  it("preserves premium metadata on catalog entries", () => {
108
- const catalog = buildPluginCatalog({ manifests: [companyBrainManifest] });
109
- expect(catalog.plugins[0].pluginKey).toBe("company-brain");
109
+ const premiumManifest: PluginManifest = {
110
+ ...lastmileManifest,
111
+ premium: {
112
+ entitlementProductKey: "lastmile-premium",
113
+ installKeyRequired: true,
114
+ installKeyPrompt: "Enter the install key.",
115
+ },
116
+ };
117
+ const catalog = buildPluginCatalog({ manifests: [premiumManifest] });
118
+ expect(catalog.plugins[0].pluginKey).toBe("lastmile");
110
119
  expect(catalog.plugins[0].premium).toEqual({
111
- entitlementProductKey: "company-brain",
120
+ entitlementProductKey: "lastmile-premium",
112
121
  installKeyRequired: true,
113
- installKeyPrompt:
114
- "Enter the ThinkWork Brain install key provided by ThinkWork to unlock this premium plugin for your tenant.",
122
+ installKeyPrompt: "Enter the install key.",
115
123
  });
116
124
  });
117
125
 
@@ -284,6 +284,62 @@ describe("validatePluginManifest", () => {
284
284
  expect(validatedServer.recordLinkHints).toEqual(server.recordLinkHints);
285
285
  });
286
286
 
287
+ it("accepts provider-neutral MCP result transforms", () => {
288
+ const ok = manifest();
289
+ const server = version(ok).components[0];
290
+ if (server.type !== "mcp-server") throw new Error("missing mcp-server");
291
+ (server as unknown as Record<string, unknown>).resultTransforms = [
292
+ {
293
+ type: "scaled-integer-to-decimal",
294
+ sourceField: "amountMicros",
295
+ targetField: "value",
296
+ scale: 6,
297
+ removeSource: true,
298
+ },
299
+ ];
300
+
301
+ const validated = validatePluginManifest(ok);
302
+ const validatedServer = validated.versions[0].components[0];
303
+ if (validatedServer.type !== "mcp-server") {
304
+ throw new Error("expected mcp-server");
305
+ }
306
+ expect(
307
+ (validatedServer as unknown as Record<string, unknown>).resultTransforms,
308
+ ).toEqual((server as unknown as Record<string, unknown>).resultTransforms);
309
+ });
310
+
311
+ it("rejects unsafe or unbounded MCP result transforms", () => {
312
+ for (const transform of [
313
+ {
314
+ type: "unknown-transform",
315
+ sourceField: "amountMicros",
316
+ targetField: "value",
317
+ scale: 6,
318
+ },
319
+ {
320
+ type: "scaled-integer-to-decimal",
321
+ sourceField: "auth.token",
322
+ targetField: "value",
323
+ scale: 6,
324
+ },
325
+ {
326
+ type: "scaled-integer-to-decimal",
327
+ sourceField: "amountMicros",
328
+ targetField: "value",
329
+ scale: 100,
330
+ },
331
+ ]) {
332
+ const bad = manifest();
333
+ const server = version(bad).components[0];
334
+ if (server.type !== "mcp-server") throw new Error("missing mcp-server");
335
+ (server as unknown as Record<string, unknown>).resultTransforms = [
336
+ transform,
337
+ ];
338
+
339
+ expect(() => validatePluginManifest(bad)).toThrow(/resultTransforms/);
340
+ }
341
+ });
342
+
287
343
  it("rejects unsafe MCP record-link route templates", () => {
288
344
  for (const routeTemplate of [
289
345
  "https://crm.example.com/object/opportunity/{id}",
@@ -13,7 +13,6 @@ import {
13
13
  describe("first-party plugin packages", () => {
14
14
  it("registers n8n from its root plugin package boundary", () => {
15
15
  expect(firstPartyPluginPackages.map((entry) => entry.packageKey)).toEqual([
16
- "company-brain",
17
16
  "company-data",
18
17
  "company-etl",
19
18
  "email-channel",
@@ -51,7 +50,6 @@ describe("first-party plugin packages", () => {
51
50
  "n8n",
52
51
  "email-channel",
53
52
  "sendgrid",
54
- "company-brain",
55
53
  "company-data",
56
54
  "company-etl",
57
55
  "twenty",
@@ -18,11 +18,6 @@ describe("generated first-party plugin registry", () => {
18
18
  exportName: entry.exportName,
19
19
  })),
20
20
  ).toEqual([
21
- {
22
- packageKey: "company-brain",
23
- packageName: "@thinkwork/plugin-company-brain",
24
- exportName: "companyBrainPluginPackage",
25
- },
26
21
  {
27
22
  packageKey: "company-data",
28
23
  packageName: "@thinkwork/plugin-company-data",
@@ -181,6 +181,21 @@ export interface McpRecordLinkHints {
181
181
  workspace?: McpRecordLinkWorkspaceHint;
182
182
  }
183
183
 
184
+ /** Provider-neutral, declarative transformation applied to successful MCP results. */
185
+ export interface McpScaledIntegerToDecimalTransform {
186
+ type: "scaled-integer-to-decimal";
187
+ /** Object field containing an integer encoded in the provider's base unit. */
188
+ sourceField: string;
189
+ /** Sibling field receiving the exact decimal string. */
190
+ targetField: string;
191
+ /** Number of base-10 fractional digits in the source integer. */
192
+ scale: number;
193
+ /** Drop the provider-specific source field after a successful conversion. */
194
+ removeSource?: boolean;
195
+ }
196
+
197
+ export type McpResultTransform = McpScaledIntegerToDecimalTransform;
198
+
184
199
  export interface McpServerComponent {
185
200
  type: "mcp-server";
186
201
  key: string;
@@ -193,6 +208,8 @@ export interface McpServerComponent {
193
208
  auth: McpServerAuth;
194
209
  /** Optional non-secret hints for generating links from trusted MCP results. */
195
210
  recordLinkHints?: McpRecordLinkHints;
211
+ /** Optional provider-owned, data-only normalization rules for tool results. */
212
+ resultTransforms?: McpResultTransform[];
196
213
  /** Optional human notes about the tools the server exposes. */
197
214
  toolNotes?: string[];
198
215
  }
@@ -697,6 +714,9 @@ function validateMcpServerComponent(
697
714
  if (component.recordLinkHints !== undefined) {
698
715
  validateRecordLinkHints(component.recordLinkHints, prefix);
699
716
  }
717
+ if (component.resultTransforms !== undefined) {
718
+ validateMcpResultTransforms(component.resultTransforms, prefix);
719
+ }
700
720
  if (component.toolNotes !== undefined) {
701
721
  if (
702
722
  !Array.isArray(component.toolNotes) ||
@@ -746,6 +766,14 @@ function validateMcpServerComponent(
746
766
  }
747
767
 
748
768
  const RECORD_LINK_FIELD_RE = /^[A-Za-z][A-Za-z0-9_.-]{0,127}$/;
769
+ const RESULT_TRANSFORM_FIELD_RE = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
770
+ const RESULT_TRANSFORM_ALLOWED_KEYS = [
771
+ "type",
772
+ "sourceField",
773
+ "targetField",
774
+ "scale",
775
+ "removeSource",
776
+ ] as const;
749
777
  const RECORD_LINK_ALLOWED_HINT_KEYS = [
750
778
  "schemaVersion",
751
779
  "source",
@@ -771,6 +799,90 @@ const RECORD_LINK_FORBIDDEN_FIELD_PARTS = [
771
799
  "header",
772
800
  ] as const;
773
801
 
802
+ function validateMcpResultTransforms(
803
+ transforms: McpResultTransform[],
804
+ prefix: string,
805
+ ): void {
806
+ const label = `${prefix}.resultTransforms`;
807
+ if (
808
+ !Array.isArray(transforms) ||
809
+ transforms.length === 0 ||
810
+ transforms.length > 8
811
+ ) {
812
+ throw new PluginManifestError(`${label} must contain 1–8 transforms`);
813
+ }
814
+ const seenTargets = new Set<string>();
815
+ for (const [index, transform] of transforms.entries()) {
816
+ const transformLabel = `${label}[${index}]`;
817
+ if (
818
+ !transform ||
819
+ typeof transform !== "object" ||
820
+ Array.isArray(transform)
821
+ ) {
822
+ throw new PluginManifestError(`${transformLabel} must be an object`);
823
+ }
824
+ rejectUnknownKeys(
825
+ transform as unknown as Record<string, unknown>,
826
+ RESULT_TRANSFORM_ALLOWED_KEYS,
827
+ transformLabel,
828
+ );
829
+ if (transform.type !== "scaled-integer-to-decimal") {
830
+ throw new PluginManifestError(
831
+ `${transformLabel}.type must be "scaled-integer-to-decimal"`,
832
+ );
833
+ }
834
+ for (const [fieldName, fieldValue] of [
835
+ ["sourceField", transform.sourceField],
836
+ ["targetField", transform.targetField],
837
+ ] as const) {
838
+ requireString(fieldValue, `${transformLabel}.${fieldName}`);
839
+ if (!RESULT_TRANSFORM_FIELD_RE.test(fieldValue)) {
840
+ throw new PluginManifestError(
841
+ `${transformLabel}.${fieldName} must be a simple non-sensitive object field`,
842
+ );
843
+ }
844
+ const normalized = fieldValue.toLowerCase();
845
+ if (
846
+ RECORD_LINK_FORBIDDEN_FIELD_PARTS.some((part) =>
847
+ normalized.includes(part),
848
+ )
849
+ ) {
850
+ throw new PluginManifestError(
851
+ `${transformLabel}.${fieldName} must not be credential-shaped`,
852
+ );
853
+ }
854
+ }
855
+ if (transform.sourceField === transform.targetField) {
856
+ throw new PluginManifestError(
857
+ `${transformLabel}.targetField must differ from sourceField`,
858
+ );
859
+ }
860
+ if (
861
+ !Number.isInteger(transform.scale) ||
862
+ transform.scale < 0 ||
863
+ transform.scale > 12
864
+ ) {
865
+ throw new PluginManifestError(
866
+ `${transformLabel}.scale must be an integer from 0 through 12`,
867
+ );
868
+ }
869
+ if (
870
+ transform.removeSource !== undefined &&
871
+ typeof transform.removeSource !== "boolean"
872
+ ) {
873
+ throw new PluginManifestError(
874
+ `${transformLabel}.removeSource must be a boolean`,
875
+ );
876
+ }
877
+ if (seenTargets.has(transform.targetField)) {
878
+ throw new PluginManifestError(
879
+ `${label} declares duplicate targetField "${transform.targetField}"`,
880
+ );
881
+ }
882
+ seenTargets.add(transform.targetField);
883
+ }
884
+ }
885
+
774
886
  function validateRecordLinkHints(
775
887
  hints: Partial<McpRecordLinkHints>,
776
888
  prefix: string,
@@ -3,7 +3,6 @@
3
3
  // pnpm --filter @thinkwork/plugin-catalog generate:plugins
4
4
 
5
5
  import { defineFirstPartyPluginPackage } from "../plugin-package";
6
- import { companyBrainPluginPackage as rawCompanyBrainPluginPackage } from "@thinkwork/plugin-company-brain";
7
6
  import { companyDataPluginPackage as rawCompanyDataPluginPackage } from "@thinkwork/plugin-company-data";
8
7
  import { companyEtlPluginPackage as rawCompanyEtlPluginPackage } from "@thinkwork/plugin-company-etl";
9
8
  import { emailChannelPluginPackage as rawEmailChannelPluginPackage } from "@thinkwork/plugin-email-channel";
@@ -13,9 +12,6 @@ import { sendgridPluginPackage as rawSendgridPluginPackage } from "@thinkwork/pl
13
12
  import { twentyPluginPackage as rawTwentyPluginPackage } from "@thinkwork/plugin-twenty";
14
13
  import { workosAuthPluginPackage as rawWorkosAuthPluginPackage } from "@thinkwork/plugin-workos-auth";
15
14
 
16
- export const companyBrainPluginPackage = defineFirstPartyPluginPackage(
17
- rawCompanyBrainPluginPackage,
18
- );
19
15
  export const companyDataPluginPackage = defineFirstPartyPluginPackage(
20
16
  rawCompanyDataPluginPackage,
21
17
  );
@@ -41,7 +37,6 @@ export const workosAuthPluginPackage = defineFirstPartyPluginPackage(
41
37
  rawWorkosAuthPluginPackage,
42
38
  );
43
39
 
44
- export const companyBrainManifest = companyBrainPluginPackage.manifest;
45
40
  export const companyDataManifest = companyDataPluginPackage.manifest;
46
41
  export const companyEtlManifest = companyEtlPluginPackage.manifest;
47
42
  export const emailChannelManifest = emailChannelPluginPackage.manifest;
@@ -52,7 +47,6 @@ export const twentyManifest = twentyPluginPackage.manifest;
52
47
  export const workosAuthManifest = workosAuthPluginPackage.manifest;
53
48
 
54
49
  export const firstPartyPluginPackages = [
55
- companyBrainPluginPackage,
56
50
  companyDataPluginPackage,
57
51
  companyEtlPluginPackage,
58
52
  emailChannelPluginPackage,
@@ -91,42 +91,10 @@ activate, publish, delete, retry, or stop.
91
91
  workflow, project, or folder that agents may inspect.
92
92
  6. Assign the `n8n--workflow-management` MCP server to the appropriate
93
93
  ThinkWork agent.
94
- 7. For n8n-to-ThinkWork agent steps, configure the separate
95
- `agentStepBridgeCredentialSecretArn` managed-app input. Do not reuse the
96
- native n8n MCP service credential; MCP auth lets ThinkWork agents call n8n,
97
- while the bridge credential lets n8n workflows call ThinkWork.
98
-
99
- ## Agent-Step Bridge Recipe
100
-
101
- The v1 bridge is webhook-first and node-later. Workflow authors should build
102
- with stock n8n nodes:
103
-
104
- 1. A Webhook or other trigger starts the n8n execution.
105
- 2. An HTTP Request node posts to ThinkWork
106
- `/api/integrations/n8n/agent-steps` with the bridge bearer credential,
107
- target `spaceId`, `agentId`, instructions, structured `input`,
108
- `workflowId`, `executionId`, `stepId`, `correlationId`, and the current
109
- n8n Wait resume URL.
110
- 3. A Wait node pauses the execution with the On webhook call resume mode. n8n
111
- exposes the per-execution resume URL as `$execution.resumeUrl`; pass that
112
- value to ThinkWork as `resumeUrl`.
113
- 4. ThinkWork creates or reuses the visible Space thread, dispatches the agent,
114
- holds for human input inside ThinkWork when needed, and later calls the n8n
115
- waiting-webhook URL with structured JSON.
116
- 5. Downstream n8n nodes branch on `status` and read `output`, `error`,
117
- `summary`, and `links`. They should not scrape the ThinkWork thread page.
118
-
119
- The idempotency key is derived from tenant, workflow id, execution id,
120
- correlation id, and step id. Retrying the same HTTP Request for the same n8n
121
- execution and bridge step should recover or replay the existing bridge run
122
- rather than creating duplicate ThinkWork threads. If a run expires, n8n resumes
123
- with a structured `expired` result so the workflow can branch explicitly.
124
-
125
- Operator runbook details live in the docs site:
126
-
127
- - `docs/runbooks/n8n-thinkwork-native-app-install.md`
128
- - `docs/src/content/docs/applications/n8n.mdx`
129
- - `docs/src/content/docs/applications/n8n-agent-step-bridge.mdx`
94
+
95
+ The former n8n→ThinkWork agent-step bridge was retired with the canonical
96
+ workflow model (THINK-217): n8n workflows now call the generic ThinkWork
97
+ workflow webhook endpoints instead.
130
98
 
131
99
  ## Verification
132
100
 
@@ -52,7 +52,6 @@ const statusOutputs = [
52
52
  "n8n_image_digest",
53
53
  "n8n_package_config_digest",
54
54
  "n8n_service_credential_secret_arn",
55
- "n8n_agent_step_bridge_credential_secret_arn",
56
55
  ] as const;
57
56
 
58
57
  const DEFAULT_DATABASE_NAME = "thinkwork_n8n";
@@ -98,13 +97,6 @@ const provisionRequiredInputs: RequiredManagedAppInput[] = [
98
97
  terraformVariable: "n8n_service_credential_secret_arn",
99
98
  secret: true,
100
99
  },
101
- {
102
- key: "agentStepBridgeCredentialSecretArn",
103
- description:
104
- "Secrets Manager ARN containing the inbound credential used by n8n workflows to call the ThinkWork agent-step bridge.",
105
- terraformVariable: "n8n_agent_step_bridge_credential_secret_arn",
106
- secret: true,
107
- },
108
100
  {
109
101
  key: "storageBucketName",
110
102
  description: "S3 bucket name used for n8n binary data and runtime files.",
@@ -209,11 +201,6 @@ export const n8nAdapter: ManagedAppAdapter = {
209
201
  "serviceCredentialSecretArn",
210
202
  "n8n serviceCredentialSecretArn",
211
203
  ),
212
- n8n_agent_step_bridge_credential_secret_arn: requireStringInput(
213
- desiredConfig,
214
- "agentStepBridgeCredentialSecretArn",
215
- "n8n agentStepBridgeCredentialSecretArn",
216
- ),
217
204
  n8n_storage_bucket_name: requireStringInput(
218
205
  desiredConfig,
219
206
  "storageBucketName",
@@ -399,10 +386,6 @@ export const n8nAdapter: ManagedAppAdapter = {
399
386
  terraformOutputs,
400
387
  "n8n_service_credential_secret_arn",
401
388
  ),
402
- agentStepBridgeCredentialSecretArn: stringOutput(
403
- terraformOutputs,
404
- "n8n_agent_step_bridge_credential_secret_arn",
405
- ),
406
389
  },
407
390
  };
408
391
  },
@@ -271,11 +271,6 @@ const N8N_INFRA_COMPONENT: InfrastructureComponent = {
271
271
  "Secrets Manager ARN containing the tenant service credential used by the native n8n MCP integration.",
272
272
  type: "string",
273
273
  },
274
- agentStepBridgeCredentialSecretArn: {
275
- description:
276
- "Secrets Manager ARN containing the inbound credential used by n8n workflows to call the ThinkWork agent-step bridge.",
277
- type: "string",
278
- },
279
274
  storageBucketName: {
280
275
  description: "S3 bucket name used for n8n binary data and runtime files.",
281
276
  type: "string",
@@ -74,14 +74,6 @@ locals {
74
74
  N8N_MCP_SERVICE_CREDENTIAL = random_password.service_credential.result
75
75
  })
76
76
  }
77
- agent_step_bridge_credential = {
78
- enabled = var.create_secret_placeholders && var.agent_step_bridge_credential_secret_arn == ""
79
- name = "thinkwork/${var.stage}/n8n/agent-step-bridge-credential"
80
- description = "Inbound credential used by n8n workflows to call the ThinkWork agent-step bridge"
81
- secret_string = jsonencode({
82
- THINKWORK_N8N_AGENT_STEP_BRIDGE_TOKEN = random_password.agent_step_bridge_credential.result
83
- })
84
- }
85
77
  }
86
78
 
87
79
  managed_secrets = {
@@ -108,18 +100,11 @@ locals {
108
100
  ? var.service_credential_secret_arn
109
101
  : try(aws_secretsmanager_secret.n8n["service_credential"].arn, "")
110
102
  )
111
- effective_agent_step_bridge_credential_secret_arn = (
112
- var.agent_step_bridge_credential_secret_arn != ""
113
- ? var.agent_step_bridge_credential_secret_arn
114
- : try(aws_secretsmanager_secret.n8n["agent_step_bridge_credential"].arn, "")
115
- )
116
-
117
103
  secret_arns = compact([
118
104
  local.effective_database_url_secret_arn,
119
105
  local.effective_encryption_key_secret_arn,
120
106
  local.effective_operator_secret_arn,
121
107
  local.effective_service_credential_secret_arn,
122
- local.effective_agent_step_bridge_credential_secret_arn,
123
108
  ])
124
109
 
125
110
  base_environment = concat(
@@ -199,11 +184,6 @@ resource "random_password" "service_credential" {
199
184
  special = false
200
185
  }
201
186
 
202
- resource "random_password" "agent_step_bridge_credential" {
203
- length = 48
204
- special = false
205
- }
206
-
207
187
  resource "aws_secretsmanager_secret" "n8n" {
208
188
  for_each = local.managed_secrets
209
189
 
@@ -325,7 +305,6 @@ resource "terraform_data" "configuration_guardrails" {
325
305
  encryption_key_secret_arn = local.effective_encryption_key_secret_arn
326
306
  operator_secret_arn = local.effective_operator_secret_arn
327
307
  service_credential_secret_arn = local.effective_service_credential_secret_arn
328
- agent_step_bridge_credential_secret_arn = local.effective_agent_step_bridge_credential_secret_arn
329
308
  storage_bucket_name = var.storage_bucket_name
330
309
  storage_prefix = local.storage_prefix
331
310
  queue_mode = var.queue_mode
@@ -369,11 +348,6 @@ resource "terraform_data" "configuration_guardrails" {
369
348
  error_message = "n8n requires service_credential_secret_arn or create_secret_placeholders = true."
370
349
  }
371
350
 
372
- precondition {
373
- condition = local.effective_agent_step_bridge_credential_secret_arn != ""
374
- error_message = "n8n requires agent_step_bridge_credential_secret_arn or create_secret_placeholders = true."
375
- }
376
-
377
351
  precondition {
378
352
  condition = var.storage_bucket_name != ""
379
353
  error_message = "n8n requires storage_bucket_name."
@@ -83,11 +83,6 @@ output "n8n_service_credential_secret_arn" {
83
83
  value = local.effective_service_credential_secret_arn
84
84
  }
85
85
 
86
- output "n8n_agent_step_bridge_credential_secret_arn" {
87
- description = "Secrets Manager ARN used for the inbound n8n agent-step bridge credential."
88
- value = local.effective_agent_step_bridge_credential_secret_arn
89
- }
90
-
91
86
  output "n8n_valkey_replication_group_id" {
92
87
  description = "ElastiCache replication group ID for the n8n queue."
93
88
  value = aws_elasticache_replication_group.n8n.id
@@ -199,12 +199,6 @@ variable "service_credential_secret_arn" {
199
199
  default = ""
200
200
  }
201
201
 
202
- variable "agent_step_bridge_credential_secret_arn" {
203
- description = "Secrets Manager ARN containing the inbound credential used by n8n workflows to call the ThinkWork agent-step bridge."
204
- type = string
205
- default = ""
206
- }
207
-
208
202
  variable "create_secret_placeholders" {
209
203
  description = "Create operator-owned Secrets Manager placeholder containers for missing n8n secrets. Placeholder values are generated once and ignored after creation so rotation survives Terraform applies."
210
204
  type = bool
@@ -91,7 +91,6 @@ describe("n8n plugin manifest", () => {
91
91
  "encryptionKeySecretArn",
92
92
  "operatorSecretArn",
93
93
  "serviceCredentialSecretArn",
94
- "agentStepBridgeCredentialSecretArn",
95
94
  "storageBucketName",
96
95
  "publicUrl",
97
96
  "certificateArn",
@@ -19,8 +19,13 @@
19
19
  "graphql": "^16.10.0"
20
20
  },
21
21
  "devDependencies": {
22
+ "@aws-sdk/client-s3": "^3.700.0",
22
23
  "@thinkwork/deployment-runner": "workspace:*",
23
24
  "@thinkwork/plugin-catalog": "workspace:*",
25
+ "@types/bcryptjs": "^2.4.6",
26
+ "@types/pg": "^8.11.0",
27
+ "bcryptjs": "^2.4.3",
28
+ "pg": "^8.13.0",
24
29
  "typescript": "^5.5.0",
25
30
  "vitest": "^2.0.0"
26
31
  }