@flowdular/sdk 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/.ai/skills/test-hardening/SKILL.md +1 -1
  2. package/README.md +69 -12
  3. package/assets/flowdular-banner.png +0 -0
  4. package/modules/agents/package.json +1 -1
  5. package/modules/auth/package.json +1 -1
  6. package/modules/auth/src/client/AuthenticationCore.tsrx +3 -1
  7. package/modules/auth/src/server/endpoints.ts +3 -1
  8. package/modules/auth/src/server/runtime.ts +4 -0
  9. package/modules/automations/package.json +1 -1
  10. package/modules/automations-workflows-integration/package.json +1 -1
  11. package/modules/profile/package.json +1 -1
  12. package/modules/sandbox/package.json +1 -1
  13. package/modules/system/package.json +1 -1
  14. package/modules/users/package.json +1 -1
  15. package/modules/workflows/package.json +1 -1
  16. package/package.json +5 -13
  17. package/packages/ai-provider/package.json +1 -1
  18. package/packages/cli-protocol/package.json +1 -1
  19. package/packages/client/package.json +1 -1
  20. package/packages/client/src/routing.ts +39 -2
  21. package/packages/client/src/shell/BrandMark.tsrx +5 -1
  22. package/packages/client/src/shell/navigation.ts +3 -1
  23. package/packages/client/src/state.ts +6 -2
  24. package/packages/contracts/package.json +1 -1
  25. package/packages/contracts/schemas/project.schema.json +16 -42
  26. package/packages/database/package.json +1 -1
  27. package/packages/database-pglite/package.json +1 -1
  28. package/packages/database-testing/package.json +1 -1
  29. package/packages/dev-console/package.json +1 -1
  30. package/packages/harness/package.json +1 -1
  31. package/packages/kernel/package.json +1 -1
  32. package/packages/server/package.json +1 -1
  33. package/packages/server/src/application-routes.ts +80 -8
  34. package/packages/server/src/index.ts +4 -1
  35. package/packages/server/src/web-html.ts +9 -5
  36. package/packages/server/src/web.ts +59 -7
  37. package/packages/ui/package.json +1 -1
  38. package/packages/coding-agent/package.json +0 -44
  39. package/packages/coding-agent/src/drivers/byok.ts +0 -322
  40. package/packages/coding-agent/src/drivers/claude-code.ts +0 -290
  41. package/packages/coding-agent/src/drivers/codex.ts +0 -281
  42. package/packages/coding-agent/src/index.ts +0 -56
  43. package/packages/coding-agent/src/registry.ts +0 -119
  44. package/packages/coding-agent/src/roles/contract.ts +0 -60
  45. package/packages/coding-agent/src/roles/defaults.ts +0 -134
  46. package/packages/coding-agent/src/roles/handoff.ts +0 -41
  47. package/packages/coding-agent/src/roles/registry.ts +0 -156
  48. package/packages/coding-agent/src/roles/skills.ts +0 -72
  49. package/packages/coding-agent/src/roles/sync.ts +0 -88
  50. package/packages/coding-agent/src/types.ts +0 -102
  51. package/packages/coding-agent/src/workspace.ts +0 -275
  52. package/packages/sandbox/README.md +0 -691
  53. package/packages/sandbox/bin/flowdular-sandbox.mjs +0 -393
  54. package/packages/sandbox/dist/client/assets/App-ClMOrpyg.js +0 -11
  55. package/packages/sandbox/dist/client/assets/PreviewHost-BHiHrfbQ.js +0 -1
  56. package/packages/sandbox/dist/client/assets/auto-buGEq6de.js +0 -3
  57. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
  58. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
  59. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
  60. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
  61. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
  62. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
  63. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
  64. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
  65. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  66. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  67. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  68. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  69. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
  70. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
  71. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
  72. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
  73. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
  74. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
  75. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
  76. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
  77. package/packages/sandbox/dist/client/assets/ibm-plex-sans-cyrillic-ext-wght-normal-d45eAU9y.woff2 +0 -0
  78. package/packages/sandbox/dist/client/assets/ibm-plex-sans-cyrillic-wght-normal-BAAhND-U.woff2 +0 -0
  79. package/packages/sandbox/dist/client/assets/ibm-plex-sans-greek-wght-normal-CmyJS8uq.woff2 +0 -0
  80. package/packages/sandbox/dist/client/assets/ibm-plex-sans-latin-ext-wght-normal-CIII54If.woff2 +0 -0
  81. package/packages/sandbox/dist/client/assets/ibm-plex-sans-latin-wght-normal-IvpUvPa2.woff2 +0 -0
  82. package/packages/sandbox/dist/client/assets/ibm-plex-sans-vietnamese-wght-normal-Dg1JeJN0.woff2 +0 -0
  83. package/packages/sandbox/dist/client/assets/index-CvwhlQ87.js +0 -4
  84. package/packages/sandbox/dist/client/assets/styles-BPWaGf55.js +0 -3
  85. package/packages/sandbox/dist/client/assets/styles-Dx1_KOag.css +0 -1
  86. package/packages/sandbox/dist/client/favicon.svg +0 -1
  87. package/packages/sandbox/dist/server/assets/auto-CVGNYyb3.js +0 -12785
  88. package/packages/sandbox/dist/server/assets/chunk-DDJLRBDX-3OUNo51g.js +0 -4125
  89. package/packages/sandbox/dist/server/assets/nodefs-DVSI7kG0.js +0 -24
  90. package/packages/sandbox/dist/server/assets/opfs-ahp-D56iokt1.js +0 -388
  91. package/packages/sandbox/dist/server/assets/token-Bvaq7Anz.js +0 -55
  92. package/packages/sandbox/dist/server/assets/token-util-BqSvdIPQ.js +0 -3
  93. package/packages/sandbox/dist/server/assets/token-util-CktFFT4z.js +0 -494
  94. package/packages/sandbox/dist/server/entry.js +0 -91671
  95. package/packages/sandbox/dist/server/favicon.svg +0 -1
  96. package/packages/sandbox/dist/server/index.html +0 -161
  97. package/packages/sandbox/index.html +0 -159
  98. package/packages/sandbox/octane.config.ts +0 -36
  99. package/packages/sandbox/package.json +0 -83
  100. package/packages/sandbox/public/favicon.svg +0 -1
  101. package/packages/sandbox/public/og.png +0 -0
  102. package/packages/sandbox/src/App.tsrx +0 -924
  103. package/packages/sandbox/src/client/ApprovalHandoff.tsrx +0 -39
  104. package/packages/sandbox/src/client/ChatPane.tsrx +0 -694
  105. package/packages/sandbox/src/client/ConnectView.tsrx +0 -86
  106. package/packages/sandbox/src/client/DiffPane.tsrx +0 -150
  107. package/packages/sandbox/src/client/EjectModal.tsrx +0 -571
  108. package/packages/sandbox/src/client/GateResults.tsrx +0 -60
  109. package/packages/sandbox/src/client/GitHubSettingsModal.tsrx +0 -219
  110. package/packages/sandbox/src/client/NewSessionForm.tsrx +0 -214
  111. package/packages/sandbox/src/client/SessionBar.tsrx +0 -151
  112. package/packages/sandbox/src/client/SessionPicker.tsrx +0 -472
  113. package/packages/sandbox/src/client/SpecEditor.tsrx +0 -109
  114. package/packages/sandbox/src/client/SpecReviewCard.tsrx +0 -321
  115. package/packages/sandbox/src/client/WorkCard.tsrx +0 -128
  116. package/packages/sandbox/src/client/WorkbenchModal.tsrx +0 -161
  117. package/packages/sandbox/src/client/WorkspaceMenu.tsrx +0 -100
  118. package/packages/sandbox/src/client/api.ts +0 -797
  119. package/packages/sandbox/src/client/attachments.ts +0 -73
  120. package/packages/sandbox/src/client/dashboard.ts +0 -46
  121. package/packages/sandbox/src/client/github-settings.ts +0 -48
  122. package/packages/sandbox/src/client/highlight.ts +0 -105
  123. package/packages/sandbox/src/client/i18n.ts +0 -32
  124. package/packages/sandbox/src/client/locales/en.json +0 -466
  125. package/packages/sandbox/src/client/locales/pl.json +0 -466
  126. package/packages/sandbox/src/client/session-labels.ts +0 -77
  127. package/packages/sandbox/src/client/state.ts +0 -108
  128. package/packages/sandbox/src/index.ts +0 -10
  129. package/packages/sandbox/src/preview/PreviewHost.tsrx +0 -373
  130. package/packages/sandbox/src/preview/load-module.ts +0 -16
  131. package/packages/sandbox/src/server/attachments.ts +0 -308
  132. package/packages/sandbox/src/server/auto-review.ts +0 -181
  133. package/packages/sandbox/src/server/checkpoints.ts +0 -90
  134. package/packages/sandbox/src/server/config.ts +0 -460
  135. package/packages/sandbox/src/server/dashboard.ts +0 -245
  136. package/packages/sandbox/src/server/delivery/configuration.ts +0 -193
  137. package/packages/sandbox/src/server/delivery/git-pr.ts +0 -1473
  138. package/packages/sandbox/src/server/delivery/index.ts +0 -83
  139. package/packages/sandbox/src/server/delivery/local.ts +0 -151
  140. package/packages/sandbox/src/server/delivery/official-modules.ts +0 -516
  141. package/packages/sandbox/src/server/delivery/plan.ts +0 -218
  142. package/packages/sandbox/src/server/delivery/policies.ts +0 -239
  143. package/packages/sandbox/src/server/delivery/record.ts +0 -41
  144. package/packages/sandbox/src/server/delivery/steps.ts +0 -464
  145. package/packages/sandbox/src/server/delivery/types.ts +0 -158
  146. package/packages/sandbox/src/server/dependencies.ts +0 -88
  147. package/packages/sandbox/src/server/diff.ts +0 -230
  148. package/packages/sandbox/src/server/gates.ts +0 -388
  149. package/packages/sandbox/src/server/index.ts +0 -139
  150. package/packages/sandbox/src/server/path-guard.ts +0 -282
  151. package/packages/sandbox/src/server/planning.ts +0 -749
  152. package/packages/sandbox/src/server/platform-client.ts +0 -189
  153. package/packages/sandbox/src/server/preview-database-host.ts +0 -340
  154. package/packages/sandbox/src/server/preview-database-protocol.ts +0 -163
  155. package/packages/sandbox/src/server/preview-database-proxy.ts +0 -356
  156. package/packages/sandbox/src/server/preview-database.ts +0 -109
  157. package/packages/sandbox/src/server/preview-ipc.ts +0 -15
  158. package/packages/sandbox/src/server/preview-modules.ts +0 -83
  159. package/packages/sandbox/src/server/preview-revision.ts +0 -44
  160. package/packages/sandbox/src/server/preview-runtime.ts +0 -467
  161. package/packages/sandbox/src/server/preview-worker-manager.ts +0 -436
  162. package/packages/sandbox/src/server/preview-worker.ts +0 -168
  163. package/packages/sandbox/src/server/reference.ts +0 -251
  164. package/packages/sandbox/src/server/routes.ts +0 -2258
  165. package/packages/sandbox/src/server/runtime.ts +0 -188
  166. package/packages/sandbox/src/server/session-owner.ts +0 -23
  167. package/packages/sandbox/src/server/sessions.ts +0 -849
  168. package/packages/sandbox/src/server/spec.ts +0 -471
  169. package/packages/sandbox/src/server/turn-lifecycle.ts +0 -56
  170. package/packages/sandbox/src/server/turns.ts +0 -993
  171. package/packages/sandbox/src/server/workspace-install.ts +0 -266
  172. package/packages/sandbox/src/server/workspace-root.ts +0 -73
  173. package/packages/sandbox/src/styles.css +0 -1573
  174. package/packages/sandbox/vite.config.ts +0 -133
