@cursor/july 0.1.1
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/AGENTS.md +290 -0
- package/LICENSE.md +3 -0
- package/README.md +1233 -0
- package/dist/ab.d.ts +290 -0
- package/dist/ab.d.ts.map +1 -0
- package/dist/ab.js +386 -0
- package/dist/bin/agent-serve.d.ts +15 -0
- package/dist/bin/agent-serve.d.ts.map +1 -0
- package/dist/bin/agent-serve.js +754 -0
- package/dist/channels/github/api.d.ts +101 -0
- package/dist/channels/github/api.d.ts.map +1 -0
- package/dist/channels/github/api.js +229 -0
- package/dist/channels/github/auth.d.ts +67 -0
- package/dist/channels/github/auth.d.ts.map +1 -0
- package/dist/channels/github/auth.js +239 -0
- package/dist/channels/github/binding.d.ts +73 -0
- package/dist/channels/github/binding.d.ts.map +1 -0
- package/dist/channels/github/binding.js +114 -0
- package/dist/channels/github/defaults.d.ts +24 -0
- package/dist/channels/github/defaults.d.ts.map +1 -0
- package/dist/channels/github/defaults.js +100 -0
- package/dist/channels/github/events.d.ts +18 -0
- package/dist/channels/github/events.d.ts.map +1 -0
- package/dist/channels/github/events.js +59 -0
- package/dist/channels/github/forward.d.ts +137 -0
- package/dist/channels/github/forward.d.ts.map +1 -0
- package/dist/channels/github/forward.js +271 -0
- package/dist/channels/github/github-channel.d.ts +127 -0
- package/dist/channels/github/github-channel.d.ts.map +1 -0
- package/dist/channels/github/github-channel.js +1688 -0
- package/dist/channels/github/index.d.ts +52 -0
- package/dist/channels/github/index.d.ts.map +1 -0
- package/dist/channels/github/index.js +51 -0
- package/dist/channels/github/replay.d.ts +115 -0
- package/dist/channels/github/replay.d.ts.map +1 -0
- package/dist/channels/github/replay.js +282 -0
- package/dist/channels/github/state.d.ts +26 -0
- package/dist/channels/github/state.d.ts.map +1 -0
- package/dist/channels/github/state.js +50 -0
- package/dist/channels/github/types.d.ts +385 -0
- package/dist/channels/github/types.d.ts.map +1 -0
- package/dist/channels/github/types.js +1 -0
- package/dist/channels/slack/api.d.ts +117 -0
- package/dist/channels/slack/api.d.ts.map +1 -0
- package/dist/channels/slack/api.js +437 -0
- package/dist/channels/slack/approvals.d.ts +32 -0
- package/dist/channels/slack/approvals.d.ts.map +1 -0
- package/dist/channels/slack/approvals.js +157 -0
- package/dist/channels/slack/assistant.d.ts +23 -0
- package/dist/channels/slack/assistant.d.ts.map +1 -0
- package/dist/channels/slack/assistant.js +67 -0
- package/dist/channels/slack/auth.d.ts +40 -0
- package/dist/channels/slack/auth.d.ts.map +1 -0
- package/dist/channels/slack/auth.js +71 -0
- package/dist/channels/slack/channel-watch.d.ts +46 -0
- package/dist/channels/slack/channel-watch.d.ts.map +1 -0
- package/dist/channels/slack/channel-watch.js +180 -0
- package/dist/channels/slack/constants.d.ts +17 -0
- package/dist/channels/slack/constants.d.ts.map +1 -0
- package/dist/channels/slack/constants.js +27 -0
- package/dist/channels/slack/credentials.d.ts +35 -0
- package/dist/channels/slack/credentials.d.ts.map +1 -0
- package/dist/channels/slack/credentials.js +88 -0
- package/dist/channels/slack/cursor-account.d.ts +76 -0
- package/dist/channels/slack/cursor-account.d.ts.map +1 -0
- package/dist/channels/slack/cursor-account.js +88 -0
- package/dist/channels/slack/defaults.d.ts +32 -0
- package/dist/channels/slack/defaults.d.ts.map +1 -0
- package/dist/channels/slack/defaults.js +380 -0
- package/dist/channels/slack/dispatch.d.ts +43 -0
- package/dist/channels/slack/dispatch.d.ts.map +1 -0
- package/dist/channels/slack/dispatch.js +185 -0
- package/dist/channels/slack/doctor.d.ts +24 -0
- package/dist/channels/slack/doctor.d.ts.map +1 -0
- package/dist/channels/slack/doctor.js +115 -0
- package/dist/channels/slack/event-id-dedupe.d.ts +10 -0
- package/dist/channels/slack/event-id-dedupe.d.ts.map +1 -0
- package/dist/channels/slack/event-id-dedupe.js +23 -0
- package/dist/channels/slack/inbound.d.ts +41 -0
- package/dist/channels/slack/inbound.d.ts.map +1 -0
- package/dist/channels/slack/inbound.js +166 -0
- package/dist/channels/slack/index.d.ts +45 -0
- package/dist/channels/slack/index.d.ts.map +1 -0
- package/dist/channels/slack/index.js +44 -0
- package/dist/channels/slack/init.d.ts +32 -0
- package/dist/channels/slack/init.d.ts.map +1 -0
- package/dist/channels/slack/init.js +82 -0
- package/dist/channels/slack/interactive.d.ts +19 -0
- package/dist/channels/slack/interactive.d.ts.map +1 -0
- package/dist/channels/slack/interactive.js +142 -0
- package/dist/channels/slack/links.d.ts +10 -0
- package/dist/channels/slack/links.d.ts.map +1 -0
- package/dist/channels/slack/links.js +19 -0
- package/dist/channels/slack/live-delivery.d.ts +75 -0
- package/dist/channels/slack/live-delivery.d.ts.map +1 -0
- package/dist/channels/slack/live-delivery.js +143 -0
- package/dist/channels/slack/log.d.ts +25 -0
- package/dist/channels/slack/log.d.ts.map +1 -0
- package/dist/channels/slack/log.js +47 -0
- package/dist/channels/slack/manifest.d.ts +57 -0
- package/dist/channels/slack/manifest.d.ts.map +1 -0
- package/dist/channels/slack/manifest.js +110 -0
- package/dist/channels/slack/pr-ref.d.ts +18 -0
- package/dist/channels/slack/pr-ref.d.ts.map +1 -0
- package/dist/channels/slack/pr-ref.js +36 -0
- package/dist/channels/slack/setup.d.ts +14 -0
- package/dist/channels/slack/setup.d.ts.map +1 -0
- package/dist/channels/slack/setup.js +86 -0
- package/dist/channels/slack/slack-channel.d.ts +15 -0
- package/dist/channels/slack/slack-channel.d.ts.map +1 -0
- package/dist/channels/slack/slack-channel.js +281 -0
- package/dist/channels/slack/socket-mode.d.ts +26 -0
- package/dist/channels/slack/socket-mode.d.ts.map +1 -0
- package/dist/channels/slack/socket-mode.js +116 -0
- package/dist/channels/slack/stream.d.ts +26 -0
- package/dist/channels/slack/stream.d.ts.map +1 -0
- package/dist/channels/slack/stream.js +39 -0
- package/dist/channels/slack/tool-approval-events.d.ts +33 -0
- package/dist/channels/slack/tool-approval-events.d.ts.map +1 -0
- package/dist/channels/slack/tool-approval-events.js +129 -0
- package/dist/channels/slack/types.d.ts +326 -0
- package/dist/channels/slack/types.d.ts.map +1 -0
- package/dist/channels/slack/types.js +4 -0
- package/dist/channels.d.ts +116 -0
- package/dist/channels.d.ts.map +1 -0
- package/dist/channels.js +294 -0
- package/dist/connections.d.ts +75 -0
- package/dist/connections.d.ts.map +1 -0
- package/dist/connections.js +75 -0
- package/dist/docs/404.html +22 -0
- package/dist/docs/ab.html +87 -0
- package/dist/docs/assets/ab.md.COdXkces.js +63 -0
- package/dist/docs/assets/ab.md.COdXkces.lean.js +1 -0
- package/dist/docs/assets/app.DqfFEmJd.js +1 -0
- package/dist/docs/assets/building-with-agents.md.CJCtZCyi.js +13 -0
- package/dist/docs/assets/building-with-agents.md.CJCtZCyi.lean.js +1 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.CcVk1uKq.js +1 -0
- package/dist/docs/assets/chunks/VPLocalSearchBox.BaLEdS15.js +9 -0
- package/dist/docs/assets/chunks/framework.CAZyNGu9.js +19 -0
- package/dist/docs/assets/chunks/theme.CZRvu_0q.js +2 -0
- package/dist/docs/assets/concepts.md.Cfb9b-k1.js +4 -0
- package/dist/docs/assets/concepts.md.Cfb9b-k1.lean.js +1 -0
- package/dist/docs/assets/deployment.md.Dx1TYNk5.js +14 -0
- package/dist/docs/assets/deployment.md.Dx1TYNk5.lean.js +1 -0
- package/dist/docs/assets/evals.md.DPZ_MAnI.js +85 -0
- package/dist/docs/assets/evals.md.DPZ_MAnI.lean.js +1 -0
- package/dist/docs/assets/guides_agent-to-agent.md.CrtrsySy.js +10 -0
- package/dist/docs/assets/guides_agent-to-agent.md.CrtrsySy.lean.js +1 -0
- package/dist/docs/assets/guides_cloud-runtime.md.CYlNMTNp.js +9 -0
- package/dist/docs/assets/guides_cloud-runtime.md.CYlNMTNp.lean.js +1 -0
- package/dist/docs/assets/guides_github.md.DwbKhCeS.js +20 -0
- package/dist/docs/assets/guides_github.md.DwbKhCeS.lean.js +1 -0
- package/dist/docs/assets/guides_human-in-the-loop.md.Dvuctx7s.js +28 -0
- package/dist/docs/assets/guides_human-in-the-loop.md.Dvuctx7s.lean.js +1 -0
- package/dist/docs/assets/guides_slack.md.bv41fHfW.js +37 -0
- package/dist/docs/assets/guides_slack.md.bv41fHfW.lean.js +1 -0
- package/dist/docs/assets/guides_webhooks.md.hFTik3lf.js +50 -0
- package/dist/docs/assets/guides_webhooks.md.hFTik3lf.lean.js +1 -0
- package/dist/docs/assets/hillclimbing.md.DHNast08.js +4 -0
- package/dist/docs/assets/hillclimbing.md.DHNast08.lean.js +1 -0
- package/dist/docs/assets/index.md.BPKcj5AI.js +20 -0
- package/dist/docs/assets/index.md.BPKcj5AI.lean.js +1 -0
- package/dist/docs/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
- package/dist/docs/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
- package/dist/docs/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
- package/dist/docs/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
- package/dist/docs/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
- package/dist/docs/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
- package/dist/docs/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
- package/dist/docs/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
- package/dist/docs/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
- package/dist/docs/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
- package/dist/docs/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
- package/dist/docs/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
- package/dist/docs/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
- package/dist/docs/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
- package/dist/docs/assets/quickstart.md.tVPiGK_L.js +35 -0
- package/dist/docs/assets/quickstart.md.tVPiGK_L.lean.js +1 -0
- package/dist/docs/assets/reference_agent-config.md.Bpd7HQwf.js +34 -0
- package/dist/docs/assets/reference_agent-config.md.Bpd7HQwf.lean.js +1 -0
- package/dist/docs/assets/reference_channels.md.D7JTR03W.js +53 -0
- package/dist/docs/assets/reference_channels.md.D7JTR03W.lean.js +1 -0
- package/dist/docs/assets/reference_cli.md.DA730zCu.js +33 -0
- package/dist/docs/assets/reference_cli.md.DA730zCu.lean.js +1 -0
- package/dist/docs/assets/reference_connections.md.C3vNH_DE.js +26 -0
- package/dist/docs/assets/reference_connections.md.C3vNH_DE.lean.js +1 -0
- package/dist/docs/assets/reference_hooks.md.BCEc3MyM.js +12 -0
- package/dist/docs/assets/reference_hooks.md.BCEc3MyM.lean.js +1 -0
- package/dist/docs/assets/reference_http-api.md.DBAahtdz.js +11 -0
- package/dist/docs/assets/reference_http-api.md.DBAahtdz.lean.js +1 -0
- package/dist/docs/assets/reference_instructions.md.BC05LEQ8.js +12 -0
- package/dist/docs/assets/reference_instructions.md.BC05LEQ8.lean.js +1 -0
- package/dist/docs/assets/reference_playground.md.CPZhfYaO.js +1 -0
- package/dist/docs/assets/reference_playground.md.CPZhfYaO.lean.js +1 -0
- package/dist/docs/assets/reference_project-layout.md.Bd_CKtNS.js +31 -0
- package/dist/docs/assets/reference_project-layout.md.Bd_CKtNS.lean.js +1 -0
- package/dist/docs/assets/reference_schedules.md.D7qijxLk.js +82 -0
- package/dist/docs/assets/reference_schedules.md.D7qijxLk.lean.js +1 -0
- package/dist/docs/assets/reference_sessions.md.DdjWabnN.js +8 -0
- package/dist/docs/assets/reference_sessions.md.DdjWabnN.lean.js +1 -0
- package/dist/docs/assets/reference_skills.md.VQnlBT3Q.js +15 -0
- package/dist/docs/assets/reference_skills.md.VQnlBT3Q.lean.js +1 -0
- package/dist/docs/assets/reference_subagents.md.CIRAVcPK.js +10 -0
- package/dist/docs/assets/reference_subagents.md.CIRAVcPK.lean.js +1 -0
- package/dist/docs/assets/reference_tools.md.DF5kwlt0.js +46 -0
- package/dist/docs/assets/reference_tools.md.DF5kwlt0.lean.js +1 -0
- package/dist/docs/assets/scaffolding-agents.md.CyYfWGdc.js +1 -0
- package/dist/docs/assets/scaffolding-agents.md.CyYfWGdc.lean.js +1 -0
- package/dist/docs/assets/style.Bj-IXa5W.css +1 -0
- package/dist/docs/assets/troubleshooting.md.DkovjS7h.js +1 -0
- package/dist/docs/assets/troubleshooting.md.DkovjS7h.lean.js +1 -0
- package/dist/docs/building-with-agents.html +37 -0
- package/dist/docs/concepts.html +28 -0
- package/dist/docs/deployment.html +38 -0
- package/dist/docs/evals.html +109 -0
- package/dist/docs/guides/agent-to-agent.html +34 -0
- package/dist/docs/guides/cloud-runtime.html +33 -0
- package/dist/docs/guides/github.html +44 -0
- package/dist/docs/guides/human-in-the-loop.html +52 -0
- package/dist/docs/guides/slack.html +61 -0
- package/dist/docs/guides/webhooks.html +74 -0
- package/dist/docs/hashmap.json +1 -0
- package/dist/docs/hillclimbing.html +28 -0
- package/dist/docs/index.html +44 -0
- package/dist/docs/quickstart.html +59 -0
- package/dist/docs/reference/agent-config.html +58 -0
- package/dist/docs/reference/channels.html +77 -0
- package/dist/docs/reference/cli.html +57 -0
- package/dist/docs/reference/connections.html +50 -0
- package/dist/docs/reference/hooks.html +36 -0
- package/dist/docs/reference/http-api.html +35 -0
- package/dist/docs/reference/instructions.html +36 -0
- package/dist/docs/reference/playground.html +25 -0
- package/dist/docs/reference/project-layout.html +55 -0
- package/dist/docs/reference/schedules.html +106 -0
- package/dist/docs/reference/sessions.html +32 -0
- package/dist/docs/reference/skills.html +39 -0
- package/dist/docs/reference/subagents.html +34 -0
- package/dist/docs/reference/tools.html +70 -0
- package/dist/docs/scaffolding-agents.html +25 -0
- package/dist/docs/troubleshooting.html +25 -0
- package/dist/docs/vp-icons.css +0 -0
- package/dist/evals.d.ts +312 -0
- package/dist/evals.d.ts.map +1 -0
- package/dist/evals.js +254 -0
- package/dist/hooks.d.ts +23 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +23 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +101 -0
- package/dist/internal/ab-collector.d.ts +42 -0
- package/dist/internal/ab-collector.d.ts.map +1 -0
- package/dist/internal/ab-collector.js +153 -0
- package/dist/internal/ab-fold.d.ts +36 -0
- package/dist/internal/ab-fold.d.ts.map +1 -0
- package/dist/internal/ab-fold.js +171 -0
- package/dist/internal/ab-snapshot.d.ts +70 -0
- package/dist/internal/ab-snapshot.d.ts.map +1 -0
- package/dist/internal/ab-snapshot.js +208 -0
- package/dist/internal/approval-gate.d.ts +34 -0
- package/dist/internal/approval-gate.d.ts.map +1 -0
- package/dist/internal/approval-gate.js +85 -0
- package/dist/internal/auth.d.ts +8 -0
- package/dist/internal/auth.d.ts.map +1 -0
- package/dist/internal/auth.js +27 -0
- package/dist/internal/authored-alias-hooks.d.ts +31 -0
- package/dist/internal/authored-alias-hooks.d.ts.map +1 -0
- package/dist/internal/authored-alias-hooks.js +43 -0
- package/dist/internal/authored-loaders.d.ts +25 -0
- package/dist/internal/authored-loaders.d.ts.map +1 -0
- package/dist/internal/authored-loaders.js +129 -0
- package/dist/internal/bounded-int.d.ts +11 -0
- package/dist/internal/bounded-int.d.ts.map +1 -0
- package/dist/internal/bounded-int.js +13 -0
- package/dist/internal/brand.d.ts +6 -0
- package/dist/internal/brand.d.ts.map +1 -0
- package/dist/internal/brand.js +17 -0
- package/dist/internal/chat-client.d.ts +19 -0
- package/dist/internal/chat-client.d.ts.map +1 -0
- package/dist/internal/chat-client.js +327 -0
- package/dist/internal/cli-ax.d.ts +58 -0
- package/dist/internal/cli-ax.d.ts.map +1 -0
- package/dist/internal/cli-ax.js +345 -0
- package/dist/internal/cli-cursor.d.ts +20 -0
- package/dist/internal/cli-cursor.d.ts.map +1 -0
- package/dist/internal/cli-cursor.js +227 -0
- package/dist/internal/cli-github.d.ts +47 -0
- package/dist/internal/cli-github.d.ts.map +1 -0
- package/dist/internal/cli-github.js +644 -0
- package/dist/internal/cli-slack.d.ts +18 -0
- package/dist/internal/cli-slack.d.ts.map +1 -0
- package/dist/internal/cli-slack.js +144 -0
- package/dist/internal/cloud-merge.d.ts +23 -0
- package/dist/internal/cloud-merge.d.ts.map +1 -0
- package/dist/internal/cloud-merge.js +28 -0
- package/dist/internal/cron.d.ts +17 -0
- package/dist/internal/cron.d.ts.map +1 -0
- package/dist/internal/cron.js +60 -0
- package/dist/internal/cursor/account-mcp.d.ts +95 -0
- package/dist/internal/cursor/account-mcp.d.ts.map +1 -0
- package/dist/internal/cursor/account-mcp.js +342 -0
- package/dist/internal/cursor/backend-client.d.ts +118 -0
- package/dist/internal/cursor/backend-client.d.ts.map +1 -0
- package/dist/internal/cursor/backend-client.js +477 -0
- package/dist/internal/cursor/credentials.d.ts +112 -0
- package/dist/internal/cursor/credentials.d.ts.map +1 -0
- package/dist/internal/cursor/credentials.js +329 -0
- package/dist/internal/cursor-event-relay.d.ts +111 -0
- package/dist/internal/cursor-event-relay.d.ts.map +1 -0
- package/dist/internal/cursor-event-relay.js +181 -0
- package/dist/internal/cursor-relay-core.d.ts +113 -0
- package/dist/internal/cursor-relay-core.d.ts.map +1 -0
- package/dist/internal/cursor-relay-core.js +364 -0
- package/dist/internal/cursor-slack-relay.d.ts +94 -0
- package/dist/internal/cursor-slack-relay.d.ts.map +1 -0
- package/dist/internal/cursor-slack-relay.js +171 -0
- package/dist/internal/describe-error.d.ts +3 -0
- package/dist/internal/describe-error.d.ts.map +1 -0
- package/dist/internal/describe-error.js +4 -0
- package/dist/internal/dev-mode.d.ts +13 -0
- package/dist/internal/dev-mode.d.ts.map +1 -0
- package/dist/internal/dev-mode.js +17 -0
- package/dist/internal/discovery.d.ts +55 -0
- package/dist/internal/discovery.d.ts.map +1 -0
- package/dist/internal/discovery.js +1427 -0
- package/dist/internal/distribution.d.ts +47 -0
- package/dist/internal/distribution.d.ts.map +1 -0
- package/dist/internal/distribution.js +82 -0
- package/dist/internal/docs-site.d.ts +28 -0
- package/dist/internal/docs-site.d.ts.map +1 -0
- package/dist/internal/docs-site.js +169 -0
- package/dist/internal/duration.d.ts +8 -0
- package/dist/internal/duration.d.ts.map +1 -0
- package/dist/internal/duration.js +35 -0
- package/dist/internal/eval-run-store.d.ts +81 -0
- package/dist/internal/eval-run-store.d.ts.map +1 -0
- package/dist/internal/eval-run-store.js +396 -0
- package/dist/internal/eval-runner.d.ts +76 -0
- package/dist/internal/eval-runner.d.ts.map +1 -0
- package/dist/internal/eval-runner.js +336 -0
- package/dist/internal/event-log.d.ts +51 -0
- package/dist/internal/event-log.d.ts.map +1 -0
- package/dist/internal/event-log.js +167 -0
- package/dist/internal/event-mapper.d.ts +46 -0
- package/dist/internal/event-mapper.d.ts.map +1 -0
- package/dist/internal/event-mapper.js +226 -0
- package/dist/internal/frontmatter.d.ts +12 -0
- package/dist/internal/frontmatter.d.ts.map +1 -0
- package/dist/internal/frontmatter.js +61 -0
- package/dist/internal/github-fanout.d.ts +32 -0
- package/dist/internal/github-fanout.d.ts.map +1 -0
- package/dist/internal/github-fanout.js +122 -0
- package/dist/internal/handleAgentServeTrigger.d.ts +59 -0
- package/dist/internal/handleAgentServeTrigger.d.ts.map +1 -0
- package/dist/internal/handleAgentServeTrigger.js +115 -0
- package/dist/internal/host-platforms.d.ts +40 -0
- package/dist/internal/host-platforms.d.ts.map +1 -0
- package/dist/internal/host-platforms.js +113 -0
- package/dist/internal/http-channel.d.ts +54 -0
- package/dist/internal/http-channel.d.ts.map +1 -0
- package/dist/internal/http-channel.js +382 -0
- package/dist/internal/ids.d.ts +19 -0
- package/dist/internal/ids.d.ts.map +1 -0
- package/dist/internal/ids.js +34 -0
- package/dist/internal/init-project.d.ts +22 -0
- package/dist/internal/init-project.d.ts.map +1 -0
- package/dist/internal/init-project.js +125 -0
- package/dist/internal/json-dir-store.d.ts +32 -0
- package/dist/internal/json-dir-store.d.ts.map +1 -0
- package/dist/internal/json-dir-store.js +100 -0
- package/dist/internal/json-schema.d.ts +18 -0
- package/dist/internal/json-schema.d.ts.map +1 -0
- package/dist/internal/json-schema.js +53 -0
- package/dist/internal/log-ring.d.ts +23 -0
- package/dist/internal/log-ring.d.ts.map +1 -0
- package/dist/internal/log-ring.js +42 -0
- package/dist/internal/map-pool.d.ts +6 -0
- package/dist/internal/map-pool.d.ts.map +1 -0
- package/dist/internal/map-pool.js +41 -0
- package/dist/internal/mcp-endpoint.d.ts +59 -0
- package/dist/internal/mcp-endpoint.d.ts.map +1 -0
- package/dist/internal/mcp-endpoint.js +355 -0
- package/dist/internal/mcp-host.d.ts +28 -0
- package/dist/internal/mcp-host.d.ts.map +1 -0
- package/dist/internal/mcp-host.js +162 -0
- package/dist/internal/net.d.ts +5 -0
- package/dist/internal/net.d.ts.map +1 -0
- package/dist/internal/net.js +30 -0
- package/dist/internal/peer-connections.d.ts +44 -0
- package/dist/internal/peer-connections.d.ts.map +1 -0
- package/dist/internal/peer-connections.js +67 -0
- package/dist/internal/playground/static.d.ts +35 -0
- package/dist/internal/playground/static.d.ts.map +1 -0
- package/dist/internal/playground/static.js +162 -0
- package/dist/internal/playground/toolchain.d.ts +57 -0
- package/dist/internal/playground/toolchain.d.ts.map +1 -0
- package/dist/internal/playground/toolchain.js +226 -0
- package/dist/internal/reminder-runner.d.ts +62 -0
- package/dist/internal/reminder-runner.d.ts.map +1 -0
- package/dist/internal/reminder-runner.js +582 -0
- package/dist/internal/reminder-schedule.d.ts +22 -0
- package/dist/internal/reminder-schedule.d.ts.map +1 -0
- package/dist/internal/reminder-schedule.js +82 -0
- package/dist/internal/reminder-store.d.ts +58 -0
- package/dist/internal/reminder-store.d.ts.map +1 -0
- package/dist/internal/reminder-store.js +106 -0
- package/dist/internal/resolved-connections.d.ts +41 -0
- package/dist/internal/resolved-connections.d.ts.map +1 -0
- package/dist/internal/resolved-connections.js +73 -0
- package/dist/internal/review-comments.d.ts +50 -0
- package/dist/internal/review-comments.d.ts.map +1 -0
- package/dist/internal/review-comments.js +194 -0
- package/dist/internal/router.d.ts +33 -0
- package/dist/internal/router.d.ts.map +1 -0
- package/dist/internal/router.js +93 -0
- package/dist/internal/run-client.d.ts +49 -0
- package/dist/internal/run-client.d.ts.map +1 -0
- package/dist/internal/run-client.js +208 -0
- package/dist/internal/schedule-runner.d.ts +31 -0
- package/dist/internal/schedule-runner.d.ts.map +1 -0
- package/dist/internal/schedule-runner.js +126 -0
- package/dist/internal/sdk-runner.d.ts +41 -0
- package/dist/internal/sdk-runner.d.ts.map +1 -0
- package/dist/internal/sdk-runner.js +221 -0
- package/dist/internal/server.d.ts +25 -0
- package/dist/internal/server.d.ts.map +1 -0
- package/dist/internal/server.js +1374 -0
- package/dist/internal/session-engine.d.ts +287 -0
- package/dist/internal/session-engine.d.ts.map +1 -0
- package/dist/internal/session-engine.js +1377 -0
- package/dist/internal/session-store.d.ts +29 -0
- package/dist/internal/session-store.d.ts.map +1 -0
- package/dist/internal/session-store.js +140 -0
- package/dist/internal/session-urls.d.ts +10 -0
- package/dist/internal/session-urls.d.ts.map +1 -0
- package/dist/internal/session-urls.js +25 -0
- package/dist/internal/stream-progress.d.ts +14 -0
- package/dist/internal/stream-progress.d.ts.map +1 -0
- package/dist/internal/stream-progress.js +195 -0
- package/dist/internal/tool-result.d.ts +20 -0
- package/dist/internal/tool-result.d.ts.map +1 -0
- package/dist/internal/tool-result.js +45 -0
- package/dist/internal/trajectory.d.ts +56 -0
- package/dist/internal/trajectory.d.ts.map +1 -0
- package/dist/internal/trajectory.js +248 -0
- package/dist/internal/workspace.d.ts +42 -0
- package/dist/internal/workspace.d.ts.map +1 -0
- package/dist/internal/workspace.js +199 -0
- package/dist/playground/assets/cursor-icons-outline-oY2V_mvK.woff2 +0 -0
- package/dist/playground/assets/index-1K-hG-7p.css +1 -0
- package/dist/playground/assets/index-FlWjhg3x.js +79 -0
- package/dist/playground/index.html +17 -0
- package/dist/reminders.d.ts +23 -0
- package/dist/reminders.d.ts.map +1 -0
- package/dist/reminders.js +22 -0
- package/dist/schedules.d.ts +23 -0
- package/dist/schedules.d.ts.map +1 -0
- package/dist/schedules.js +26 -0
- package/dist/skills.d.ts +22 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +22 -0
- package/dist/tools.d.ts +92 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +68 -0
- package/dist/types.d.ts +1609 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +80 -0
- package/docs/README.md +174 -0
- package/docs/ab.md +355 -0
- package/docs/building-with-agents.md +129 -0
- package/docs/concepts.md +177 -0
- package/docs/deployment.md +218 -0
- package/docs/evals.md +383 -0
- package/docs/guides/agent-to-agent.md +118 -0
- package/docs/guides/cloud-runtime.md +122 -0
- package/docs/guides/github.md +172 -0
- package/docs/guides/human-in-the-loop.md +131 -0
- package/docs/guides/slack.md +245 -0
- package/docs/guides/webhooks.md +201 -0
- package/docs/hillclimbing.md +93 -0
- package/docs/quickstart.md +183 -0
- package/docs/reference/agent-config.md +154 -0
- package/docs/reference/channels.md +206 -0
- package/docs/reference/cli.md +257 -0
- package/docs/reference/connections.md +137 -0
- package/docs/reference/hooks.md +74 -0
- package/docs/reference/http-api.md +219 -0
- package/docs/reference/instructions.md +77 -0
- package/docs/reference/playground.md +67 -0
- package/docs/reference/project-layout.md +119 -0
- package/docs/reference/schedules.md +220 -0
- package/docs/reference/sessions.md +165 -0
- package/docs/reference/skills.md +79 -0
- package/docs/reference/subagents.md +74 -0
- package/docs/reference/tools.md +203 -0
- package/docs/scaffolding-agents.md +113 -0
- package/docs/troubleshooting.md +95 -0
- package/package.json +185 -0
- package/skills/ab/SKILL.md +139 -0
- package/skills/create-agent/SKILL.md +219 -0
- package/skills/debug/SKILL.md +71 -0
- package/skills/evals/SKILL.md +217 -0
- package/skills/framework-map/SKILL.md +142 -0
- package/skills/github/SKILL.md +144 -0
- package/skills/hillclimb/SKILL.md +162 -0
- package/skills/setup-slack/SKILL.md +180 -0
- package/src/ab.ts +675 -0
- package/src/bin/agent-serve.ts +949 -0
- package/src/channels/github/api.ts +374 -0
- package/src/channels/github/auth.ts +275 -0
- package/src/channels/github/binding.ts +223 -0
- package/src/channels/github/defaults.ts +111 -0
- package/src/channels/github/events.ts +72 -0
- package/src/channels/github/forward.ts +381 -0
- package/src/channels/github/github-channel.ts +2270 -0
- package/src/channels/github/index.ts +52 -0
- package/src/channels/github/replay.ts +387 -0
- package/src/channels/github/state.ts +95 -0
- package/src/channels/github/types.ts +490 -0
- package/src/channels/slack/api.ts +580 -0
- package/src/channels/slack/approvals.ts +198 -0
- package/src/channels/slack/assistant.ts +76 -0
- package/src/channels/slack/auth.ts +112 -0
- package/src/channels/slack/channel-watch.ts +219 -0
- package/src/channels/slack/constants.ts +30 -0
- package/src/channels/slack/credentials.ts +90 -0
- package/src/channels/slack/cursor-account.ts +166 -0
- package/src/channels/slack/defaults.ts +450 -0
- package/src/channels/slack/dispatch.ts +293 -0
- package/src/channels/slack/doctor.ts +135 -0
- package/src/channels/slack/event-id-dedupe.ts +30 -0
- package/src/channels/slack/inbound.ts +219 -0
- package/src/channels/slack/index.ts +45 -0
- package/src/channels/slack/init.ts +133 -0
- package/src/channels/slack/interactive.ts +194 -0
- package/src/channels/slack/links.ts +23 -0
- package/src/channels/slack/live-delivery.ts +201 -0
- package/src/channels/slack/log.ts +83 -0
- package/src/channels/slack/manifest.ts +179 -0
- package/src/channels/slack/pr-ref.ts +50 -0
- package/src/channels/slack/setup.ts +97 -0
- package/src/channels/slack/slack-channel.ts +374 -0
- package/src/channels/slack/socket-mode.ts +154 -0
- package/src/channels/slack/stream.ts +63 -0
- package/src/channels/slack/tool-approval-events.ts +136 -0
- package/src/channels/slack/types.ts +349 -0
- package/src/channels.ts +385 -0
- package/src/connections.ts +89 -0
- package/src/evals.ts +501 -0
- package/src/hooks.ts +33 -0
- package/src/index.ts +184 -0
- package/src/internal/ab-collector.ts +210 -0
- package/src/internal/ab-fold.ts +228 -0
- package/src/internal/ab-snapshot.ts +335 -0
- package/src/internal/approval-gate.ts +115 -0
- package/src/internal/auth.ts +39 -0
- package/src/internal/authored-alias-hooks.ts +69 -0
- package/src/internal/authored-loaders.ts +138 -0
- package/src/internal/bounded-int.ts +27 -0
- package/src/internal/brand.ts +29 -0
- package/src/internal/chat-client.ts +407 -0
- package/src/internal/cli-ax.ts +474 -0
- package/src/internal/cli-cursor.ts +275 -0
- package/src/internal/cli-github.ts +849 -0
- package/src/internal/cli-slack.ts +175 -0
- package/src/internal/cloud-merge.ts +41 -0
- package/src/internal/cron.ts +78 -0
- package/src/internal/cursor/account-mcp.ts +472 -0
- package/src/internal/cursor/backend-client.ts +614 -0
- package/src/internal/cursor/credentials.ts +401 -0
- package/src/internal/cursor-event-relay.ts +281 -0
- package/src/internal/cursor-relay-core.ts +444 -0
- package/src/internal/cursor-slack-relay.ts +242 -0
- package/src/internal/describe-error.ts +4 -0
- package/src/internal/dev-mode.ts +20 -0
- package/src/internal/discovery.ts +1709 -0
- package/src/internal/distribution.ts +97 -0
- package/src/internal/docs-site.ts +191 -0
- package/src/internal/duration.ts +39 -0
- package/src/internal/eval-run-store.ts +519 -0
- package/src/internal/eval-runner.ts +522 -0
- package/src/internal/event-log.ts +175 -0
- package/src/internal/event-mapper.ts +272 -0
- package/src/internal/frontmatter.ts +77 -0
- package/src/internal/github-fanout.ts +159 -0
- package/src/internal/handleAgentServeTrigger.ts +156 -0
- package/src/internal/host-platforms.ts +144 -0
- package/src/internal/http-channel.ts +546 -0
- package/src/internal/ids.ts +44 -0
- package/src/internal/init-project.ts +165 -0
- package/src/internal/json-dir-store.ts +109 -0
- package/src/internal/json-schema.ts +69 -0
- package/src/internal/log-ring.ts +59 -0
- package/src/internal/map-pool.ts +32 -0
- package/src/internal/mcp-endpoint.ts +518 -0
- package/src/internal/mcp-host.ts +211 -0
- package/src/internal/net.ts +31 -0
- package/src/internal/peer-connections.ts +100 -0
- package/src/internal/playground/static.ts +189 -0
- package/src/internal/playground/toolchain.ts +277 -0
- package/src/internal/reminder-runner.ts +756 -0
- package/src/internal/reminder-schedule.ts +106 -0
- package/src/internal/reminder-store.ts +145 -0
- package/src/internal/resolved-connections.ts +145 -0
- package/src/internal/review-comments.ts +268 -0
- package/src/internal/router.ts +145 -0
- package/src/internal/run-client.ts +320 -0
- package/src/internal/schedule-runner.ts +161 -0
- package/src/internal/sdk-runner.ts +315 -0
- package/src/internal/server.ts +1737 -0
- package/src/internal/session-engine.ts +1810 -0
- package/src/internal/session-store.ts +147 -0
- package/src/internal/session-urls.ts +40 -0
- package/src/internal/stream-progress.ts +254 -0
- package/src/internal/tool-result.ts +54 -0
- package/src/internal/trajectory.ts +306 -0
- package/src/internal/workspace.ts +223 -0
- package/src/reminders.ts +35 -0
- package/src/schedules.ts +37 -0
- package/src/skills.ts +27 -0
- package/src/tools.ts +127 -0
- package/src/types.ts +1848 -0
|
@@ -0,0 +1,1709 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem discovery: walks a project's `agent/` directory and compiles
|
|
3
|
+
* the authored files (markdown and TypeScript) into an {@link AgentProject}
|
|
4
|
+
* manifest. Identity comes from the path — a tool at `agent/tools/foo.ts`
|
|
5
|
+
* is named `foo`, a subagent at `agent/subagents/researcher/` is named
|
|
6
|
+
* `researcher` — so no definition carries a `name` field.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Dirent } from "node:fs";
|
|
10
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
11
|
+
import {
|
|
12
|
+
basename,
|
|
13
|
+
extname,
|
|
14
|
+
isAbsolute,
|
|
15
|
+
join,
|
|
16
|
+
relative,
|
|
17
|
+
resolve,
|
|
18
|
+
} from "node:path";
|
|
19
|
+
import { pathToFileURL } from "node:url";
|
|
20
|
+
import { type ABConfigFile, resolveABMaxPlaygroundSessions } from "../ab.js";
|
|
21
|
+
import { httpChannel } from "../channels.js";
|
|
22
|
+
import {
|
|
23
|
+
type AgentConfigDefinition,
|
|
24
|
+
type AgentProject,
|
|
25
|
+
type AgentProjectInfo,
|
|
26
|
+
type ChannelDefinition,
|
|
27
|
+
type ConnectionDefinition,
|
|
28
|
+
type ConnectionTransport,
|
|
29
|
+
DEFAULT_MODEL,
|
|
30
|
+
type Diagnostic,
|
|
31
|
+
type DiscoveredAB,
|
|
32
|
+
type DiscoveredChannel,
|
|
33
|
+
type DiscoveredConnection,
|
|
34
|
+
type DiscoveredHook,
|
|
35
|
+
type DiscoveredSchedule,
|
|
36
|
+
type DiscoveredSkill,
|
|
37
|
+
type DiscoveredTool,
|
|
38
|
+
type HookDefinition,
|
|
39
|
+
type HttpChannelDefinition,
|
|
40
|
+
type InstructionsDefinition,
|
|
41
|
+
type ModelSetting,
|
|
42
|
+
type ResolvedAgent,
|
|
43
|
+
type ScheduleDefinition,
|
|
44
|
+
type SeedFile,
|
|
45
|
+
type SkillDefinition,
|
|
46
|
+
type SkillFileSource,
|
|
47
|
+
type ToolDefinition,
|
|
48
|
+
toPublicCloudInfo,
|
|
49
|
+
} from "../types.js";
|
|
50
|
+
import { registerAuthoredModuleLoaders } from "./authored-loaders.js";
|
|
51
|
+
import { getDefinitionKind } from "./brand.js";
|
|
52
|
+
import { isValidCron } from "./cron.js";
|
|
53
|
+
import { parseFrontmatter } from "./frontmatter.js";
|
|
54
|
+
import { toolInputJsonSchema } from "./json-schema.js";
|
|
55
|
+
import { MCP_ENDPOINT_PATH, mcpEndpointToolNames } from "./mcp-endpoint.js";
|
|
56
|
+
|
|
57
|
+
const MODULE_EXTENSIONS = new Set([".ts", ".mts", ".js", ".mjs"]);
|
|
58
|
+
const NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
|
|
59
|
+
const SKILL_DESCRIPTION_FALLBACK = (name: string): string =>
|
|
60
|
+
`Instructions for the ${name} skill.`;
|
|
61
|
+
|
|
62
|
+
export interface LoadAgentProjectOptions {
|
|
63
|
+
/** Override the module loader (embedders / tests). */
|
|
64
|
+
importModule?: (absolutePath: string) => Promise<Record<string, unknown>>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type ImportModule = (absolutePath: string) => Promise<Record<string, unknown>>;
|
|
68
|
+
|
|
69
|
+
async function defaultImportModule(
|
|
70
|
+
absolutePath: string
|
|
71
|
+
): Promise<Record<string, unknown>> {
|
|
72
|
+
return (await import(pathToFileURL(absolutePath).href)) as Record<
|
|
73
|
+
string,
|
|
74
|
+
unknown
|
|
75
|
+
>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface DiscoveryContext {
|
|
79
|
+
rootDir: string;
|
|
80
|
+
diagnostics: Diagnostic[];
|
|
81
|
+
importModule: ImportModule;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* True when `dir` looks like an agent project root: it has a nested `agent/`
|
|
86
|
+
* directory, or flat-layout markers (`agent.ts` / `instructions.md`) directly
|
|
87
|
+
* inside it.
|
|
88
|
+
*/
|
|
89
|
+
export async function isAgentProjectDir(dir: string): Promise<boolean> {
|
|
90
|
+
const resolved = resolve(dir);
|
|
91
|
+
if (!(await isDirectory(resolved))) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
if (await isDirectory(join(resolved, "agent"))) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
return hasFlatLayoutMarkers(resolved);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Discover the agent projects directly under `rootDir` (one level deep):
|
|
102
|
+
* each immediate child directory that is itself an agent project becomes a
|
|
103
|
+
* mounted agent, keyed by a slug derived from its directory name. Used for
|
|
104
|
+
* multi-agent serving, where one server hosts many agents under
|
|
105
|
+
* `/<slug>/...`. Throws when no child projects are found.
|
|
106
|
+
*/
|
|
107
|
+
export async function loadAgentProjects(
|
|
108
|
+
rootDir: string,
|
|
109
|
+
options: LoadAgentProjectOptions = {}
|
|
110
|
+
): Promise<Array<{ slug: string; project: AgentProject }>> {
|
|
111
|
+
const resolved = resolve(rootDir);
|
|
112
|
+
if (!(await isDirectory(resolved))) {
|
|
113
|
+
throw new Error(`Agent projects directory does not exist: ${resolved}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const results: Array<{ slug: string; project: AgentProject }> = [];
|
|
117
|
+
const seenSlugs = new Set<string>();
|
|
118
|
+
for (const entry of await sortedEntries(resolved)) {
|
|
119
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (entry.name === "node_modules") {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const childDir = join(resolved, entry.name);
|
|
126
|
+
if (!(await isAgentProjectDir(childDir))) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const slug = assertSlug(entry.name, resolved);
|
|
130
|
+
if (seenSlugs.has(slug)) {
|
|
131
|
+
throw new Error(`Duplicate agent slug "${slug}" under ${resolved}.`);
|
|
132
|
+
}
|
|
133
|
+
seenSlugs.add(slug);
|
|
134
|
+
results.push({ slug, project: await loadAgentProject(childDir, options) });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (results.length === 0) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
`No agent projects found directly under ${resolved}. Each agent should be a subdirectory with its own agent/ directory (or flat-layout markers).`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
return results;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Resolve mounts for `serve` / CLI. Defaults to multi-agent layout:
|
|
147
|
+
* - `mode: "single"` — one agent at `/v1/*` (empty slug).
|
|
148
|
+
* - otherwise, if `rootDir` is itself an agent project — one agent under
|
|
149
|
+
* `/<dirname>/...`.
|
|
150
|
+
* - otherwise, every immediate child agent project under `/<slug>/...`.
|
|
151
|
+
*/
|
|
152
|
+
export async function resolveAgentMounts(
|
|
153
|
+
rootDir: string,
|
|
154
|
+
options: LoadAgentProjectOptions & {
|
|
155
|
+
mode?: "single" | "multi";
|
|
156
|
+
} = {}
|
|
157
|
+
): Promise<Array<{ slug: string; project: AgentProject }>> {
|
|
158
|
+
const { mode = "multi", ...discovery } = options;
|
|
159
|
+
if (mode === "single") {
|
|
160
|
+
return [{ slug: "", project: await loadAgentProject(rootDir, discovery) }];
|
|
161
|
+
}
|
|
162
|
+
if (await isAgentProjectDir(rootDir)) {
|
|
163
|
+
const resolved = resolve(rootDir);
|
|
164
|
+
const slug = assertSlug(basename(resolved), resolved);
|
|
165
|
+
return [
|
|
166
|
+
{
|
|
167
|
+
slug,
|
|
168
|
+
project: await loadAgentProject(rootDir, discovery),
|
|
169
|
+
},
|
|
170
|
+
];
|
|
171
|
+
}
|
|
172
|
+
return loadAgentProjects(rootDir, discovery);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function assertSlug(name: string, parentDir: string): string {
|
|
176
|
+
if (!NAME_PATTERN.test(name) || RESERVED_SLUGS.has(name)) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
`Cannot mount agent directory "${name}" under ${parentDir}: its name must match ${NAME_PATTERN} and not be one of ${[...RESERVED_SLUGS].join(", ")}.`
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
return name;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** URL path segments reserved by the server; agents cannot use them as slugs. */
|
|
185
|
+
export const RESERVED_SLUGS: Set<string> = new Set([
|
|
186
|
+
"v1",
|
|
187
|
+
"playground",
|
|
188
|
+
"docs",
|
|
189
|
+
]);
|
|
190
|
+
|
|
191
|
+
export async function loadAgentProject(
|
|
192
|
+
dir: string,
|
|
193
|
+
options: LoadAgentProjectOptions = {}
|
|
194
|
+
): Promise<AgentProject> {
|
|
195
|
+
// Public API: embedders can call discovery directly (without `serve()` or
|
|
196
|
+
// the CLI), and it dynamically imports authored TypeScript.
|
|
197
|
+
await registerAuthoredModuleLoaders();
|
|
198
|
+
const rootDir = resolve(dir);
|
|
199
|
+
if (!(await isDirectory(rootDir))) {
|
|
200
|
+
throw new Error(`Agent project directory does not exist: ${rootDir}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const nestedAgentDir = join(rootDir, "agent");
|
|
204
|
+
let agentDir: string;
|
|
205
|
+
if (await isDirectory(nestedAgentDir)) {
|
|
206
|
+
agentDir = nestedAgentDir;
|
|
207
|
+
} else if (await hasFlatLayoutMarkers(rootDir)) {
|
|
208
|
+
agentDir = rootDir;
|
|
209
|
+
} else {
|
|
210
|
+
throw new Error(
|
|
211
|
+
`No agent found under ${rootDir}: expected an agent/ directory (or a flat layout with agent.ts / instructions.md at the root)`
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const diagnostics: Diagnostic[] = [];
|
|
216
|
+
const ctx: DiscoveryContext = {
|
|
217
|
+
rootDir,
|
|
218
|
+
diagnostics,
|
|
219
|
+
importModule: options.importModule ?? defaultImportModule,
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const name = (await readPackageName(rootDir)) ?? basename(rootDir);
|
|
223
|
+
const agent = await loadAgent(ctx, agentDir, {
|
|
224
|
+
name,
|
|
225
|
+
isRoot: true,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const { channels, httpChannelDefinition } = await loadChannels(
|
|
229
|
+
ctx,
|
|
230
|
+
join(agentDir, "channels")
|
|
231
|
+
);
|
|
232
|
+
const schedules = await loadSchedules(ctx, join(agentDir, "schedules"));
|
|
233
|
+
const hooks = await loadHooks(ctx, join(agentDir, "hooks"));
|
|
234
|
+
const abs = await loadABs(ctx, agentDir);
|
|
235
|
+
const abConfig = await loadABConfig(ctx, agentDir);
|
|
236
|
+
|
|
237
|
+
if (agentDir !== rootDir) {
|
|
238
|
+
await warnUnknownSlots(ctx, agentDir);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
rootDir,
|
|
243
|
+
agentDir,
|
|
244
|
+
name: agent.name,
|
|
245
|
+
agent,
|
|
246
|
+
httpChannel: httpChannelDefinition ?? httpChannel(),
|
|
247
|
+
channels,
|
|
248
|
+
schedules,
|
|
249
|
+
hooks,
|
|
250
|
+
abs,
|
|
251
|
+
...(abConfig === undefined ? {} : { abConfig }),
|
|
252
|
+
diagnostics,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** True when the project has diagnostics that must block serving. */
|
|
257
|
+
export function hasErrorDiagnostics(project: AgentProject): boolean {
|
|
258
|
+
return project.diagnostics.some((d) => d.severity === "error");
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function formatDiagnostics(diagnostics: Diagnostic[]): string {
|
|
262
|
+
return diagnostics
|
|
263
|
+
.map((d) => {
|
|
264
|
+
const location = d.path === undefined ? "" : ` (${d.path})`;
|
|
265
|
+
return `${d.severity}: ${d.message}${location}`;
|
|
266
|
+
})
|
|
267
|
+
.join("\n");
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ============================================================================
|
|
271
|
+
// Agents (root + subagents)
|
|
272
|
+
// ============================================================================
|
|
273
|
+
|
|
274
|
+
async function loadAgent(
|
|
275
|
+
ctx: DiscoveryContext,
|
|
276
|
+
agentDir: string,
|
|
277
|
+
options: { name: string; isRoot: boolean }
|
|
278
|
+
): Promise<ResolvedAgent> {
|
|
279
|
+
const config = await loadAgentConfig(ctx, agentDir, options.isRoot);
|
|
280
|
+
const instructions = await loadInstructions(
|
|
281
|
+
ctx,
|
|
282
|
+
agentDir,
|
|
283
|
+
config,
|
|
284
|
+
options.isRoot
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
// Subagents run as Cursor SDK custom subagents (prompt + description +
|
|
288
|
+
// model); their other slots are inert today, so don't load them — just
|
|
289
|
+
// warn that they exist.
|
|
290
|
+
if (!options.isRoot) {
|
|
291
|
+
await warnUnsupportedSubagentSlots(ctx, agentDir);
|
|
292
|
+
return {
|
|
293
|
+
name: config?.name ?? options.name,
|
|
294
|
+
...(config?.description === undefined
|
|
295
|
+
? {}
|
|
296
|
+
: { description: config.description }),
|
|
297
|
+
...(config?.model === undefined ? {} : { model: config.model }),
|
|
298
|
+
...(instructions === undefined ? {} : { instructions }),
|
|
299
|
+
runtime: "local",
|
|
300
|
+
tools: [],
|
|
301
|
+
skills: [],
|
|
302
|
+
connections: [],
|
|
303
|
+
subagents: [],
|
|
304
|
+
seedFiles: [],
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const tools = await loadTools(ctx, join(agentDir, "tools"));
|
|
309
|
+
const skills = await loadSkills(ctx, join(agentDir, "skills"));
|
|
310
|
+
const connections = await loadConnections(
|
|
311
|
+
ctx,
|
|
312
|
+
await resolveMcpConnectionsDir(ctx, agentDir)
|
|
313
|
+
);
|
|
314
|
+
const seedFiles = await loadSeedFiles(ctx, agentDir);
|
|
315
|
+
const subagents = await loadSubagents(ctx, join(agentDir, "subagents"));
|
|
316
|
+
const runtime = config?.runtime === "cloud" ? "cloud" : "local";
|
|
317
|
+
// Keep authored `cloud` even when default runtime is local — per-session
|
|
318
|
+
// `send({ cloud })` merges it under cloudOverride for handoff sessions.
|
|
319
|
+
const cloud = config?.cloud;
|
|
320
|
+
const localCwd = resolveAgentCwd(ctx, config?.local?.cwd);
|
|
321
|
+
const local =
|
|
322
|
+
localCwd === undefined
|
|
323
|
+
? undefined
|
|
324
|
+
: {
|
|
325
|
+
cwd: localCwd,
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
if (runtime === "cloud") {
|
|
329
|
+
warnCloudRuntimeLimitations(ctx, agentDir, {
|
|
330
|
+
serverTools: tools.filter((tool) => tool.execution === "server").length,
|
|
331
|
+
agentTools: tools.filter((tool) => tool.execution === "agent").length,
|
|
332
|
+
skills: skills.length,
|
|
333
|
+
seedFiles: seedFiles.length,
|
|
334
|
+
hasCloudOptions: cloud !== undefined,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
name: config?.name ?? options.name,
|
|
340
|
+
description: config?.description,
|
|
341
|
+
// Root agents always get a concrete model: authored value or DEFAULT_MODEL.
|
|
342
|
+
model: isValidModelSetting(config?.model) ? config.model : DEFAULT_MODEL,
|
|
343
|
+
instructions,
|
|
344
|
+
runtime,
|
|
345
|
+
cloud,
|
|
346
|
+
local,
|
|
347
|
+
tools,
|
|
348
|
+
skills,
|
|
349
|
+
connections,
|
|
350
|
+
subagents,
|
|
351
|
+
seedFiles,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** Absolute harness cwd parent, or undefined when unset / blank. */
|
|
356
|
+
function resolveAgentCwd(
|
|
357
|
+
ctx: DiscoveryContext,
|
|
358
|
+
authored: string | undefined
|
|
359
|
+
): string | undefined {
|
|
360
|
+
if (authored === undefined) {
|
|
361
|
+
return undefined;
|
|
362
|
+
}
|
|
363
|
+
const trimmed = authored.trim();
|
|
364
|
+
if (trimmed === "") {
|
|
365
|
+
return undefined;
|
|
366
|
+
}
|
|
367
|
+
return isAbsolute(trimmed) ? trimmed : resolve(ctx.rootDir, trimmed);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async function loadAgentConfig(
|
|
371
|
+
ctx: DiscoveryContext,
|
|
372
|
+
agentDir: string,
|
|
373
|
+
isRoot: boolean
|
|
374
|
+
): Promise<AgentConfigDefinition | undefined> {
|
|
375
|
+
const configPath = await findModuleFile(agentDir, "agent");
|
|
376
|
+
if (configPath === undefined) {
|
|
377
|
+
ctx.diagnostics.push({
|
|
378
|
+
severity: "error",
|
|
379
|
+
path: displayPath(ctx, join(agentDir, "agent.ts")),
|
|
380
|
+
message: isRoot
|
|
381
|
+
? "agent.ts is required on the root agent: default-export defineAgent({ ... })"
|
|
382
|
+
: "agent.ts is required on a subagent: default-export defineAgent({ description: ... })",
|
|
383
|
+
});
|
|
384
|
+
return undefined;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const definition = await importDefault(ctx, configPath, "agent");
|
|
388
|
+
if (definition === undefined) {
|
|
389
|
+
return undefined;
|
|
390
|
+
}
|
|
391
|
+
const config = definition as AgentConfigDefinition;
|
|
392
|
+
|
|
393
|
+
if (!isRoot && isBlank(config.description)) {
|
|
394
|
+
ctx.diagnostics.push({
|
|
395
|
+
severity: "error",
|
|
396
|
+
path: displayPath(ctx, configPath),
|
|
397
|
+
message:
|
|
398
|
+
"Subagents require a description; the parent model reads it to decide when to delegate.",
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
return config;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async function loadInstructions(
|
|
405
|
+
ctx: DiscoveryContext,
|
|
406
|
+
agentDir: string,
|
|
407
|
+
config: AgentConfigDefinition | undefined,
|
|
408
|
+
isRoot: boolean
|
|
409
|
+
): Promise<string | undefined> {
|
|
410
|
+
const parts: string[] = [];
|
|
411
|
+
|
|
412
|
+
const markdownPath = join(agentDir, "instructions.md");
|
|
413
|
+
if (await isFile(markdownPath)) {
|
|
414
|
+
parts.push((await readFile(markdownPath, "utf8")).trim());
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const modulePath = await findModuleFile(agentDir, "instructions");
|
|
418
|
+
if (modulePath !== undefined) {
|
|
419
|
+
const loaded = await loadInstructionsModule(ctx, modulePath);
|
|
420
|
+
if (loaded !== undefined) {
|
|
421
|
+
parts.push(loaded);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const instructionsDir = join(agentDir, "instructions");
|
|
426
|
+
if (await isDirectory(instructionsDir)) {
|
|
427
|
+
for (const entry of await sortedEntries(instructionsDir)) {
|
|
428
|
+
if (!entry.isFile()) {
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
const entryPath = join(instructionsDir, entry.name);
|
|
432
|
+
if (entry.name.endsWith(".md")) {
|
|
433
|
+
parts.push((await readFile(entryPath, "utf8")).trim());
|
|
434
|
+
} else if (isModuleFile(entry.name)) {
|
|
435
|
+
const loaded = await loadInstructionsModule(ctx, entryPath);
|
|
436
|
+
if (loaded !== undefined) {
|
|
437
|
+
parts.push(loaded);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (config?.instructions !== undefined && !isBlank(config.instructions)) {
|
|
444
|
+
parts.push(config.instructions.trim());
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const combined = parts.filter((part) => part !== "").join("\n\n");
|
|
448
|
+
if (combined === "") {
|
|
449
|
+
if (isRoot) {
|
|
450
|
+
ctx.diagnostics.push({
|
|
451
|
+
severity: "error",
|
|
452
|
+
path: displayPath(ctx, markdownPath),
|
|
453
|
+
message:
|
|
454
|
+
"instructions.md is required on the root agent (the always-on system prompt).",
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
return undefined;
|
|
458
|
+
}
|
|
459
|
+
return combined;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
async function loadInstructionsModule(
|
|
463
|
+
ctx: DiscoveryContext,
|
|
464
|
+
modulePath: string
|
|
465
|
+
): Promise<string | undefined> {
|
|
466
|
+
let mod: Record<string, unknown>;
|
|
467
|
+
try {
|
|
468
|
+
mod = await ctx.importModule(modulePath);
|
|
469
|
+
} catch (error) {
|
|
470
|
+
ctx.diagnostics.push({
|
|
471
|
+
severity: "error",
|
|
472
|
+
path: displayPath(ctx, modulePath),
|
|
473
|
+
message: `Failed to load module: ${errorMessage(error)}`,
|
|
474
|
+
});
|
|
475
|
+
return undefined;
|
|
476
|
+
}
|
|
477
|
+
const value = mod.default;
|
|
478
|
+
if (typeof value === "string") {
|
|
479
|
+
return value.trim();
|
|
480
|
+
}
|
|
481
|
+
if (getDefinitionKind(value) === "instructions") {
|
|
482
|
+
return (value as InstructionsDefinition).markdown.trim();
|
|
483
|
+
}
|
|
484
|
+
ctx.diagnostics.push({
|
|
485
|
+
severity: "error",
|
|
486
|
+
path: displayPath(ctx, modulePath),
|
|
487
|
+
message:
|
|
488
|
+
"Instructions modules must default-export a string or defineInstructions({ markdown }).",
|
|
489
|
+
});
|
|
490
|
+
return undefined;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// ============================================================================
|
|
494
|
+
// Tools
|
|
495
|
+
// ============================================================================
|
|
496
|
+
|
|
497
|
+
async function loadTools(
|
|
498
|
+
ctx: DiscoveryContext,
|
|
499
|
+
toolsDir: string
|
|
500
|
+
): Promise<DiscoveredTool[]> {
|
|
501
|
+
if (!(await isDirectory(toolsDir))) {
|
|
502
|
+
return [];
|
|
503
|
+
}
|
|
504
|
+
const tools: DiscoveredTool[] = [];
|
|
505
|
+
for (const entry of await sortedEntries(toolsDir)) {
|
|
506
|
+
const entryPath = join(toolsDir, entry.name);
|
|
507
|
+
if (entry.isDirectory()) {
|
|
508
|
+
ctx.diagnostics.push({
|
|
509
|
+
severity: "warning",
|
|
510
|
+
path: displayPath(ctx, entryPath),
|
|
511
|
+
message: "Nested tool directories are not supported; ignoring.",
|
|
512
|
+
});
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
if (!entry.isFile() || !isModuleFile(entry.name)) {
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
const name = moduleStem(entry.name);
|
|
519
|
+
if (!NAME_PATTERN.test(name)) {
|
|
520
|
+
ctx.diagnostics.push({
|
|
521
|
+
severity: "error",
|
|
522
|
+
path: displayPath(ctx, entryPath),
|
|
523
|
+
message: `Tool name "${name}" must match ${NAME_PATTERN} (the filename becomes the tool name the model sees).`,
|
|
524
|
+
});
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
const definition = await importDefault(ctx, entryPath, "tool");
|
|
528
|
+
if (definition === undefined) {
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
const tool = definition as ToolDefinition;
|
|
532
|
+
if (isBlank(tool.description)) {
|
|
533
|
+
ctx.diagnostics.push({
|
|
534
|
+
severity: "error",
|
|
535
|
+
path: displayPath(ctx, entryPath),
|
|
536
|
+
message: "Tools require a non-empty description.",
|
|
537
|
+
});
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
const execution = tool.execution === "agent" ? "agent" : "server";
|
|
541
|
+
const needsApprovalConfigured =
|
|
542
|
+
tool.needsApproval === true || typeof tool.needsApproval === "function";
|
|
543
|
+
if (execution === "agent") {
|
|
544
|
+
if (isBlank(tool.script)) {
|
|
545
|
+
ctx.diagnostics.push({
|
|
546
|
+
severity: "error",
|
|
547
|
+
path: displayPath(ctx, entryPath),
|
|
548
|
+
message:
|
|
549
|
+
'Tools with execution: "agent" require a non-empty `script` (shell body; JSON on stdin, result on stdout).',
|
|
550
|
+
});
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
if (typeof tool.execute === "function") {
|
|
554
|
+
ctx.diagnostics.push({
|
|
555
|
+
severity: "warning",
|
|
556
|
+
path: displayPath(ctx, entryPath),
|
|
557
|
+
message:
|
|
558
|
+
'`execute` is ignored when execution is "agent"; the Cursor agent runs `script` instead.',
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
if (needsApprovalConfigured) {
|
|
562
|
+
ctx.diagnostics.push({
|
|
563
|
+
severity: "error",
|
|
564
|
+
path: displayPath(ctx, entryPath),
|
|
565
|
+
message:
|
|
566
|
+
'`needsApproval` is only supported for execution: "server" tools (in-process).',
|
|
567
|
+
});
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
} else if (typeof tool.execute !== "function") {
|
|
571
|
+
ctx.diagnostics.push({
|
|
572
|
+
severity: "error",
|
|
573
|
+
path: displayPath(ctx, entryPath),
|
|
574
|
+
message:
|
|
575
|
+
'Tools require an execute(input, ctx) function (or set execution: "agent" with a script).',
|
|
576
|
+
});
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
let inputSchema: DiscoveredTool["inputSchema"];
|
|
580
|
+
try {
|
|
581
|
+
inputSchema = toolInputJsonSchema(tool.inputSchema);
|
|
582
|
+
} catch (error) {
|
|
583
|
+
ctx.diagnostics.push({
|
|
584
|
+
severity: "error",
|
|
585
|
+
path: displayPath(ctx, entryPath),
|
|
586
|
+
message: `Failed to convert inputSchema to JSON Schema: ${errorMessage(error)}`,
|
|
587
|
+
});
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
tools.push({
|
|
591
|
+
name,
|
|
592
|
+
description: tool.description,
|
|
593
|
+
inputSchema,
|
|
594
|
+
execution,
|
|
595
|
+
needsApproval: needsApprovalConfigured,
|
|
596
|
+
...(execution === "agent" && tool.script !== undefined
|
|
597
|
+
? { script: tool.script }
|
|
598
|
+
: {}),
|
|
599
|
+
definition: tool,
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
return tools;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// ============================================================================
|
|
606
|
+
// Skills
|
|
607
|
+
// ============================================================================
|
|
608
|
+
|
|
609
|
+
async function loadSkills(
|
|
610
|
+
ctx: DiscoveryContext,
|
|
611
|
+
skillsDir: string
|
|
612
|
+
): Promise<DiscoveredSkill[]> {
|
|
613
|
+
if (!(await isDirectory(skillsDir))) {
|
|
614
|
+
return [];
|
|
615
|
+
}
|
|
616
|
+
const skills: DiscoveredSkill[] = [];
|
|
617
|
+
for (const entry of await sortedEntries(skillsDir)) {
|
|
618
|
+
const entryPath = join(skillsDir, entry.name);
|
|
619
|
+
if (entry.isDirectory()) {
|
|
620
|
+
const skill = await loadPackagedSkill(ctx, entryPath, entry.name);
|
|
621
|
+
if (skill !== undefined) {
|
|
622
|
+
skills.push(skill);
|
|
623
|
+
}
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
if (!entry.isFile()) {
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
if (entry.name.endsWith(".md")) {
|
|
630
|
+
const name = entry.name.slice(0, -".md".length);
|
|
631
|
+
if (!validateSkillName(ctx, name, entryPath)) {
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
const { data, body } = parseFrontmatter(
|
|
635
|
+
await readFile(entryPath, "utf8")
|
|
636
|
+
);
|
|
637
|
+
skills.push({
|
|
638
|
+
name,
|
|
639
|
+
description:
|
|
640
|
+
data.description ??
|
|
641
|
+
deriveSkillDescription(body) ??
|
|
642
|
+
SKILL_DESCRIPTION_FALLBACK(name),
|
|
643
|
+
markdown: body.trim(),
|
|
644
|
+
files: {},
|
|
645
|
+
});
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
if (isModuleFile(entry.name)) {
|
|
649
|
+
const name = moduleStem(entry.name);
|
|
650
|
+
if (!validateSkillName(ctx, name, entryPath)) {
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
const definition = await importDefault(ctx, entryPath, "skill");
|
|
654
|
+
if (definition === undefined) {
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
const skill = definition as SkillDefinition;
|
|
658
|
+
const files: Record<string, SkillFileSource> = {};
|
|
659
|
+
for (const [rel, contents] of Object.entries(skill.files ?? {})) {
|
|
660
|
+
files[rel] = { contents };
|
|
661
|
+
}
|
|
662
|
+
skills.push({
|
|
663
|
+
name,
|
|
664
|
+
description:
|
|
665
|
+
skill.description ??
|
|
666
|
+
deriveSkillDescription(skill.markdown) ??
|
|
667
|
+
SKILL_DESCRIPTION_FALLBACK(name),
|
|
668
|
+
markdown: skill.markdown.trim(),
|
|
669
|
+
files,
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return skills;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
async function loadPackagedSkill(
|
|
677
|
+
ctx: DiscoveryContext,
|
|
678
|
+
skillDir: string,
|
|
679
|
+
name: string
|
|
680
|
+
): Promise<DiscoveredSkill | undefined> {
|
|
681
|
+
const skillMdPath = join(skillDir, "SKILL.md");
|
|
682
|
+
if (!(await isFile(skillMdPath))) {
|
|
683
|
+
ctx.diagnostics.push({
|
|
684
|
+
severity: "warning",
|
|
685
|
+
path: displayPath(ctx, skillDir),
|
|
686
|
+
message: "Skill directories must contain a SKILL.md; ignoring.",
|
|
687
|
+
});
|
|
688
|
+
return undefined;
|
|
689
|
+
}
|
|
690
|
+
if (!validateSkillName(ctx, name, skillDir)) {
|
|
691
|
+
return undefined;
|
|
692
|
+
}
|
|
693
|
+
const { data, body } = parseFrontmatter(await readFile(skillMdPath, "utf8"));
|
|
694
|
+
if (data.description === undefined || isBlank(data.description)) {
|
|
695
|
+
ctx.diagnostics.push({
|
|
696
|
+
severity: "error",
|
|
697
|
+
path: displayPath(ctx, skillMdPath),
|
|
698
|
+
message:
|
|
699
|
+
"Packaged skills require `description` frontmatter in SKILL.md (there is no filename slug to fall back on).",
|
|
700
|
+
});
|
|
701
|
+
return undefined;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const files: Record<string, SkillFileSource> = {};
|
|
705
|
+
for (const filePath of await walkFiles(skillDir)) {
|
|
706
|
+
const rel = toPosix(relative(skillDir, filePath));
|
|
707
|
+
if (rel === "SKILL.md") {
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
710
|
+
files[rel] = { sourcePath: filePath };
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
return {
|
|
714
|
+
name,
|
|
715
|
+
description: data.description,
|
|
716
|
+
markdown: body.trim(),
|
|
717
|
+
files,
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/** One-line `/v1/info` label for a connection transport. */
|
|
722
|
+
function connectionTransportLabel(transport: ConnectionTransport): string {
|
|
723
|
+
if ("cursorAccount" in transport) {
|
|
724
|
+
return transport.servers === undefined
|
|
725
|
+
? "cursor-account"
|
|
726
|
+
: `cursor-account:${transport.servers.join(",")}`;
|
|
727
|
+
}
|
|
728
|
+
if ("agent" in transport) {
|
|
729
|
+
return `agent:${transport.agent}`;
|
|
730
|
+
}
|
|
731
|
+
if ("url" in transport) {
|
|
732
|
+
return transport.url;
|
|
733
|
+
}
|
|
734
|
+
return transport.command;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function connectionTransport(
|
|
738
|
+
connection: ConnectionDefinition
|
|
739
|
+
): ConnectionTransport | undefined {
|
|
740
|
+
if ("cursorAccount" in connection && connection.cursorAccount === true) {
|
|
741
|
+
return {
|
|
742
|
+
cursorAccount: true,
|
|
743
|
+
...(connection.servers === undefined
|
|
744
|
+
? {}
|
|
745
|
+
: { servers: connection.servers }),
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
if ("agent" in connection && !isBlank(connection.agent)) {
|
|
749
|
+
return { agent: connection.agent };
|
|
750
|
+
}
|
|
751
|
+
if ("url" in connection && !isBlank(connection.url)) {
|
|
752
|
+
return {
|
|
753
|
+
...(connection.type === undefined ? {} : { type: connection.type }),
|
|
754
|
+
url: connection.url,
|
|
755
|
+
...(connection.headers === undefined
|
|
756
|
+
? {}
|
|
757
|
+
: { headers: connection.headers }),
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
if ("command" in connection && !isBlank(connection.command)) {
|
|
761
|
+
return {
|
|
762
|
+
...(connection.type === undefined ? {} : { type: connection.type }),
|
|
763
|
+
command: connection.command,
|
|
764
|
+
...(connection.args === undefined ? {} : { args: connection.args }),
|
|
765
|
+
...(connection.env === undefined ? {} : { env: connection.env }),
|
|
766
|
+
...(connection.cwd === undefined ? {} : { cwd: connection.cwd }),
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
return undefined;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function validateSkillName(
|
|
773
|
+
ctx: DiscoveryContext,
|
|
774
|
+
name: string,
|
|
775
|
+
entryPath: string
|
|
776
|
+
): boolean {
|
|
777
|
+
if (NAME_PATTERN.test(name)) {
|
|
778
|
+
return true;
|
|
779
|
+
}
|
|
780
|
+
ctx.diagnostics.push({
|
|
781
|
+
severity: "error",
|
|
782
|
+
path: displayPath(ctx, entryPath),
|
|
783
|
+
message: `Skill name "${name}" must match ${NAME_PATTERN}.`,
|
|
784
|
+
});
|
|
785
|
+
return false;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* Advertise the first non-empty, non-code-fence line of the body, with any
|
|
790
|
+
* leading `#`, `>`, `*`, or `-` markers stripped.
|
|
791
|
+
*/
|
|
792
|
+
export function deriveSkillDescription(markdown: string): string | undefined {
|
|
793
|
+
let inFence = false;
|
|
794
|
+
for (const rawLine of markdown.split(/\r?\n/)) {
|
|
795
|
+
const line = rawLine.trim();
|
|
796
|
+
if (line.startsWith("```")) {
|
|
797
|
+
inFence = !inFence;
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
if (inFence || line === "") {
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
const stripped = line.replace(/^[#>*\s-]+/, "").trim();
|
|
804
|
+
if (stripped !== "") {
|
|
805
|
+
return stripped.length > 200 ? `${stripped.slice(0, 197)}...` : stripped;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return undefined;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// ============================================================================
|
|
812
|
+
// MCP connections
|
|
813
|
+
// ============================================================================
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* Prefer `agent/mcp-connections/`. Keep loading `agent/connections/` with a
|
|
817
|
+
* deprecation warning so existing agents keep working during the rename.
|
|
818
|
+
*/
|
|
819
|
+
async function resolveMcpConnectionsDir(
|
|
820
|
+
ctx: DiscoveryContext,
|
|
821
|
+
agentDir: string
|
|
822
|
+
): Promise<string> {
|
|
823
|
+
const preferred = join(agentDir, "mcp-connections");
|
|
824
|
+
const legacy = join(agentDir, "connections");
|
|
825
|
+
const hasPreferred = await isDirectory(preferred);
|
|
826
|
+
const hasLegacy = await isDirectory(legacy);
|
|
827
|
+
if (hasPreferred && hasLegacy) {
|
|
828
|
+
ctx.diagnostics.push({
|
|
829
|
+
severity: "warning",
|
|
830
|
+
path: displayPath(ctx, legacy),
|
|
831
|
+
message:
|
|
832
|
+
"Both agent/mcp-connections/ and agent/connections/ exist; using mcp-connections/ and ignoring connections/. Rename or remove the legacy directory.",
|
|
833
|
+
});
|
|
834
|
+
return preferred;
|
|
835
|
+
}
|
|
836
|
+
if (hasLegacy) {
|
|
837
|
+
ctx.diagnostics.push({
|
|
838
|
+
severity: "warning",
|
|
839
|
+
path: displayPath(ctx, legacy),
|
|
840
|
+
message:
|
|
841
|
+
"agent/connections/ is deprecated; rename it to agent/mcp-connections/. Loading from the legacy path for now.",
|
|
842
|
+
});
|
|
843
|
+
return legacy;
|
|
844
|
+
}
|
|
845
|
+
return preferred;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
async function loadConnections(
|
|
849
|
+
ctx: DiscoveryContext,
|
|
850
|
+
connectionsDir: string
|
|
851
|
+
): Promise<DiscoveredConnection[]> {
|
|
852
|
+
if (!(await isDirectory(connectionsDir))) {
|
|
853
|
+
return [];
|
|
854
|
+
}
|
|
855
|
+
const connections: DiscoveredConnection[] = [];
|
|
856
|
+
for (const entry of await sortedEntries(connectionsDir)) {
|
|
857
|
+
const entryPath = join(connectionsDir, entry.name);
|
|
858
|
+
if (!entry.isFile() || !isModuleFile(entry.name)) {
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
const name = moduleStem(entry.name);
|
|
862
|
+
if (!NAME_PATTERN.test(name)) {
|
|
863
|
+
ctx.diagnostics.push({
|
|
864
|
+
severity: "error",
|
|
865
|
+
path: displayPath(ctx, entryPath),
|
|
866
|
+
message: `MCP connection name "${name}" must match ${NAME_PATTERN}.`,
|
|
867
|
+
});
|
|
868
|
+
continue;
|
|
869
|
+
}
|
|
870
|
+
const definition = await importDefault(ctx, entryPath, "connection");
|
|
871
|
+
if (definition === undefined) {
|
|
872
|
+
continue;
|
|
873
|
+
}
|
|
874
|
+
const connection = definition as ConnectionDefinition;
|
|
875
|
+
const transport = connectionTransport(connection);
|
|
876
|
+
if (transport === undefined) {
|
|
877
|
+
ctx.diagnostics.push({
|
|
878
|
+
severity: "error",
|
|
879
|
+
path: displayPath(ctx, entryPath),
|
|
880
|
+
message:
|
|
881
|
+
"MCP connections require a `url` (http/sse), a `command` (stdio), an `agent` (peer agent slug on the same serve host), or `cursorAccount: true` (the Cursor account's MCP connectors).",
|
|
882
|
+
});
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
if (
|
|
886
|
+
"cursorAccount" in transport &&
|
|
887
|
+
transport.servers !== undefined &&
|
|
888
|
+
(transport.servers.length === 0 ||
|
|
889
|
+
transport.servers.some((server) => isBlank(server)))
|
|
890
|
+
) {
|
|
891
|
+
ctx.diagnostics.push({
|
|
892
|
+
severity: "error",
|
|
893
|
+
path: displayPath(ctx, entryPath),
|
|
894
|
+
message:
|
|
895
|
+
"Cursor account MCP connection `servers` must be a non-empty list of connector names (or omitted to expose every connected server).",
|
|
896
|
+
});
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
if ("agent" in transport && !NAME_PATTERN.test(transport.agent)) {
|
|
900
|
+
ctx.diagnostics.push({
|
|
901
|
+
severity: "error",
|
|
902
|
+
path: displayPath(ctx, entryPath),
|
|
903
|
+
message: `Peer MCP connection agent "${transport.agent}" must be a mount slug matching ${NAME_PATTERN}.`,
|
|
904
|
+
});
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
connections.push({
|
|
908
|
+
name,
|
|
909
|
+
transport,
|
|
910
|
+
...(connection.description === undefined
|
|
911
|
+
? {}
|
|
912
|
+
: { description: connection.description }),
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
return connections;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// ============================================================================
|
|
919
|
+
// Subagents
|
|
920
|
+
// ============================================================================
|
|
921
|
+
|
|
922
|
+
async function loadSubagents(
|
|
923
|
+
ctx: DiscoveryContext,
|
|
924
|
+
subagentsDir: string
|
|
925
|
+
): Promise<ResolvedAgent[]> {
|
|
926
|
+
if (!(await isDirectory(subagentsDir))) {
|
|
927
|
+
return [];
|
|
928
|
+
}
|
|
929
|
+
const subagents: ResolvedAgent[] = [];
|
|
930
|
+
for (const entry of await sortedEntries(subagentsDir)) {
|
|
931
|
+
if (!entry.isDirectory()) {
|
|
932
|
+
continue;
|
|
933
|
+
}
|
|
934
|
+
const subagentDir = join(subagentsDir, entry.name);
|
|
935
|
+
if (!NAME_PATTERN.test(entry.name)) {
|
|
936
|
+
ctx.diagnostics.push({
|
|
937
|
+
severity: "error",
|
|
938
|
+
path: displayPath(ctx, subagentDir),
|
|
939
|
+
message: `Subagent name "${entry.name}" must match ${NAME_PATTERN}.`,
|
|
940
|
+
});
|
|
941
|
+
continue;
|
|
942
|
+
}
|
|
943
|
+
subagents.push(
|
|
944
|
+
await loadAgent(ctx, subagentDir, { name: entry.name, isRoot: false })
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
return subagents;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
async function warnUnsupportedSubagentSlots(
|
|
951
|
+
ctx: DiscoveryContext,
|
|
952
|
+
subagentDir: string
|
|
953
|
+
): Promise<void> {
|
|
954
|
+
const unsupported = [
|
|
955
|
+
["tools", "tools"],
|
|
956
|
+
["skills", "skills"],
|
|
957
|
+
["mcp-connections", "MCP connections"],
|
|
958
|
+
["connections", "MCP connections"],
|
|
959
|
+
["subagents", "nested subagents"],
|
|
960
|
+
["channels", "channels"],
|
|
961
|
+
["schedules", "schedules"],
|
|
962
|
+
["hooks", "hooks"],
|
|
963
|
+
["sandbox", "sandbox files"],
|
|
964
|
+
] as const;
|
|
965
|
+
for (const [slot, label] of unsupported) {
|
|
966
|
+
if (await isDirectory(join(subagentDir, slot))) {
|
|
967
|
+
ctx.diagnostics.push({
|
|
968
|
+
severity: "warning",
|
|
969
|
+
path: displayPath(ctx, join(subagentDir, slot)),
|
|
970
|
+
message: `Subagent ${label} are not supported on the Cursor harness yet; the subagent runs with the parent's execution surface. Ignoring.`,
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
function warnCloudRuntimeLimitations(
|
|
977
|
+
ctx: DiscoveryContext,
|
|
978
|
+
agentDir: string,
|
|
979
|
+
counts: {
|
|
980
|
+
serverTools: number;
|
|
981
|
+
agentTools: number;
|
|
982
|
+
skills: number;
|
|
983
|
+
seedFiles: number;
|
|
984
|
+
hasCloudOptions: boolean;
|
|
985
|
+
}
|
|
986
|
+
): void {
|
|
987
|
+
const agentPath = displayPath(ctx, join(agentDir, "agent.ts"));
|
|
988
|
+
if (!counts.hasCloudOptions) {
|
|
989
|
+
ctx.diagnostics.push({
|
|
990
|
+
severity: "warning",
|
|
991
|
+
path: agentPath,
|
|
992
|
+
message:
|
|
993
|
+
'runtime: "cloud" usually needs a `cloud` block (e.g. repos / env). Without it the Cursor cloud API may reject create.',
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
if (counts.serverTools > 0) {
|
|
997
|
+
ctx.diagnostics.push({
|
|
998
|
+
severity: "warning",
|
|
999
|
+
path: displayPath(ctx, join(agentDir, "tools")),
|
|
1000
|
+
message:
|
|
1001
|
+
'In-process tools (execution: "server", the default) are not available on cloud agents. Use execution: "agent", MCP connections, or tools that already live in the cloud workspace.',
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
if (counts.agentTools > 0) {
|
|
1005
|
+
ctx.diagnostics.push({
|
|
1006
|
+
severity: "warning",
|
|
1007
|
+
path: displayPath(ctx, join(agentDir, "tools")),
|
|
1008
|
+
message:
|
|
1009
|
+
'Agent tools (execution: "agent") are described on the first cloud prompt (with script bodies); they are not written into the cloud VM filesystem automatically.',
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
if (counts.skills > 0) {
|
|
1013
|
+
ctx.diagnostics.push({
|
|
1014
|
+
severity: "warning",
|
|
1015
|
+
path: displayPath(ctx, join(agentDir, "skills")),
|
|
1016
|
+
message:
|
|
1017
|
+
"Authored skills are materialized into the local session workspace only; cloud agents see skills that already exist in the cloud repo.",
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
if (counts.seedFiles > 0) {
|
|
1021
|
+
ctx.diagnostics.push({
|
|
1022
|
+
severity: "warning",
|
|
1023
|
+
path: displayPath(ctx, join(agentDir, "sandbox")),
|
|
1024
|
+
message:
|
|
1025
|
+
"sandbox/workspace seed files apply to the local session workspace only; cloud agents use the configured cloud repos.",
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// ============================================================================
|
|
1031
|
+
// Channels
|
|
1032
|
+
// ============================================================================
|
|
1033
|
+
|
|
1034
|
+
async function loadChannels(
|
|
1035
|
+
ctx: DiscoveryContext,
|
|
1036
|
+
channelsDir: string
|
|
1037
|
+
): Promise<{
|
|
1038
|
+
channels: DiscoveredChannel[];
|
|
1039
|
+
httpChannelDefinition: HttpChannelDefinition | undefined;
|
|
1040
|
+
}> {
|
|
1041
|
+
const channels: DiscoveredChannel[] = [];
|
|
1042
|
+
let httpChannelDefinition: HttpChannelDefinition | undefined;
|
|
1043
|
+
if (!(await isDirectory(channelsDir))) {
|
|
1044
|
+
return { channels, httpChannelDefinition };
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
for (const entry of await sortedEntries(channelsDir)) {
|
|
1048
|
+
const entryPath = join(channelsDir, entry.name);
|
|
1049
|
+
if (!entry.isFile() || !isModuleFile(entry.name)) {
|
|
1050
|
+
continue;
|
|
1051
|
+
}
|
|
1052
|
+
const id = moduleStem(entry.name);
|
|
1053
|
+
if (!NAME_PATTERN.test(id)) {
|
|
1054
|
+
ctx.diagnostics.push({
|
|
1055
|
+
severity: "error",
|
|
1056
|
+
path: displayPath(ctx, entryPath),
|
|
1057
|
+
message: `Channel id "${id}" must match ${NAME_PATTERN}.`,
|
|
1058
|
+
});
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
let mod: Record<string, unknown>;
|
|
1063
|
+
try {
|
|
1064
|
+
mod = await ctx.importModule(entryPath);
|
|
1065
|
+
} catch (error) {
|
|
1066
|
+
ctx.diagnostics.push({
|
|
1067
|
+
severity: "error",
|
|
1068
|
+
path: displayPath(ctx, entryPath),
|
|
1069
|
+
message: `Failed to load module: ${errorMessage(error)}`,
|
|
1070
|
+
});
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
1073
|
+
const kind = getDefinitionKind(mod.default);
|
|
1074
|
+
|
|
1075
|
+
if (kind === "http-channel") {
|
|
1076
|
+
if (id !== "http") {
|
|
1077
|
+
ctx.diagnostics.push({
|
|
1078
|
+
severity: "error",
|
|
1079
|
+
path: displayPath(ctx, entryPath),
|
|
1080
|
+
message:
|
|
1081
|
+
"httpChannel() configures the built-in HTTP channel and must be authored at channels/http.ts.",
|
|
1082
|
+
});
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
1085
|
+
httpChannelDefinition = mod.default as HttpChannelDefinition;
|
|
1086
|
+
continue;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
if (kind !== "channel") {
|
|
1090
|
+
ctx.diagnostics.push({
|
|
1091
|
+
severity: "error",
|
|
1092
|
+
path: displayPath(ctx, entryPath),
|
|
1093
|
+
message:
|
|
1094
|
+
id === "http"
|
|
1095
|
+
? "channels/http.ts must default-export httpChannel({ ... })."
|
|
1096
|
+
: "Channel modules must default-export defineChannel({ ... }).",
|
|
1097
|
+
});
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
if (id === "http") {
|
|
1101
|
+
ctx.diagnostics.push({
|
|
1102
|
+
severity: "error",
|
|
1103
|
+
path: displayPath(ctx, entryPath),
|
|
1104
|
+
message:
|
|
1105
|
+
"The channel id `http` is reserved for the built-in HTTP channel; use httpChannel() to configure it.",
|
|
1106
|
+
});
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
1109
|
+
channels.push({ id, definition: mod.default as ChannelDefinition });
|
|
1110
|
+
}
|
|
1111
|
+
return { channels, httpChannelDefinition };
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// ============================================================================
|
|
1115
|
+
// Schedules
|
|
1116
|
+
// ============================================================================
|
|
1117
|
+
|
|
1118
|
+
async function loadSchedules(
|
|
1119
|
+
ctx: DiscoveryContext,
|
|
1120
|
+
schedulesDir: string
|
|
1121
|
+
): Promise<DiscoveredSchedule[]> {
|
|
1122
|
+
if (!(await isDirectory(schedulesDir))) {
|
|
1123
|
+
return [];
|
|
1124
|
+
}
|
|
1125
|
+
const schedules: DiscoveredSchedule[] = [];
|
|
1126
|
+
for (const filePath of await walkFiles(schedulesDir)) {
|
|
1127
|
+
const rel = toPosix(relative(schedulesDir, filePath));
|
|
1128
|
+
if (rel.endsWith(".md")) {
|
|
1129
|
+
const name = rel.slice(0, -".md".length);
|
|
1130
|
+
const { data, body } = parseFrontmatter(await readFile(filePath, "utf8"));
|
|
1131
|
+
const cron = data.cron;
|
|
1132
|
+
if (cron === undefined || !isValidCron(cron)) {
|
|
1133
|
+
ctx.diagnostics.push({
|
|
1134
|
+
severity: "error",
|
|
1135
|
+
path: displayPath(ctx, filePath),
|
|
1136
|
+
message:
|
|
1137
|
+
cron === undefined
|
|
1138
|
+
? "Markdown schedules require `cron` frontmatter."
|
|
1139
|
+
: `Invalid cron expression: "${cron}".`,
|
|
1140
|
+
});
|
|
1141
|
+
continue;
|
|
1142
|
+
}
|
|
1143
|
+
const markdown = body.trim();
|
|
1144
|
+
if (markdown === "") {
|
|
1145
|
+
ctx.diagnostics.push({
|
|
1146
|
+
severity: "error",
|
|
1147
|
+
path: displayPath(ctx, filePath),
|
|
1148
|
+
message: "Markdown schedules require a non-empty prompt body.",
|
|
1149
|
+
});
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
1152
|
+
schedules.push({
|
|
1153
|
+
name,
|
|
1154
|
+
cron,
|
|
1155
|
+
kind: "markdown",
|
|
1156
|
+
definition: {
|
|
1157
|
+
__agentServe: "schedule",
|
|
1158
|
+
cron,
|
|
1159
|
+
markdown,
|
|
1160
|
+
},
|
|
1161
|
+
});
|
|
1162
|
+
continue;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
if (!isModuleFile(basename(filePath))) {
|
|
1166
|
+
continue;
|
|
1167
|
+
}
|
|
1168
|
+
const name = rel.slice(0, -extname(rel).length);
|
|
1169
|
+
const definition = await importDefault(ctx, filePath, "schedule");
|
|
1170
|
+
if (definition === undefined) {
|
|
1171
|
+
continue;
|
|
1172
|
+
}
|
|
1173
|
+
const schedule = definition as ScheduleDefinition;
|
|
1174
|
+
if ((schedule.markdown === undefined) === (schedule.run === undefined)) {
|
|
1175
|
+
ctx.diagnostics.push({
|
|
1176
|
+
severity: "error",
|
|
1177
|
+
path: displayPath(ctx, filePath),
|
|
1178
|
+
message: "Schedules require exactly one of `markdown` or `run`.",
|
|
1179
|
+
});
|
|
1180
|
+
continue;
|
|
1181
|
+
}
|
|
1182
|
+
if (!isValidCron(schedule.cron)) {
|
|
1183
|
+
ctx.diagnostics.push({
|
|
1184
|
+
severity: "error",
|
|
1185
|
+
path: displayPath(ctx, filePath),
|
|
1186
|
+
message: `Invalid cron expression: "${schedule.cron}".`,
|
|
1187
|
+
});
|
|
1188
|
+
continue;
|
|
1189
|
+
}
|
|
1190
|
+
schedules.push({
|
|
1191
|
+
name,
|
|
1192
|
+
cron: schedule.cron,
|
|
1193
|
+
kind: schedule.run === undefined ? "markdown" : "run",
|
|
1194
|
+
definition: schedule,
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
return schedules;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// ============================================================================
|
|
1201
|
+
// Hooks
|
|
1202
|
+
// ============================================================================
|
|
1203
|
+
|
|
1204
|
+
async function loadHooks(
|
|
1205
|
+
ctx: DiscoveryContext,
|
|
1206
|
+
hooksDir: string
|
|
1207
|
+
): Promise<DiscoveredHook[]> {
|
|
1208
|
+
if (!(await isDirectory(hooksDir))) {
|
|
1209
|
+
return [];
|
|
1210
|
+
}
|
|
1211
|
+
const hooks: DiscoveredHook[] = [];
|
|
1212
|
+
for (const filePath of await walkFiles(hooksDir)) {
|
|
1213
|
+
if (!isModuleFile(basename(filePath))) {
|
|
1214
|
+
continue;
|
|
1215
|
+
}
|
|
1216
|
+
const rel = toPosix(relative(hooksDir, filePath));
|
|
1217
|
+
const name = rel.slice(0, -extname(rel).length);
|
|
1218
|
+
const definition = await importDefault(ctx, filePath, "hook");
|
|
1219
|
+
if (definition === undefined) {
|
|
1220
|
+
continue;
|
|
1221
|
+
}
|
|
1222
|
+
const hook = definition as HookDefinition;
|
|
1223
|
+
if (
|
|
1224
|
+
typeof hook.events !== "object" ||
|
|
1225
|
+
hook.events === null ||
|
|
1226
|
+
Object.keys(hook.events).length === 0
|
|
1227
|
+
) {
|
|
1228
|
+
ctx.diagnostics.push({
|
|
1229
|
+
severity: "warning",
|
|
1230
|
+
path: displayPath(ctx, filePath),
|
|
1231
|
+
message: "Hook declares no event handlers.",
|
|
1232
|
+
});
|
|
1233
|
+
continue;
|
|
1234
|
+
}
|
|
1235
|
+
hooks.push({ name, definition: hook });
|
|
1236
|
+
}
|
|
1237
|
+
return hooks;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// ============================================================================
|
|
1241
|
+
// A/B metrics (agent/ab.ts or agent/ab/*.ts) + agent/ab.config.ts
|
|
1242
|
+
// ============================================================================
|
|
1243
|
+
|
|
1244
|
+
async function loadABs(
|
|
1245
|
+
ctx: DiscoveryContext,
|
|
1246
|
+
agentDir: string
|
|
1247
|
+
): Promise<DiscoveredAB[]> {
|
|
1248
|
+
const abs: DiscoveredAB[] = [];
|
|
1249
|
+
const seen = new Set<string>();
|
|
1250
|
+
|
|
1251
|
+
const single = await findModuleFile(agentDir, "ab");
|
|
1252
|
+
if (single !== undefined) {
|
|
1253
|
+
const definition = await importDefault(ctx, single, "ab");
|
|
1254
|
+
if (definition !== undefined) {
|
|
1255
|
+
const name = resolveABName(definition, "ab");
|
|
1256
|
+
seen.add(name);
|
|
1257
|
+
abs.push({
|
|
1258
|
+
name,
|
|
1259
|
+
definition: definition as DiscoveredAB["definition"],
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
const absDir = join(agentDir, "ab");
|
|
1265
|
+
if (await isDirectory(absDir)) {
|
|
1266
|
+
for (const filePath of await walkFiles(absDir)) {
|
|
1267
|
+
if (!isModuleFile(basename(filePath))) {
|
|
1268
|
+
continue;
|
|
1269
|
+
}
|
|
1270
|
+
const rel = toPosix(relative(absDir, filePath));
|
|
1271
|
+
const stem = rel.slice(0, -extname(rel).length);
|
|
1272
|
+
const definition = await importDefault(ctx, filePath, "ab");
|
|
1273
|
+
if (definition === undefined) {
|
|
1274
|
+
continue;
|
|
1275
|
+
}
|
|
1276
|
+
const name = resolveABName(definition, stem);
|
|
1277
|
+
if (seen.has(name)) {
|
|
1278
|
+
ctx.diagnostics.push({
|
|
1279
|
+
severity: "error",
|
|
1280
|
+
path: displayPath(ctx, filePath),
|
|
1281
|
+
message: `Duplicate A/B experiment name ${JSON.stringify(name)}.`,
|
|
1282
|
+
});
|
|
1283
|
+
continue;
|
|
1284
|
+
}
|
|
1285
|
+
seen.add(name);
|
|
1286
|
+
abs.push({
|
|
1287
|
+
name,
|
|
1288
|
+
definition: definition as DiscoveredAB["definition"],
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
return abs;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
async function loadABConfig(
|
|
1297
|
+
ctx: DiscoveryContext,
|
|
1298
|
+
agentDir: string
|
|
1299
|
+
): Promise<ABConfigFile | undefined> {
|
|
1300
|
+
const path = await findModuleFile(agentDir, "ab.config");
|
|
1301
|
+
if (path === undefined) {
|
|
1302
|
+
return undefined;
|
|
1303
|
+
}
|
|
1304
|
+
try {
|
|
1305
|
+
const mod = await ctx.importModule(path);
|
|
1306
|
+
if (mod.default === null || typeof mod.default !== "object") {
|
|
1307
|
+
ctx.diagnostics.push({
|
|
1308
|
+
severity: "error",
|
|
1309
|
+
path: displayPath(ctx, path),
|
|
1310
|
+
message:
|
|
1311
|
+
"ab.config.ts must default-export defineABConfig({ ... }) from @anysphere/agent-serve/ab.",
|
|
1312
|
+
});
|
|
1313
|
+
return undefined;
|
|
1314
|
+
}
|
|
1315
|
+
return mod.default as ABConfigFile;
|
|
1316
|
+
} catch (error) {
|
|
1317
|
+
ctx.diagnostics.push({
|
|
1318
|
+
severity: "error",
|
|
1319
|
+
path: displayPath(ctx, path),
|
|
1320
|
+
message: `Failed to load ab.config: ${error instanceof Error ? error.message : String(error)}`,
|
|
1321
|
+
});
|
|
1322
|
+
return undefined;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
function resolveABName(definition: unknown, fallback: string): string {
|
|
1327
|
+
if (
|
|
1328
|
+
typeof definition === "object" &&
|
|
1329
|
+
definition !== null &&
|
|
1330
|
+
"name" in definition &&
|
|
1331
|
+
typeof (definition as { name: unknown }).name === "string" &&
|
|
1332
|
+
(definition as { name: string }).name.length > 0
|
|
1333
|
+
) {
|
|
1334
|
+
return (definition as { name: string }).name;
|
|
1335
|
+
}
|
|
1336
|
+
return fallback;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
// ============================================================================
|
|
1340
|
+
// Sandbox seed files
|
|
1341
|
+
// ============================================================================
|
|
1342
|
+
|
|
1343
|
+
async function loadSeedFiles(
|
|
1344
|
+
ctx: DiscoveryContext,
|
|
1345
|
+
agentDir: string
|
|
1346
|
+
): Promise<SeedFile[]> {
|
|
1347
|
+
const sandboxDir = join(agentDir, "sandbox");
|
|
1348
|
+
for (const candidate of ["sandbox.ts", join("sandbox", "sandbox.ts")]) {
|
|
1349
|
+
if (await isFile(join(agentDir, candidate))) {
|
|
1350
|
+
ctx.diagnostics.push({
|
|
1351
|
+
severity: "warning",
|
|
1352
|
+
path: displayPath(ctx, join(agentDir, candidate)),
|
|
1353
|
+
message:
|
|
1354
|
+
"Custom sandbox backends are not supported; the agent runs in the session workspace directory. Ignoring.",
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
const workspaceDir = join(sandboxDir, "workspace");
|
|
1359
|
+
if (!(await isDirectory(workspaceDir))) {
|
|
1360
|
+
return [];
|
|
1361
|
+
}
|
|
1362
|
+
const seedFiles: SeedFile[] = [];
|
|
1363
|
+
for (const filePath of await walkFiles(workspaceDir)) {
|
|
1364
|
+
seedFiles.push({
|
|
1365
|
+
relativePath: toPosix(relative(workspaceDir, filePath)),
|
|
1366
|
+
sourcePath: filePath,
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
return seedFiles;
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
// ============================================================================
|
|
1373
|
+
// Info projection
|
|
1374
|
+
// ============================================================================
|
|
1375
|
+
|
|
1376
|
+
export function projectInfo(project: AgentProject): AgentProjectInfo {
|
|
1377
|
+
const agent = project.agent;
|
|
1378
|
+
const httpRoutes: AgentProjectInfo["channels"][number]["routes"] = [
|
|
1379
|
+
{ method: "POST", path: "/v1/session", url: "/v1/session" },
|
|
1380
|
+
{
|
|
1381
|
+
method: "POST",
|
|
1382
|
+
path: "/v1/session/:sessionId",
|
|
1383
|
+
url: "/v1/session/:sessionId",
|
|
1384
|
+
},
|
|
1385
|
+
{
|
|
1386
|
+
method: "POST",
|
|
1387
|
+
path: "/v1/session/:sessionId/stop",
|
|
1388
|
+
url: "/v1/session/:sessionId/stop",
|
|
1389
|
+
},
|
|
1390
|
+
{
|
|
1391
|
+
method: "GET",
|
|
1392
|
+
path: "/v1/session/:sessionId/stream",
|
|
1393
|
+
url: "/v1/session/:sessionId/stream",
|
|
1394
|
+
},
|
|
1395
|
+
{
|
|
1396
|
+
method: "GET",
|
|
1397
|
+
path: "/v1/session/:sessionId/approvals",
|
|
1398
|
+
url: "/v1/session/:sessionId/approvals",
|
|
1399
|
+
},
|
|
1400
|
+
{
|
|
1401
|
+
method: "POST",
|
|
1402
|
+
path: "/v1/session/:sessionId/approvals/:callId",
|
|
1403
|
+
url: "/v1/session/:sessionId/approvals/:callId",
|
|
1404
|
+
},
|
|
1405
|
+
];
|
|
1406
|
+
return {
|
|
1407
|
+
name: project.name,
|
|
1408
|
+
model: agent.model,
|
|
1409
|
+
runtime: agent.runtime,
|
|
1410
|
+
cloud:
|
|
1411
|
+
agent.cloud === undefined ? undefined : toPublicCloudInfo(agent.cloud),
|
|
1412
|
+
local: agent.local,
|
|
1413
|
+
mcp: { path: MCP_ENDPOINT_PATH, tools: mcpEndpointToolNames(project) },
|
|
1414
|
+
instructions:
|
|
1415
|
+
agent.instructions === undefined
|
|
1416
|
+
? undefined
|
|
1417
|
+
: {
|
|
1418
|
+
chars: agent.instructions.length,
|
|
1419
|
+
preview:
|
|
1420
|
+
agent.instructions.length > 280
|
|
1421
|
+
? `${agent.instructions.slice(0, 277)}...`
|
|
1422
|
+
: agent.instructions,
|
|
1423
|
+
},
|
|
1424
|
+
tools: agent.tools.map((tool) => ({
|
|
1425
|
+
name: tool.name,
|
|
1426
|
+
description: tool.description,
|
|
1427
|
+
execution: tool.execution,
|
|
1428
|
+
needsApproval: tool.needsApproval,
|
|
1429
|
+
inputSchema: tool.inputSchema,
|
|
1430
|
+
})),
|
|
1431
|
+
skills: agent.skills.map((skill) => ({
|
|
1432
|
+
name: skill.name,
|
|
1433
|
+
description: skill.description,
|
|
1434
|
+
})),
|
|
1435
|
+
connections: agent.connections.map((connection) => ({
|
|
1436
|
+
name: connection.name,
|
|
1437
|
+
transport: connectionTransportLabel(connection.transport),
|
|
1438
|
+
})),
|
|
1439
|
+
subagents: agent.subagents.map((subagent) => ({
|
|
1440
|
+
name: subagent.name,
|
|
1441
|
+
description: subagent.description,
|
|
1442
|
+
model: subagent.model,
|
|
1443
|
+
})),
|
|
1444
|
+
channels: [
|
|
1445
|
+
{ id: "http", routes: httpRoutes },
|
|
1446
|
+
...project.channels.map((channel) => {
|
|
1447
|
+
const info: AgentProjectInfo["channels"][number] = {
|
|
1448
|
+
id: channel.id,
|
|
1449
|
+
routes: (channel.definition.routes ?? []).map((r) => {
|
|
1450
|
+
const routeInfo: AgentProjectInfo["channels"][number]["routes"][number] =
|
|
1451
|
+
{
|
|
1452
|
+
method: r.method,
|
|
1453
|
+
path: r.path,
|
|
1454
|
+
url: `/v1/channels/${channel.id}${r.path}`,
|
|
1455
|
+
};
|
|
1456
|
+
if (r.description !== undefined) {
|
|
1457
|
+
routeInfo.description = r.description;
|
|
1458
|
+
}
|
|
1459
|
+
if (r.bodySchema !== undefined) {
|
|
1460
|
+
routeInfo.bodySchema = toolInputJsonSchema(r.bodySchema);
|
|
1461
|
+
}
|
|
1462
|
+
if (r.querySchema !== undefined) {
|
|
1463
|
+
routeInfo.querySchema = toolInputJsonSchema(r.querySchema);
|
|
1464
|
+
}
|
|
1465
|
+
return routeInfo;
|
|
1466
|
+
}),
|
|
1467
|
+
};
|
|
1468
|
+
const meta = channel.definition.meta;
|
|
1469
|
+
if (meta?.kind !== undefined) {
|
|
1470
|
+
info.kind = meta.kind;
|
|
1471
|
+
}
|
|
1472
|
+
if (meta?.githubWebhookEvents !== undefined) {
|
|
1473
|
+
info.githubWebhookEvents = meta.githubWebhookEvents;
|
|
1474
|
+
}
|
|
1475
|
+
if (meta?.slackChannelPosts !== undefined) {
|
|
1476
|
+
info.slackChannelPosts = meta.slackChannelPosts;
|
|
1477
|
+
}
|
|
1478
|
+
return info;
|
|
1479
|
+
}),
|
|
1480
|
+
],
|
|
1481
|
+
schedules: project.schedules.map((schedule) => ({
|
|
1482
|
+
name: schedule.name,
|
|
1483
|
+
cron: schedule.cron,
|
|
1484
|
+
kind: schedule.kind,
|
|
1485
|
+
})),
|
|
1486
|
+
hooks: project.hooks.map((hook) => hook.name),
|
|
1487
|
+
abs: project.abs.map((experiment) => experiment.name),
|
|
1488
|
+
...(project.abConfig === undefined && project.abs.length === 0
|
|
1489
|
+
? {}
|
|
1490
|
+
: {
|
|
1491
|
+
abConfig: {
|
|
1492
|
+
maxPlaygroundSessions: resolveABMaxPlaygroundSessions(
|
|
1493
|
+
project.abConfig?.maxPlaygroundSessions
|
|
1494
|
+
),
|
|
1495
|
+
durableSamples: project.abConfig?.persistSamples !== undefined,
|
|
1496
|
+
durableSnapshots: project.abConfig?.persistSnapshots !== undefined,
|
|
1497
|
+
},
|
|
1498
|
+
}),
|
|
1499
|
+
diagnostics: project.diagnostics,
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
// ============================================================================
|
|
1504
|
+
// Shared helpers
|
|
1505
|
+
// ============================================================================
|
|
1506
|
+
|
|
1507
|
+
async function importDefault(
|
|
1508
|
+
ctx: DiscoveryContext,
|
|
1509
|
+
modulePath: string,
|
|
1510
|
+
expectedKind:
|
|
1511
|
+
| "agent"
|
|
1512
|
+
| "tool"
|
|
1513
|
+
| "skill"
|
|
1514
|
+
| "connection"
|
|
1515
|
+
| "schedule"
|
|
1516
|
+
| "hook"
|
|
1517
|
+
| "ab"
|
|
1518
|
+
): Promise<unknown> {
|
|
1519
|
+
let mod: Record<string, unknown>;
|
|
1520
|
+
try {
|
|
1521
|
+
mod = await ctx.importModule(modulePath);
|
|
1522
|
+
} catch (error) {
|
|
1523
|
+
ctx.diagnostics.push({
|
|
1524
|
+
severity: "error",
|
|
1525
|
+
path: displayPath(ctx, modulePath),
|
|
1526
|
+
message: `Failed to load module: ${errorMessage(error)}`,
|
|
1527
|
+
});
|
|
1528
|
+
return undefined;
|
|
1529
|
+
}
|
|
1530
|
+
const kind = getDefinitionKind(mod.default);
|
|
1531
|
+
if (kind !== expectedKind) {
|
|
1532
|
+
const helper =
|
|
1533
|
+
expectedKind === "ab"
|
|
1534
|
+
? "defineAB"
|
|
1535
|
+
: `define${expectedKind[0]?.toUpperCase()}${expectedKind.slice(1)}`;
|
|
1536
|
+
ctx.diagnostics.push({
|
|
1537
|
+
severity: "error",
|
|
1538
|
+
path: displayPath(ctx, modulePath),
|
|
1539
|
+
message:
|
|
1540
|
+
kind === undefined
|
|
1541
|
+
? `Module must default-export ${helper}({ ... }).`
|
|
1542
|
+
: `Expected a ${expectedKind} definition but found "${kind}" (wrong authored slot?).`,
|
|
1543
|
+
});
|
|
1544
|
+
return undefined;
|
|
1545
|
+
}
|
|
1546
|
+
return mod.default;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
async function warnUnknownSlots(
|
|
1550
|
+
ctx: DiscoveryContext,
|
|
1551
|
+
agentDir: string
|
|
1552
|
+
): Promise<void> {
|
|
1553
|
+
const known = new Set([
|
|
1554
|
+
"agent",
|
|
1555
|
+
"instructions",
|
|
1556
|
+
"tools",
|
|
1557
|
+
"skills",
|
|
1558
|
+
"mcp-connections",
|
|
1559
|
+
// Legacy slot name; resolveMcpConnectionsDir warns and still loads it.
|
|
1560
|
+
"connections",
|
|
1561
|
+
"subagents",
|
|
1562
|
+
"channels",
|
|
1563
|
+
"schedules",
|
|
1564
|
+
"hooks",
|
|
1565
|
+
"ab",
|
|
1566
|
+
"sandbox",
|
|
1567
|
+
"lib",
|
|
1568
|
+
// Vite playground custom tool chips (examples/*/agent/playground/tools).
|
|
1569
|
+
"playground",
|
|
1570
|
+
]);
|
|
1571
|
+
for (const entry of await sortedEntries(agentDir)) {
|
|
1572
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) {
|
|
1573
|
+
continue;
|
|
1574
|
+
}
|
|
1575
|
+
if (entry.name === "node_modules" || known.has(entry.name)) {
|
|
1576
|
+
continue;
|
|
1577
|
+
}
|
|
1578
|
+
// Footgun: evals live at project-root `evals/`, not under `agent/`.
|
|
1579
|
+
if (entry.name === "evals") {
|
|
1580
|
+
ctx.diagnostics.push({
|
|
1581
|
+
severity: "warning",
|
|
1582
|
+
path: displayPath(ctx, join(agentDir, entry.name)),
|
|
1583
|
+
message:
|
|
1584
|
+
'Directory "evals" under agent/ is ignored — put defineEval files at project-root evals/ instead.',
|
|
1585
|
+
});
|
|
1586
|
+
continue;
|
|
1587
|
+
}
|
|
1588
|
+
ctx.diagnostics.push({
|
|
1589
|
+
severity: "warning",
|
|
1590
|
+
path: displayPath(ctx, join(agentDir, entry.name)),
|
|
1591
|
+
message: `Unrecognized directory "${entry.name}" under agent/ (not an authored slot; use lib/ for shared code).`,
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
async function hasFlatLayoutMarkers(rootDir: string): Promise<boolean> {
|
|
1597
|
+
if (await isFile(join(rootDir, "instructions.md"))) {
|
|
1598
|
+
return true;
|
|
1599
|
+
}
|
|
1600
|
+
return (await findModuleFile(rootDir, "agent")) !== undefined;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
async function readPackageName(rootDir: string): Promise<string | undefined> {
|
|
1604
|
+
const packageJsonPath = join(rootDir, "package.json");
|
|
1605
|
+
if (!(await isFile(packageJsonPath))) {
|
|
1606
|
+
return undefined;
|
|
1607
|
+
}
|
|
1608
|
+
try {
|
|
1609
|
+
const parsed = JSON.parse(await readFile(packageJsonPath, "utf8")) as {
|
|
1610
|
+
name?: unknown;
|
|
1611
|
+
};
|
|
1612
|
+
if (typeof parsed.name === "string" && parsed.name !== "") {
|
|
1613
|
+
return parsed.name;
|
|
1614
|
+
}
|
|
1615
|
+
} catch {
|
|
1616
|
+
// Malformed package.json falls back to the directory name.
|
|
1617
|
+
}
|
|
1618
|
+
return undefined;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
async function findModuleFile(
|
|
1622
|
+
dir: string,
|
|
1623
|
+
stem: string
|
|
1624
|
+
): Promise<string | undefined> {
|
|
1625
|
+
for (const extension of [".ts", ".mts", ".js", ".mjs"]) {
|
|
1626
|
+
const candidate = join(dir, `${stem}${extension}`);
|
|
1627
|
+
if (await isFile(candidate)) {
|
|
1628
|
+
return candidate;
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
return undefined;
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
function isModuleFile(fileName: string): boolean {
|
|
1635
|
+
if (fileName.endsWith(".d.ts") || fileName.startsWith(".")) {
|
|
1636
|
+
return false;
|
|
1637
|
+
}
|
|
1638
|
+
return MODULE_EXTENSIONS.has(extname(fileName));
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
function moduleStem(fileName: string): string {
|
|
1642
|
+
return fileName.slice(0, -extname(fileName).length);
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
async function sortedEntries(dir: string): Promise<Dirent[]> {
|
|
1646
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
1647
|
+
return entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
async function walkFiles(dir: string): Promise<string[]> {
|
|
1651
|
+
const files: string[] = [];
|
|
1652
|
+
for (const entry of await sortedEntries(dir)) {
|
|
1653
|
+
if (entry.name.startsWith(".")) {
|
|
1654
|
+
continue;
|
|
1655
|
+
}
|
|
1656
|
+
const entryPath = join(dir, entry.name);
|
|
1657
|
+
if (entry.isDirectory()) {
|
|
1658
|
+
files.push(...(await walkFiles(entryPath)));
|
|
1659
|
+
} else if (entry.isFile()) {
|
|
1660
|
+
files.push(entryPath);
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
return files;
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
async function isDirectory(path: string): Promise<boolean> {
|
|
1667
|
+
try {
|
|
1668
|
+
return (await stat(path)).isDirectory();
|
|
1669
|
+
} catch {
|
|
1670
|
+
return false;
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
async function isFile(path: string): Promise<boolean> {
|
|
1675
|
+
try {
|
|
1676
|
+
return (await stat(path)).isFile();
|
|
1677
|
+
} catch {
|
|
1678
|
+
return false;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
function isValidModelSetting(model: unknown): model is ModelSetting {
|
|
1683
|
+
if (typeof model === "string") {
|
|
1684
|
+
return model.trim() !== "";
|
|
1685
|
+
}
|
|
1686
|
+
return (
|
|
1687
|
+
typeof model === "object" &&
|
|
1688
|
+
model !== null &&
|
|
1689
|
+
typeof (model as { id?: unknown }).id === "string" &&
|
|
1690
|
+
(model as { id: string }).id.trim() !== ""
|
|
1691
|
+
);
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
function isBlank(value: unknown): boolean {
|
|
1695
|
+
return typeof value !== "string" || value.trim() === "";
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
function toPosix(path: string): string {
|
|
1699
|
+
return path.split("\\").join("/");
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
function displayPath(ctx: DiscoveryContext, absolutePath: string): string {
|
|
1703
|
+
const rel = relative(ctx.rootDir, absolutePath);
|
|
1704
|
+
return rel.startsWith("..") ? absolutePath : toPosix(rel);
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
function errorMessage(error: unknown): string {
|
|
1708
|
+
return error instanceof Error ? error.message : String(error);
|
|
1709
|
+
}
|