forgeos 0.1.0-alpha.2 → 0.1.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/AGENTS.md +38 -3
  2. package/CHANGELOG.md +29 -0
  3. package/README.md +25 -10
  4. package/package.json +8 -5
  5. package/src/forge/_generated/actionSubscriptions.json +2 -2
  6. package/src/forge/_generated/actionSubscriptions.ts +3 -3
  7. package/src/forge/_generated/agentAdapterManifest.json +2 -2
  8. package/src/forge/_generated/agentAdapterManifest.ts +3 -3
  9. package/src/forge/_generated/agentContract.json +2 -2
  10. package/src/forge/_generated/agentContract.ts +183 -50
  11. package/src/forge/_generated/agentQuickstart.md +3 -1
  12. package/src/forge/_generated/agentTools.json +2 -0
  13. package/src/forge/_generated/agentTools.md +16 -0
  14. package/src/forge/_generated/agentTools.ts +12 -0
  15. package/src/forge/_generated/aiContext.ts +67 -1
  16. package/src/forge/_generated/aiModels.json +2 -2
  17. package/src/forge/_generated/aiModels.ts +17 -1
  18. package/src/forge/_generated/aiProviders.json +1 -1
  19. package/src/forge/_generated/aiProviders.ts +1 -1
  20. package/src/forge/_generated/aiRegistry.json +2 -2
  21. package/src/forge/_generated/aiRegistry.ts +7 -5
  22. package/src/forge/_generated/api.json +2 -2
  23. package/src/forge/_generated/api.ts +1 -1
  24. package/src/forge/_generated/appGraph.json +2 -2
  25. package/src/forge/_generated/appGraph.ts +512 -260
  26. package/src/forge/_generated/appMap.md +21 -1
  27. package/src/forge/_generated/artifactManifest.json +2 -2
  28. package/src/forge/_generated/artifactManifest.ts +2 -2
  29. package/src/forge/_generated/authClaims.json +1 -1
  30. package/src/forge/_generated/authClaims.ts +1 -1
  31. package/src/forge/_generated/authConfig.json +1 -1
  32. package/src/forge/_generated/authConfig.ts +1 -1
  33. package/src/forge/_generated/authContext.ts +1 -1
  34. package/src/forge/_generated/authRegistry.json +1 -1
  35. package/src/forge/_generated/authRegistry.ts +1 -1
  36. package/src/forge/_generated/buildInfo.json +2 -2
  37. package/src/forge/_generated/buildInfo.ts +4 -4
  38. package/src/forge/_generated/capabilityMap.json +2 -2
  39. package/src/forge/_generated/capabilityMap.md +1 -1
  40. package/src/forge/_generated/capabilityMap.ts +2 -2
  41. package/src/forge/_generated/client.ts +1 -1
  42. package/src/forge/_generated/clientApi.ts +1 -1
  43. package/src/forge/_generated/clientManifest.json +2 -2
  44. package/src/forge/_generated/clientManifest.ts +3 -3
  45. package/src/forge/_generated/clientTypes.ts +1 -1
  46. package/src/forge/_generated/configRegistry.json +1 -1
  47. package/src/forge/_generated/configRegistry.ts +1 -1
  48. package/src/forge/_generated/dataGraph.json +2 -2
  49. package/src/forge/_generated/dataGraph.ts +3 -3
  50. package/src/forge/_generated/db.json +1 -1
  51. package/src/forge/_generated/db.ts +1 -1
  52. package/src/forge/_generated/dbSecurityManifest.json +1 -1
  53. package/src/forge/_generated/dbSecurityManifest.ts +1 -1
  54. package/src/forge/_generated/dbSessionContext.json +1 -1
  55. package/src/forge/_generated/dbSessionContext.ts +1 -1
  56. package/src/forge/_generated/deployManifest.json +2 -2
  57. package/src/forge/_generated/deployManifest.ts +7 -7
  58. package/src/forge/_generated/devManifest.json +2 -2
  59. package/src/forge/_generated/devManifest.ts +18 -3
  60. package/src/forge/_generated/envSchema.json +1 -1
  61. package/src/forge/_generated/envSchema.ts +1 -1
  62. package/src/forge/_generated/frontendGraph.json +1 -1
  63. package/src/forge/_generated/frontendGraph.ts +1 -1
  64. package/src/forge/_generated/importGuards.json +1 -1
  65. package/src/forge/_generated/importGuards.ts +1 -1
  66. package/src/forge/_generated/index.ts +2 -1
  67. package/src/forge/_generated/liveProductionManifest.json +1 -1
  68. package/src/forge/_generated/liveProductionManifest.ts +1 -1
  69. package/src/forge/_generated/liveProtocol.json +1 -1
  70. package/src/forge/_generated/liveProtocol.ts +1 -1
  71. package/src/forge/_generated/liveQueryRegistry.json +2 -2
  72. package/src/forge/_generated/liveQueryRegistry.ts +3 -3
  73. package/src/forge/_generated/liveTransportConfig.json +1 -1
  74. package/src/forge/_generated/liveTransportConfig.ts +1 -1
  75. package/src/forge/_generated/makeRegistry.json +2 -2
  76. package/src/forge/_generated/makeRegistry.ts +16 -2
  77. package/src/forge/_generated/makeTemplates.json +2 -2
  78. package/src/forge/_generated/makeTemplates.ts +6 -1
  79. package/src/forge/_generated/mockMap.json +1 -1
  80. package/src/forge/_generated/mockMap.ts +1 -1
  81. package/src/forge/_generated/operationPlaybooks.md +34 -14
  82. package/src/forge/_generated/packageGraph.json +2 -2
  83. package/src/forge/_generated/packageGraph.ts +8808 -4723
  84. package/src/forge/_generated/packageUpgradeRegistry.json +2 -2
  85. package/src/forge/_generated/packageUpgradeRegistry.ts +2 -2
  86. package/src/forge/_generated/permissionMatrix.json +2 -2
  87. package/src/forge/_generated/permissionMatrix.ts +3 -3
  88. package/src/forge/_generated/policyRegistry.json +2 -2
  89. package/src/forge/_generated/policyRegistry.ts +3 -3
  90. package/src/forge/_generated/queryRegistry.json +2 -2
  91. package/src/forge/_generated/queryRegistry.ts +3 -3
  92. package/src/forge/_generated/react.d.ts +1 -1
  93. package/src/forge/_generated/react.ts +1 -1
  94. package/src/forge/_generated/reactManifest.json +2 -2
  95. package/src/forge/_generated/reactManifest.ts +3 -3
  96. package/src/forge/_generated/releaseManifest.json +2 -2
  97. package/src/forge/_generated/releaseManifest.ts +3 -3
  98. package/src/forge/_generated/rlsPolicies.json +1 -1
  99. package/src/forge/_generated/rlsPolicies.sql +1 -1
  100. package/src/forge/_generated/rlsPolicies.ts +1 -1
  101. package/src/forge/_generated/runtimeGraph.json +2 -2
  102. package/src/forge/_generated/runtimeGraph.ts +3 -3
  103. package/src/forge/_generated/runtimeMatrix.json +2 -2
  104. package/src/forge/_generated/runtimeMatrix.ts +8684 -1939
  105. package/src/forge/_generated/runtimeRegistry.ts +1 -1
  106. package/src/forge/_generated/runtimeRules.md +13 -1
  107. package/src/forge/_generated/secretRegistry.json +1 -1
  108. package/src/forge/_generated/secretRegistry.ts +1 -1
  109. package/src/forge/_generated/secretsContext.ts +1 -1
  110. package/src/forge/_generated/serverApi.ts +1 -1
  111. package/src/forge/_generated/sourceMapManifest.json +2 -2
  112. package/src/forge/_generated/sourceMapManifest.ts +2 -2
  113. package/src/forge/_generated/sqlPlan.json +1 -1
  114. package/src/forge/_generated/sqlPlan.ts +1 -1
  115. package/src/forge/_generated/subscriptionManifest.json +2 -2
  116. package/src/forge/_generated/subscriptionManifest.ts +3 -3
  117. package/src/forge/_generated/symbolicationManifest.json +2 -2
  118. package/src/forge/_generated/symbolicationManifest.ts +2 -2
  119. package/src/forge/_generated/telemetryRegistry.json +2 -2
  120. package/src/forge/_generated/telemetryRegistry.ts +3 -3
  121. package/src/forge/_generated/telemetrySinks.json +2 -2
  122. package/src/forge/_generated/telemetrySinks.ts +2 -2
  123. package/src/forge/_generated/tenantScope.json +2 -2
  124. package/src/forge/_generated/tenantScope.ts +3 -3
  125. package/src/forge/_generated/testGraph.json +2 -2
  126. package/src/forge/_generated/testGraph.ts +339 -17
  127. package/src/forge/_generated/testPlanRegistry.json +2 -2
  128. package/src/forge/_generated/testPlanRegistry.ts +2 -2
  129. package/src/forge/_generated/uiRoutes.json +1 -1
  130. package/src/forge/_generated/uiRoutes.ts +1 -1
  131. package/src/forge/_generated/uiScenarios.json +1 -1
  132. package/src/forge/_generated/uiScenarios.ts +1 -1
  133. package/src/forge/_generated/uiTestManifest.json +2 -2
  134. package/src/forge/_generated/uiTestManifest.ts +2 -2
  135. package/src/forge/_generated/workflowRegistry.json +2 -2
  136. package/src/forge/_generated/workflowRegistry.ts +3 -3
  137. package/src/forge/_generated/workflowSubscriptions.json +2 -2
  138. package/src/forge/_generated/workflowSubscriptions.ts +3 -3
  139. package/src/forge/cli/ai.ts +351 -1
  140. package/src/forge/cli/auth.ts +36 -1
  141. package/src/forge/cli/commands.ts +19 -0
  142. package/src/forge/cli/parse.ts +67 -8
  143. package/src/forge/cli/rls.ts +529 -17
  144. package/src/forge/cli/secrets.ts +46 -1
  145. package/src/forge/cli/security.ts +269 -0
  146. package/src/forge/compiler/agent-contract/build.ts +289 -8
  147. package/src/forge/compiler/agent-contract/types.ts +43 -0
  148. package/src/forge/compiler/ai-registry/build.ts +62 -1
  149. package/src/forge/compiler/ai-registry/constants.ts +1 -1
  150. package/src/forge/compiler/ai-registry/parse.ts +98 -4
  151. package/src/forge/compiler/app-graph/forge-apis.ts +1 -0
  152. package/src/forge/compiler/dev-manifest/build.ts +3 -0
  153. package/src/forge/compiler/diagnostics/codes.ts +15 -0
  154. package/src/forge/compiler/diagnostics/create.ts +1 -1
  155. package/src/forge/compiler/make-registry/build.ts +13 -0
  156. package/src/forge/compiler/orchestrator/plan.ts +11 -0
  157. package/src/forge/compiler/orchestrator/serialize.ts +68 -0
  158. package/src/forge/compiler/package-graph/compiler.ts +13 -3
  159. package/src/forge/compiler/types/ai-registry.ts +25 -1
  160. package/src/forge/compiler/types/app-graph.ts +1 -0
  161. package/src/forge/compiler/types/cli.ts +1 -0
  162. package/src/forge/compiler/types/dev-manifest.ts +3 -0
  163. package/src/forge/dev/server.ts +508 -1
  164. package/src/forge/make/index.ts +126 -3
  165. package/src/forge/make/templates.ts +188 -0
  166. package/src/forge/make/types.ts +1 -0
  167. package/src/forge/runtime/ai/context.ts +210 -5
  168. package/src/forge/runtime/ai/types.ts +70 -0
  169. package/src/forge/runtime/auth/claims.ts +32 -0
  170. package/src/forge/runtime/auth/errors.ts +2 -0
  171. package/src/forge/runtime/context/create-context.ts +30 -6
  172. package/src/forge/runtime/db/memory-adapter.ts +2 -2
  173. package/src/forge/runtime/telemetry/scrubber.ts +56 -5
  174. package/src/forge/runtime/webhooks/security.ts +184 -0
  175. package/src/forge/server.ts +93 -0
  176. package/src/forge/version.ts +1 -1
  177. package/templates/b2b-support-web/package.json +1 -0
  178. package/templates/b2b-support-web/tsconfig.json +4 -1
  179. package/templates/minimal-web/package.json +1 -0
  180. package/templates/minimal-web/tsconfig.json +3 -1
