thinkwork-cli 0.12.15 → 0.12.17
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/{api-client-4VT3FWOH.js → api-client-JUBCQQDF.js} +1 -1
- package/dist/{chunk-H7AB42ES.js → chunk-STY44FQO.js} +13 -0
- package/dist/cli.js +1107 -10225
- package/dist/commands/enterprise/templates/deploy-repo/docs/runbook.md +3 -8
- package/dist/commands/enterprise/templates/deploy-repo/terraform/main.tf +1 -184
- package/dist/drizzle/0050_brain_v0_entity_subtype.sql +2 -2
- package/dist/drizzle/0051_brain_v0_tenant_entity_pages.sql +6 -6
- package/dist/drizzle/0052_brain_v0_external_refs.sql +2 -2
- package/dist/drizzle/0053_brain_v0_unresolved_mentions_subtype.sql +2 -2
- package/dist/drizzle/0145_knowledge_graph_thread_ingest.sql +14 -14
- package/dist/drizzle/0146_knowledge_graph_source_scope.sql +1 -1
- package/dist/drizzle/0157_knowledge_graph_observations_source.sql +2 -2
- package/dist/drizzle/0158_wiki_tenant_scope.sql +1 -1
- package/dist/drizzle/0165_plugin_premium_entitlements.sql +2 -2
- package/dist/drizzle/{0166_company_brain_substrate_contract.sql → 0166_brain_substrate_contract.sql} +9 -9
- package/dist/drizzle/{0167_company_brain_artifact_manifest_runtime.sql → 0167_brain_artifact_manifest_runtime.sql} +5 -5
- package/dist/drizzle/0168_eval_skill_gate.sql +1 -1
- package/dist/drizzle/0183_okf_artifact_manifests.sql +1 -1
- package/dist/drizzle/0197_rcm_context_columns.sql +28 -0
- package/dist/drizzle/0200_eval_results_execution_tier.sql +40 -0
- package/dist/drizzle/0201_brain_dream_runs.sql +72 -0
- package/dist/drizzle/0202_backfill_thread_participants_from_senders.sql +110 -0
- package/dist/drizzle/0203_threads_mode_override.sql +61 -0
- package/dist/drizzle/0204_thread_turns_triggering_message_id.sql +48 -0
- package/dist/drizzle/0205_routines_git_python.sql +185 -0
- package/dist/drizzle/0206_agent_loop_versions_routine_actions.sql +40 -0
- package/dist/drizzle/0207_tenant_credentials_github_repo_kind.sql +52 -0
- package/dist/drizzle/0208_knowledge_graph_neutral_identifiers.sql +253 -0
- package/dist/drizzle/0209_living_artifacts_schema.sql +109 -0
- package/dist/drizzle/0210_think137_automations_additive_schema.sql +113 -0
- package/dist/drizzle/0211_backfill_agent_loop_versions_target_spec.sql +120 -0
- package/dist/drizzle/0212_fold_webhooks_into_automations.sql +312 -0
- package/dist/drizzle/0213_agent_loop_versions_judge_evidence_nullable.sql +63 -0
- package/dist/drizzle/0214_think137_drop_judge_evidence_roi.sql +99 -0
- package/dist/drizzle/0215_agent_loop_versions_goal_worker_policy_nullable.sql +70 -0
- package/dist/drizzle/0216_drop_agent_loop_versions_goal_worker_policy.sql +81 -0
- package/dist/drizzle/0217_agents_capability_folder_dispatch.sql +12 -0
- package/dist/drizzle/0218_document_plates.sql +30 -0
- package/dist/drizzle/0219_document_section_waivers.sql +29 -0
- package/dist/drizzle/0220_artifacts_created_by_user.sql +21 -0
- package/dist/drizzle/0221_artifact_shares.sql +29 -0
- package/dist/drizzle/0221_workflow_interpreter_thin_slice.sql +71 -0
- package/dist/drizzle/0222_compliance_drainer_select_audit_events.sql +21 -0
- package/dist/drizzle/0223_document_conformance_reports.sql +38 -0
- package/dist/drizzle/0224_artifact_refresh_state.sql +11 -0
- package/dist/drizzle/0225_think216_agent_loop_convergence.sql +21 -0
- package/dist/drizzle/0226_drop_n8n_agent_step_runs.sql +6 -0
- package/dist/drizzle/0227_analyst_reader_role.sql +1055 -0
- package/dist/drizzle/0228_tenant_credentials_rds_iam_kind.sql +62 -0
- package/dist/drizzle/0229_analyst_reader_rds_iam_grant.sql +79 -0
- package/dist/drizzle/0230_analyst_rls.sql +1613 -0
- package/dist/drizzle/0231_cost_events_cache_write_enforcement_exempt.sql +11 -0
- package/dist/plugins/catalog/package.json +0 -1
- package/dist/plugins/catalog/src/__tests__/build-catalog.test.ts +0 -1
- package/dist/plugins/catalog/src/__tests__/catalog.test.ts +16 -8
- package/dist/plugins/catalog/src/__tests__/contracts.test.ts +56 -0
- package/dist/plugins/catalog/src/__tests__/plugin-package.test.ts +0 -2
- package/dist/plugins/catalog/src/__tests__/plugin-registry.test.ts +0 -5
- package/dist/plugins/catalog/src/contracts.ts +112 -0
- package/dist/plugins/catalog/src/registry/generated-first-party.ts +0 -6
- package/dist/plugins/n8n/README.md +4 -36
- package/dist/plugins/n8n/src/deployment/managed-app.ts +0 -17
- package/dist/plugins/n8n/src/manifest.ts +0 -5
- package/dist/plugins/n8n/terraform/n8n/main.tf +0 -26
- package/dist/plugins/n8n/terraform/n8n/outputs.tf +0 -5
- package/dist/plugins/n8n/terraform/n8n/variables.tf +0 -6
- package/dist/plugins/n8n/test/manifest.test.ts +0 -1
- package/dist/plugins/twenty/package.json +5 -0
- package/dist/plugins/twenty/scripts/lib/__tests__/load-records.test.ts +232 -0
- package/dist/plugins/twenty/scripts/lib/__tests__/mappers.test.ts +489 -0
- package/dist/plugins/twenty/scripts/lib/__tests__/members-ensure.test.ts +199 -0
- package/dist/plugins/twenty/scripts/lib/__tests__/provision-members-db.test.ts +43 -0
- package/dist/plugins/twenty/scripts/lib/__tests__/schema-ensure.test.ts +230 -0
- package/dist/plugins/twenty/scripts/lib/__tests__/twenty-client.test.ts +207 -0
- package/dist/plugins/twenty/scripts/lib/lastmile-reader.ts +282 -0
- package/dist/plugins/twenty/scripts/lib/load-attachments.ts +220 -0
- package/dist/plugins/twenty/scripts/lib/load-records.ts +680 -0
- package/dist/plugins/twenty/scripts/lib/mappers.ts +559 -0
- package/dist/plugins/twenty/scripts/lib/members-ensure.ts +381 -0
- package/dist/plugins/twenty/scripts/lib/provision-members-db.ts +191 -0
- package/dist/plugins/twenty/scripts/lib/schema-ensure.ts +425 -0
- package/dist/plugins/twenty/scripts/lib/twenty-client.ts +246 -0
- package/dist/plugins/twenty/scripts/migrate-lastmile.ts +610 -0
- package/dist/plugins/twenty/scripts/provision-twenty-members.ts +214 -0
- package/dist/plugins/twenty/src/manifest.ts +99 -0
- package/dist/plugins/twenty/terraform/twenty/README.md +1 -1
- package/dist/plugins/twenty/test/manifest.test.ts +12 -2
- package/dist/terraform/examples/greenfield/main.tf +90 -380
- package/dist/terraform/examples/greenfield/terraform.tfvars.example +0 -1
- package/dist/terraform/modules/app/agentcore-pi/README.md +1 -3
- package/dist/terraform/modules/app/agentcore-pi/main.tf +36 -32
- package/dist/terraform/modules/app/agentcore-pi/variables.tf +23 -15
- package/dist/terraform/modules/app/agentcore-runtime/main.tf +3 -3
- package/dist/terraform/modules/app/customer-domain/main.tf +16 -2
- package/dist/terraform/modules/app/deployment-control-plane/main.tf +5 -0
- package/dist/terraform/modules/app/deployment-control-plane/runner.py +85 -207
- package/dist/terraform/modules/app/deployment-control-plane/test_runner_bundle.py +56 -2
- package/dist/terraform/modules/app/hindsight-memory/main.tf +52 -4
- package/dist/terraform/modules/app/lambda-api/eval-fanout.tf +7 -0
- package/dist/terraform/modules/app/lambda-api/handlers.tf +493 -89
- package/dist/terraform/modules/app/lambda-api/iam-grouped.tf +215 -96
- package/dist/terraform/modules/app/lambda-api/main.tf +24 -5
- package/dist/terraform/modules/app/lambda-api/outputs.tf +19 -11
- package/dist/terraform/modules/app/lambda-api/variables.tf +94 -67
- package/dist/terraform/modules/app/ses-email/main.tf +14 -2
- package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/main.tf +493 -0
- package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/outputs.tf +34 -0
- package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/variables.tf +32 -0
- package/dist/terraform/modules/data/aurora-postgres/main.tf +55 -0
- package/dist/terraform/modules/data/aurora-postgres/outputs.tf +15 -0
- package/dist/terraform/modules/data/s3-buckets/main.tf +26 -0
- package/dist/terraform/modules/thinkwork/README.md +0 -1
- package/dist/terraform/modules/thinkwork/main.tf +167 -446
- package/dist/terraform/modules/thinkwork/outputs.tf +2 -142
- package/dist/terraform/modules/thinkwork/variables.tf +55 -309
- package/dist/terraform/schema.graphql +4 -0
- package/dist/workspace-defaults/files/AGENTS.md +45 -31
- package/dist/workspace-defaults/files/CONTEXT.md +48 -4
- package/dist/workspace-defaults/files/GUARDRAILS.md +18 -0
- package/dist/workspace-defaults/files/MEMORY_GUIDE.md +32 -4
- package/dist/workspace-defaults/files/SPACE.md +34 -19
- package/dist/workspace-defaults/files/TOOLS.md +30 -0
- package/dist/workspace-defaults/files/skills/document-composer/SKILL.md +154 -0
- package/dist/workspace-defaults/files/skills/document-composer/references/authoring-rules.md +101 -0
- package/package.json +2 -1
- package/dist/plugins/company-brain/README.md +0 -37
- package/dist/plugins/company-brain/package.json +0 -33
- package/dist/plugins/company-brain/runtime/cognee/Dockerfile +0 -11
- package/dist/plugins/company-brain/smoke/cognee-managed-app-smoke.mjs +0 -458
- package/dist/plugins/company-brain/smoke/cognee-memory-cutover-smoke.mjs +0 -539
- package/dist/plugins/company-brain/smoke/company-brain-context-engine-smoke.mjs +0 -312
- package/dist/plugins/company-brain/smoke/company-brain-operations-smoke.mjs +0 -449
- package/dist/plugins/company-brain/smoke/company-brain-plugin-smoke.mjs +0 -655
- package/dist/plugins/company-brain/smoke/hindsight-memory-isolation-smoke.mjs +0 -474
- package/dist/plugins/company-brain/src/api/cognee-client.ts +0 -896
- package/dist/plugins/company-brain/src/api/cognee-cluster-identity.ts +0 -49
- package/dist/plugins/company-brain/src/api/cognee-memory-scope.ts +0 -84
- package/dist/plugins/company-brain/src/api/context-engine-provider.ts +0 -888
- package/dist/plugins/company-brain/src/api/migration.ts +0 -665
- package/dist/plugins/company-brain/src/deployment/cognee-managed-app.ts +0 -396
- package/dist/plugins/company-brain/src/index.ts +0 -62
- package/dist/plugins/company-brain/src/manifest.ts +0 -112
- package/dist/plugins/company-brain/terraform/cognee/README.md +0 -263
- package/dist/plugins/company-brain/terraform/cognee/main.tf +0 -766
- package/dist/plugins/company-brain/terraform/cognee/outputs.tf +0 -144
- package/dist/plugins/company-brain/terraform/cognee/variables.tf +0 -402
- package/dist/plugins/company-brain/test/api/cognee-client.test.ts +0 -562
- package/dist/plugins/company-brain/test/api/cognee-memory-scope.test.ts +0 -74
- package/dist/plugins/company-brain/test/api/context-engine-provider.test.ts +0 -447
- package/dist/plugins/company-brain/test/api/migration.test.ts +0 -465
- package/dist/plugins/company-brain/test/manifest.test.ts +0 -95
- 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:*",
|
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
verifyPluginCatalog,
|
|
14
14
|
type SignedPluginCatalogDocument,
|
|
15
15
|
} from "../catalog";
|
|
16
|
-
import {
|
|
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: [
|
|
99
|
+
manifests: [n8nManifest, lastmileManifest],
|
|
99
100
|
});
|
|
100
101
|
|
|
101
102
|
expect(catalog.plugins.map((plugin) => plugin.displayName)).toEqual([
|
|
102
103
|
"LastMile",
|
|
103
|
-
"
|
|
104
|
+
"n8n",
|
|
104
105
|
]);
|
|
105
106
|
});
|
|
106
107
|
|
|
107
108
|
it("preserves premium metadata on catalog entries", () => {
|
|
108
|
-
const
|
|
109
|
-
|
|
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: "
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
|
@@ -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
|
}
|