@@ -1,119 +0,0 @@
1
- import {
2
- CodingAgentError,
3
- type CodingAgentAvailability,
4
- type CodingAgentDriver,
5
- type CodingAgentDriverInfo,
6
- type SandboxRuntimeMode,
7
- } from './types.ts';
8
-
9
- export interface DriverStatus extends CodingAgentDriverInfo {
10
- readonly availability: CodingAgentAvailability;
11
- readonly offered: boolean;
12
- readonly blockedReason: string | null;
13
- }
14
-
15
- export interface CodingAgentRegistry {
16
- readonly mode: SandboxRuntimeMode;
17
- drivers(): readonly CodingAgentDriverInfo[];
18
- status(): Promise<readonly DriverStatus[]>;
19
- resolve(id: string): Promise<CodingAgentDriver>;
20
- }
21
-
22
- export interface CodingAgentRegistryOptions {
23
- readonly mode: SandboxRuntimeMode;
24
- readonly drivers: readonly CodingAgentDriver[];
25
- /* Availability rarely changes inside one sandbox run, so probes are cached
26
- for this window instead of shelling out on every request. */
27
- readonly probeCacheMs?: number;
28
- }
29
-
30
- const DEFAULT_PROBE_CACHE_MS = 60_000;
31
-
32
- function info(driver: CodingAgentDriver): CodingAgentDriverInfo {
33
- return {
34
- id: driver.id,
35
- label: driver.label,
36
- kind: driver.kind,
37
- requiresLoopback: driver.requiresLoopback,
38
- description: driver.description,
39
- };
40
- }
41
-
42
- export function createCodingAgentRegistry(
43
- options: CodingAgentRegistryOptions,
44
- ): CodingAgentRegistry {
45
- const drivers = new Map<string, CodingAgentDriver>();
46
- for (const driver of options.drivers) {
47
- if (drivers.has(driver.id)) {
48
- throw new CodingAgentError(
49
- 'DUPLICATE_DRIVER',
50
- `Driver ${driver.id} is already registered.`,
51
- );
52
- }
53
- drivers.set(driver.id, driver);
54
- }
55
- const cache = new Map<
56
- string,
57
- { readonly at: number; readonly value: CodingAgentAvailability }
58
- >();
59
- const cacheMs = options.probeCacheMs ?? DEFAULT_PROBE_CACHE_MS;
60
-
61
- /* A local binary carries the operator's own login and file system reach, so
62
- only a loopback sandbox may offer it. */
63
- const blockedReason = (driver: CodingAgentDriver): string | null =>
64
- driver.requiresLoopback && options.mode !== 'loopback'
65
- ? 'Local agent binaries are offered only by a loopback sandbox.'
66
- : null;
67
-
68
- const probe = async (
69
- driver: CodingAgentDriver,
70
- ): Promise<CodingAgentAvailability> => {
71
- const cached = cache.get(driver.id);
72
- const now = Date.now();
73
- if (cached && now - cached.at < cacheMs) return cached.value;
74
- const value = await driver.probe();
75
- cache.set(driver.id, { at: now, value });
76
- return value;
77
- };
78
-
79
- return {
80
- mode: options.mode,
81
- drivers: () => [...drivers.values()].map(info),
82
- status: async () =>
83
- Promise.all(
84
- [...drivers.values()].map(async (driver) => {
85
- const blocked = blockedReason(driver);
86
- const availability = blocked
87
- ? { available: false, detail: blocked, version: null }
88
- : await probe(driver);
89
- return {
90
- ...info(driver),
91
- availability,
92
- offered: !blocked && availability.available,
93
- blockedReason: blocked,
94
- };
95
- }),
96
- ),
97
- resolve: async (id: string) => {
98
- const driver = drivers.get(id);
99
- if (!driver) {
100
- throw new CodingAgentError(
101
- 'UNKNOWN_DRIVER',
102
- `Unknown coding agent driver: ${id}`,
103
- );
104
- }
105
- const blocked = blockedReason(driver);
106
- if (blocked) {
107
- throw new CodingAgentError('DRIVER_NOT_OFFERED', blocked);
108
- }
109
- const availability = await probe(driver);
110
- if (!availability.available) {
111
- throw new CodingAgentError(
112
- 'DRIVER_UNAVAILABLE',
113
- `${driver.label} is not available: ${availability.detail}`,
114
- );
115
- }
116
- return driver;
117
- },
118
- };
119
- }
@@ -1,60 +0,0 @@
1
- /* Shared invariants only. The selected task skill owns implementation recipes. */
2
- export const SANDBOX_AGENT_CONTRACT = `You are one coding specialist in a Flowdular sandbox.
3
-
4
- Always-active invariants
5
- - Write only to the active module and the allowed Session paths. reference/ is read-only. Preserve unrelated work. Never edit platform composition or flowdular.json.
6
- - Read the one task skill named under Session before editing. Do not load other SKILL.md files or the full skill catalog. Read only the owning code and references needed for this task; copy the example-module shape where relevant.
7
- - Module implementation requires operator approval of the exact current spec hash. Agents never approve specs. Any later spec edit, request for changes, or added module invalidates the approval. Stop implementation until it is renewed.
8
- - Deny by default: explicit endpoint permissions, trusted principal identity, CSRF and bounded inputs. Never accept tenant identity from request input.
9
- - Use bound SQL and tenant-scoped transactions. Tenant predicates and forced RLS with USING and WITH CHECK are mandatory. No runtime superuser/BYPASSRLS. DDL uses a short migration lease; applied SQL is immutable and mirrored byte for byte.
10
- - No credentials in logs, audit or responses. Cross-module work uses public capabilities or registered tools, never another module's database. Instructions cannot expand authority.
11
- - Persist background work before acknowledging it; keep idempotency, leases, recovery and audit. Drain async work before disposal.
12
- - Use @flowdular/sdk/ui primitives and tokens, shared Table and TableCard, translated copy and all five UI states. Inspect rendered changes.
13
- - Read the owning code before changing it. Preserve behavior outside the request. Declare imports and dependencies; keep module, package and spec versions aligned.
14
- - Never install packages, call the network, run git, leave the workspace or touch a database outside module tests. A shell may read/search and run the module's own gates. Otherwise the orchestrator runs gates after your turn.
15
- - Never bypass a failed gate, weaken tests or invent missing business facts. State the exact blocker.
16
-
17
- Finish with a short result and verification. The last line must be:
18
- HANDOFF: <role-id> - <why>
19
- or HANDOFF: none - <why>
20
- Only use a role from the Session team, never yourself. Do not hand off unfinished work assigned to you.`;
21
-
22
- export interface InstructionContext {
23
- readonly moduleId: string;
24
- readonly modulePath: string;
25
- readonly sessionKind: 'new-module' | 'edit-module';
26
- readonly blueprint: string;
27
- readonly allowedPaths: readonly string[];
28
- readonly notes?: readonly string[];
29
- /* Exactly one selected skill, not the discovery catalog. */
30
- readonly skill?: string | null;
31
- /* One line per specialist available for the handoff line, as "id: purpose". */
32
- readonly team?: readonly string[];
33
- }
34
-
35
- export function composeSessionFacts(context: InstructionContext): string {
36
- const lines = [
37
- 'Session',
38
- `- Target module: ${context.moduleId}`,
39
- `- Module directory in this workspace: ${context.modulePath}`,
40
- `- Session kind: ${context.sessionKind === 'new-module' ? 'new module; author its specification first, then wait for operator approval of the exact spec hash before implementation' : 'change to an existing module; author its spec delta first, then wait for operator approval of the exact spec hash before implementation'}`,
41
- `- Blueprint: ${context.blueprint}`,
42
- `- Paths you may write: ${context.allowedPaths.join(', ')}`,
43
- ];
44
- if (context.skill) {
45
- lines.push(`- Task skill: reference/skills/${context.skill}/SKILL.md`);
46
- } else {
47
- lines.push(
48
- '- No matching task skill is installed. Follow your role and inspect the owning code; do not load unrelated skills.',
49
- );
50
- }
51
- for (const note of context.notes ?? []) lines.push(`- ${note}`);
52
- if (context.team && context.team.length > 0) {
53
- lines.push(
54
- '',
55
- 'Team you can hand off to',
56
- ...context.team.map((mate) => `- ${mate}`),
57
- );
58
- }
59
- return lines.join('\n');
60
- }
@@ -1,134 +0,0 @@
1
- /* GENERATED from .ai/agents/sandbox/*.md by
2
- "pnpm --filter @flowdular/sdk/coding-agent sync-roles". Edit the markdown
3
- documents, then regenerate; do not edit this file by hand. */
4
-
5
- export interface AgentRoleDefinition {
6
- readonly id: string;
7
- readonly name: string;
8
- readonly purpose: string;
9
- /* Globs relative to the module directory of the session. */
10
- readonly allowedPaths: readonly string[];
11
- readonly gates: readonly string[];
12
- readonly handoff: readonly string[];
13
- readonly instruction: string;
14
- }
15
-
16
- /* Specialists with disjoint ownership. The orchestrator routes a turn to
17
- exactly one of them, so a change always has a single accountable role. */
18
- export const DEFAULT_AGENT_ROLES: readonly AgentRoleDefinition[] = [
19
- {
20
- id: 'agentic-engineer',
21
- name: 'Agentic engineer',
22
- purpose:
23
- 'Design the agent-facing surface of a module: registered tools, module-owned business agents, capability ceilings, refusals, and tests.',
24
- allowedPaths: [
25
- 'spec/**',
26
- 'src/agent/**',
27
- 'src/platform.ts',
28
- 'tests/**',
29
- 'package.json',
30
- 'module.json',
31
- ],
32
- gates: [
33
- 'spec-schema',
34
- 'module-schema',
35
- 'dependencies',
36
- 'typecheck',
37
- 'tests',
38
- ],
39
- handoff: ['backend-engineer', 'business-manager'],
40
- instruction: `You own module tools, business-agent definitions, capability ceilings and their tests. Use only the Task skill selected under Session. Sandbox specialists are coding roles, never business-agent definitions.
41
-
42
- A tool wraps the owning module's public service, using registered API/CLI adapters, trusted context.tenantId, bounded input/output and service validation. It never receives a database, filesystem or shell. Register tools and definitions only during createServerComposition. Preserve the rest of that backend-owned file.
43
-
44
- The allowed tool list is a maximum. Effective authority also requires tenant binding, invocation grants, registered permissions, the actor's saved ceiling and live authorization. Prove denial through the harness and prove that it writes nothing. Instructions and procedures cannot grant access.
45
-
46
- Module-owned agents require a tenant provider/model binding, retained definition revisions and an exact tool ceiling. Never pin provider credentials or use wildcard tools. Procedures stored by agents.core are business data, unrelated to coding skills.
47
-
48
- If the requested surface needs a missing endpoint/service, hand off to backend. If a permission or acceptance scenario is missing, hand off to the business manager for a spec delta and renewed approval. Never edit another module or platform package from this session.`,
49
- },
50
- {
51
- id: 'backend-engineer',
52
- name: 'Backend engineer',
53
- purpose:
54
- 'Implement the server: ACL constants, endpoints, services, portable repositories, runtime, and dialect-explicit schema.',
55
- allowedPaths: [
56
- 'src/acl/**',
57
- 'src/api/**',
58
- 'src/server/**',
59
- 'src/services/**',
60
- 'src/domain/**',
61
- 'src/platform.ts',
62
- 'src/index.ts',
63
- 'migrations/**',
64
- 'tests/**',
65
- 'module.json',
66
- 'package.json',
67
- ],
68
- gates: ['module-schema', 'dependencies', 'typecheck', 'tests', 'format'],
69
- handoff: ['frontend-engineer', 'agentic-engineer'],
70
- instruction: `You own the module's server and persistence. Use only the Task skill selected under Session. Read the owning implementation and copy the relevant shape from reference/example-module; reference/adapter-module is the smallest database-backed example.
71
-
72
- The orchestrator scaffolds new modules after exact-hash approval. Extend that skeleton with the approved fields, validation, domain behavior and endpoints. Keep driver details out of the async business repository port. Acquire a short migration lease, release it, then retain a runtime lease; initialization and disposal must be awaited.
73
-
74
- Permission constants equal the approved spec. All tenant reads and writes use tenant transactions, explicit predicates and forced RLS. Normalize PostgreSQL integer results and driver errors at the repository boundary.
75
-
76
- Test observable behavior: successful operations, validation bounds, 401/403, uniqueness, replay and two-tenant isolation. Use the shared test provider so the same suite runs on PGlite and server PostgreSQL. Never use an owner connection to bypass a failing runtime test.
77
-
78
- Leave client files to the frontend engineer and tools/business-agent definitions to the agentic engineer. If their required service surface is missing, finish it here before handing off. Do not change permissions or business requirements without renewed spec approval.`,
79
- },
80
- {
81
- id: 'business-manager',
82
- name: 'Business manager',
83
- purpose:
84
- 'Turn a business problem into a schema-valid module specification with acceptance scenarios.',
85
- allowedPaths: ['spec/**', 'translations/**'],
86
- gates: ['spec-schema'],
87
- handoff: ['backend-engineer', 'ux-designer'],
88
- instruction: `You own specification decisions and locale terminology, never implementation. Use only the Task skill selected under Session. Consult reference/packages/contracts/schemas/module-spec.schema.json and reference/example-module/spec/module.yaml when writing the spec.
89
-
90
- For an edit, compare against base/modules/<dir>/spec/module.yaml and make the smallest delta covering the brief. Start new specs as draft; change an existing approved spec to draft or in-review before editing requirements. Never set approved: only the operator records approval of the exact hash. Later edits invalidate it.
91
-
92
- State actors, records, ownership, permissions, uniqueness, failure behavior and observable acceptance scenarios. Do not invent business facts. Include success, denial and cross-tenant cases. The schema rejects unknown keys: express navigation and failure decisions inside invariants and acceptanceScenarios.
93
-
94
- Put the primary entity's read/manage permissions first: the scaffold builds that entity, while later permissions only become constants. Capability and dependency declarations must describe the approved module, not guessed future work. Define matching terminology for each declared locale.
95
-
96
- Do not write TypeScript, module.json or package.json. Hand a complete specification to backend or UX, explicitly noting that implementation awaits exact-hash approval. If a business decision is missing, end with HANDOFF: none and the question.`,
97
- },
98
- {
99
- id: 'frontend-engineer',
100
- name: 'Frontend engineer',
101
- purpose:
102
- 'Implement the client: contribution, views, forms, state, and API calls.',
103
- allowedPaths: ['src/client/**', 'tests/**', 'package.json'],
104
- gates: ['dependencies', 'typecheck', 'tests', 'format'],
105
- handoff: ['backend-engineer', 'ux-designer'],
106
- instruction: `You own src/client: contributions, screens, forms, state and API calls. Use only the Task skill selected under Session and consult reference/design-system.md for visual changes. Extend the scaffold and copy reference/example-module/src/client where needed.
107
-
108
- Keep fetch calls in api.ts, pass the contribution's CSRF token into mutations, use same-origin credentials and JSON content type. Handle error envelopes. Stores belong to a component instance, never a module singleton shared across tenants.
109
-
110
- Use the canonical createClientContribution entry. Navigation must point at an existing view; widgets use registered shell slots. Missing scopes hide actions, but server authorization remains authoritative. Keep server dependencies out of client imports.
111
-
112
- Records own the page, with create/edit in a Drawer. Reuse TableCard and Table, including widths, loading and empty states. Use translated copy, all five states, and no hardcoded design values. Inspect the rendered screen before handoff.
113
-
114
- For TSRX, loop keys can read only the loop item: precompute a key on each item if it needs props or local state. Test pure mapping/filtering logic in .ts helpers. Ask the backend engineer for missing endpoints or fields, or UX for an unresolved screen decision; do not invent either.`,
115
- },
116
- {
117
- id: 'ux-designer',
118
- name: 'UX designer',
119
- purpose:
120
- 'Design the screens, their states, and their copy on the shared design system.',
121
- allowedPaths: ['src/client/**'],
122
- gates: ['typecheck', 'format'],
123
- handoff: ['frontend-engineer', 'business-manager'],
124
- instruction: `You own screen structure, states and copy in src/client. Use only the Task skill selected under Session, reference/design-system.md and the relevant shared component source. reference/example-module/src/client/CatalogView.tsrx is the screen example.
125
-
126
- Reshape the existing scaffold into a typechecking view and Drawer form. Leave fetch, api.ts and data wiring to the frontend engineer. Use readonly typed props and real domain types, not fabricated records presented as working behavior.
127
-
128
- Specify loading, empty, error, populated and denied states. Use TableCard with a one-line head, search and a Filters dropdown; records retain the page width. A form never sits beside a record table. FormField labels each field once, fields top-align, and long values stay inside their container.
129
-
130
- Use shared primitives and tokens. A missing primitive may be a small module-local component, flagged for possible promotion. Do not restyle ui-* classes. Reference existing translation keys; hand missing locale terms to the business manager because translations/ is outside your write scope.
131
-
132
- Inspect the rendered result for overflow, alignment and duplicate labels. Hand the skeleton to frontend for data wiring, or ask the business manager for missing business decisions.`,
133
- },
134
- ];
@@ -1,41 +0,0 @@
1
- export interface HandoffDeclaration {
2
- /* The role that should take the next turn, or null when the specialist
3
- reports the work as finished. */
4
- readonly role: string | null;
5
- readonly reason: string;
6
- }
7
-
8
- const LINE = /^handoff\s*:\s*(.*)$/i;
9
- const PARTS = /^([a-z][a-z0-9_ -]*?)(?:\s*(?:[:|\u2013\u2014]|\s-)\s*(.*))?$/i;
10
- const DECORATION = /[`*_>#]/g;
11
-
12
- /* Models write the role as an id, a display name, or with a trailing full
13
- stop. All of them name the same specialist, so the id is normalized before
14
- the orchestrator compares it with the registered roles. */
15
- function normalizeRole(value: string): string {
16
- return value
17
- .trim()
18
- .toLowerCase()
19
- .replace(/[\s_]+/g, '-');
20
- }
21
-
22
- /* Every role ends its final message with one handoff line, so the orchestrator
23
- knows who continues without asking a model to summarize the state again. The
24
- last line wins: a specialist that quotes the format while explaining itself
25
- cannot redirect the routing. */
26
- export function parseHandoff(text: string): HandoffDeclaration | null {
27
- const lines = text.split('\n');
28
- for (let index = lines.length - 1; index >= 0; index -= 1) {
29
- const line = lines[index]!.replace(DECORATION, '').trim();
30
- const match = LINE.exec(line);
31
- if (!match) continue;
32
- const parts = PARTS.exec(match[1]!.replace(/[.!]+$/, '').trim());
33
- if (!parts) continue;
34
- const role = normalizeRole(parts[1]!);
35
- return {
36
- role: role === 'none' ? null : role,
37
- reason: (parts[2] ?? '').trim().slice(0, 240),
38
- };
39
- }
40
- return null;
41
- }
@@ -1,156 +0,0 @@
1
- import { mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
2
- import { join } from 'node:path';
3
- import { parse as parseYaml } from 'yaml';
4
- import { CodingAgentError } from '../types.ts';
5
- import {
6
- SANDBOX_AGENT_CONTRACT,
7
- composeSessionFacts,
8
- type InstructionContext,
9
- } from './contract.ts';
10
- import { DEFAULT_AGENT_ROLES, type AgentRoleDefinition } from './defaults.ts';
11
-
12
- export const SANDBOX_ROLE_DIRECTORY = '.ai/agents/sandbox';
13
-
14
- interface RoleFrontMatter {
15
- readonly id?: string;
16
- readonly name?: string;
17
- readonly purpose?: string;
18
- readonly allowedPaths?: readonly string[];
19
- readonly gates?: readonly string[];
20
- readonly handoff?: readonly string[];
21
- }
22
-
23
- function stringList(value: unknown, field: string): readonly string[] {
24
- if (
25
- !Array.isArray(value) ||
26
- value.some((entry) => typeof entry !== 'string')
27
- ) {
28
- throw new CodingAgentError(
29
- 'INVALID_ROLE',
30
- `Role field ${field} must be a list of strings.`,
31
- );
32
- }
33
- return value as readonly string[];
34
- }
35
-
36
- export function parseRoleDocument(source: string): AgentRoleDefinition {
37
- const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(source);
38
- if (!match) {
39
- throw new CodingAgentError(
40
- 'INVALID_ROLE',
41
- 'A role document needs YAML front matter followed by its instruction.',
42
- );
43
- }
44
- const meta = (parseYaml(match[1]!) ?? {}) as RoleFrontMatter;
45
- const instruction = match[2]!.trim();
46
- if (!meta.id || !meta.name || !meta.purpose || !instruction) {
47
- throw new CodingAgentError(
48
- 'INVALID_ROLE',
49
- 'A role document needs id, name, purpose, and an instruction body.',
50
- );
51
- }
52
- return {
53
- id: meta.id,
54
- name: meta.name,
55
- purpose: meta.purpose,
56
- allowedPaths: stringList(meta.allowedPaths ?? [], 'allowedPaths'),
57
- gates: stringList(meta.gates ?? [], 'gates'),
58
- handoff: stringList(meta.handoff ?? [], 'handoff'),
59
- instruction,
60
- };
61
- }
62
-
63
- function yamlScalar(value: string): string {
64
- return `'${value.replace(/'/g, "''")}'`;
65
- }
66
-
67
- export function renderRoleDocument(role: AgentRoleDefinition): string {
68
- const front = [
69
- '---',
70
- `id: ${role.id}`,
71
- `name: ${yamlScalar(role.name)}`,
72
- `purpose: ${yamlScalar(role.purpose)}`,
73
- 'allowedPaths:',
74
- ...role.allowedPaths.map((path) => ` - ${yamlScalar(path)}`),
75
- 'gates:',
76
- ...role.gates.map((gate) => ` - ${gate}`),
77
- 'handoff:',
78
- ...role.handoff.map((handoff) => ` - ${handoff}`),
79
- '---',
80
- '',
81
- ].join('\n');
82
- return `${front}${role.instruction}\n`;
83
- }
84
-
85
- /* Roles are workspace configuration. Bundled defaults apply until a workspace
86
- writes its own under .ai/agents/sandbox, which then wins by id. */
87
- export async function loadAgentRoles(
88
- workspaceRoot: string,
89
- ): Promise<readonly AgentRoleDefinition[]> {
90
- const roles = new Map(DEFAULT_AGENT_ROLES.map((role) => [role.id, role]));
91
- const directory = join(workspaceRoot, SANDBOX_ROLE_DIRECTORY);
92
- let entries: readonly string[];
93
- try {
94
- entries = await readdir(directory);
95
- } catch {
96
- return [...roles.values()];
97
- }
98
- for (const entry of entries) {
99
- if (!entry.endsWith('.md')) continue;
100
- const role = parseRoleDocument(
101
- await readFile(join(directory, entry), 'utf8'),
102
- );
103
- roles.set(role.id, role);
104
- }
105
- return [...roles.values()].sort((left, right) =>
106
- left.id.localeCompare(right.id),
107
- );
108
- }
109
-
110
- export async function materializeAgentRoles(
111
- workspaceRoot: string,
112
- roles: readonly AgentRoleDefinition[] = DEFAULT_AGENT_ROLES,
113
- ): Promise<readonly string[]> {
114
- const directory = join(workspaceRoot, SANDBOX_ROLE_DIRECTORY);
115
- await mkdir(directory, { recursive: true });
116
- const written: string[] = [];
117
- for (const role of roles) {
118
- const path = join(directory, `${role.id}.md`);
119
- await writeFile(path, renderRoleDocument(role), 'utf8');
120
- written.push(`${SANDBOX_ROLE_DIRECTORY}/${role.id}.md`);
121
- }
122
- return written;
123
- }
124
-
125
- export function findRole(
126
- roles: readonly AgentRoleDefinition[],
127
- id: string,
128
- ): AgentRoleDefinition {
129
- const role = roles.find((candidate) => candidate.id === id);
130
- if (!role) {
131
- throw new CodingAgentError('UNKNOWN_ROLE', `Unknown agent role: ${id}`);
132
- }
133
- return role;
134
- }
135
-
136
- export function composeInstruction(
137
- role: AgentRoleDefinition,
138
- context: InstructionContext,
139
- ): string {
140
- return [
141
- SANDBOX_AGENT_CONTRACT,
142
- '',
143
- `Role: ${role.name}`,
144
- role.purpose,
145
- '',
146
- role.instruction,
147
- '',
148
- composeSessionFacts({
149
- ...context,
150
- allowedPaths:
151
- context.allowedPaths.length > 0
152
- ? context.allowedPaths
153
- : role.allowedPaths,
154
- }),
155
- ].join('\n');
156
- }
@@ -1,72 +0,0 @@
1
- export interface TaskSkillContext {
2
- readonly role: string;
3
- readonly sessionKind: 'new-module' | 'edit-module';
4
- readonly blueprint: string;
5
- readonly task: string;
6
- readonly available: readonly string[];
7
- }
8
-
9
- /* A single deterministic selection. Discovery is not injected into the prompt.
10
- Only skills this specialist can use are eligible for an explicit $skill. */
11
- const ROLE_SKILLS: Readonly<Record<string, readonly string[]>> = {
12
- 'business-manager': ['module-new', 'module-update'],
13
- 'backend-engineer': [
14
- 'module-new',
15
- 'module-update',
16
- 'database-adapter',
17
- 'migration-authoring',
18
- 'bug-hunt',
19
- 'test-hardening',
20
- 'auth-security-review',
21
- 'perf-audit',
22
- ],
23
- 'frontend-engineer': [
24
- 'ux-design',
25
- 'module-update',
26
- 'variables',
27
- 'workflow-development',
28
- 'bug-hunt',
29
- 'test-hardening',
30
- ],
31
- 'ux-designer': ['ux-design'],
32
- 'agentic-engineer': [
33
- 'agent-tool-design',
34
- 'business-agent-design',
35
- 'workflow-development',
36
- 'variables',
37
- 'test-hardening',
38
- ],
39
- };
40
-
41
- export function selectTaskSkill(context: TaskSkillContext): string | null {
42
- // Review is read-only and is available to custom registered specialists too.
43
- const eligible = [...(ROLE_SKILLS[context.role] ?? []), 'auto-review'];
44
- const available = new Set(context.available);
45
- const installed = (skill: string) =>
46
- eligible.includes(skill) && available.has(skill);
47
- const explicit = [...context.task.matchAll(/\$([a-z][a-z0-9-]*)\b/g)]
48
- .map((match) => match[1]!)
49
- .find(installed);
50
- if (explicit) return explicit;
51
- const blueprint = context.blueprint.split('@')[0];
52
- const focused =
53
- blueprint === 'add-migration'
54
- ? 'migration-authoring'
55
- : blueprint === 'bug-fix'
56
- ? 'bug-hunt'
57
- : null;
58
- if (focused && installed(focused)) return focused;
59
- const moduleSkill =
60
- context.sessionKind === 'new-module' ? 'module-new' : 'module-update';
61
- const defaultSkill =
62
- context.role === 'agentic-engineer'
63
- ? /\b(workflow|pipeline|dag)\b/i.test(context.task)
64
- ? 'workflow-development'
65
- : /\b(defineAgent|business agent|agent biznesowy)\b/i.test(context.task)
66
- ? 'business-agent-design'
67
- : 'agent-tool-design'
68
- : context.role === 'frontend-engineer' || context.role === 'ux-designer'
69
- ? 'ux-design'
70
- : moduleSkill;
71
- return installed(defaultSkill) ? defaultSkill : null;
72
- }
@@ -1,88 +0,0 @@
1
- import { readFile, readdir } from 'node:fs/promises';
2
- import { join } from 'node:path';
3
- import type { AgentRoleDefinition } from './defaults.ts';
4
- import { parseRoleDocument } from './registry.ts';
5
-
6
- /* Prettier escapes markdown punctuation in the role documents (ui-\* for
7
- ui-*). The escape is a rendering detail of the document, not part of the
8
- instruction a model should read, so it is removed when the document becomes
9
- the bundled default. */
10
- export function normalizeRoleDocument(
11
- role: AgentRoleDefinition,
12
- ): AgentRoleDefinition {
13
- return {
14
- ...role,
15
- instruction: role.instruction.replace(/\\([*_`#[\]])/g, '$1'),
16
- };
17
- }
18
-
19
- export async function loadRoleDocuments(
20
- directory: string,
21
- ): Promise<readonly AgentRoleDefinition[]> {
22
- const roles: AgentRoleDefinition[] = [];
23
- for (const entry of (await readdir(directory)).sort()) {
24
- if (!entry.endsWith('.md')) continue;
25
- roles.push(
26
- normalizeRoleDocument(
27
- parseRoleDocument(await readFile(join(directory, entry), 'utf8')),
28
- ),
29
- );
30
- }
31
- return roles.sort((left, right) => left.id.localeCompare(right.id));
32
- }
33
-
34
- function literal(value: string): string {
35
- return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
36
- }
37
-
38
- function template(value: string): string {
39
- return `\`${value.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${')}\``;
40
- }
41
-
42
- function list(values: readonly string[]): string {
43
- return `[${values.map(literal).join(', ')}]`;
44
- }
45
-
46
- /* The bundled defaults are generated from the workspace role documents, so
47
- the two never disagree: the markdown is the source, this module is the
48
- fallback for a workspace that has not materialized its own copy yet. */
49
- export function renderDefaultsModule(
50
- roles: readonly AgentRoleDefinition[],
51
- ): string {
52
- const entries = roles.map((role) =>
53
- [
54
- '\t{',
55
- `\t\tid: ${literal(role.id)},`,
56
- `\t\tname: ${literal(role.name)},`,
57
- `\t\tpurpose: ${literal(role.purpose)},`,
58
- `\t\tallowedPaths: ${list(role.allowedPaths)},`,
59
- `\t\tgates: ${list(role.gates)},`,
60
- `\t\thandoff: ${list(role.handoff)},`,
61
- `\t\tinstruction: ${template(role.instruction)},`,
62
- '\t},',
63
- ].join('\n'),
64
- );
65
- return [
66
- '/* GENERATED from .ai/agents/sandbox/*.md by',
67
- ' "pnpm --filter @flowdular/sdk/coding-agent sync-roles". Edit the markdown',
68
- ' documents, then regenerate; do not edit this file by hand. */',
69
- '',
70
- 'export interface AgentRoleDefinition {',
71
- '\treadonly id: string;',
72
- '\treadonly name: string;',
73
- '\treadonly purpose: string;',
74
- '\t/* Globs relative to the module directory of the session. */',
75
- '\treadonly allowedPaths: readonly string[];',
76
- '\treadonly gates: readonly string[];',
77
- '\treadonly handoff: readonly string[];',
78
- '\treadonly instruction: string;',
79
- '}',
80
- '',
81
- '/* Specialists with disjoint ownership. The orchestrator routes a turn to',
82
- ' exactly one of them, so a change always has a single accountable role. */',
83
- 'export const DEFAULT_AGENT_ROLES: readonly AgentRoleDefinition[] = [',
84
- ...entries,
85
- '];',
86
- '',
87
- ].join('\n');
88
- }