@cloudgrid-io/mcp 0.20.53 → 0.20.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -0
- package/package.json +5 -3
- package/src/corpus/playbook.md +2 -2
- package/src/corpus/templates/_cloudgrid.yaml.reference +1 -1
- package/src/corpus/templates/admin-dashboard/cloudgrid.yaml +1 -1
- package/src/corpus/templates/ai-app/cloudgrid.yaml +1 -1
- package/src/corpus/templates/ai-knowledge-base/cloudgrid.yaml +1 -1
- package/src/corpus/templates/analytics-dashboard/cloudgrid.yaml +1 -1
- package/src/corpus/templates/api-dashboard/cloudgrid.yaml +1 -1
- package/src/corpus/templates/api-docs/cloudgrid.yaml +1 -1
- package/src/corpus/templates/api-service/cloudgrid.yaml +1 -1
- package/src/corpus/templates/app-with-data/cloudgrid.yaml +1 -1
- package/src/corpus/templates/appointment-booking/cloudgrid.yaml +1 -1
- package/src/corpus/templates/approval-workflow/cloudgrid.yaml +1 -1
- package/src/corpus/templates/billing-dashboard/cloudgrid.yaml +1 -1
- package/src/corpus/templates/blog-cms/cloudgrid.yaml +1 -1
- package/src/corpus/templates/booking-system/cloudgrid.yaml +1 -1
- package/src/corpus/templates/calendar-scheduler/cloudgrid.yaml +1 -1
- package/src/corpus/templates/changelog/cloudgrid.yaml +1 -1
- package/src/corpus/templates/company-website/cloudgrid.yaml +1 -1
- package/src/corpus/templates/course-platform/cloudgrid.yaml +1 -1
- package/src/corpus/templates/crm/cloudgrid.yaml +1 -1
- package/src/corpus/templates/dashboard/cloudgrid.yaml +1 -1
- package/src/corpus/templates/deck/cloudgrid.yaml +1 -1
- package/src/corpus/templates/directory/cloudgrid.yaml +1 -1
- package/src/corpus/templates/docs-app/package.json +1 -1
- package/src/corpus/templates/docs-site/cloudgrid.yaml +1 -1
- package/src/corpus/templates/erp/cloudgrid.yaml +1 -1
- package/src/corpus/templates/event-board/cloudgrid.yaml +1 -1
- package/src/corpus/templates/expense-tracker/cloudgrid.yaml +1 -1
- package/src/corpus/templates/feature-request-board/cloudgrid.yaml +1 -1
- package/src/corpus/templates/financial-dashboard/cloudgrid.yaml +1 -1
- package/src/corpus/templates/forum/cloudgrid.yaml +1 -1
- package/src/corpus/templates/hr-portal/cloudgrid.yaml +1 -1
- package/src/corpus/templates/internal-tools-portal/cloudgrid.yaml +1 -1
- package/src/corpus/templates/inventory/cloudgrid.yaml +1 -1
- package/src/corpus/templates/invoice/cloudgrid.yaml +1 -1
- package/src/corpus/templates/job-board/cloudgrid.yaml +1 -1
- package/src/corpus/templates/kanban/cloudgrid.yaml +1 -1
- package/src/corpus/templates/landing-page/cloudgrid.yaml +1 -1
- package/src/corpus/templates/lms/cloudgrid.yaml +1 -1
- package/src/corpus/templates/marketplace/cloudgrid.yaml +1 -1
- package/src/corpus/templates/membership-site/cloudgrid.yaml +1 -1
- package/src/corpus/templates/monitoring-dashboard/cloudgrid.yaml +1 -1
- package/src/corpus/templates/multi-service/cloudgrid.yaml +1 -1
- package/src/corpus/templates/online-store/cloudgrid.yaml +1 -1
- package/src/corpus/templates/portfolio/cloudgrid.yaml +1 -1
- package/src/corpus/templates/product-catalog/cloudgrid.yaml +1 -1
- package/src/corpus/templates/product-launch/cloudgrid.yaml +1 -1
- package/src/corpus/templates/project-management/cloudgrid.yaml +1 -1
- package/src/corpus/templates/property-listings/cloudgrid.yaml +1 -1
- package/src/corpus/templates/quiz-platform/cloudgrid.yaml +1 -1
- package/src/corpus/templates/report/cloudgrid.yaml +1 -1
- package/src/corpus/templates/restaurant-reservations/cloudgrid.yaml +1 -1
- package/src/corpus/templates/revenue-dashboard/cloudgrid.yaml +1 -1
- package/src/corpus/templates/saas-marketing/cloudgrid.yaml +1 -1
- package/src/corpus/templates/semantic-search/cloudgrid.yaml +1 -1
- package/src/corpus/templates/simple-semantic-search/cloudgrid.yaml +1 -1
- package/src/corpus/templates/status-page/cloudgrid.yaml +1 -1
- package/src/corpus/templates/subscription-management/cloudgrid.yaml +1 -1
- package/src/corpus/templates/task-manager/cloudgrid.yaml +1 -1
- package/src/corpus/templates/ticket-system/cloudgrid.yaml +1 -1
- package/src/corpus/templates/time-tracking/cloudgrid.yaml +1 -1
- package/src/corpus/templates/travel-booking/cloudgrid.yaml +1 -1
- package/src/corpus/templates/waitlist/cloudgrid.yaml +1 -1
- package/src/corpus/templates/web-app/cloudgrid.yaml +1 -1
- package/src/session-logger.js +4 -1
- package/src/tools/deploy.js +205 -44
- package/src/tools/register.js +11 -8
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
# Entity slug (REQUIRED). 2-42 chars, lowercase alphanumeric + hyphens.
|
|
21
21
|
# Must start and end with a letter or digit.
|
|
22
|
-
# Becomes part of your public URL: <name>-<4hex
|
|
22
|
+
# Becomes part of your public URL: <name>-<4hex>--<org>.cloudgrid.io
|
|
23
23
|
name: my-project-board
|
|
24
24
|
|
|
25
25
|
# One-line summary. Max 280 chars. If omitted, Grid Brain auto-generates one.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
# Entity slug (REQUIRED). 2-42 chars, lowercase alphanumeric + hyphens.
|
|
21
21
|
# Must start and end with a letter or digit.
|
|
22
|
-
# Becomes part of your public URL: <name>-<4hex
|
|
22
|
+
# Becomes part of your public URL: <name>-<4hex>--<org>.cloudgrid.io
|
|
23
23
|
name: property-listings
|
|
24
24
|
|
|
25
25
|
# One-line summary. Max 280 chars. If omitted, Grid Brain auto-generates one.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
# Entity slug (REQUIRED). 2-42 chars, lowercase alphanumeric + hyphens.
|
|
21
21
|
# Must start and end with a letter or digit.
|
|
22
|
-
# Becomes part of your public URL: <name>-<4hex
|
|
22
|
+
# Becomes part of your public URL: <name>-<4hex>--<org>.cloudgrid.io
|
|
23
23
|
name: quiz-platform
|
|
24
24
|
|
|
25
25
|
# One-line summary. Max 280 chars. If omitted, Grid Brain auto-generates one.
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
# Entity slug (REQUIRED). 2-42 chars, lowercase alphanumeric + hyphens.
|
|
27
27
|
# Must start and end with a letter or digit.
|
|
28
|
-
# Becomes part of your public URL: <name>-<4hex
|
|
28
|
+
# Becomes part of your public URL: <name>-<4hex>--<org>.cloudgrid.io
|
|
29
29
|
name: report
|
|
30
30
|
|
|
31
31
|
# One-line summary. Max 280 chars. If omitted, Grid Brain auto-generates one.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
# Entity slug (REQUIRED). 2-42 chars, lowercase alphanumeric + hyphens.
|
|
21
21
|
# Must start and end with a letter or digit.
|
|
22
|
-
# Becomes part of your public URL: <name>-<4hex
|
|
22
|
+
# Becomes part of your public URL: <name>-<4hex>--<org>.cloudgrid.io
|
|
23
23
|
name: restaurant-reservations
|
|
24
24
|
|
|
25
25
|
# One-line summary. Max 280 chars. If omitted, Grid Brain auto-generates one.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
# Entity slug (REQUIRED). 2-42 chars, lowercase alphanumeric + hyphens.
|
|
21
21
|
# Must start and end with a letter or digit.
|
|
22
|
-
# Becomes part of your public URL: <name>-<4hex
|
|
22
|
+
# Becomes part of your public URL: <name>-<4hex>--<org>.cloudgrid.io
|
|
23
23
|
name: revenue-dashboard
|
|
24
24
|
|
|
25
25
|
# One-line summary. Max 280 chars. If omitted, Grid Brain auto-generates one.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
# Entity slug (REQUIRED). 2-42 chars, lowercase alphanumeric + hyphens.
|
|
21
21
|
# Must start and end with a letter or digit.
|
|
22
|
-
# Becomes part of your public URL: <name>-<4hex
|
|
22
|
+
# Becomes part of your public URL: <name>-<4hex>--<org>.cloudgrid.io
|
|
23
23
|
name: saas-marketing
|
|
24
24
|
|
|
25
25
|
# One-line summary. Max 280 chars. If omitted, Grid Brain auto-generates one.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
# Entity slug (REQUIRED). 2-42 chars, lowercase alphanumeric + hyphens.
|
|
21
21
|
# Must start and end with a letter or digit.
|
|
22
|
-
# Becomes part of your public URL: <name>-<4hex
|
|
22
|
+
# Becomes part of your public URL: <name>-<4hex>--<org>.cloudgrid.io
|
|
23
23
|
name: semantic-search
|
|
24
24
|
|
|
25
25
|
# One-line summary. Max 280 chars. If omitted, Grid Brain auto-generates one.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
# Entity slug (REQUIRED). 2-42 chars, lowercase alphanumeric + hyphens.
|
|
21
21
|
# Must start and end with a letter or digit.
|
|
22
|
-
# Becomes part of your public URL: <name>-<4hex
|
|
22
|
+
# Becomes part of your public URL: <name>-<4hex>--<org>.cloudgrid.io
|
|
23
23
|
name: simple-semantic-search
|
|
24
24
|
description: Semantic search over a bundled document, backed by pgvector.
|
|
25
25
|
icon: "🔎"
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
# Entity slug (REQUIRED). 2-42 chars, lowercase alphanumeric + hyphens.
|
|
21
21
|
# Must start and end with a letter or digit.
|
|
22
|
-
# Becomes part of your public URL: <name>-<4hex
|
|
22
|
+
# Becomes part of your public URL: <name>-<4hex>--<org>.cloudgrid.io
|
|
23
23
|
name: status-page
|
|
24
24
|
|
|
25
25
|
# One-line summary. Max 280 chars. If omitted, Grid Brain auto-generates one.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
# Entity slug (REQUIRED). 2-42 chars, lowercase alphanumeric + hyphens.
|
|
21
21
|
# Must start and end with a letter or digit.
|
|
22
|
-
# Becomes part of your public URL: <name>-<4hex
|
|
22
|
+
# Becomes part of your public URL: <name>-<4hex>--<org>.cloudgrid.io
|
|
23
23
|
name: subscription-management
|
|
24
24
|
|
|
25
25
|
# One-line summary. Max 280 chars. If omitted, Grid Brain auto-generates one.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
# Entity slug (REQUIRED). 2-42 chars, lowercase alphanumeric + hyphens.
|
|
21
21
|
# Must start and end with a letter or digit.
|
|
22
|
-
# Becomes part of your public URL: <name>-<4hex
|
|
22
|
+
# Becomes part of your public URL: <name>-<4hex>--<org>.cloudgrid.io
|
|
23
23
|
name: my-tasks
|
|
24
24
|
|
|
25
25
|
# One-line summary. Max 280 chars. If omitted, Grid Brain auto-generates one.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
# Entity slug (REQUIRED). 2-42 chars, lowercase alphanumeric + hyphens.
|
|
21
21
|
# Must start and end with a letter or digit.
|
|
22
|
-
# Becomes part of your public URL: <name>-<4hex
|
|
22
|
+
# Becomes part of your public URL: <name>-<4hex>--<org>.cloudgrid.io
|
|
23
23
|
name: my-tickets
|
|
24
24
|
|
|
25
25
|
# One-line summary. Max 280 chars. If omitted, Grid Brain auto-generates one.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
# Entity slug (REQUIRED). 2-42 chars, lowercase alphanumeric + hyphens.
|
|
21
21
|
# Must start and end with a letter or digit.
|
|
22
|
-
# Becomes part of your public URL: <name>-<4hex
|
|
22
|
+
# Becomes part of your public URL: <name>-<4hex>--<org>.cloudgrid.io
|
|
23
23
|
name: time-tracking
|
|
24
24
|
|
|
25
25
|
# One-line summary. Max 280 chars. If omitted, Grid Brain auto-generates one.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
# Entity slug (REQUIRED). 2-42 chars, lowercase alphanumeric + hyphens.
|
|
21
21
|
# Must start and end with a letter or digit.
|
|
22
|
-
# Becomes part of your public URL: <name>-<4hex
|
|
22
|
+
# Becomes part of your public URL: <name>-<4hex>--<org>.cloudgrid.io
|
|
23
23
|
name: travel-booking
|
|
24
24
|
|
|
25
25
|
# One-line summary. Max 280 chars. If omitted, Grid Brain auto-generates one.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
# Entity slug (REQUIRED). 2-42 chars, lowercase alphanumeric + hyphens.
|
|
21
21
|
# Must start and end with a letter or digit.
|
|
22
|
-
# Becomes part of your public URL: <name>-<4hex
|
|
22
|
+
# Becomes part of your public URL: <name>-<4hex>--<org>.cloudgrid.io
|
|
23
23
|
name: waitlist
|
|
24
24
|
|
|
25
25
|
# One-line summary. Max 280 chars. If omitted, Grid Brain auto-generates one.
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
# Entity slug (REQUIRED). 2-42 chars, lowercase alphanumeric + hyphens.
|
|
27
27
|
# Must start and end with a letter or digit.
|
|
28
|
-
# Becomes part of your public URL: <name>-<4hex
|
|
28
|
+
# Becomes part of your public URL: <name>-<4hex>--<org>.cloudgrid.io
|
|
29
29
|
name: web-app
|
|
30
30
|
|
|
31
31
|
# One-line summary. Max 280 chars. If omitted, Grid Brain auto-generates one.
|
package/src/session-logger.js
CHANGED
|
@@ -122,7 +122,10 @@ export class SessionLogger {
|
|
|
122
122
|
this.startedMs = now();
|
|
123
123
|
this.calls = [];
|
|
124
124
|
this.header = null;
|
|
125
|
-
|
|
125
|
+
// Scrub FIRST, then cap — same rationale as setUserRequest (:252-253): an
|
|
126
|
+
// uncapped value (the CLOUDGRID_USER_REQUEST path) would push the whole trail
|
|
127
|
+
// past the Slack 38KB clip. Both entry points now agree on the 2000 cap.
|
|
128
|
+
this.userRequest = userRequest ? scrubText(userRequest).slice(0, 2000) : null;
|
|
126
129
|
this.narrative = null;
|
|
127
130
|
this.flushed = false;
|
|
128
131
|
this.idleTimer = null;
|
package/src/tools/deploy.js
CHANGED
|
@@ -160,12 +160,33 @@ export async function runCreateGrid(ctx, { slug, name } = {}) {
|
|
|
160
160
|
};
|
|
161
161
|
}
|
|
162
162
|
const finalSlug = data.slug ?? s;
|
|
163
|
+
// A brand-new grid provisions its infrastructure in the background: the API
|
|
164
|
+
// returns 202 with { status: "provisioning", poll_url } while the org's infra
|
|
165
|
+
// (bucket/GSA/WI/K8s) is still being set up. Until it is ready, an
|
|
166
|
+
// infra-dependent write (grid_plug) is refused with 409 ORG_PROVISIONING.
|
|
167
|
+
// There is no read endpoint that reports this readiness (render_ready and
|
|
168
|
+
// /orgs/:slug/status.ready are hardwired true under the flat-arch decision),
|
|
169
|
+
// so we do NOT instruct an immediate re-call that would fail: instead we say
|
|
170
|
+
// the grid is finishing setup and that grid_plug itself waits for readiness
|
|
171
|
+
// (it retries ORG_PROVISIONING for a bounded budget) before it deploys. Issue #235.
|
|
172
|
+
const provisioning = res.status === 202 || data.status === "provisioning" || Boolean(data.poll_url);
|
|
163
173
|
const lines = [`Created grid ${finalSlug} — the user is its admin.`];
|
|
164
|
-
if (
|
|
165
|
-
|
|
174
|
+
if (provisioning) {
|
|
175
|
+
lines.push(
|
|
176
|
+
"It's finishing setup in the background — a brand-new grid provisions its infrastructure, usually within ~30s. " +
|
|
177
|
+
`Go ahead and call grid_plug with grid: ${finalSlug}: it waits for the grid to be ready, then deploys. ` +
|
|
178
|
+
"You do NOT need to poll a status or insert a manual delay, and calling it right away will not fail — it holds until the grid is ready.",
|
|
179
|
+
);
|
|
180
|
+
} else {
|
|
181
|
+
lines.push(`The grid is ready — call grid_plug with grid: ${finalSlug} to deploy.`);
|
|
182
|
+
}
|
|
166
183
|
return {
|
|
167
184
|
text: lines.join("\n"),
|
|
168
|
-
structured: {
|
|
185
|
+
structured: {
|
|
186
|
+
created: true,
|
|
187
|
+
...(provisioning ? { provisioning: true } : {}),
|
|
188
|
+
grid: { slug: finalSlug, name: data.name ?? String(name || s) },
|
|
189
|
+
},
|
|
169
190
|
};
|
|
170
191
|
}
|
|
171
192
|
|
|
@@ -186,6 +207,7 @@ const SECRET_PATTERNS = [
|
|
|
186
207
|
[/github_pat_[A-Za-z0-9_]{30,}/, "a GitHub fine-grained token"],
|
|
187
208
|
[/AKIA[0-9A-Z]{16}/, "an AWS access key id"],
|
|
188
209
|
[/xox[baprs]-[A-Za-z0-9-]{10,}/, "a Slack token"],
|
|
210
|
+
[/\bsk_(?:live|test)_[0-9A-Za-z]{16,}\b/, "a Stripe secret key"],
|
|
189
211
|
];
|
|
190
212
|
|
|
191
213
|
export function scanInlineSecrets(text) {
|
|
@@ -312,13 +334,17 @@ const GUEST_ORG_SLUG = "guest";
|
|
|
312
334
|
// path (create + edit, anon + authed) — flat-arch-aware per grid, matching the
|
|
313
335
|
// host that actually serves. ALWAYS prefer `data.url` (see resolvePlugUrl);
|
|
314
336
|
// this client-side derivation exists only for the rare response where `url`
|
|
315
|
-
// came back empty (the server composes it best-effort). It mirrors the
|
|
316
|
-
// `entityUrl()` rules
|
|
317
|
-
//
|
|
337
|
+
// came back empty (the server composes it best-effort). It mirrors the canonical
|
|
338
|
+
// `entityUrl()` rules (the flat-only platform host), but only the server knows
|
|
339
|
+
// the per-entity nuances (custom domains, guest-org placement), so it must never
|
|
340
|
+
// be the primary source:
|
|
318
341
|
// - inspiration (HTML drops): path-based at the org apex
|
|
319
342
|
// https://<grid>.cloudgrid.io/<slug>
|
|
320
|
-
// - runtime (app/agent):
|
|
321
|
-
// https://<slug
|
|
343
|
+
// - runtime (app/agent): flat platform host
|
|
344
|
+
// https://<slug>--<grid>.cloudgrid.io
|
|
345
|
+
// The old nested `<slug>.<grid>.cloudgrid.io` host is retired — it falls outside
|
|
346
|
+
// the single `*.cloudgrid.io` platform wildcard, so it has no cert and no ingress
|
|
347
|
+
// (matches `composeFlatEntityHost`).
|
|
322
348
|
// Anonymous drops are grid-less in the response (`grid: null`); they live under
|
|
323
349
|
// the Guest Org, so the apex slug is the constant `guest`.
|
|
324
350
|
function composePlugUrl(data) {
|
|
@@ -327,7 +353,7 @@ function composePlugUrl(data) {
|
|
|
327
353
|
const grid = data?.grid || GUEST_ORG_SLUG;
|
|
328
354
|
const kind = data?.detection?.kind;
|
|
329
355
|
if (kind === "app" || kind === "agent") {
|
|
330
|
-
return `https://${slug}
|
|
356
|
+
return `https://${slug}--${grid}.cloudgrid.io`;
|
|
331
357
|
}
|
|
332
358
|
// inspiration (and any unknown/static kind) — path-based at the org apex.
|
|
333
359
|
return `https://${grid}.cloudgrid.io/${slug}`;
|
|
@@ -433,7 +459,8 @@ const REPORT_SECRET_KEY_PATTERNS = [
|
|
|
433
459
|
|
|
434
460
|
// Light client-side scrub of obviously secret-looking values in the report
|
|
435
461
|
// context. Redacts values under secret-looking KEYS; leaves everything else
|
|
436
|
-
// intact (the server
|
|
462
|
+
// intact (the server applies the same key-name filter, not a value-based one —
|
|
463
|
+
// a secret embedded in a value transits unredacted). Bounded depth so a
|
|
437
464
|
// pathological object can't loop.
|
|
438
465
|
export function scrubReportContext(obj, depth = 0) {
|
|
439
466
|
if (depth > 5 || obj === null || typeof obj !== "object") return obj;
|
|
@@ -459,8 +486,8 @@ export function scrubReportContext(obj, depth = 0) {
|
|
|
459
486
|
// source (mcp-stdio | mcp-hosted), client (the calling agent from MCP clientInfo),
|
|
460
487
|
// platform, mcp_version. Sent BOTH as top-level fields AND mirrored in
|
|
461
488
|
// `context.origin`. The POST /errors handler only persists known top-level keys
|
|
462
|
-
// (it drops unknown ones), and `context` is stored
|
|
463
|
-
// so `context.origin` is the durable carrier — belt-and-suspenders.
|
|
489
|
+
// (it drops unknown ones), and `context` is stored after the server's key-name
|
|
490
|
+
// scrub, so `context.origin` is the durable carrier — belt-and-suspenders.
|
|
464
491
|
//
|
|
465
492
|
// Auth: signed-in → Bearer; anon+web → the trusted-server headers (works once the
|
|
466
493
|
// endpoint accepts the credential; until then a 401 degrades to "sign in to
|
|
@@ -607,8 +634,9 @@ export async function runReport(
|
|
|
607
634
|
// grid_pull — continue/edit an existing entity IN PLACE (POST /entities/:id/pickup).
|
|
608
635
|
// Like `git clone` of the SAME entity: your next grid_plug (target_entity_id)
|
|
609
636
|
// updates it. Needs push access — you must OWN it or be a COLLABORATOR. A
|
|
610
|
-
// view-only caller is told they can't edit/plug it (
|
|
611
|
-
// or
|
|
637
|
+
// view-only caller is told they can't edit/plug it (fork a copy with grid_pickup,
|
|
638
|
+
// or GET collaborator push access with the CLI `grid collab` — grants permission
|
|
639
|
+
// only, pull again once granted). A claim_token also
|
|
612
640
|
// claims an anonymous drop into your account (ownership transfer).
|
|
613
641
|
export async function runPull(ctx, { claim_token, claim_url, entity_id }) {
|
|
614
642
|
const token = await ctx.getToken();
|
|
@@ -701,8 +729,9 @@ export async function runPull(ctx, { claim_token, claim_url, entity_id }) {
|
|
|
701
729
|
return {
|
|
702
730
|
text:
|
|
703
731
|
`You don't have push access to this entity, so you can't edit or plug it. Two options: ` +
|
|
704
|
-
`make your own separate copy with grid_pickup, or
|
|
705
|
-
|
|
732
|
+
`make your own separate copy (a fork) with grid_pickup, or GET collaborator push access to the SAME ` +
|
|
733
|
+
`entity with the CLI \`grid collab <entity>\` — that grants permission only and fetches nothing, so ` +
|
|
734
|
+
`pull again once it is granted (if the owner gates access, \`grid collab\` becomes a request they approve).`,
|
|
706
735
|
structured: { can_edit: false, owner_is_you: false, access: "view_only" },
|
|
707
736
|
};
|
|
708
737
|
}
|
|
@@ -729,7 +758,7 @@ export async function runPull(ctx, { claim_token, claim_url, entity_id }) {
|
|
|
729
758
|
head = `You have collaborator push access to ${slug}${where} — your next grid_plug (target_entity_id) updates the SHARED entity; the team sees the new version and can roll it back.`;
|
|
730
759
|
canEdit = true;
|
|
731
760
|
} else {
|
|
732
|
-
head = `You can view ${slug}${where} but do NOT have push access — you can't edit or plug it. Make your own copy with grid_pickup, or
|
|
761
|
+
head = `You can view ${slug}${where} but do NOT have push access — you can't edit or plug it. Make your own separate copy (a fork) with grid_pickup, or GET collaborator push access to the SAME entity with the CLI \`grid collab ${slug}\` (it grants permission only and fetches nothing — pull again once granted).`;
|
|
733
762
|
canEdit = false;
|
|
734
763
|
}
|
|
735
764
|
const lines = [head];
|
|
@@ -1086,6 +1115,16 @@ export function errorGuidance({ status, code, edition, isEdit, isAnon, signedIn
|
|
|
1086
1115
|
"If the user is signed in, use the signed-in path instead of anonymous."
|
|
1087
1116
|
);
|
|
1088
1117
|
}
|
|
1118
|
+
// 409 ORG_PROVISIONING — the target grid's infrastructure is still being set
|
|
1119
|
+
// up (async org provisioning), so an infra-dependent write (plug) is refused
|
|
1120
|
+
// for now. This is DISTINCT from EDIT_REJECTED below and must be checked first:
|
|
1121
|
+
// it is time-bounded and retryable, not a rejected re-plug. runPlug retries it
|
|
1122
|
+
// internally with a bounded budget; this guidance is the budget-exhausted tail,
|
|
1123
|
+
// so the wording tells the agent to wait and re-call rather than surface a raw
|
|
1124
|
+
// code. (Issue #235.)
|
|
1125
|
+
if (status === 409 && code === "ORG_PROVISIONING") {
|
|
1126
|
+
return "The grid is still finishing setup — a brand-new grid provisions its infrastructure in the background (usually within ~30s). Wait ~15s and call grid_plug again with the SAME parameters; it deploys once the grid is ready. Do not switch to anonymous and do not send the user to the console.";
|
|
1127
|
+
}
|
|
1089
1128
|
// 409 EDIT_REJECTED — an in-place re-plug the server won't take.
|
|
1090
1129
|
if (status === 409) {
|
|
1091
1130
|
return "The entity cannot be updated right now (a plug is in progress, or it is archived/expired/claimed). An explicit re-plug never silently creates; retry later, or omit target_entity_id to create a new entity.";
|
|
@@ -1464,6 +1503,9 @@ export async function runPlug(ctx, input, deps = {}) {
|
|
|
1464
1503
|
}
|
|
1465
1504
|
|
|
1466
1505
|
// ── Wire assembly ───────────────────────────────────────────────────────────
|
|
1506
|
+
// Built fresh per attempt: the ORG_PROVISIONING retry loop below re-POSTs, and
|
|
1507
|
+
// a FormData body is consumed by a send, so each attempt needs its own.
|
|
1508
|
+
const buildForm = () => {
|
|
1467
1509
|
const form = new FormData();
|
|
1468
1510
|
for (const a of artifacts) {
|
|
1469
1511
|
// Folder-walk / artifact_files parts ride as octet-stream (server sniffs by
|
|
@@ -1504,32 +1546,84 @@ export async function runPlug(ctx, input, deps = {}) {
|
|
|
1504
1546
|
form.append("hints_kind", hints.kind);
|
|
1505
1547
|
}
|
|
1506
1548
|
if (hints?.yaml) form.append("hints_yaml", hints.yaml);
|
|
1549
|
+
return form;
|
|
1550
|
+
};
|
|
1507
1551
|
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1552
|
+
// One POST attempt: assemble a fresh body, send, read + parse. Kept as a
|
|
1553
|
+
// closure so the ORG_PROVISIONING retry loop can re-run it (issue #235).
|
|
1554
|
+
const sendPlug = async () => {
|
|
1555
|
+
let res;
|
|
1556
|
+
try {
|
|
1557
|
+
res = await fetchImpl(`${API_BASE}/api/v2/plug`, {
|
|
1558
|
+
method: "POST",
|
|
1559
|
+
headers,
|
|
1560
|
+
body: buildForm(),
|
|
1561
|
+
signal: AbortSignal.timeout(uploadTimeoutMs),
|
|
1562
|
+
});
|
|
1563
|
+
} catch (err) {
|
|
1564
|
+
if (err?.name === "AbortError" || err?.name === "TimeoutError") {
|
|
1565
|
+
throw new Error(
|
|
1566
|
+
`The plug request timed out after ${Math.round(uploadTimeoutMs / 1000)}s. ` +
|
|
1567
|
+
`The build may still be running on CloudGrid — check the build status ` +
|
|
1568
|
+
`(poll_url / grid_status, or your grid) before plugging again, so you don't create a duplicate.`,
|
|
1569
|
+
);
|
|
1570
|
+
}
|
|
1571
|
+
throw new Error(`Could not reach CloudGrid at ${API_BASE}: ${err.message}`);
|
|
1572
|
+
}
|
|
1573
|
+
const raw = await res.text();
|
|
1574
|
+
let data = null;
|
|
1575
|
+
try {
|
|
1576
|
+
data = JSON.parse(raw);
|
|
1577
|
+
} catch {
|
|
1578
|
+
/* handled below */
|
|
1579
|
+
}
|
|
1580
|
+
return { res, raw, data };
|
|
1581
|
+
};
|
|
1582
|
+
|
|
1583
|
+
// ── ORG_PROVISIONING retry (issue #235) ─────────────────────────────────────
|
|
1584
|
+
// A plug into a brand-new grid whose infra is still provisioning is refused
|
|
1585
|
+
// with 409 ORG_PROVISIONING. That write 409 is the ONLY signal that the grid
|
|
1586
|
+
// is not yet ready (no read endpoint reports it), so the plug IS the readiness
|
|
1587
|
+
// probe: retry the POST with a light backoff until it clears or the budget
|
|
1588
|
+
// runs out. A terminal 'failed' provisioning (hint says setup did not
|
|
1589
|
+
// complete) is NOT retried — it won't recover on its own. The wait is a
|
|
1590
|
+
// bounded server-side block matching the deploy-poll liveness pattern already
|
|
1591
|
+
// used after a create; the caller sees one honest result, never a raw code.
|
|
1592
|
+
const sleep = deps.sleep || sleepMs;
|
|
1593
|
+
const provisionBudgetMs = ctx.plugProvisionBudgetMs ?? PLUG_PROVISION_RETRY_BUDGET_MS;
|
|
1594
|
+
const provisionBaseMs = ctx.plugProvisionIntervalMs ?? PLUG_PROVISION_RETRY_BASE_MS;
|
|
1595
|
+
const provisionMaxMs = ctx.plugProvisionMaxMs ?? PLUG_PROVISION_RETRY_MAX_MS;
|
|
1596
|
+
const provisionStart = Date.now();
|
|
1597
|
+
let attempt = 0;
|
|
1598
|
+
let res, raw, data;
|
|
1599
|
+
for (;;) {
|
|
1600
|
+
({ res, raw, data } = await sendPlug());
|
|
1601
|
+
if (res.ok) break;
|
|
1602
|
+
if (res.status === 409 && data?.error?.code === "ORG_PROVISIONING") {
|
|
1603
|
+
const hint = data?.error?.details?.[0]?.hint || "";
|
|
1604
|
+
// Terminal failure — provisioning did not complete; retrying is futile.
|
|
1605
|
+
if (/did not (complete|finish)/i.test(hint)) {
|
|
1606
|
+
throw new Error(
|
|
1607
|
+
`The grid "${orgSlug || grid || "your grid"}" did not finish provisioning, so it can't accept a deploy. ${hint} ` +
|
|
1608
|
+
"This won't recover on its own from here — the grid likely needs to be recreated (grid_create_grid with a new slug), or an admin must retry provisioning.",
|
|
1609
|
+
);
|
|
1610
|
+
}
|
|
1611
|
+
const delay = Math.min(
|
|
1612
|
+
provisionMaxMs,
|
|
1613
|
+
Math.round(provisionBaseMs * Math.pow(1.5, attempt)),
|
|
1522
1614
|
);
|
|
1615
|
+
if (Date.now() - provisionStart + delay <= provisionBudgetMs) {
|
|
1616
|
+
attempt += 1;
|
|
1617
|
+
await sleep(delay);
|
|
1618
|
+
continue;
|
|
1619
|
+
}
|
|
1620
|
+
// Budget exhausted — fall through to the error handler, which appends the
|
|
1621
|
+
// ORG_PROVISIONING guidance (errorGuidance): honest "still setting up,
|
|
1622
|
+
// wait and re-call", never the raw code or the EDIT_REJECTED wording.
|
|
1523
1623
|
}
|
|
1524
|
-
|
|
1525
|
-
}
|
|
1526
|
-
const raw = await res.text();
|
|
1527
|
-
let data = null;
|
|
1528
|
-
try {
|
|
1529
|
-
data = JSON.parse(raw);
|
|
1530
|
-
} catch {
|
|
1531
|
-
/* handled below */
|
|
1624
|
+
break;
|
|
1532
1625
|
}
|
|
1626
|
+
|
|
1533
1627
|
if (!res.ok) {
|
|
1534
1628
|
const code = data?.error?.code;
|
|
1535
1629
|
const msg = data?.error?.message || data?.message || raw || `HTTP ${res.status}`;
|
|
@@ -1890,6 +1984,13 @@ export async function runVisibility(ctx, { target, visibility, inside, outside,
|
|
|
1890
1984
|
if (!inside || !outside) {
|
|
1891
1985
|
throw new Error("Both axes are required: inside (private|spaces|grid) and outside (none|link|public).");
|
|
1892
1986
|
}
|
|
1987
|
+
if (indexed === true) {
|
|
1988
|
+
// Search-indexing is the axis value `outside: public`, not a separate
|
|
1989
|
+
// flag. Accepting `indexed` here and building the body without it would
|
|
1990
|
+
// silently drop it (the exact accept-and-ignore class #2326 closes).
|
|
1991
|
+
// Mirror the CLI (visibility.ts:375-379): reject, point at the axis value.
|
|
1992
|
+
throw new Error("indexed does not apply with inside/outside. Use outside: public for a search-indexed link.");
|
|
1993
|
+
}
|
|
1893
1994
|
if (inside === "spaces" && normSpaces.length === 0) {
|
|
1894
1995
|
throw new Error("inside: spaces needs at least one space slug in `spaces`.");
|
|
1895
1996
|
}
|
|
@@ -1914,20 +2015,65 @@ export async function runVisibility(ctx, { target, visibility, inside, outside,
|
|
|
1914
2015
|
}
|
|
1915
2016
|
if (mode === "public") mode = "link"; // alias; `indexed: true` governs search-indexing
|
|
1916
2017
|
if (mode === "authenticated") {
|
|
1917
|
-
// Retired as a first-class mode — the axis body it equals
|
|
2018
|
+
// Retired as a first-class mode — the axis body it equals
|
|
2019
|
+
// (private + link + require_signin). Its param guards MUST match that
|
|
2020
|
+
// combo (below): a sign-in-gated link can be neither search-indexed nor
|
|
2021
|
+
// space-scoped, so `indexed`/`spaces` here must throw, not be dropped.
|
|
2022
|
+
if (indexed === true) {
|
|
2023
|
+
throw new Error("require_signin cannot combine with indexed: a sign-in-gated link cannot be search-indexed.");
|
|
2024
|
+
}
|
|
2025
|
+
if (normSpaces.length > 0) {
|
|
2026
|
+
throw new Error("`spaces` only applies with visibility: grid (or inside: spaces).");
|
|
2027
|
+
}
|
|
1918
2028
|
body = { share_scope: "private", external_access: "link", require_signin: true };
|
|
1919
2029
|
requested = "link with sign-in required (the retired 'authenticated')";
|
|
1920
2030
|
} else if (mode === "space") {
|
|
2031
|
+
// `space` is a pure internal audience (share_scope: spaces, external_access:
|
|
2032
|
+
// none). require_signin/indexed are external-link concerns — they have no
|
|
2033
|
+
// cell here, so reject rather than silently ignore (same defect class).
|
|
2034
|
+
if (require_signin === true) {
|
|
2035
|
+
throw new Error("require_signin only applies with visibility: link (or outside: link).");
|
|
2036
|
+
}
|
|
2037
|
+
if (indexed === true) {
|
|
2038
|
+
throw new Error("`indexed` only applies with visibility: link.");
|
|
2039
|
+
}
|
|
1921
2040
|
if (normSpaces.length === 0) {
|
|
1922
2041
|
throw new Error("'space' visibility needs the `spaces` list (which space slugs can see it) — or use inside: spaces with outside: none.");
|
|
1923
2042
|
}
|
|
1924
2043
|
body = { share_scope: "spaces", external_access: "none", visibility_spaces: normSpaces };
|
|
1925
2044
|
requested = `selected spaces (${normSpaces.join(", ")})`;
|
|
1926
2045
|
} else if (mode === "private" || mode === "grid" || mode === "link") {
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
2046
|
+
if (require_signin === true && mode !== "link") {
|
|
2047
|
+
throw new Error("require_signin only applies with visibility: link (or outside: link).");
|
|
2048
|
+
}
|
|
2049
|
+
if (indexed === true && mode !== "link") {
|
|
2050
|
+
throw new Error("`indexed` only applies with visibility: link.");
|
|
2051
|
+
}
|
|
2052
|
+
if (normSpaces.length > 0 && mode !== "grid") {
|
|
2053
|
+
throw new Error("`spaces` only applies with visibility: grid (or inside: spaces).");
|
|
2054
|
+
}
|
|
2055
|
+
if (mode === "link" && require_signin === true) {
|
|
2056
|
+
// A sign-in-gated link is the axis cell (private, link, require_signin);
|
|
2057
|
+
// search-indexing is the (·, public) cell. The two are mutually
|
|
2058
|
+
// exclusive: the server's axis body has NO representable state for both.
|
|
2059
|
+
// `link_indexed` on an axis body is silently DISCARDED —
|
|
2060
|
+
// validateAxisBody (packages/shared/src/visibility-write.ts:113) never
|
|
2061
|
+
// reads it and AxisBody (:23) has no such field, and
|
|
2062
|
+
// axesToLegacyVisibility (:78) for (private, link, require_signin)
|
|
2063
|
+
// returns { visibility: 'authenticated' } with no link_indexed. So the
|
|
2064
|
+
// server resolves link_indexed to null and drops `indexed`. Reject the
|
|
2065
|
+
// combination rather than forward a key the server throws away.
|
|
2066
|
+
if (indexed === true) {
|
|
2067
|
+
throw new Error("require_signin cannot combine with indexed: a sign-in-gated link cannot be search-indexed.");
|
|
2068
|
+
}
|
|
2069
|
+
body = { share_scope: "private", external_access: "link", require_signin: true };
|
|
2070
|
+
requested = "link with sign-in required";
|
|
2071
|
+
} else {
|
|
2072
|
+
body = { visibility: mode };
|
|
2073
|
+
if (mode === "grid" && normSpaces.length > 0) body.visibility_spaces = normSpaces;
|
|
2074
|
+
if (mode === "link") body.link_indexed = indexed === true;
|
|
2075
|
+
requested = mode + (mode === "link" && indexed === true ? " (search-indexed)" : "");
|
|
2076
|
+
}
|
|
1931
2077
|
} else {
|
|
1932
2078
|
throw new Error(`Unknown visibility '${visibility}'. Use private | grid | link (public = link alias), or the inside/outside axes.`);
|
|
1933
2079
|
}
|
|
@@ -2028,6 +2174,21 @@ const DEPLOY_POLL_BUDGET_MS = 45_000;
|
|
|
2028
2174
|
const DEPLOY_POLL_INTERVAL_MS = 3_000;
|
|
2029
2175
|
const sleepMs = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2030
2176
|
|
|
2177
|
+
// ── Provisioning retry budget (issue #235) ──────────────────────────────────
|
|
2178
|
+
// A plug into a brand-new grid is refused with 409 ORG_PROVISIONING until the
|
|
2179
|
+
// grid's infra finishes provisioning (field estimate ~15-30s; the platform's
|
|
2180
|
+
// own default remaining-time estimate is 30s). Since the ONLY signal that the
|
|
2181
|
+
// grid can accept a plug is this write 409 (no read endpoint reports it), the
|
|
2182
|
+
// plug retries the write with a light backoff until it clears or the budget
|
|
2183
|
+
// runs out. Budget = 45s, matching DEPLOY_POLL_BUDGET_MS — a value the MCP
|
|
2184
|
+
// client already tolerates within a single plug call (that post-create deploy
|
|
2185
|
+
// poll blocks up to 45s today), and comfortably inside PLUG_UPLOAD_TIMEOUT_MS
|
|
2186
|
+
// (120s). The guard rejects BEFORE parsing the upload body (plug.ts), so a
|
|
2187
|
+
// retry is cheap server-side.
|
|
2188
|
+
const PLUG_PROVISION_RETRY_BUDGET_MS = 45_000;
|
|
2189
|
+
const PLUG_PROVISION_RETRY_BASE_MS = 2_000;
|
|
2190
|
+
const PLUG_PROVISION_RETRY_MAX_MS = 8_000;
|
|
2191
|
+
|
|
2031
2192
|
// GET one deploy-trace snapshot. Returns { status, error } — status is the
|
|
2032
2193
|
// server's word ("queued"/"building"/"success"/"failed"), error is the
|
|
2033
2194
|
// user-language message when failed. Throws only on a non-OK HTTP response.
|