Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion internal/mcp/agent_guide.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,18 +300,21 @@ func publishDomainDecision() *toolforge.GuideDecisionBuilder {
Detail(publishCidLead.Then(htmlRootClause).
Static("Call websites_create with only {\"cid\": \"<cid>\"} — no domain, no label, no platform. The platform auto-generates a subdomain and manages DNS. Do NOT invent a label or call websites_platform_domain_availability. Do not infer a desire for custom naming from a generic request to create or publish a website.").
Then(validateAfterCreateClause).
Then(cdnDeployNoticeClause).
Then(reconcileNoSleep).
Then(siteBundleUpload())),
toolforge.Branch("Yes — user explicitly supplied or requested a specific label (e.g. \"call it acme\", \"use myapp\")").
Steps("websites_platform_domains_list", "websites_platform_domain_availability", "websites_create", "websites_validate").
Detail(publishCidLead.Then(htmlRootClause).
Static("List platform roots with websites_platform_domains_list, then check the label is claimable with websites_platform_domain_availability <label>, then call websites_create with {\"cid\": \"<cid>\", \"platform\": true, \"label\": \"<label>\"}. Only use this branch when the user explicitly named a label — never invent one to perform the availability step.").
Then(validateAfterCreateClause).
Then(cdnDeployNoticeClause).
Then(reconcilePlain)),
toolforge.Branch("Yes — user owns a custom domain (e.g. example.com)").
Steps("websites_create", "websites_validate").
Detail(publishCidLead.Then(htmlRootClause).
Static("Call websites_create with {\"cid\": \"<cid>\", \"website\": \"<domain>\"}. The domain is used directly as a custom domain (not a platform subdomain). Read pinner://websites/<domain>/dns-requirements for DNS records to publish. If dns_hosting=true (managed), DNS is reconciled asynchronously — validation may report the old CID right after the update; that is reconciliation lag, not failure, so re-call websites_validate without starting a new flow. If self-managed, publish the _dnslink TXT and validation TXT before calling websites_validate.").
Then(cdnDeployNoticeClause).
Static("For a Handshake (alt-root) name such as acme/ (i.e. the user has a Handshake domain, not an ICANN TLD domain), pass {\"namespace\": \"hns\"} alongside the website so the site binds under the HNS namespace. Read pinner://websites/<domain>/dns-requirements for the HNS delegation bundle and publish the parent NS/DS/GLUE records on-chain in the HNS wallet; with managed DNS the authoritative side is handled for you.")),
)
}
Expand Down Expand Up @@ -435,7 +438,8 @@ func buildAgentGuide(profile *hostenv.PlatformProfile) AgentGuide {
))).
Flow(toolforge.Flow("update_website", "Update an existing website").
Steps("websites_get", "websites_update", "websites_validate").
Detail(toolforge.Static("Update a deployed website's content without recreating it. 1) websites_get <domain> first to capture the current target_type and dns_hosting_enabled — never guess them. 2) If the new CID is external, pins_add it first; updating an unpinned CID returns CidNotPinned. 3) websites_update <domain> with the new cid (target-type is inherited when omitted; change it only when intentionally switching IPFS<->IPNS). 4) websites_validate. If DNS hosting is managed, validation may report the old CID right after the update — that is reconciliation lag, not failure; re-call websites_validate without starting a new flow."))).
Detail(toolforge.Static("Update a deployed website's content without recreating it. 1) websites_get <domain> first to capture the current target_type and dns_hosting_enabled — never guess them. 2) If the new CID is external, pins_add it first; updating an unpinned CID returns CidNotPinned. 3) websites_update <domain> with the new cid (target-type is inherited when omitted; change it only when intentionally switching IPFS<->IPNS). 4) websites_validate. If DNS hosting is managed, validation may report the old CID right after the update — that is reconciliation lag, not failure; re-call websites_validate without starting a new flow.").
Then(cdnDeployNoticeClause))).
Resolve(p)

// The resolved guide is filtered to the server surface: flows whose
Expand Down
33 changes: 33 additions & 0 deletions internal/mcp/agent_guide_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,39 @@ func TestAgentGuidePublishBranchesPerProfile(t *testing.T) {
}
}