@@ -46,6 +46,7 @@ import type {
46
46
  AgentHttpEndpointInfo,
47
47
  AgentIntegrationInfo,
48
48
  AgentRuntimeRule,
49
+ AgentToolRegistry,
49
50
  AgentPlaybook,
50
51
  } from "./types.ts";
51
52
 
@@ -78,9 +79,11 @@ export interface AgentContractInput {
78
79
  export interface AgentContractArtifacts {
79
80
  contract: AgentContract;
80
81
  capabilityMap: AgentCapabilityMap;
82
+ toolRegistry: AgentToolRegistry;
81
83
  agentsMd: string;
82
84
  appMapMd: string;
83
85
  capabilityMapMd: string;
86
+ agentToolsMd: string;
84
87
  runtimeRulesMd: string;
85
88
  operationPlaybooksMd: string;
86
89
  agentQuickstartMd: string;
@@ -245,26 +248,26 @@ function runtimeRules(): AgentRuntimeRule[] {
245
248
  {
246
249
  context: "command",
247
250
  allowed: ["ctx.db writes", "ctx.emit", "ctx.telemetry buffered events"],
248
- forbidden: ["network packages", "ctx.secrets", "ctx.ai", "process.env", "filesystem access"],
251
+ forbidden: ["network packages", "ctx.secrets", "ctx.ai", "ctx.ai.runAgent", "ctx.agent.run", "process.env", "filesystem access"],
249
252
  },
250
253
  {
251
254
  context: "query",
252
255
  allowed: ["ctx.db reads", "ctx.telemetry buffered events"],
253
- forbidden: ["insert/update/delete", "ctx.emit", "ctx.secrets", "ctx.ai", "network integrations"],
256
+ forbidden: ["insert/update/delete", "ctx.emit", "ctx.secrets", "ctx.ai", "ctx.ai.runAgent", "ctx.agent.run", "network integrations"],
254
257
  },
255
258
  {
256
259
  context: "liveQuery",
257
260
  allowed: ["ctx.db reads", "tenant-scoped subscriptions"],
258
- forbidden: ["insert/update/delete", "ctx.emit", "ctx.secrets", "ctx.ai", "network integrations"],
261
+ forbidden: ["insert/update/delete", "ctx.emit", "ctx.secrets", "ctx.ai", "ctx.ai.runAgent", "ctx.agent.run", "network integrations"],
259
262
  },
260
263
  {
261
264
  context: "action",
262
- allowed: ["ctx.secrets", "integrations", "ctx.ai", "ctx.db reads/writes", "network packages"],
265
+ allowed: ["ctx.secrets", "integrations", "ctx.ai", "ctx.ai.runAgent", "ctx.agent.run", "AI SDK tools", "ctx.db reads/writes", "network packages"],
263
266
  forbidden: ["uncommitted transactional side effects"],
264
267
  },
265
268
  {
266
269
  context: "workflow",
267
- allowed: ["durable steps", "ctx.secrets", "integrations", "ctx.ai", "retries"],
270
+ allowed: ["durable steps", "ctx.secrets", "integrations", "ctx.ai", "ctx.ai.runAgent", "ctx.agent.run", "AI SDK ToolLoopAgent", "retries"],
268
271
  forbidden: ["non-idempotent step behavior without guards"],
269
272
  },
270
273
  ];
@@ -320,6 +323,28 @@ function playbooks(): AgentPlaybook[] {
320
323
  "Reconnect with Last-Event-ID or ?lastRevision=<revision> to verify resume behavior.",
321
324
  ],
322
325
  },
326
+ {
327
+ title: "Add an AI tool",
328
+ steps: [
329
+ "Add a server-only file under src/ai or src/tools.",
330
+ "Export aiTool({ description, inputSchema, outputSchema, risk, needsApproval, handler }).",
331
+ "Use zod schemas for inputSchema and outputSchema.",
332
+ "Access secrets through the tool context, not process.env.",
333
+ "Mark destructive or external side effects with risk and needsApproval.",
334
+ "Run forge generate and inspect src/forge/_generated/aiRegistry.json.",
335
+ ],
336
+ },
337
+ {
338
+ title: "Add an agent",
339
+ steps: [
340
+ "Export agent({ provider, model, instructions, tools, stopWhen }) from server-only source.",
341
+ "Prefer AI SDK ToolLoopAgent semantics through ctx.agent.run or ctx.ai.runAgent instead of custom loops.",
342
+ "Use stopWhen with stepCount or terminal tool calls to prevent unbounded loops.",
343
+ "Run agents only in actions, workflows, endpoints, or server code.",
344
+ "Run forge inspect all --json and confirm agentContract.ai.agents lists the agent.",
345
+ "Use forge ai trace <traceId> --json to inspect agent runs and tool calls.",
346
+ ],
347
+ },
323
348
  {
324
349
  title: "Add a table",
325
350
  steps: [
@@ -355,7 +380,8 @@ function playbooks(): AgentPlaybook[] {
355
380
  title: "Safely refactor a feature",
356
381
  steps: [
357
382
  "Run forge refactor rename field <table.field> <table.field> --dry-run --json.",
358
- "Rename codemods are AST-aware for extract-action, rename field, and rename table.",
383
+ "Run forge refactor rename command <oldName> <newName> --dry-run --json when renaming runtime entrypoints.",
384
+ "Rename codemods are AST-aware for extract-action, rename command, rename field, and rename table.",
359
385
  "Field renames are scoped to the target table, so tickets.priority only rewrites references linked to tickets.",
360
386
  "Review filesToModify, migrationPlan, diagnostics, and risk.",
361
387
  "Use --allow-high-risk only for intentional high-risk refactors.",
@@ -428,6 +454,7 @@ function playbooks(): AgentPlaybook[] {
428
454
  title: "Add or update frontend",
429
455
  steps: [
430
456
  "Run forge make ui --framework vite --dry-run --json when the app does not have a web root.",
457
+ "Run forge make ai-chat support --dry-run --json to add a chat surface backed by /ai/agents/chat streaming and /ai/agents/run JSON automation.",
431
458
  "Use web/lib/forge.ts as the generated client bridge.",
432
459
  "Mount ForgeProvider once in the web app provider/layout layer; use devAuth for local development.",
433
460
  "Use useQuery, useCommand, and useLiveQuery instead of raw /commands or /queries fetches.",
@@ -656,6 +683,78 @@ function buildCapabilityMap(contract: AgentContract): AgentCapabilityMap {
656
683
  };
657
684
  }
658
685
 
686
+ function autoToolName(kind: "command" | "query" | "liveQuery", name: string): string {
687
+ return `forge_${kind}_${name}`.replace(/[^A-Za-z0-9_$]/g, "_");
688
+ }
689
+
690
+ function buildAgentToolRegistry(contract: AgentContract): AgentToolRegistry {
691
+ const autoTools: AgentToolRegistry["autoTools"] = [
692
+ ...contract.commands.map((command) => ({
693
+ name: autoToolName("command", command.name),
694
+ sourceKind: "command" as const,
695
+ sourceName: command.name,
696
+ ...(command.policy ? { policy: command.policy } : {}),
697
+ file: command.file,
698
+ http: command.http,
699
+ frontend: command.frontend,
700
+ tablesRead: command.tablesRead,
701
+ tablesWritten: command.tablesWritten,
702
+ emits: command.emits,
703
+ dependencies: [],
704
+ readOnly: false,
705
+ risk: "write" as const,
706
+ needsApproval: true,
707
+ requiresAuth: command.policy !== undefined && command.policy !== "public",
708
+ execution: "forge-runtime-endpoint" as const,
709
+ })),
710
+ ...contract.queries.map((query) => ({
711
+ name: autoToolName("query", query.name),
712
+ sourceKind: "query" as const,
713
+ sourceName: query.name,
714
+ ...(query.policy ? { policy: query.policy } : {}),
715
+ file: query.file,
716
+ http: query.http,
717
+ frontend: query.frontend,
718
+ tablesRead: query.tablesRead,
719
+ tablesWritten: [],
720
+ emits: [],
721
+ dependencies: [],
722
+ readOnly: true,
723
+ risk: "read" as const,
724
+ needsApproval: false,
725
+ requiresAuth: query.policy !== undefined && query.policy !== "public",
726
+ execution: "forge-runtime-endpoint" as const,
727
+ })),
728
+ ...contract.liveQueries.map((liveQuery) => ({
729
+ name: autoToolName("liveQuery", liveQuery.name),
730
+ sourceKind: "liveQuery" as const,
731
+ sourceName: liveQuery.name,
732
+ ...(liveQuery.policy ? { policy: liveQuery.policy } : {}),
733
+ file: liveQuery.file,
734
+ http: liveQuery.http,
735
+ frontend: liveQuery.frontend,
736
+ tablesRead: liveQuery.tablesRead,
737
+ tablesWritten: [],
738
+ emits: [],
739
+ dependencies: liveQuery.dependencies,
740
+ readOnly: true,
741
+ risk: "read" as const,
742
+ needsApproval: false,
743
+ requiresAuth: liveQuery.policy !== undefined && liveQuery.policy !== "public",
744
+ execution: "forge-runtime-endpoint" as const,
745
+ })),
746
+ ].sort((a, b) => a.name.localeCompare(b.name));
747
+
748
+ return {
749
+ schemaVersion: "0.1.0",
750
+ generatorVersion: GENERATOR_VERSION,
751
+ project: contract.project,
752
+ explicitTools: contract.ai.tools,
753
+ autoTools,
754
+ agents: contract.ai.agents,
755
+ };
756
+ }
757
+
659
758
  function jsAccess(group: string, name: string): string {
660
759
  return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name)
661
760
  ? `api.${group}.${name}`
@@ -951,6 +1050,23 @@ export function buildAgentContractArtifacts(
951
1050
  ...(generation.purpose ? { purpose: generation.purpose } : {}),
952
1051
  }))
953
1052
  .sort((a, b) => `${a.file}:${a.method}:${a.model}`.localeCompare(`${b.file}:${b.method}:${b.model}`)),
1053
+ tools: input.aiRegistry.tools.map((tool) => ({
1054
+ name: tool.name,
1055
+ file: tool.file,
1056
+ ...(tool.description ? { description: tool.description } : {}),
1057
+ risk: tool.risk,
1058
+ strict: tool.strict,
1059
+ needsApproval: tool.needsApproval,
1060
+ })),
1061
+ agents: input.aiRegistry.agents.map((agent) => ({
1062
+ name: agent.name,
1063
+ file: agent.file,
1064
+ provider: agent.provider,
1065
+ model: agent.model,
1066
+ ...(agent.instructions ? { instructions: agent.instructions } : {}),
1067
+ tools: agent.tools,
1068
+ stopWhen: agent.stopWhen,
1069
+ })),
954
1070
  },
955
1071
  client: {
956
1072
  queries: input.clientManifest.queries,
@@ -1038,14 +1154,17 @@ export function buildAgentContractArtifacts(
1038
1154
  : null;
1039
1155
  const userNotes = extractUserNotes(existingAgents);
1040
1156
  const agentsMd = renderAgentsMd(contract, userNotes);
1157
+ const toolRegistry = buildAgentToolRegistry(contract);
1041
1158
  const capabilityMap = buildCapabilityMap(contract);
1042
1159
  const capabilityMapMd = renderCapabilityMapMd(capabilityMap);
1160
+ const agentToolsMd = renderAgentToolsMd(toolRegistry);
1043
1161
  const appMapMd = renderAppMapMd(contract);
1044
1162
  const runtimeRulesMd = renderRuntimeRulesMd(contract.rules);
1045
1163
  const operationPlaybooksMd = renderOperationPlaybooksMd(contract.playbooks);
1046
1164
  const agentQuickstartMd = renderAgentQuickstartMd();
1047
1165
  const diagnostics = scanAgentContractForLeaks(contract, [
1048
1166
  agentsMd,
1167
+ agentToolsMd,
1049
1168
  capabilityMapMd,
1050
1169
  appMapMd,
1051
1170
  runtimeRulesMd,
@@ -1056,9 +1175,11 @@ export function buildAgentContractArtifacts(
1056
1175
  return {
1057
1176
  contract,
1058
1177
  capabilityMap,
1178
+ toolRegistry,
1059
1179
  agentsMd,
1060
1180
  appMapMd,
1061
1181
  capabilityMapMd,
1182
+ agentToolsMd,
1062
1183
  runtimeRulesMd,
1063
1184
  operationPlaybooksMd,
1064
1185
  agentQuickstartMd,
@@ -1099,6 +1220,15 @@ export function serializeCapabilityMapTs(capabilityMap: AgentCapabilityMap): str
1099
1220
  return `export const capabilityMap = ${JSON.stringify(parsed, null, 2)} as const;\n`;
1100
1221
  }
1101
1222
 
1223
+ export function serializeAgentToolRegistryJson(registry: AgentToolRegistry): string {
1224
+ return serializeCanonical(registry);
1225
+ }
1226
+
1227
+ export function serializeAgentToolRegistryTs(registry: AgentToolRegistry): string {
1228
+ const parsed = JSON.parse(serializeAgentToolRegistryJson(registry)) as unknown;
1229
+ return `export const agentTools = ${JSON.stringify(parsed, null, 2)} as const;\n`;
1230
+ }
1231
+
1102
1232
  function renderAgentsMd(contract: AgentContract, userNotes: string): string {
1103
1233
  const tenantTables = contract.data.tables
1104
1234
  .filter((table) => table.tenantScoped)
@@ -1107,6 +1237,12 @@ function renderAgentsMd(contract: AgentContract, userNotes: string): string {
1107
1237
  `${policy.name}: ${policy.roles.length > 0 ? policy.roles.join(", ") : policy.kind}`,
1108
1238
  );
1109
1239
  const secrets = contract.secrets.map((secret) => `${secret.name}${secret.required ? " (required)" : " (optional)"}`);
1240
+ const aiTools = contract.ai.tools.map((tool) =>
1241
+ `${tool.name}: ${tool.description ?? "no description"} (${tool.risk}${tool.needsApproval ? ", approval" : ""})`,
1242
+ );
1243
+ const aiAgents = contract.ai.agents.map((agent) =>
1244
+ `${agent.name}: ${agent.provider}/${agent.model} with ${agent.tools.length > 0 ? agent.tools.join(", ") : "no tools"}`,
1245
+ );
1110
1246
 
1111
1247
  return normalizeNewlines(`# AGENTS.md
1112
1248
 
@@ -1178,6 +1314,7 @@ forge inspect app --json
1178
1314
  forge inspect all --json
1179
1315
  forge inspect frontend --json
1180
1316
  forge inspect capabilities --json
1317
+ forge inspect agent-tools --json
1181
1318
  forge deps inspect <package> --json
1182
1319
  forge deps api <package> <symbol> --json
1183
1320
  forge deps trace <package> --json
@@ -1191,6 +1328,9 @@ forge doctor
1191
1328
  forge doctor windows --json
1192
1329
  forge setup windows --json
1193
1330
  forge agent print-context --json
1331
+ forge ai tools --json
1332
+ forge ai agents --json
1333
+ forge ai trace <traceId> --json
1194
1334
  forge verify --smoke
1195
1335
  forge verify --standard
1196
1336
  forge verify --strict
@@ -1210,6 +1350,21 @@ ${renderList(policies)}
1210
1350
 
1211
1351
  ${renderList(secrets)}
1212
1352
 
1353
+ ## AI Tools And Agents
1354
+
1355
+ - AI SDK engine: Vercel AI SDK v6.
1356
+ - Forge layer: generated registry, runtime rules, telemetry, secrets, tenant/auth context, and agent contract.
1357
+ - Use \`ctx.agent.run\` or \`ctx.ai.runAgent\` only in actions, workflows, endpoints, and server code.
1358
+ - Do not create custom tool loops; use Forge tools and AI SDK \`ToolLoopAgent\` through the Forge runtime.
1359
+
1360
+ Tools:
1361
+
1362
+ ${renderList(aiTools)}
1363
+
1364
+ Agents:
1365
+
1366
+ ${renderList(aiAgents)}
1367
+
1213
1368
  ## Auth
1214
1369
 
1215
1370
  - Modes: ${contract.auth.modes.join(", ")}
@@ -1268,6 +1423,7 @@ Use:
1268
1423
  forge make resource <name> --fields title:text,status:enum(open,closed) --dry-run --json
1269
1424
  forge make resource <name> --fields title:text,status:enum(open,closed) --with-ui --yes
1270
1425
  forge make ui --framework vite --dry-run --json
1426
+ forge make ai-chat support --dry-run --json
1271
1427
  \`\`\`
1272
1428
 
1273
1429
  Review the plan before applying when the resource touches schema or policies.
@@ -1304,11 +1460,13 @@ Use:
1304
1460
  \`\`\`bash
1305
1461
  forge refactor rename field tickets.priority tickets.urgency --dry-run --json
1306
1462
  forge refactor rename field tickets.priority tickets.urgency --yes
1463
+ forge refactor rename command createTicket openTicket --dry-run --json
1464
+ forge refactor rename command createTicket openTicket --yes
1307
1465
  \`\`\`
1308
1466
 
1309
- These codemods are AST-aware for \`extract-action\`, \`rename field\`, and \`rename table\`. Field renames are scoped to the target table, so \`tickets.priority\` only rewrites references linked to \`tickets\`.
1467
+ These codemods are AST-aware for \`extract-action\`, \`rename command\`, \`rename field\`, and \`rename table\`. Command renames update runtime registries, generated client references, frontend hooks, tests, and string references where safe. Field renames are scoped to the target table, so \`tickets.priority\` only rewrites references linked to \`tickets\`.
1310
1468
 
1311
- Never edit \`src/forge/_generated/**\` directly. Review migration hints before applying field or table renames.
1469
+ Never edit \`src/forge/_generated/**\` directly. Review migration hints before applying command, field, or table renames.
1312
1470
 
1313
1471
  ### Plan impact-based tests
1314
1472
 
@@ -1334,6 +1492,19 @@ forge repair plan --from-last-test-run --write
1334
1492
 
1335
1493
  Apply only high-confidence deterministic repairs automatically. Review medium or low confidence repairs before changing code.
1336
1494
 
1495
+ ### Add AI tools or agents
1496
+
1497
+ Use:
1498
+
1499
+ \`\`\`bash
1500
+ forge generate
1501
+ forge inspect all --json
1502
+ forge ai check --json
1503
+ forge ai trace <traceId> --json
1504
+ \`\`\`
1505
+
1506
+ Define tools with \`aiTool({ inputSchema, outputSchema, risk, needsApproval, handler })\` and agents with \`agent({ provider, model, instructions, tools, stopWhen })\`. Execute agents with \`ctx.agent.run\` or \`ctx.ai.runAgent\` only from actions, workflows, endpoints, or server code. In dev, POST \`/ai/agents/run\` returns JSON for automation and POST \`/ai/agents/chat\` returns an AI SDK UIMessage stream for React \`useChat\`; both accept \`agent: "<exportedAgentName>"\` and use generated auto-tools from \`agentTools.json\`.
1507
+
1337
1508
  ### Export agent adapters
1338
1509
 
1339
1510
  Use:
@@ -1471,6 +1642,48 @@ function renderAppMapMd(contract: AgentContract): string {
1471
1642
  lines.push(`### ${workflow.name}`, `Trigger: ${workflow.trigger ?? "manual"}`, "Steps:", ...renderList(workflow.steps).split("\n"), "");
1472
1643
  }
1473
1644
 
1645
+ lines.push("## AI", "");
1646
+ lines.push("### Providers", "", ...renderList(contract.ai.providers).split("\n"), "");
1647
+ lines.push("### Generations", "");
1648
+ for (const generation of contract.ai.generations) {
1649
+ lines.push(
1650
+ `- ${generation.method}: ${generation.provider}/${generation.model} in ${generation.file}${generation.purpose ? ` (${generation.purpose})` : ""}`,
1651
+ );
1652
+ }
1653
+ if (contract.ai.generations.length === 0) {
1654
+ lines.push("- none");
1655
+ }
1656
+ lines.push("", "### Tools", "");
1657
+ for (const tool of contract.ai.tools) {
1658
+ lines.push(
1659
+ `#### ${tool.name}`,
1660
+ `File: ${tool.file}`,
1661
+ `Risk: ${tool.risk}`,
1662
+ `Strict: ${tool.strict ? "yes" : "no"}`,
1663
+ `Needs approval: ${String(tool.needsApproval)}`,
1664
+ `Description: ${tool.description ?? "none"}`,
1665
+ "",
1666
+ );
1667
+ }
1668
+ if (contract.ai.tools.length === 0) {
1669
+ lines.push("- none", "");
1670
+ }
1671
+ lines.push("### Agents", "");
1672
+ for (const agent of contract.ai.agents) {
1673
+ lines.push(
1674
+ `#### ${agent.name}`,
1675
+ `File: ${agent.file}`,
1676
+ `Model: ${agent.provider}/${agent.model}`,
1677
+ "Tools:",
1678
+ ...renderList(agent.tools).split("\n"),
1679
+ `Stop when: ${JSON.stringify(agent.stopWhen)}`,
1680
+ "",
1681
+ );
1682
+ }
1683
+ if (contract.ai.agents.length === 0) {
1684
+ lines.push("- none", "");
1685
+ }
1686
+
1474
1687
  lines.push("## Frontend", "");
1475
1688
  lines.push(`Present: ${contract.frontend.present ? "yes" : "no"}`);
1476
1689
  lines.push(`Framework: ${contract.frontend.framework}`);
@@ -1631,6 +1844,72 @@ function renderCapabilityMapMd(capabilityMap: AgentCapabilityMap): string {
1631
1844
  return normalizeNewlines(lines.join("\n"));
1632
1845
  }
1633
1846
 
1847
+ function renderAgentToolsMd(registry: AgentToolRegistry): string {
1848
+ const lines = [
1849
+ "# Agent Tools",
1850
+ "",
1851
+ `Project: ${registry.project.name}`,
1852
+ "",
1853
+ "## Explicit AI Tools",
1854
+ "",
1855
+ ];
1856
+
1857
+ for (const tool of registry.explicitTools) {
1858
+ lines.push(
1859
+ `### ${tool.name}`,
1860
+ `File: ${tool.file}`,
1861
+ `Risk: ${tool.risk}`,
1862
+ `Strict: ${tool.strict ? "yes" : "no"}`,
1863
+ `Needs approval: ${String(tool.needsApproval)}`,
1864
+ `Description: ${tool.description ?? "none"}`,
1865
+ "",
1866
+ );
1867
+ }
1868
+ if (registry.explicitTools.length === 0) {
1869
+ lines.push("- none", "");
1870
+ }
1871
+
1872
+ lines.push("## Auto Tools From Forge Runtime", "");
1873
+ for (const tool of registry.autoTools) {
1874
+ lines.push(
1875
+ `### ${tool.name}`,
1876
+ `Source: ${tool.sourceKind} ${tool.sourceName}`,
1877
+ `File: ${tool.file}`,
1878
+ `HTTP: ${tool.http.method} ${tool.http.path}`,
1879
+ `Policy: ${tool.policy ?? "none"}`,
1880
+ `Requires auth: ${tool.requiresAuth ? "yes" : "no"}`,
1881
+ `Read-only: ${tool.readOnly ? "yes" : "no"}`,
1882
+ `Risk: ${tool.risk}`,
1883
+ `Needs approval: ${String(tool.needsApproval)}`,
1884
+ `Frontend hook: \`${tool.frontend.hook}\``,
1885
+ `Reads: ${tool.tablesRead.length > 0 ? tool.tablesRead.join(", ") : "none"}`,
1886
+ `Writes: ${tool.tablesWritten.length > 0 ? tool.tablesWritten.join(", ") : "none"}`,
1887
+ `Emits: ${tool.emits.length > 0 ? tool.emits.join(", ") : "none"}`,
1888
+ "",
1889
+ );
1890
+ }
1891
+ if (registry.autoTools.length === 0) {
1892
+ lines.push("- none", "");
1893
+ }
1894
+
1895
+ lines.push("## Agents", "");
1896
+ for (const agent of registry.agents) {
1897
+ lines.push(
1898
+ `### ${agent.name}`,
1899
+ `File: ${agent.file}`,
1900
+ `Model: ${agent.provider}/${agent.model}`,
1901
+ `Tools: ${agent.tools.length > 0 ? agent.tools.join(", ") : "none"}`,
1902
+ `Stop when: ${JSON.stringify(agent.stopWhen)}`,
1903
+ "",
1904
+ );
1905
+ }
1906
+ if (registry.agents.length === 0) {
1907
+ lines.push("- none", "");
1908
+ }
1909
+
1910
+ return normalizeNewlines(lines.join("\n"));
1911
+ }
1912
+
1634
1913
  function renderOperationPlaybooksMd(playbookEntries: AgentPlaybook[]): string {
1635
1914
  const lines = ["# Operation Playbooks", ""];
1636
1915
  for (const playbook of playbookEntries) {
@@ -1657,7 +1936,9 @@ forge dev
1657
1936
  forge inspect all --json
1658
1937
  forge inspect frontend --json
1659
1938
  forge inspect capabilities --json
1939
+ forge inspect agent-tools --json
1660
1940
  forge check --json
1941
+ forge ai trace <traceId> --json
1661
1942
  \`\`\`
1662
1943
 
1663
1944
  Never edit:
@@ -145,6 +145,23 @@ export interface AgentAiInfo {
145
145
  file: string;
146
146
  purpose?: string;
147
147
  }>;
148
+ tools: Array<{
149
+ name: string;
150
+ file: string;
151
+ description?: string;
152
+ risk: string;
153
+ strict: boolean;
154
+ needsApproval: boolean | "dynamic";
155
+ }>;
156
+ agents: Array<{
157
+ name: string;
158
+ file: string;
159
+ provider: string;
160
+ model: string;
161
+ instructions?: string;
162
+ tools: string[];
163
+ stopWhen: unknown;
164
+ }>;
148
165
  }
149
166
 
150
167
  export interface AgentClientInfo {
@@ -306,6 +323,32 @@ export interface AgentCapabilityMap {
306
323
  diagnostics: Diagnostic[];
307
324
  }
308
325
 
326
+ export interface AgentToolRegistry {
327
+ schemaVersion: "0.1.0";
328
+ generatorVersion: string;
329
+ project: AgentProjectInfo;
330
+ explicitTools: AgentAiInfo["tools"];
331
+ autoTools: Array<{
332
+ name: string;
333
+ sourceKind: "command" | "query" | "liveQuery";
334
+ sourceName: string;
335
+ policy?: string;
336
+ file: string;
337
+ http: AgentHttpEndpointInfo;
338
+ frontend: AgentFrontendUsageInfo;
339
+ tablesRead: string[];
340
+ tablesWritten: string[];
341
+ emits: string[];
342
+ dependencies: Array<{ table: string; scope: "tenant" | "global" }>;
343
+ readOnly: boolean;
344
+ risk: "read" | "write";
345
+ needsApproval: boolean;
346
+ requiresAuth: boolean;
347
+ execution: "forge-runtime-endpoint";
348
+ }>;
349
+ agents: AgentAiInfo["agents"];
350
+ }
351
+
309
352
  export interface AgentPlaybook {
310
353
  title: string;
311
354
  steps: string[];
@@ -4,9 +4,11 @@ import { canonicalJson } from "../primitives/serialize.ts";
4
4
  import type { AppGraph } from "../types/app-graph.ts";
5
5
  import type {
6
6
  AiGenerationCall,
7
+ AiAgentDefinition,
7
8
  AiModelDefinition,
8
9
  AiProviderDefinition,
9
10
  AiRegistry,
11
+ AiToolDefinition,
10
12
  ForgeAiProvider,
11
13
  } from "../types/ai-registry.ts";
12
14
  import type { ClassifiedPackage } from "../classifier/runtime-matrix.ts";
@@ -15,9 +17,17 @@ import {
15
17
  AI_REGISTRY_ANALYZER_VERSION,
16
18
  AI_REGISTRY_SCHEMA_VERSION,
17
19
  } from "./constants.ts";
18
- import { parseAiCallsFromSlice } from "./parse.ts";
20
+ import {
21
+ parseAiAgentMeta,
22
+ parseAiCallsFromSlice,
23
+ parseAiToolMeta,
24
+ } from "./parse.ts";
19
25
 
20
26
  const KNOWN_MODELS: AiModelDefinition[] = [
27
+ {
28
+ provider: "openai",
29
+ model: "gpt-5.4",
30
+ },
21
31
  {
22
32
  provider: "openai",
23
33
  model: "gpt-4o",
@@ -30,6 +40,10 @@ const KNOWN_MODELS: AiModelDefinition[] = [
30
40
  inputCostPer1kTokensUsd: 0.00015,
31
41
  outputCostPer1kTokensUsd: 0.0006,
32
42
  },
43
+ {
44
+ provider: "anthropic",
45
+ model: "claude-sonnet-4.5",
46
+ },
33
47
  {
34
48
  provider: "anthropic",
35
49
  model: "claude-3-5-sonnet-20241022",
@@ -42,6 +56,14 @@ const KNOWN_MODELS: AiModelDefinition[] = [
42
56
  inputCostPer1kTokensUsd: 0.0008,
43
57
  outputCostPer1kTokensUsd: 0.004,
44
58
  },
59
+ {
60
+ provider: "gateway",
61
+ model: "openai/gpt-5.4",
62
+ },
63
+ {
64
+ provider: "gateway",
65
+ model: "anthropic/claude-sonnet-4.5",
66
+ },
45
67
  {
46
68
  provider: "gateway",
47
69
  model: "openai/gpt-4o",
@@ -123,12 +145,39 @@ export function buildAiRegistry(
123
145
  classified: ClassifiedPackage[],
124
146
  ): AiRegistry {
125
147
  const generations: AiGenerationCall[] = [];
148
+ const tools: AiToolDefinition[] = [];
149
+ const agents: AiAgentDefinition[] = [];
126
150
 
127
151
  for (const symbol of appGraph.symbols) {
128
152
  const sourceSlice =
129
153
  typeof symbol.meta.sourceSlice === "string" ? symbol.meta.sourceSlice : "";
130
154
  if (sourceSlice.length === 0) continue;
131
155
 
156
+ if (symbol.kind === "aiTool") {
157
+ const meta = parseAiToolMeta(sourceSlice);
158
+ tools.push({
159
+ name: symbol.name,
160
+ file: symbol.file,
161
+ ...(meta.description ? { description: meta.description } : {}),
162
+ risk: meta.risk,
163
+ strict: meta.strict,
164
+ needsApproval: meta.needsApproval,
165
+ });
166
+ }
167
+
168
+ if (symbol.kind === "agent") {
169
+ const meta = parseAiAgentMeta(sourceSlice);
170
+ agents.push({
171
+ name: symbol.name,
172
+ file: symbol.file,
173
+ provider: meta.provider ?? "gateway",
174
+ model: meta.model ?? "unknown",
175
+ ...(meta.instructions ? { instructions: meta.instructions } : {}),
176
+ tools: meta.tools,
177
+ stopWhen: meta.stopWhen,
178
+ });
179
+ }
180
+
132
181
  for (const call of parseAiCallsFromSlice(sourceSlice)) {
133
182
  generations.push({
134
183
  provider: call.provider ?? "openai",
@@ -145,6 +194,16 @@ export function buildAiRegistry(
145
194
  if (fileCmp !== 0) return fileCmp;
146
195
  return a.method.localeCompare(b.method);
147
196
  });
197
+ tools.sort((a, b) => {
198
+ const nameCmp = a.name.localeCompare(b.name);
199
+ if (nameCmp !== 0) return nameCmp;
200
+ return a.file.localeCompare(b.file);
201
+ });
202
+ agents.sort((a, b) => {
203
+ const nameCmp = a.name.localeCompare(b.name);
204
+ if (nameCmp !== 0) return nameCmp;
205
+ return a.file.localeCompare(b.file);
206
+ });
148
207
 
149
208
  return {
150
209
  schemaVersion: AI_REGISTRY_SCHEMA_VERSION,
@@ -158,6 +217,8 @@ export function buildAiRegistry(
158
217
  ),
159
218
  providers: buildProviders(classified),
160
219
  generations,
220
+ tools,
221
+ agents,
161
222
  diagnostics: [],
162
223
  };
163
224
  }
@@ -1,2 +1,2 @@
1
1
  export const AI_REGISTRY_SCHEMA_VERSION = "1";
2
- export const AI_REGISTRY_ANALYZER_VERSION = "1.0.0";
2
+ export const AI_REGISTRY_ANALYZER_VERSION = "1.1.0";