create-flowdular 0.2.4 → 0.2.6
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/README.md +11 -0
- package/agent-template/.agents/skills/agent-tool-design/SKILL.md +203 -0
- package/agent-template/.agents/skills/auth-security-review/SKILL.md +90 -0
- package/agent-template/.agents/skills/auto-review/SKILL.md +103 -0
- package/agent-template/.agents/skills/bug-hunt/SKILL.md +104 -0
- package/agent-template/.agents/skills/business-agent-design/SKILL.md +182 -0
- package/agent-template/.agents/skills/cli-extension/SKILL.md +108 -0
- package/agent-template/.agents/skills/core-extend/SKILL.md +99 -0
- package/agent-template/.agents/skills/database-adapter/SKILL.md +198 -0
- package/agent-template/.agents/skills/database-adapter/references/first-run-and-matrix.md +105 -0
- package/agent-template/.agents/skills/migration-authoring/SKILL.md +161 -0
- package/agent-template/.agents/skills/module-new/SKILL.md +171 -0
- package/agent-template/.agents/skills/module-update/SKILL.md +91 -0
- package/agent-template/.agents/skills/perf-audit/SKILL.md +98 -0
- package/agent-template/.agents/skills/release-eject-pr/SKILL.md +107 -0
- package/agent-template/.agents/skills/spec-approval/SKILL.md +106 -0
- package/agent-template/.agents/skills/test-hardening/SKILL.md +79 -0
- package/agent-template/.agents/skills/translations-i18n/SKILL.md +78 -0
- package/agent-template/.agents/skills/ux-design/SKILL.md +92 -0
- package/agent-template/.agents/skills/variables/SKILL.md +156 -0
- package/agent-template/.agents/skills/workflow-development/SKILL.md +192 -0
- package/agent-template/.ai/README.md +62 -0
- package/agent-template/.ai/agents/README.md +27 -0
- package/agent-template/.ai/agents/module-executor.md +36 -0
- package/agent-template/.ai/agents/reviewer.md +23 -0
- package/agent-template/.ai/agents/sandbox/agentic-engineer.md +31 -0
- package/agent-template/.ai/agents/sandbox/backend-engineer.md +36 -0
- package/agent-template/.ai/agents/sandbox/business-manager.md +23 -0
- package/agent-template/.ai/agents/sandbox/frontend-engineer.md +27 -0
- package/agent-template/.ai/agents/sandbox/ux-designer.md +23 -0
- package/agent-template/.ai/agents/spec-author.md +29 -0
- package/agent-template/.ai/blueprints/add-migration/README.md +5 -0
- package/agent-template/.ai/blueprints/add-migration/allowed-paths.yaml +23 -0
- package/agent-template/.ai/blueprints/add-migration/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/add-migration/examples/invalid/input-destructive.json +6 -0
- package/agent-template/.ai/blueprints/add-migration/examples/invalid/plan-unnumbered-file.json +9 -0
- package/agent-template/.ai/blueprints/add-migration/examples/valid/input.json +6 -0
- package/agent-template/.ai/blueprints/add-migration/examples/valid/plan.json +9 -0
- package/agent-template/.ai/blueprints/add-migration/gates.yaml +30 -0
- package/agent-template/.ai/blueprints/add-migration/input.schema.json +23 -0
- package/agent-template/.ai/blueprints/add-migration/plan.schema.json +54 -0
- package/agent-template/.ai/blueprints/add-migration/required-files.yaml +18 -0
- package/agent-template/.ai/blueprints/add-migration/spec-requirements.yaml +13 -0
- package/agent-template/.ai/blueprints/add-migration/steps.yaml +62 -0
- package/agent-template/.ai/blueprints/author-spec/README.md +5 -0
- package/agent-template/.ai/blueprints/author-spec/allowed-paths.yaml +7 -0
- package/agent-template/.ai/blueprints/author-spec/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/author-spec/examples/invalid/input-missing-outcome.json +5 -0
- package/agent-template/.ai/blueprints/author-spec/examples/valid/input.json +6 -0
- package/agent-template/.ai/blueprints/author-spec/gates.yaml +13 -0
- package/agent-template/.ai/blueprints/author-spec/input.schema.json +20 -0
- package/agent-template/.ai/blueprints/author-spec/plan.schema.json +14 -0
- package/agent-template/.ai/blueprints/author-spec/required-files.yaml +6 -0
- package/agent-template/.ai/blueprints/author-spec/spec-requirements.yaml +35 -0
- package/agent-template/.ai/blueprints/author-spec/steps.yaml +28 -0
- package/agent-template/.ai/blueprints/author-spec/templates/module.yaml +45 -0
- package/agent-template/.ai/blueprints/bug-fix/README.md +5 -0
- package/agent-template/.ai/blueprints/bug-fix/allowed-paths.yaml +29 -0
- package/agent-template/.ai/blueprints/bug-fix/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/bug-fix/examples/invalid/input-no-symptom.json +4 -0
- package/agent-template/.ai/blueprints/bug-fix/examples/invalid/plan-no-test.json +8 -0
- package/agent-template/.ai/blueprints/bug-fix/examples/valid/input.json +6 -0
- package/agent-template/.ai/blueprints/bug-fix/examples/valid/plan.json +8 -0
- package/agent-template/.ai/blueprints/bug-fix/gates.yaml +30 -0
- package/agent-template/.ai/blueprints/bug-fix/input.schema.json +25 -0
- package/agent-template/.ai/blueprints/bug-fix/plan.schema.json +53 -0
- package/agent-template/.ai/blueprints/bug-fix/required-files.yaml +7 -0
- package/agent-template/.ai/blueprints/bug-fix/spec-requirements.yaml +7 -0
- package/agent-template/.ai/blueprints/bug-fix/steps.yaml +51 -0
- package/agent-template/.ai/blueprints/core-extend/README.md +5 -0
- package/agent-template/.ai/blueprints/core-extend/allowed-paths.yaml +49 -0
- package/agent-template/.ai/blueprints/core-extend/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/core-extend/examples/invalid/input-unknown-package.json +5 -0
- package/agent-template/.ai/blueprints/core-extend/examples/invalid/plan-missing-gates.json +7 -0
- package/agent-template/.ai/blueprints/core-extend/examples/valid/input.json +6 -0
- package/agent-template/.ai/blueprints/core-extend/examples/valid/plan.json +10 -0
- package/agent-template/.ai/blueprints/core-extend/gates.yaml +16 -0
- package/agent-template/.ai/blueprints/core-extend/input.schema.json +54 -0
- package/agent-template/.ai/blueprints/core-extend/plan.schema.json +39 -0
- package/agent-template/.ai/blueprints/core-extend/required-files.yaml +36 -0
- package/agent-template/.ai/blueprints/core-extend/spec-requirements.yaml +17 -0
- package/agent-template/.ai/blueprints/core-extend/steps.yaml +54 -0
- package/agent-template/.ai/blueprints/edit-module/README.md +9 -0
- package/agent-template/.ai/blueprints/edit-module/allowed-paths.yaml +27 -0
- package/agent-template/.ai/blueprints/edit-module/blueprint.json +20 -0
- package/agent-template/.ai/blueprints/edit-module/examples/invalid/input-unknown-change.json +5 -0
- package/agent-template/.ai/blueprints/edit-module/examples/invalid/plan-touches-platform.json +15 -0
- package/agent-template/.ai/blueprints/edit-module/examples/valid/input.json +5 -0
- package/agent-template/.ai/blueprints/edit-module/examples/valid/plan.json +25 -0
- package/agent-template/.ai/blueprints/edit-module/gates.yaml +30 -0
- package/agent-template/.ai/blueprints/edit-module/input.schema.json +31 -0
- package/agent-template/.ai/blueprints/edit-module/plan.schema.json +65 -0
- package/agent-template/.ai/blueprints/edit-module/required-files.yaml +80 -0
- package/agent-template/.ai/blueprints/edit-module/spec-requirements.yaml +15 -0
- package/agent-template/.ai/blueprints/edit-module/steps.yaml +115 -0
- package/agent-template/.ai/blueprints/new-module/README.md +7 -0
- package/agent-template/.ai/blueprints/new-module/allowed-paths.yaml +27 -0
- package/agent-template/.ai/blueprints/new-module/blueprint.json +20 -0
- package/agent-template/.ai/blueprints/new-module/examples/invalid/input-spec-outside-modules.json +4 -0
- package/agent-template/.ai/blueprints/new-module/examples/invalid/plan-unknown-gate.json +8 -0
- package/agent-template/.ai/blueprints/new-module/examples/valid/input.json +5 -0
- package/agent-template/.ai/blueprints/new-module/examples/valid/plan.json +22 -0
- package/agent-template/.ai/blueprints/new-module/gates.yaml +30 -0
- package/agent-template/.ai/blueprints/new-module/input.schema.json +21 -0
- package/agent-template/.ai/blueprints/new-module/plan.schema.json +58 -0
- package/agent-template/.ai/blueprints/new-module/required-files.yaml +73 -0
- package/agent-template/.ai/blueprints/new-module/spec-requirements.yaml +30 -0
- package/agent-template/.ai/blueprints/new-module/steps.yaml +138 -0
- package/agent-template/.ai/blueprints/release/README.md +5 -0
- package/agent-template/.ai/blueprints/release/allowed-paths.yaml +19 -0
- package/agent-template/.ai/blueprints/release/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/release/examples/invalid/input-bad-version.json +4 -0
- package/agent-template/.ai/blueprints/release/examples/invalid/plan-bad-branch.json +7 -0
- package/agent-template/.ai/blueprints/release/examples/valid/input.json +5 -0
- package/agent-template/.ai/blueprints/release/examples/valid/plan.json +20 -0
- package/agent-template/.ai/blueprints/release/gates.yaml +20 -0
- package/agent-template/.ai/blueprints/release/input.schema.json +24 -0
- package/agent-template/.ai/blueprints/release/plan.schema.json +46 -0
- package/agent-template/.ai/blueprints/release/required-files.yaml +19 -0
- package/agent-template/.ai/blueprints/release/spec-requirements.yaml +8 -0
- package/agent-template/.ai/blueprints/release/steps.yaml +47 -0
- package/agent-template/.ai/blueprints/security-review/README.md +5 -0
- package/agent-template/.ai/blueprints/security-review/allowed-paths.yaml +6 -0
- package/agent-template/.ai/blueprints/security-review/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/security-review/examples/invalid/input-unknown-kind.json +4 -0
- package/agent-template/.ai/blueprints/security-review/examples/invalid/plan-finding-without-scenario.json +14 -0
- package/agent-template/.ai/blueprints/security-review/examples/valid/input.json +4 -0
- package/agent-template/.ai/blueprints/security-review/examples/valid/plan.json +19 -0
- package/agent-template/.ai/blueprints/security-review/gates.yaml +22 -0
- package/agent-template/.ai/blueprints/security-review/input.schema.json +20 -0
- package/agent-template/.ai/blueprints/security-review/plan.schema.json +65 -0
- package/agent-template/.ai/blueprints/security-review/required-files.yaml +6 -0
- package/agent-template/.ai/blueprints/security-review/spec-requirements.yaml +9 -0
- package/agent-template/.ai/blueprints/security-review/steps.yaml +38 -0
- package/agent-template/.ai/examples/README.md +8 -0
- package/agent-template/.ai/examples/bad/client-imports-server/README.md +20 -0
- package/agent-template/.ai/examples/bad/client-imports-server/api.ts +12 -0
- package/agent-template/.ai/examples/bad/missing-acl/README.md +23 -0
- package/agent-template/.ai/examples/bad/missing-acl/endpoints.ts +12 -0
- package/agent-template/.ai/examples/bad/tenant-from-body/README.md +19 -0
- package/agent-template/.ai/examples/bad/tenant-from-body/endpoints.ts +33 -0
- package/agent-template/.ai/examples/client-contribution/CustomerListView.tsrx +34 -0
- package/agent-template/.ai/examples/client-contribution/README.md +11 -0
- package/agent-template/.ai/examples/client-contribution/contribution.tsrx +48 -0
- package/agent-template/.ai/examples/client-contribution/index.ts +20 -0
- package/agent-template/.ai/examples/client-contribution/permissions.ts +8 -0
- package/agent-template/.ai/examples/customer-cli-extension/README.md +14 -0
- package/agent-template/.ai/examples/customer-cli-extension/commands.json +17 -0
- package/agent-template/.ai/examples/customer-cli-extension/index.ts +36 -0
- package/agent-template/.ai/examples/module-create/task-packet.json +11 -0
- package/agent-template/.ai/guides/application-development.md +97 -0
- package/agent-template/.ai/policies/capabilities.yaml +164 -0
- package/agent-template/.ai/policies/model-routing.yaml +72 -0
- package/agent-template/.ai/policies/path-ownership.yaml +65 -0
- package/agent-template/.ai/policies/task-budgets.yaml +37 -0
- package/agent-template/.ai/references/catalog/LICENSE +21 -0
- package/agent-template/.ai/references/catalog/migrations/0001_catalog_core.down.sql +2 -0
- package/agent-template/.ai/references/catalog/migrations/0001_catalog_core.up.sql +21 -0
- package/agent-template/.ai/references/catalog/migrations/0002_catalog_history.down.sql +3 -0
- package/agent-template/.ai/references/catalog/migrations/0002_catalog_history.up.sql +20 -0
- package/agent-template/.ai/references/catalog/migrations/0003_catalog_history_service_actors.down.sql +3 -0
- package/agent-template/.ai/references/catalog/migrations/0003_catalog_history_service_actors.up.sql +36 -0
- package/agent-template/.ai/references/catalog/migrations/0004_catalog_idempotency_ledger.down.sql +3 -0
- package/agent-template/.ai/references/catalog/migrations/0004_catalog_idempotency_ledger.up.sql +19 -0
- package/agent-template/.ai/references/catalog/migrations/README.md +3 -0
- package/agent-template/.ai/references/catalog/module.json +27 -0
- package/agent-template/.ai/references/catalog/package.json +49 -0
- package/agent-template/.ai/references/catalog/spec/module.yaml +86 -0
- package/agent-template/.ai/references/catalog/src/acl/permissions.ts +6 -0
- package/agent-template/.ai/references/catalog/src/agent/tools.ts +164 -0
- package/agent-template/.ai/references/catalog/src/api/endpoints.ts +243 -0
- package/agent-template/.ai/references/catalog/src/client/CatalogHistoryDrawer.tsrx +123 -0
- package/agent-template/.ai/references/catalog/src/client/CatalogItemForm.tsrx +190 -0
- package/agent-template/.ai/references/catalog/src/client/CatalogView.tsrx +473 -0
- package/agent-template/.ai/references/catalog/src/client/api.ts +111 -0
- package/agent-template/.ai/references/catalog/src/client/contribution.tsrx +61 -0
- package/agent-template/.ai/references/catalog/src/client/index.ts +18 -0
- package/agent-template/.ai/references/catalog/src/client/navigation-copy.ts +9 -0
- package/agent-template/.ai/references/catalog/src/client/state.ts +24 -0
- package/agent-template/.ai/references/catalog/src/domain/types.ts +32 -0
- package/agent-template/.ai/references/catalog/src/domain/variables.ts +111 -0
- package/agent-template/.ai/references/catalog/src/index.ts +31 -0
- package/agent-template/.ai/references/catalog/src/platform.ts +35 -0
- package/agent-template/.ai/references/catalog/src/server/index.ts +4 -0
- package/agent-template/.ai/references/catalog/src/server/runtime.ts +86 -0
- package/agent-template/.ai/references/catalog/src/services/catalog-service.ts +306 -0
- package/agent-template/.ai/references/catalog/src/services/database-repository.ts +440 -0
- package/agent-template/.ai/references/catalog/src/services/index.ts +4 -0
- package/agent-template/.ai/references/catalog/src/services/migration.ts +171 -0
- package/agent-template/.ai/references/catalog/src/services/repository.ts +36 -0
- package/agent-template/.ai/references/catalog/src/services/target-idempotency.ts +59 -0
- package/agent-template/.ai/references/catalog/tests/agent-tools.test.ts +277 -0
- package/agent-template/.ai/references/catalog/tests/endpoints.test.ts +320 -0
- package/agent-template/.ai/references/catalog/tests/idempotency.test.ts +297 -0
- package/agent-template/.ai/references/catalog/tests/migrations.test.ts +149 -0
- package/agent-template/.ai/references/catalog/tests/module.test.ts +271 -0
- package/agent-template/.ai/references/catalog/tests/support/database.ts +76 -0
- package/agent-template/.ai/references/catalog/translations/en.json +101 -0
- package/agent-template/.ai/references/catalog/translations/pl.json +101 -0
- package/agent-template/.ai/references/catalog/tsconfig.json +15 -0
- package/agent-template/.ai/references/catalog/vitest.config.ts +16 -0
- package/agent-template/.ai/references/catalog.provenance.json +55 -0
- package/agent-template/.ai/rules/flowdular.md +86 -0
- package/agent-template/.ai/skills/README.md +36 -0
- package/agent-template/.ai/skills/agent-tool-design/SKILL.md +209 -0
- package/agent-template/.ai/skills/auth-security-review/SKILL.md +96 -0
- package/agent-template/.ai/skills/auto-review/SKILL.md +112 -0
- package/agent-template/.ai/skills/bug-hunt/SKILL.md +110 -0
- package/agent-template/.ai/skills/business-agent-design/SKILL.md +188 -0
- package/agent-template/.ai/skills/cli-extension/SKILL.md +114 -0
- package/agent-template/.ai/skills/core-extend/SKILL.md +104 -0
- package/agent-template/.ai/skills/database-adapter/SKILL.md +204 -0
- package/agent-template/.ai/skills/database-adapter/references/first-run-and-matrix.md +105 -0
- package/agent-template/.ai/skills/migration-authoring/SKILL.md +167 -0
- package/agent-template/.ai/skills/module-new/SKILL.md +180 -0
- package/agent-template/.ai/skills/module-update/SKILL.md +100 -0
- package/agent-template/.ai/skills/perf-audit/SKILL.md +105 -0
- package/agent-template/.ai/skills/release-eject-pr/SKILL.md +113 -0
- package/agent-template/.ai/skills/spec-approval/SKILL.md +112 -0
- package/agent-template/.ai/skills/test-hardening/SKILL.md +86 -0
- package/agent-template/.ai/skills/translations-i18n/SKILL.md +85 -0
- package/agent-template/.ai/skills/ux-design/SKILL.md +97 -0
- package/agent-template/.ai/skills/variables/SKILL.md +164 -0
- package/agent-template/.ai/skills/workflow-development/SKILL.md +199 -0
- package/agent-template/.claude/skills/agent-tool-design/SKILL.md +203 -0
- package/agent-template/.claude/skills/auth-security-review/SKILL.md +90 -0
- package/agent-template/.claude/skills/auto-review/SKILL.md +103 -0
- package/agent-template/.claude/skills/bug-hunt/SKILL.md +104 -0
- package/agent-template/.claude/skills/business-agent-design/SKILL.md +182 -0
- package/agent-template/.claude/skills/cli-extension/SKILL.md +108 -0
- package/agent-template/.claude/skills/core-extend/SKILL.md +99 -0
- package/agent-template/.claude/skills/database-adapter/SKILL.md +198 -0
- package/agent-template/.claude/skills/database-adapter/references/first-run-and-matrix.md +105 -0
- package/agent-template/.claude/skills/migration-authoring/SKILL.md +161 -0
- package/agent-template/.claude/skills/module-new/SKILL.md +171 -0
- package/agent-template/.claude/skills/module-update/SKILL.md +91 -0
- package/agent-template/.claude/skills/perf-audit/SKILL.md +98 -0
- package/agent-template/.claude/skills/release-eject-pr/SKILL.md +107 -0
- package/agent-template/.claude/skills/spec-approval/SKILL.md +106 -0
- package/agent-template/.claude/skills/test-hardening/SKILL.md +79 -0
- package/agent-template/.claude/skills/translations-i18n/SKILL.md +78 -0
- package/agent-template/.claude/skills/ux-design/SKILL.md +92 -0
- package/agent-template/.claude/skills/variables/SKILL.md +156 -0
- package/agent-template/.claude/skills/workflow-development/SKILL.md +192 -0
- package/agent-template/AGENTS.md +77 -0
- package/agent-template/CLAUDE.md +77 -0
- package/agent-template/docs/adr/0001-development-reload.md +16 -0
- package/agent-template/docs/adr/0002-durable-agent-execution.md +21 -0
- package/agent-template/docs/adr/0003-module-settings.md +22 -0
- package/agent-template/docs/adr/0004-enterprise-access-and-audit.md +36 -0
- package/agent-template/docs/adr/0005-sandbox-runtime-and-coding-agents.md +81 -0
- package/agent-template/docs/adr/0006-agentic-workflows.md +1702 -0
- package/agent-template/docs/adr/0007-module-owned-agents.md +429 -0
- package/agent-template/docs/adr/0008-database-adapter-contract.md +90 -0
- package/agent-template/docs/agent-contract.md +45 -0
- package/agent-template/docs/configuration.md +122 -0
- package/agent-template/docs/database-adapters.md +346 -0
- package/agent-template/docs/design-system.md +217 -0
- package/agent-template/docs/modules.md +146 -0
- package/agent-template/platform/scripts/build.mjs +38 -0
- package/agent-template/rulesync.jsonc +11 -0
- package/dist/bin.js +40 -2
- package/package.json +3 -2
- package/template/default/.prettierignore +9 -0
- package/template/default/README.md +12 -0
- package/template/default/flowdular.json +3 -3
- package/template/default/modules/example/package.json +2 -2
- package/template/default/package.json +6 -2
- package/template/default/platform/octane.config.ts +17 -6
- package/template/default/platform/package.json +3 -2
- package/template/default/pnpm-workspace.yaml +1 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: variables
|
|
3
|
+
description: >-
|
|
4
|
+
Build variable-aware fields and templates on the {{ }} contract, the scope
|
|
5
|
+
mask, and server-side resolution, with agents.core as the worked example.
|
|
6
|
+
roles:
|
|
7
|
+
- frontend-engineer
|
|
8
|
+
- ux-designer
|
|
9
|
+
- agentic-engineer
|
|
10
|
+
- backend-engineer
|
|
11
|
+
- module-executor
|
|
12
|
+
when: A field must let a value embed {{ variable }} tokens filled from other fields, the request context, or another module.
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Variables (templating and linked fields)
|
|
16
|
+
|
|
17
|
+
A variable field lets a stored value embed `{{ key }}` tokens that are filled at
|
|
18
|
+
run time from context or another module's data. The contract is pure and lives
|
|
19
|
+
in `@flowdular/sdk/contracts` (`packages/contracts/src/variables.ts`); the fields are
|
|
20
|
+
presentational primitives in `@flowdular/sdk/ui`; resolution happens on the server
|
|
21
|
+
before the consumer sees the text. `agents.core` is the worked example: an agent
|
|
22
|
+
author writes instructions as a template and the run snapshot carries the
|
|
23
|
+
resolved text.
|
|
24
|
+
|
|
25
|
+
## The `{{ }}` contract
|
|
26
|
+
|
|
27
|
+
`VariableDefinition { key, label, kind, scope?, sample?, description? }` is one
|
|
28
|
+
offerable variable. `kind` is `text | number | date | money | identifier`.
|
|
29
|
+
`key` is a dot path whose segments start lowercase and may continue in
|
|
30
|
+
camelCase (`context.user.displayName`), matched by `VARIABLE_KEY_PATTERN` /
|
|
31
|
+
`isVariableKey`.
|
|
32
|
+
|
|
33
|
+
- `extractVariables(template)`: the distinct trimmed `{{ key }}` tokens, in
|
|
34
|
+
first-seen order.
|
|
35
|
+
- `validateTemplate(template, available, allowedScopes?)`: `{ unknown, forbidden }`.
|
|
36
|
+
`unknown` are tokens not in `available`; `forbidden` are tokens whose def
|
|
37
|
+
declares a `scope` not present in `allowedScopes` (omit `allowedScopes` to skip
|
|
38
|
+
the scope check).
|
|
39
|
+
- `resolveTemplate(template, values, { onMissing })`: substitutes each
|
|
40
|
+
`{{ key }}` with `values[key]`. `onMissing` is `keep` (default, leave the token
|
|
41
|
+
verbatim) or `blank`.
|
|
42
|
+
- `tokenizeTemplate(template)`: the segments the UI overlay highlights; the
|
|
43
|
+
`text` fields concatenate back to the exact input.
|
|
44
|
+
|
|
45
|
+
Rules the resolver guarantees: tokens are `{{ key }}` with optional inner spaces;
|
|
46
|
+
`\{{` outputs a literal `{{`; substitution is a single pass, so a value that
|
|
47
|
+
itself looks like a token is emitted verbatim (never recursive); only own,
|
|
48
|
+
string keys resolve, so prototype keys (`__proto__`, `toString`) never resolve.
|
|
49
|
+
No eval, no expressions, only key substitution.
|
|
50
|
+
|
|
51
|
+
## The scope mask
|
|
52
|
+
|
|
53
|
+
`VariableDefinition.scope` is the permission required to read the source. A
|
|
54
|
+
variable is offered and resolved only when the principal holds that scope:
|
|
55
|
+
|
|
56
|
+
- The UI fields never fetch and never check scopes. The caller passes
|
|
57
|
+
`variables` already filtered to what this principal may use, so a variable the
|
|
58
|
+
principal cannot read is simply absent from the menu and highlights as an error
|
|
59
|
+
pill if typed.
|
|
60
|
+
- On the server, filter the definition list by the principal's scopes before
|
|
61
|
+
building `values`, or call `validateTemplate(..., allowedScopes)` and refuse a
|
|
62
|
+
template whose `forbidden` is non-empty. A scope-less variable
|
|
63
|
+
(`context.*`) is always allowed.
|
|
64
|
+
|
|
65
|
+
## The UI fields (`@flowdular/sdk/ui`)
|
|
66
|
+
|
|
67
|
+
`VariableTextarea` (multiline), `VariableInput` (single line), and
|
|
68
|
+
`VariableSelect` (one literal option or one variable token) are presentational.
|
|
69
|
+
All take `value`, `onInput`, `variables: readonly VariableDefinition[]`, optional
|
|
70
|
+
`sampleValues?: Record<string,string>`, required translated `label` (accessible
|
|
71
|
+
name), `name` (so a `FormData` submit still captures it), `required`, `disabled`,
|
|
72
|
+
and `error`. Input and textarea also require translated `insertLabel`,
|
|
73
|
+
`variablesLabel`, and `emptyLabel`; the shared primitive has no English copy to
|
|
74
|
+
fall back to. Select takes `options: readonly VariableSelectLiteralOption[]` and
|
|
75
|
+
requires translated `literalGroupLabel` and `variablesGroupLabel`. A caller also
|
|
76
|
+
localizes every `VariableDefinition.label` before passing the definitions, since
|
|
77
|
+
that label is visible in the picker.
|
|
78
|
+
|
|
79
|
+
- The `braces` affordance (a `{}` icon in `ICON_PATHS`) opens a menu of the
|
|
80
|
+
available variables with label, key, and current or sample value; picking one
|
|
81
|
+
inserts `{{ key }}` at the caret. Typing `{{` opens the same menu filtered by
|
|
82
|
+
what follows; ArrowUp/Down and Enter pick, Escape closes.
|
|
83
|
+
- Tokens are highlighted by an overlay layer (`tokenizeTemplate`) sitting behind
|
|
84
|
+
a transparent control, so `{{ key }}` reads as a pill while the real value
|
|
85
|
+
stays plain text; an unknown or forbidden token gets the error pill. All
|
|
86
|
+
color comes from tokens; measurement is client-only in an effect, so SSR is
|
|
87
|
+
safe. See `packages/ui/src/components/VariableField.tsrx` and its wrappers.
|
|
88
|
+
- `VariableSelect` stays a native `<select>`. Literal values and allowed
|
|
89
|
+
`{{ key }}` tokens are real `<option>` values, so keyboard navigation,
|
|
90
|
+
validation, disabled state, accessible naming, and `FormData` submission keep
|
|
91
|
+
browser semantics. Samples appear only in option labels. The component never
|
|
92
|
+
resolves the selected token.
|
|
93
|
+
|
|
94
|
+
Keep the fields presentational: the caller supplies `variables` and
|
|
95
|
+
`sampleValues`, the component never fetches.
|
|
96
|
+
|
|
97
|
+
The platform variable registry (`@flowdular/sdk/kernel`) registers definitions and
|
|
98
|
+
their execution-time resolvers. Reach the shared instance with
|
|
99
|
+
`platformVariableRegistry(context.capabilities)`. `list(scopes)` requires an
|
|
100
|
+
explicit permission snapshot and is the only
|
|
101
|
+
definition list a server sends to a field. `resolve(template, request)` takes a
|
|
102
|
+
trusted tenant id, actor, immutable permission snapshot, `AbortSignal`, explicit
|
|
103
|
+
record bindings and optional values owned by the consumer. It validates the
|
|
104
|
+
whole template before invoking a source. An unknown token, missing scope,
|
|
105
|
+
missing binding, aborted request, unavailable record, or source failure is a
|
|
106
|
+
refusal. Source exceptions are replaced with a generic error so SQL, provider,
|
|
107
|
+
and record details do not cross the module boundary.
|
|
108
|
+
|
|
109
|
+
The source declares `requiredBindings` per variable. For example, `party.name`
|
|
110
|
+
requires `partyId`; the resolver receives that id explicitly and asks the
|
|
111
|
+
parties public capability or read tool under `request.tenantId`. It never infers
|
|
112
|
+
a record from browser state and never reads the parties database. Local form
|
|
113
|
+
values go in `request.values`, while cross-module values must come from the
|
|
114
|
+
registered resolver. The resolved text is returned to the server consumer only;
|
|
115
|
+
the raw template remains stored.
|
|
116
|
+
|
|
117
|
+
## Server-side resolution rule
|
|
118
|
+
|
|
119
|
+
Resolve the template before the consumer sees it, and keep the raw template
|
|
120
|
+
stored. The stored record keeps the `{{ }}` template; the run or send snapshot
|
|
121
|
+
carries the resolved text. Never resolve in the client and never store the
|
|
122
|
+
resolved text back onto the definition.
|
|
123
|
+
|
|
124
|
+
Worked example in `agents.core`:
|
|
125
|
+
|
|
126
|
+
- `modules/agents/src/domain/context-variables.ts` declares
|
|
127
|
+
`AGENT_CONTEXT_VARIABLES` (`context.tenantName`, `context.today`,
|
|
128
|
+
`context.user.displayName`, `context.user.email`, all scope-less) and
|
|
129
|
+
`agentContextValues(input)` that builds their values from the run's
|
|
130
|
+
tenant/principal/date.
|
|
131
|
+
- `AgentService.enqueueRun` (`services/agent-service.ts`) resolves
|
|
132
|
+
`agent.instructions` with `resolveTemplate` against those values before it
|
|
133
|
+
builds the instruction snapshot; the stored definition is untouched. The
|
|
134
|
+
endpoint (`api/endpoints.ts`) supplies the tenant name and principal from the
|
|
135
|
+
request; `today` comes from the queue timestamp.
|
|
136
|
+
- `AgentDefinitionForm.tsrx` feeds `VariableTextarea` the context variable list
|
|
137
|
+
and sample values, so an author gets the menu and highlighting.
|
|
138
|
+
|
|
139
|
+
## Adding a variable source via a capability or tool
|
|
140
|
+
|
|
141
|
+
Business-data variables (for example `{{ party.name }}`) resolve through a
|
|
142
|
+
public capability or an agent read tool owned by the source module:
|
|
143
|
+
|
|
144
|
+
1. Declare the `VariableDefinition` with `scope` equal to the source tool's
|
|
145
|
+
`requiredPermissions` (`AgentTool` in `packages/harness/src/runtime.ts`). The
|
|
146
|
+
tool's permission is the mask: one source of truth, no parallel table.
|
|
147
|
+
2. Register the source on the shared registry during module composition. Declare
|
|
148
|
+
the record id in `requiredBindings`; do not accept a tenant binding.
|
|
149
|
+
3. Offer it only through `registry.list(principal.scopes)`. The UI fields take
|
|
150
|
+
this already-filtered list and never fetch.
|
|
151
|
+
4. Call `registry.resolve` on the server with the trusted tenant, actor,
|
|
152
|
+
permission snapshot, signal, and explicit bindings. The source invokes only
|
|
153
|
+
its owning public capability or read tool and maps the bounded result to
|
|
154
|
+
strings. The registry refuses before the source runs if the scope or binding
|
|
155
|
+
is absent.
|
|
156
|
+
|
|
157
|
+
`automations.core` is the live cross-module example. `agent.name` requires the
|
|
158
|
+
schedule's explicit `agentId`, and its resolver calls the `agents.run-queue`
|
|
159
|
+
capability with the active tenant. A binding containing an agent from another
|
|
160
|
+
tenant resolves to no record and the run is refused. The schedule keeps the raw
|
|
161
|
+
template; only the queued run receives the resolved input.
|
|
162
|
+
|
|
163
|
+
Register a new tool with the `agent-tool-design` skill; this skill covers only
|
|
164
|
+
how its output becomes a resolvable variable.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: workflow-development
|
|
3
|
+
description: >-
|
|
4
|
+
Build, publish, invoke, and test a workflows.core DAG through its typed graph
|
|
5
|
+
and public execution capability without bypassing agent, action, tenant, or
|
|
6
|
+
audit boundaries.
|
|
7
|
+
roles:
|
|
8
|
+
- agentic-engineer
|
|
9
|
+
- backend-engineer
|
|
10
|
+
- frontend-engineer
|
|
11
|
+
- module-executor
|
|
12
|
+
when: A brief asks for a workflow, pipeline, canvas node, workflow action, or a module feature that starts a workflow.
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Build and integrate an agentic workflow
|
|
16
|
+
|
|
17
|
+
`workflows.core` owns durable directed acyclic workflows. A workflow coordinates
|
|
18
|
+
pinned agent revisions, deterministic gates, schema validators, registered
|
|
19
|
+
module actions, data mappings, and terminal output. It does not own schedules or
|
|
20
|
+
webhook secrets. Those remain optional concerns of `automations.core`.
|
|
21
|
+
|
|
22
|
+
Read `docs/adr/0006-agentic-workflows.md`, the approved
|
|
23
|
+
`modules/workflows/spec/module.yaml`, and the contracts in
|
|
24
|
+
`modules/workflows/src/domain/types.ts` before changing a workflow surface.
|
|
25
|
+
|
|
26
|
+
## Pick the correct extension point
|
|
27
|
+
|
|
28
|
+
- A workflow definition belongs in `workflows.core` and is edited through its
|
|
29
|
+
API or canvas. Do not hardcode a tenant workflow in source.
|
|
30
|
+
- A business operation that a workflow may call is a versioned agent action.
|
|
31
|
+
Register it through the agents action catalog. If missing, implement it in a
|
|
32
|
+
separate `agent-tool-design` phase with permission, input, output, timeout,
|
|
33
|
+
idempotency and audit tests before returning to workflow integration.
|
|
34
|
+
- A business module that starts a workflow resolves
|
|
35
|
+
`workflows.execution.v1` from `context.capabilities`. It never imports a
|
|
36
|
+
workflow repository or database.
|
|
37
|
+
- A schedule or signed webhook remains in `automations.core`. Its optional
|
|
38
|
+
bridge invokes the workflow capability with a service actor and a separate
|
|
39
|
+
schedule or webhook origin.
|
|
40
|
+
- If the workflow module is absent, the capability registry returns `null`.
|
|
41
|
+
Hide an optional feature or return a clear stable refusal.
|
|
42
|
+
|
|
43
|
+
## Graph contract
|
|
44
|
+
|
|
45
|
+
Version one is a bounded DAG. The graph contains:
|
|
46
|
+
|
|
47
|
+
- `input`: accepts the invocation envelope.
|
|
48
|
+
- `agent`: calls one exact immutable agent revision and validates structured
|
|
49
|
+
output.
|
|
50
|
+
- `agent-decision`: produces one schema-valid `pass` or `fail` outcome.
|
|
51
|
+
- `gate`: evaluates the versioned allowlisted logic language.
|
|
52
|
+
- `validator`: validates an envelope against a pinned JSON schema.
|
|
53
|
+
- `action`: calls one exact registered action contract version.
|
|
54
|
+
- `merge`: waits for all declared incoming paths.
|
|
55
|
+
- `output`: settles the workflow with a typed result.
|
|
56
|
+
|
|
57
|
+
Every port names a schema. Every edge connects compatible ports. Mappings are
|
|
58
|
+
declarative literals, JSON pointer paths, or templates with explicit variable
|
|
59
|
+
bindings. Never add JavaScript, dynamic imports, shell commands, downloaded
|
|
60
|
+
code, arbitrary expressions, or hidden provider decisions to graph data.
|
|
61
|
+
|
|
62
|
+
The hard limits live in `WORKFLOW_LIMITS` in
|
|
63
|
+
`modules/workflows/src/domain/types.ts`. Validation must reject a cycle,
|
|
64
|
+
dangling edge, unreachable node, missing terminal output, incompatible port,
|
|
65
|
+
missing exact dependency, oversized graph, or unsupported action risk before
|
|
66
|
+
publication.
|
|
67
|
+
|
|
68
|
+
## Revisions and publication
|
|
69
|
+
|
|
70
|
+
Draft saves use optimistic concurrency through `expectedRevision`. A successful
|
|
71
|
+
save creates the next draft revision. A conflict never overwrites another
|
|
72
|
+
editor.
|
|
73
|
+
|
|
74
|
+
Publication:
|
|
75
|
+
|
|
76
|
+
1. Validates and compiles the graph.
|
|
77
|
+
2. Resolves exact agent revisions and exact action contract versions.
|
|
78
|
+
3. Rejects missing, archived, incompatible, external, or destructive
|
|
79
|
+
dependencies.
|
|
80
|
+
4. Stores an immutable content-addressed published revision.
|
|
81
|
+
5. Leaves earlier revisions and their run evidence unchanged.
|
|
82
|
+
|
|
83
|
+
Never replace a pinned dependency with its latest version during execution.
|
|
84
|
+
Editing after publication creates another draft.
|
|
85
|
+
|
|
86
|
+
## Execution modes
|
|
87
|
+
|
|
88
|
+
Use the smallest mode that proves the change:
|
|
89
|
+
|
|
90
|
+
- Dry-run validates a draft and returns issues, compiled order, references,
|
|
91
|
+
permissions, checksum, and limits. It creates no run and invokes nothing.
|
|
92
|
+
- Simulation persists a run history but uses fixtures for nondeterministic
|
|
93
|
+
nodes. It advances virtual time without sleeping and never calls a provider,
|
|
94
|
+
action, or business mutation.
|
|
95
|
+
- Live runs only published revisions. It may call pinned agents and approved
|
|
96
|
+
read or workspace-write actions under the initiating permission snapshot.
|
|
97
|
+
|
|
98
|
+
Do not disguise simulation as live execution. Do not use live mode to test an
|
|
99
|
+
invalid draft.
|
|
100
|
+
|
|
101
|
+
## Invoke a published workflow from a module
|
|
102
|
+
|
|
103
|
+
Resolve the capability at request or service call time, after platform
|
|
104
|
+
composition has completed:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import {
|
|
108
|
+
WORKFLOW_EXECUTION_CAPABILITY,
|
|
109
|
+
type WorkflowExecutionCapability,
|
|
110
|
+
} from '@flowdular/sdk/modules/workflows/server';
|
|
111
|
+
|
|
112
|
+
const workflows = context.capabilities.get<WorkflowExecutionCapability>(
|
|
113
|
+
WORKFLOW_EXECUTION_CAPABILITY,
|
|
114
|
+
);
|
|
115
|
+
if (!workflows) throw new ModuleError('WORKFLOWS_UNAVAILABLE');
|
|
116
|
+
|
|
117
|
+
const accepted = await workflows.enqueue(
|
|
118
|
+
{
|
|
119
|
+
workflowKey: 'catalog-enrichment',
|
|
120
|
+
input: { itemId },
|
|
121
|
+
idempotencyKey: `catalog:${itemId}:${version}`,
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
tenantId,
|
|
125
|
+
actor,
|
|
126
|
+
origin: {
|
|
127
|
+
kind: 'module',
|
|
128
|
+
moduleId: 'catalog.core',
|
|
129
|
+
operationId: 'catalog.enrichment.start',
|
|
130
|
+
},
|
|
131
|
+
permissionSnapshot,
|
|
132
|
+
},
|
|
133
|
+
);
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The module manifest declares `workflows.core` only when workflow support is a
|
|
137
|
+
required feature. An optional integration belongs in a small bridge module that
|
|
138
|
+
depends on both sides. Do not duplicate the capability interface locally to
|
|
139
|
+
avoid a dependency declaration.
|
|
140
|
+
|
|
141
|
+
Trusted context and business input are separate. Tenant, actor, origin, and
|
|
142
|
+
permission snapshot never come from the request body. The idempotency key is
|
|
143
|
+
stable for one logical operation. Reusing it with different input is a
|
|
144
|
+
conflict, not a second run.
|
|
145
|
+
|
|
146
|
+
## Actor and permission rules
|
|
147
|
+
|
|
148
|
+
- A user action uses the real user actor.
|
|
149
|
+
- A tool invoked by an agent uses the real agent actor and child run
|
|
150
|
+
correlation supplied by `AgentToolContext`.
|
|
151
|
+
- A schedule or webhook uses a service actor whose `configuredBy` is the real
|
|
152
|
+
user who configured it. Origin stays `schedule` or `webhook`.
|
|
153
|
+
- A workflow definition grants no scope. Live execution intersects the caller
|
|
154
|
+
snapshot with each node's agent or action requirements.
|
|
155
|
+
- A cross-tenant id, foreign cursor, missing scope, absent dependency, or
|
|
156
|
+
mismatched action version is refused before data or provider work.
|
|
157
|
+
|
|
158
|
+
## History, recovery, and cancellation
|
|
159
|
+
|
|
160
|
+
The browser observes execution. It never owns execution. Enqueue persists the
|
|
161
|
+
run before returning. Workers use leases and recover expired work from stored
|
|
162
|
+
node state, child ids, and stable side-effect idempotency keys.
|
|
163
|
+
|
|
164
|
+
Every transition appends an ordered schema-versioned event. Run, node,
|
|
165
|
+
attempt, and edge states are separate projections. `pass` and `fail` are normal
|
|
166
|
+
outcome ports, not technical statuses.
|
|
167
|
+
|
|
168
|
+
Cancellation is durable and cooperative. It prevents new nodes, asks the
|
|
169
|
+
current child agent or action to cancel, records whether it acknowledged, and
|
|
170
|
+
ignores late output for routing while keeping its safe evidence.
|
|
171
|
+
|
|
172
|
+
History responses contain redacted bounded evidence. They never expose provider
|
|
173
|
+
credentials, session tokens, hidden reasoning, encrypted payload blobs, or
|
|
174
|
+
unrestricted request bodies.
|
|
175
|
+
|
|
176
|
+
## Required tests
|
|
177
|
+
|
|
178
|
+
For a graph or runtime change, prove:
|
|
179
|
+
|
|
180
|
+
1. Deterministic compile order and rejection of cycles, dangling edges,
|
|
181
|
+
incompatible ports, unreachable nodes, and missing output.
|
|
182
|
+
2. Tenant isolation plus one unauthenticated and one unscoped refusal for every
|
|
183
|
+
endpoint group.
|
|
184
|
+
3. Exact agent and action revision refusal with no latest-version fallback.
|
|
185
|
+
4. Dry-run produces no run, provider call, action call, or business write.
|
|
186
|
+
5. Simulation uses fixtures and virtual duration with no real wait.
|
|
187
|
+
6. Live enqueue is idempotent and persists before acceptance.
|
|
188
|
+
7. Recovery before and after child enqueue does not duplicate work.
|
|
189
|
+
8. Retry records the chosen delay before waiting and reuses the side-effect
|
|
190
|
+
idempotency key.
|
|
191
|
+
9. Cancellation prevents downstream work and records late results safely.
|
|
192
|
+
10. Event replay, cursor binding, payload redaction, retention, usage, cost,
|
|
193
|
+
and audit hash-chain integrity.
|
|
194
|
+
11. The canvas shows validation, loading, empty, error, denied, simulation,
|
|
195
|
+
live, cancelled, and recovered states, including small-screen read mode.
|
|
196
|
+
|
|
197
|
+
Run the module typecheck and tests, `pnpm flowdular module validate`, then the
|
|
198
|
+
full `pnpm verify`. For a new module integration, update its approved spec and
|
|
199
|
+
move `specVersion`, `module.json` version, and package version together.
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-tool-design
|
|
3
|
+
description: >-
|
|
4
|
+
Register an API or CLI tool that lets business agents act on a module, with
|
|
5
|
+
the real harness, permission, idempotency, audit, and test contract.
|
|
6
|
+
---
|
|
7
|
+
# Design and register an agent tool
|
|
8
|
+
|
|
9
|
+
A module lets agents act on it by registering tools during composition. A tool
|
|
10
|
+
wraps one service operation, takes the tenant from the run context, reuses the
|
|
11
|
+
service validation, bounds its output, and declares the exact permission the
|
|
12
|
+
matching endpoint requires. `parties.core` and `catalog.core` are the reference
|
|
13
|
+
implementations.
|
|
14
|
+
|
|
15
|
+
This skill designs tools, not business-agent behavior. When a module should
|
|
16
|
+
also ship a ready business agent through `defineAgent()`, read
|
|
17
|
+
`business-agent-design` and register only the exact tools that agent needs.
|
|
18
|
+
|
|
19
|
+
## 1. The contract in code
|
|
20
|
+
|
|
21
|
+
- Composition: `PlatformServerContext` (`modules/auth/src/server/composition.ts`) carries `agentTools: PlatformToolRegistry` (`register(tools)`, `list()`; `packages/kernel/src/tool-registry.ts`; a duplicate tool id throws at boot), `settings: ModuleSettingsRuntime`, and `capabilities: PlatformCapabilityRegistry` (`register(id, service)`, `get(id)`, `has(id)`; `packages/kernel/src/capability-registry.ts`). `platform/octane.config.ts` creates the registries, passes them to every module's `createServerComposition`, declares each `settings`, owns each `dispose`, then calls each `start`.
|
|
22
|
+
- Ordering is a non-issue: `agents.core` (`modules/agents/src/platform.ts`) passes `tools: () => context.agentTools.list()` into `createAgentRuntime`, and the harness is built lazily in `start()`, which runs after every module has composed. Tools any module registers during its own compose are therefore visible, whatever the module order.
|
|
23
|
+
- Helpers: import `defineApiAgentTool` from `@flowdular/sdk/harness/tool-adapters` and the types `AgentTool`, `AgentToolContext` from `@flowdular/sdk/harness/runtime`. Both subpaths are free of the Vercel AI SDK; only the harness root (`@flowdular/sdk/harness`) and `@flowdular/sdk/modules/agents/server` pull it. `defineApiAgentTool` returns a frozen `AgentTool { id, transport: 'api', target, description, requiredPermissions, inputSchema?, execute }`. `defineCliAgentTool({ id, capability: { id, risk }, ... })` wraps a CLI capability and throws at definition time for `external` or `destructive` risk.
|
|
24
|
+
- Skills inside `agents.core` are tenant database records behind `agents.skills.*`, appended to agent instructions. They are unrelated to `.ai/skills/**`, which are files for coding agents.
|
|
25
|
+
- A read tool's output can also become a resolvable `{{ variable }}` for variable-aware fields: the tool's `requiredPermissions` is the variable's scope mask. Register a source on `platformVariableRegistry(context.capabilities)`, require an explicit record binding, and invoke the tool with the trusted tenant, actor permission snapshot, and signal. See the `variables` skill for the complete refusal contract.
|
|
26
|
+
- ADR 0002 (`docs/adr/0002-durable-agent-execution.md`): instructions are data, tools are registered by the composition, each tool records an endpoint id or a CLI capability id plus its required permissions, runs are durable with leases.
|
|
27
|
+
|
|
28
|
+
## 2. Tool shape
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
// src/agent/tools.ts
|
|
32
|
+
import { defineApiAgentTool } from '@flowdular/sdk/harness/tool-adapters';
|
|
33
|
+
import type { AgentTool } from '@flowdular/sdk/harness/runtime';
|
|
34
|
+
import { PARTY_PERMISSIONS } from '../acl/permissions.ts';
|
|
35
|
+
import type { PartyKind } from '../domain/types.ts';
|
|
36
|
+
import type { PartiesRuntime } from '../server/runtime.ts';
|
|
37
|
+
|
|
38
|
+
const MAX_TOOL_ROWS = 200;
|
|
39
|
+
|
|
40
|
+
export function partiesAgentTools(
|
|
41
|
+
runtime: PartiesRuntime,
|
|
42
|
+
): readonly AgentTool[] {
|
|
43
|
+
return [
|
|
44
|
+
defineApiAgentTool({
|
|
45
|
+
id: 'parties.customer.list', // ^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)+$
|
|
46
|
+
endpointId: 'parties.records.list', // the read endpoint this wraps
|
|
47
|
+
description: 'List customers and suppliers of the active tenant.',
|
|
48
|
+
requiredPermissions: [PARTY_PERMISSIONS.read],
|
|
49
|
+
inputSchema: {
|
|
50
|
+
type: 'object',
|
|
51
|
+
additionalProperties: false,
|
|
52
|
+
properties: {
|
|
53
|
+
status: { type: 'string', enum: ['active', 'archived'] },
|
|
54
|
+
query: { type: 'string', maxLength: 120 },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
execute: async (input, context) => {
|
|
58
|
+
const value = (input ?? {}) as Record<string, unknown>;
|
|
59
|
+
const query =
|
|
60
|
+
typeof value.query === 'string'
|
|
61
|
+
? value.query.trim().toLocaleLowerCase('en-US')
|
|
62
|
+
: '';
|
|
63
|
+
return runtime
|
|
64
|
+
.service()
|
|
65
|
+
.list(context.tenantId) // tenant from the run, never from input
|
|
66
|
+
.filter(
|
|
67
|
+
(party) =>
|
|
68
|
+
query === '' ||
|
|
69
|
+
party.name.toLocaleLowerCase('en-US').includes(query),
|
|
70
|
+
)
|
|
71
|
+
.slice(0, MAX_TOOL_ROWS); // bound output
|
|
72
|
+
},
|
|
73
|
+
}),
|
|
74
|
+
defineApiAgentTool({
|
|
75
|
+
id: 'parties.customer.create',
|
|
76
|
+
endpointId: 'parties.records.create',
|
|
77
|
+
description: 'Create a customer or supplier owned by the active tenant.',
|
|
78
|
+
requiredPermissions: [PARTY_PERMISSIONS.manage],
|
|
79
|
+
inputSchema: {
|
|
80
|
+
type: 'object',
|
|
81
|
+
additionalProperties: false,
|
|
82
|
+
required: ['name', 'kind'],
|
|
83
|
+
properties: {
|
|
84
|
+
name: { type: 'string', maxLength: 160 },
|
|
85
|
+
kind: { type: 'string', enum: ['customer', 'supplier', 'both'] },
|
|
86
|
+
vatId: { type: 'string', maxLength: 20 },
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
execute: async (input, context) => {
|
|
90
|
+
const value = (input ?? {}) as Record<string, unknown>;
|
|
91
|
+
// The service revalidates every field, so a tool cannot persist
|
|
92
|
+
// what the endpoint would reject.
|
|
93
|
+
return runtime.service().create(context.tenantId, {
|
|
94
|
+
name: String(value.name ?? ''),
|
|
95
|
+
kind: value.kind as PartyKind,
|
|
96
|
+
vatId: typeof value.vatId === 'string' ? value.vatId : null,
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
}),
|
|
100
|
+
];
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
// src/platform.ts, inside createServerComposition, before the return.
|
|
106
|
+
// register takes readonly unknown[], so no cast is needed.
|
|
107
|
+
context.agentTools.register(partiesAgentTools(runtime));
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Export the factory from `src/server/index.ts` so tests and the composition reach it.
|
|
111
|
+
|
|
112
|
+
Dependencies: `package.json` gets `"@flowdular/sdk/harness": "workspace:*"`. You do not import `@flowdular/sdk/modules/agents` and you do not add `agents.core` to `module.json`: registration flows through the platform-provided registry on the composition context, not an import of agents.core. Adding a scenario bumps `spec/module.yaml` `specVersion` and `module.json` `version` together.
|
|
113
|
+
|
|
114
|
+
## 3. Execution model you design against (`packages/harness/src/runtime.ts`, `AgentHarness.execute`)
|
|
115
|
+
|
|
116
|
+
- A tool is offered only when the agent definition lists it in `allowedTools`, the run's explicit `toolGrants` include it, every `requiredPermissions` entry is in the enqueue-time permission ceiling, and the initiating user still holds every permission when the tool is called. The auth runtime reauthorizes the trusted actor and tenant before every call. A stored snapshot never becomes future authority, newly granted scopes do not elevate an old run, and a service actor stays tool-less until its owning module supplies an explicit revocable policy. Otherwise the harness emits `tool.denied` and throws a stable refusal. `assertToolsRegistered` rejects a new run whose agent names an unregistered tool, while an exact idempotent retry returns its already persisted run before consulting mutable definitions or registries.
|
|
117
|
+
- `invokeTool` validates `input` against `inputSchema` first, using a small JSON Schema subset (`type`, `enum`, `required`, `properties`, `additionalProperties: false`, `items`; `packages/harness/src/tool-contract.ts` `validateToolInput`). A violation emits `tool.denied` (reason `TOOL_INPUT_INVALID`) before `execute` runs. The subset does not check string length or format, so the tool enforces those by passing input through the module service.
|
|
118
|
+
- `execute(input, { runId, tenantId, requestedBy, actor, idempotencyKey, permissions, signal })`. Take the tenant from `context.tenantId`. `permissions` is the intersection of the original ceiling and live authorization. `actor` describes the agent run for record history. `additionalProperties: false` already makes the harness refuse a stray `tenantId` field, but never read one anyway.
|
|
119
|
+
- A mutating tool with `idempotency: 'required'` is executable only after the target module implements a durable ledger and the definition declares `idempotencyProtection: 'target-ledger'`. The harness derives a stable key from the durable run id and deterministic tool-call ordinal. The target ledger binds `(tenant, tool id, key)` to a canonical input hash and the first result. A replay returns that result without another mutation; the same key with another tool or input fails closed. Provider tool-call ids are audit metadata only. Never add the declaration before the target migration, repository transaction, and crash-recovery test exist.
|
|
120
|
+
- Each call has a deadline (`tool.timeoutMs`, default 30 s, range 250 to 600000) and the harness caps serialized output at 32 KB (`boundToolOutput`), marking `truncated`. Still page or limit your rows so one call cannot dominate the run window.
|
|
121
|
+
- Events per call land in the run's persisted audit chain: `tool.started`, then `tool.completed` (metadata `tool`, `outputCharacters`, `truncated`) on success, `tool.failed` (reason) on error, or `tool.denied` (reason) when not granted or input-invalid.
|
|
122
|
+
- Runs are enqueued by `POST /api/agent-runs` behind `agents.runs.execute`, claimed by `AgentWorker` with a lease, observed through `GET /api/agent-runs` and the SSE stream. The registered tool ids surface in `GET /api/agents` `tools`, which the Agents form reads to build the allowed-tools grid. Never make a tool block on user input.
|
|
123
|
+
|
|
124
|
+
## 4. Deliverables for a module
|
|
125
|
+
|
|
126
|
+
1. Spec: one acceptance scenario per tool group (`PARTIES-AGENT-TOOL`): endpoint wrapped, permission required, validated input, what it refuses (no tenant input; a `manage` tool needs an explicit scenario; bounded output). Bump `specVersion` and `module.json` `version` together.
|
|
127
|
+
2. `src/agent/tools.ts`: every tool calls the module service through the runtime (never a repository, database handle, filesystem or shell), takes `context.tenantId`, and reuses the service validation.
|
|
128
|
+
3. The `register` line in `src/platform.ts`, and the factory exported from `src/server/index.ts`.
|
|
129
|
+
4. For a mutating tool, a numbered migration and target-side idempotency ledger, with the migration mirrored byte for byte in `src/services/migration.ts`. The service commits the business mutation and ledger result in one transaction.
|
|
130
|
+
5. Tests, below, including a replay of the complete harness execution with the same run id and no second business row.
|
|
131
|
+
6. In the Agents screen an agent definition lists the tool id in its allowed tools, the request carries it in `toolGrants`, and the initiating principal still holds the permission. Without all three the tool stays invisible to the model.
|
|
132
|
+
|
|
133
|
+
## 4b. Worked example
|
|
134
|
+
|
|
135
|
+
Spec scenario:
|
|
136
|
+
|
|
137
|
+
```yaml
|
|
138
|
+
acceptanceScenarios:
|
|
139
|
+
- id: PARTIES-AGENT-TOOL
|
|
140
|
+
given: An agent run holds parties.records.manage in its permission snapshot and the tool parties.customer.create in its grants.
|
|
141
|
+
when: The agent invokes the tool with a valid party, and a run without the manage scope invokes the same tool.
|
|
142
|
+
then: The scoped run creates a tenant-owned party from the run tenant and the harness denies the unscoped run before any write.
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Module-local test (`tests/agent-tools.test.ts`) drives `execute` directly. It does
|
|
146
|
+
not assert on `context.permissions`: RBAC is the harness's job, not the tool's.
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import type { AgentToolContext } from '@flowdular/sdk/harness/runtime';
|
|
150
|
+
import { describe, expect, it } from 'vitest';
|
|
151
|
+
import { partiesAgentTools } from '../src/agent/tools.ts';
|
|
152
|
+
import { createPartiesRuntime } from '../src/server/runtime.ts';
|
|
153
|
+
|
|
154
|
+
const context = (tenantId: string): AgentToolContext => ({
|
|
155
|
+
runId: 'run-1',
|
|
156
|
+
tenantId,
|
|
157
|
+
requestedBy: 'account-1',
|
|
158
|
+
permissions: new Set<string>(),
|
|
159
|
+
signal: new AbortController().signal,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('creates under the run tenant and ignores a tenant in the input', async () => {
|
|
163
|
+
const runtime = createPartiesRuntime({ databasePath: ':memory:' });
|
|
164
|
+
const [, create] = partiesAgentTools(runtime);
|
|
165
|
+
await create!.execute(
|
|
166
|
+
{ name: 'Acme', kind: 'customer', tenantId: 'tenant-b' },
|
|
167
|
+
context('tenant-a'),
|
|
168
|
+
);
|
|
169
|
+
expect(runtime.service().list('tenant-a')).toHaveLength(1);
|
|
170
|
+
expect(runtime.service().list('tenant-b')).toHaveLength(0);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('reuses the service validation so a tool cannot bypass the endpoint', async () => {
|
|
174
|
+
const [, create] = partiesAgentTools(
|
|
175
|
+
createPartiesRuntime({ databasePath: ':memory:' }),
|
|
176
|
+
);
|
|
177
|
+
await expect(
|
|
178
|
+
create!.execute(
|
|
179
|
+
{ name: 'Acme', kind: 'customer', vatId: 'PL-123' },
|
|
180
|
+
context('tenant-a'),
|
|
181
|
+
),
|
|
182
|
+
).rejects.toMatchObject({ code: 'INVALID_VAT_ID' });
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Prove RBAC through the harness (in `modules/agents/tests` with a fake provider,
|
|
187
|
+
where the create tool runs against a `:memory:` repository): a run holding the
|
|
188
|
+
`manage` scope creates the row and emits `tool.started`/`tool.completed`; a run
|
|
189
|
+
whose snapshot lacks it emits `tool.denied` (`TOOL_NOT_GRANTED`) and writes
|
|
190
|
+
nothing.
|
|
191
|
+
|
|
192
|
+
## 5. Settings a tool may depend on
|
|
193
|
+
|
|
194
|
+
Declare `settings: defineModuleSettings({...})` (from `@flowdular/sdk/kernel`) by returning it from the composition, keep a reference to `PlatformServerContext.settings` in the tool factory, and read it per call as `settings.get<number>(context.tenantId, '<module>.core', 'key')` at request time, never at boot. Declared settings render in the module's drawer under Administration, Modules automatically.
|
|
195
|
+
|
|
196
|
+
## Pitfalls
|
|
197
|
+
|
|
198
|
+
- A tool id equal to an endpoint id is a convention, not a requirement; keep them parallel for traceability. A read-by-id tool with no dedicated endpoint reuses the read endpoint id under the same permission.
|
|
199
|
+
- `requiredPermissions` must be exactly the endpoint's permission; a weaker list lets a run bypass the endpoint's ACL because the tool calls the service directly.
|
|
200
|
+
- Register once per composition; a duplicate id throws in the registry at boot and the platform does not start.
|
|
201
|
+
- Import the helpers from `@flowdular/sdk/harness/tool-adapters` and `@flowdular/sdk/harness/runtime`; never import the harness root or `@flowdular/sdk/modules/agents` from `src/index.ts` or the client, which would pull the Vercel AI SDK into the client bundle.
|
|
202
|
+
- The harness validates only the schema subset; deep validation is the service's job. Pass input through the service so a tool cannot persist what the endpoint would reject.
|
|
203
|
+
- Playground runs use the tenant's readiness-probed provider; the local simulation provider performs no network call and is the only provider in a fresh install.
|