// TestAgentGuideCDNDeployNotice verifies every website publish path (generic
// platform subdomain, explicit label, custom domain) and the update flow tells
// the agent to inform the user that CDN deployment can take up to 5 minutes, so
// the user is set to expect the site to not be reachable at its URL immediately.
func TestAgentGuideCDNDeployNotice(t *testing.T) {
for _, p := range []*hostenv.PlatformProfile{
strPtr(hostenv.ProfileStdioGeneric),
strPtr(hostenv.ProfileHTTPGeneric),
strPtr(hostenv.ProfileGrokHTTP),
strPtr(hostenv.ProfileOpenAITunnel),
} {
guide := buildAgentGuide(p)

pub := guideFlowByName(t, guide, "publish_website")
require.NotNil(t, pub.Decision, "publish_website must be a decision flow for %s", p.Transport)
var domain *GuideDecision
for _, br := range pub.Decision.Branches {
if domain == nil && br.Next != nil {
domain = br.Next
}
}
require.NotNil(t, domain, "publish_website must nest the domain decision for %s", p.Transport)
require.Len(t, domain.Branches, 3, "publish_website must keep the generic/label/custom-domain branches for %s", p.Transport)
for _, br := range domain.Branches {
require.Contains(t, br.Detail, "5 minutes to fully deploy to the CDN",
"every publish domain branch must carry the CDN deploy notice for %s", p.Transport)
}

up := guideFlowByName(t, guide, "update_website")
require.Contains(t, up.Detail, "5 minutes to fully deploy to the CDN", "update_website must carry the CDN deploy notice for %s", p.Transport)
}
}

// TestAgentGuideSandboxFilesRouteToFileNotMint regresses audit F-002. On a host
// with both FeatFileHostInput and mint (OpenAI HTTP, host_file_first), an
// assistant-generated sandbox file must route to the `file` parameter; the mint
Expand Down
7 changes: 7 additions & 0 deletions internal/mcp/guide_fragments.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ var htmlRootClause = toolforge.Static("Upload with wrap=true and do NOT set an e
// the platform-subdomain publish branches.
var validateAfterCreateClause = toolforge.Static("After creation, call websites_validate to confirm DNS propagation.")

// cdnDeployNoticeClause tells the agent to inform the user that a newly
// published or updated site can take up to 5 minutes to fully deploy to the
// CDN, so it may not be reachable at its URL immediately even though
// create/validation succeeded. CDN deployment is independent of validation
// (platform-domain validation is instant).
var cdnDeployNoticeClause = toolforge.Static("Let the user know that after publishing, the site can take up to 5 minutes to fully deploy to the CDN, so it may not be reachable at its URL immediately even though create/validation succeeded.")

// reconcileNoSleep is the reconciliation-lag guidance for hosts with no sleep
// tool: pending validation is lag, not failure, so re-check later.
var reconcileNoSleep = toolforge.Static("On this host there is no sleep tool: if validation is still pending or failing, treat that as reconciliation lag rather than failure and re-call websites_validate after other work, without starting a new flow.")
Expand Down
5 changes: 3 additions & 2 deletions internal/mcp/prompttemplates/website_onboarding.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ Read the pinner://websites/{domain}/dns-requirements resource (substitute the do
{{define "website_step_validate"}}Read the pinner://websites/{id}/validation-status resource (use the website ID from the create step's response). If validation fails, instruct the user to wait for DNS propagation (may take minutes to hours) and retry. Call `websites_wizard_step` with input: {"retry": true} to re-run validation. Call with input: {} to accept the current status and finish.
{{end}}

{{define "website_step_complete"}}When the wizard returns "complete": true, the website onboarding is done. Summarize the result for the user: domain, CID, target type, DNS mode, and validation status. If validation failed, provide the reason and suggest next steps. DNS validation confirms reachability, not content correctness — if the root URL serves a directory listing or 404 instead of the expected page, the CID's directory layout is likely wrong (e.g. missing index.html at the root).
{{define "website_step_complete"}}When the wizard returns "complete": true, the website onboarding is done. Summarize the result for the user: domain, CID, target type, DNS mode, and validation status. Let the user know the site can take up to 5 minutes to fully deploy to the CDN, so it may not resolve at its URL immediately even though create/validation succeeded. If validation failed, provide the reason and suggest next steps. DNS validation confirms reachability, not content correctness — if the root URL serves a directory listing or 404 instead of the expected page, the CID's directory layout is likely wrong (e.g. missing index.html at the root).
{{end}}

{{/* website-update prompt templates. Rendered when updating an existing website's
Expand Down Expand Up @@ -203,5 +203,6 @@ If the error mentions an unpinned CID (CID_NOT_PINNED), go back to the ensure_pi

{{define "website_update_step_complete"}}Update workflow finished.
Summarize the new CID ({{.CID}}){{if .CurrentType}}, target type preserved ({{.CurrentType}}){{end}}, and the current
validation status. If DNS is still reconciling, advise checking again in a few minutes.
validation status. Let the user know the site can take up to 5 minutes to fully deploy to the CDN, so it may
not resolve at its URL immediately after the update.
{{end}}
Loading