@superblocksteam/gateway 2.0.156 → 2.0.157-next.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/README.md +30 -3
- package/dist/capabilities/integration-metadata.d.ts +28 -0
- package/dist/capabilities/integration-metadata.js +174 -0
- package/dist/capabilities/integration-metadata.js.map +1 -0
- package/dist/capabilities/lifecycle.d.ts +12 -1
- package/dist/capabilities/lifecycle.js +100 -51
- package/dist/capabilities/lifecycle.js.map +1 -1
- package/dist/capabilities/types.d.ts +5 -0
- package/dist/capabilities/types.js.map +1 -1
- package/dist/capture/library-parent-script.d.ts +1 -0
- package/dist/capture/library-parent-script.js.map +1 -1
- package/dist/deps.d.ts +2 -0
- package/dist/integrations/map.d.ts +1 -0
- package/dist/integrations/map.js +1 -1
- package/dist/integrations/map.js.map +1 -1
- package/dist/sabs/browser-context.js +1 -0
- package/dist/sabs/browser-context.js.map +1 -1
- package/dist/sabs/editor-client-methods.js +4 -5
- package/dist/sabs/editor-client-methods.js.map +1 -1
- package/dist/sabs/session-peer.d.ts +4 -0
- package/dist/sabs/websocket-session-peer.d.ts +2 -1
- package/dist/sabs/websocket-session-peer.js +21 -11
- package/dist/sabs/websocket-session-peer.js.map +1 -1
- package/dist/server/client.d.ts +10 -4
- package/dist/server/client.js +13 -4
- package/dist/server/client.js.map +1 -1
- package/dist/start.d.ts +10 -0
- package/dist/start.js +5 -2
- package/dist/start.js.map +1 -1
- package/dist/telemetry/mcp-client.d.ts +22 -0
- package/dist/telemetry/mcp-client.js +129 -0
- package/dist/telemetry/mcp-client.js.map +1 -0
- package/dist/telemetry/runtime.d.ts +9 -0
- package/dist/telemetry/runtime.js +121 -0
- package/dist/telemetry/runtime.js.map +1 -0
- package/dist/transports/mcp/admin-tools.d.ts +3 -1
- package/dist/transports/mcp/admin-tools.js +12 -1
- package/dist/transports/mcp/admin-tools.js.map +1 -1
- package/dist/transports/mcp/app-status-html.d.ts +2 -2
- package/dist/transports/mcp/app-status-html.js +139 -8
- package/dist/transports/mcp/app-status-html.js.map +1 -1
- package/dist/transports/mcp/mount.js +89 -24
- package/dist/transports/mcp/mount.js.map +1 -1
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -4,8 +4,8 @@ Standalone Superblocks entry point for a **single MCP connector** (Admin + Build
|
|
|
4
4
|
over **stdio**, running as the already-logged-in Superblocks CLI user.
|
|
5
5
|
|
|
6
6
|
Builder tools (`start_app`, `import_app`, `edit_app`, `check_app_progress`,
|
|
7
|
-
`get_app`, `preview_app`, `publish_app`) plus
|
|
8
|
-
`@superblocksteam/mcp-server` share this process.
|
|
7
|
+
`get_app`, `get_integration_metadata`, `preview_app`, `publish_app`) plus
|
|
8
|
+
customer Admin tools from `@superblocksteam/mcp-server` share this process.
|
|
9
9
|
|
|
10
10
|
The MCP host owns process lifecycle: it spawns `superblocks gateway serve`.
|
|
11
11
|
There is no foreground HTTP `/mcp`, no OAuth resource server, and no linked-grant
|
|
@@ -147,7 +147,18 @@ package only the customer Admin surface. Builder tools are not registered.
|
|
|
147
147
|
## MCP tools
|
|
148
148
|
|
|
149
149
|
After `gateway setup`, restart Claude Code or Cursor and call `start_app`,
|
|
150
|
-
`edit_app`, `check_app_progress`, `get_app`, `
|
|
150
|
+
`edit_app`, `check_app_progress`, `get_app`, `get_integration_metadata`,
|
|
151
|
+
`preview_app`, and `publish_app`. `get_integration_metadata` reads tables,
|
|
152
|
+
columns, and types from a connected integration; use `search`, `limit`, and
|
|
153
|
+
`offset` for large results. It needs no application: without one it mints an
|
|
154
|
+
`integrations:build` token scoped to that single integration. Pass
|
|
155
|
+
`applicationId` only for an integration owned by one application, such as a
|
|
156
|
+
Native DB, which is invisible without app context.
|
|
157
|
+
It resolves the integration through the RBAC-annotated superset list rather than
|
|
158
|
+
the org-wide lookup by id, so it answers `integration_not_permitted` without
|
|
159
|
+
build permission on that integration and `integration_not_supported` for a
|
|
160
|
+
plugin Clark cannot use as a tool.
|
|
161
|
+
`get_integration_config_schema` is the create-integration form, not live data.
|
|
151
162
|
See `.env.example` for optional configuration.
|
|
152
163
|
|
|
153
164
|
## Env
|
|
@@ -168,3 +179,19 @@ Gateway debug logs include tool names, result states, decision branches, and
|
|
|
168
179
|
entry-point stacks. They intentionally omit credentials, prompts, answers, and
|
|
169
180
|
result payloads. Run `superblocks gateway setup --client <client> --debug`, then
|
|
170
181
|
restart the MCP host. Re-run setup without `--debug` to turn them off.
|
|
182
|
+
|
|
183
|
+
## Remote traceability
|
|
184
|
+
|
|
185
|
+
Gateway exports one OpenTelemetry trace per MCP process through the authenticated
|
|
186
|
+
Server `/api/v1/traces` proxy. Each tool call is a child span with the MCP client
|
|
187
|
+
name and version, tool name, duration, outcome, organization ID, and application
|
|
188
|
+
ID when available.
|
|
189
|
+
|
|
190
|
+
Prompts, answers, result payloads, credentials, and raw error messages are never
|
|
191
|
+
added to these spans. Use the application ID to correlate a trace with the
|
|
192
|
+
existing server-side app and build records.
|
|
193
|
+
|
|
194
|
+
Cloud-Prem uses `SUPERBLOCKS_DEPLOYMENT_TYPE=cloud-prem` (or `GATEWAY_LOCAL_AGENT`)
|
|
195
|
+
so the stricter telemetry policy is selected. Laptop agents still drop client,
|
|
196
|
+
organization, and application attributes until the Tier 2 traces contract lists
|
|
197
|
+
them; tool name, duration, and outcome still export.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type CapabilityContext } from "./lifecycle.js";
|
|
2
|
+
import type { CapabilityResult } from "./types.js";
|
|
3
|
+
export type GetIntegrationMetadataInput = {
|
|
4
|
+
applicationId?: string;
|
|
5
|
+
integrationId: string;
|
|
6
|
+
limit?: number;
|
|
7
|
+
offset?: number;
|
|
8
|
+
search?: string;
|
|
9
|
+
};
|
|
10
|
+
type MetadataEntry = {
|
|
11
|
+
path: string;
|
|
12
|
+
value: boolean | null | number | string;
|
|
13
|
+
};
|
|
14
|
+
export type GetIntegrationMetadataResult = {
|
|
15
|
+
/** Absent when the metadata was read through an integration-scoped token. */
|
|
16
|
+
applicationId?: string;
|
|
17
|
+
entries: MetadataEntry[];
|
|
18
|
+
hasMore: boolean;
|
|
19
|
+
integrationId: string;
|
|
20
|
+
integrationName: string;
|
|
21
|
+
nextOffset: number | null;
|
|
22
|
+
offset: number;
|
|
23
|
+
pluginId: string;
|
|
24
|
+
returnedCount: number;
|
|
25
|
+
totalCount: number;
|
|
26
|
+
};
|
|
27
|
+
export declare function getIntegrationMetadata(context: CapabilityContext, input: GetIntegrationMetadataInput): Promise<CapabilityResult<GetIntegrationMetadataResult>>;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { pickOrchestratorBaseUrl } from "../agents/resolve-orchestrator-url.js";
|
|
2
|
+
import { integrationIsToolSupported } from "../integrations/filters.js";
|
|
3
|
+
import { hasBuildPermission } from "../integrations/map.js";
|
|
4
|
+
import { pickEditorProfileKey } from "../integrations/profile-key.js";
|
|
5
|
+
import { HttpOrchestratorClient } from "../orchestrator/client.js";
|
|
6
|
+
import { sanitizeAgentFacingText, sanitizeAgentFacingValue, } from "../sabs/agent-facing-text.js";
|
|
7
|
+
import { ensurePrincipal, resolveApplicationId, } from "./lifecycle.js";
|
|
8
|
+
const DEFAULT_LIMIT = 100;
|
|
9
|
+
function labelPart(value) {
|
|
10
|
+
return typeof value === "string"
|
|
11
|
+
? value.trim().replaceAll(/[.[\]]/g, "_")
|
|
12
|
+
: "";
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A table name alone is ambiguous: `public.users` and `analytics.users` are
|
|
16
|
+
* different tables, so qualify with the schema whenever the row carries one.
|
|
17
|
+
*/
|
|
18
|
+
function arrayEntryLabel(value, index) {
|
|
19
|
+
if (value && typeof value === "object") {
|
|
20
|
+
const row = value;
|
|
21
|
+
const name = labelPart(row.name);
|
|
22
|
+
if (name) {
|
|
23
|
+
const schema = labelPart(row.schema);
|
|
24
|
+
return schema ? `${schema}.${name}` : name;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return String(index);
|
|
28
|
+
}
|
|
29
|
+
function metadataEntries(value, path = "", entries = []) {
|
|
30
|
+
if (value === null ||
|
|
31
|
+
typeof value === "boolean" ||
|
|
32
|
+
typeof value === "number" ||
|
|
33
|
+
typeof value === "string") {
|
|
34
|
+
entries.push({ path, value });
|
|
35
|
+
return entries;
|
|
36
|
+
}
|
|
37
|
+
if (Array.isArray(value)) {
|
|
38
|
+
const seen = new Set();
|
|
39
|
+
value.forEach((entry, index) => {
|
|
40
|
+
const label = arrayEntryLabel(entry, index);
|
|
41
|
+
// Two rows that still label identically would be indistinguishable, so
|
|
42
|
+
// fall back to the index for the later one.
|
|
43
|
+
const unique = seen.has(label) ? `${label}#${index}` : label;
|
|
44
|
+
seen.add(unique);
|
|
45
|
+
metadataEntries(entry, `${path}[${unique}]`, entries);
|
|
46
|
+
});
|
|
47
|
+
return entries;
|
|
48
|
+
}
|
|
49
|
+
if (value && typeof value === "object") {
|
|
50
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
51
|
+
metadataEntries(entry, path ? `${path}.${key}` : key, entries);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return entries;
|
|
55
|
+
}
|
|
56
|
+
export async function getIntegrationMetadata(context, input) {
|
|
57
|
+
const principal = await ensurePrincipal(context);
|
|
58
|
+
if (principal.kind !== "ok") {
|
|
59
|
+
return principal;
|
|
60
|
+
}
|
|
61
|
+
let applicationId;
|
|
62
|
+
// Named for the agent before the integration resolves, so a failure on the
|
|
63
|
+
// way to the metadata still reports which integration it was reaching for.
|
|
64
|
+
let label = sanitizeAgentFacingText(input.integrationId);
|
|
65
|
+
try {
|
|
66
|
+
// An app is preferred but not required. App-owned integrations (Native DB
|
|
67
|
+
// among them) are only visible through an app-scoped token, so use one
|
|
68
|
+
// whenever an app is in play and fall back to the integration-scoped token
|
|
69
|
+
// rather than making the caller invent an application.
|
|
70
|
+
const resolved = await resolveApplicationId(context, principal.value, input.applicationId);
|
|
71
|
+
applicationId = resolved.kind === "ok" ? resolved.value : undefined;
|
|
72
|
+
// The RBAC-annotated list, not the org-wide lookup by id: only this one
|
|
73
|
+
// carries the permissions that say whether the caller may build with the
|
|
74
|
+
// integration at all.
|
|
75
|
+
const rows = await context.server.getSupersetIntegrations({
|
|
76
|
+
apiKey: principal.value.apiKey,
|
|
77
|
+
...(applicationId && { applicationId }),
|
|
78
|
+
organizationId: principal.value.organizationId,
|
|
79
|
+
});
|
|
80
|
+
const integration = rows.find((row) => row.id === input.integrationId);
|
|
81
|
+
if (!integration) {
|
|
82
|
+
return {
|
|
83
|
+
...(applicationId && { applicationId }),
|
|
84
|
+
kind: "error",
|
|
85
|
+
code: "integration_not_accessible",
|
|
86
|
+
message: `Integration ${label} was not found or is not accessible.`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const integrationName = sanitizeAgentFacingText(integration.name);
|
|
90
|
+
const pluginId = sanitizeAgentFacingText(integration.pluginId);
|
|
91
|
+
label = integrationName;
|
|
92
|
+
if (!hasBuildPermission(integration)) {
|
|
93
|
+
return {
|
|
94
|
+
...(applicationId && { applicationId }),
|
|
95
|
+
kind: "error",
|
|
96
|
+
code: "integration_not_permitted",
|
|
97
|
+
message: `You do not have build permission on ${integrationName}.`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
if (!integrationIsToolSupported(integration.pluginId)) {
|
|
101
|
+
return {
|
|
102
|
+
...(applicationId && { applicationId }),
|
|
103
|
+
kind: "error",
|
|
104
|
+
code: "integration_not_supported",
|
|
105
|
+
message: `${integrationName} is a ${pluginId} integration, which this tool cannot inspect.`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const profiles = await context.server.getOrganizationProfiles({
|
|
109
|
+
apiKey: principal.value.apiKey,
|
|
110
|
+
organizationId: principal.value.organizationId,
|
|
111
|
+
});
|
|
112
|
+
const profileKey = pickEditorProfileKey(context.config.profileKey, profiles);
|
|
113
|
+
const inventory = await context.server.getAgentInventory({
|
|
114
|
+
apiKey: principal.value.apiKey,
|
|
115
|
+
organizationId: principal.value.organizationId,
|
|
116
|
+
});
|
|
117
|
+
const orchestratorUrl = pickOrchestratorBaseUrl({
|
|
118
|
+
agentType: inventory.agentType,
|
|
119
|
+
agents: inventory.agents,
|
|
120
|
+
profileKey,
|
|
121
|
+
});
|
|
122
|
+
const scopedToken = await context.server.issueScopedToken(applicationId
|
|
123
|
+
? {
|
|
124
|
+
apiKey: principal.value.apiKey,
|
|
125
|
+
applicationId,
|
|
126
|
+
scope: "apps:update",
|
|
127
|
+
}
|
|
128
|
+
: {
|
|
129
|
+
apiKey: principal.value.apiKey,
|
|
130
|
+
integrationId: input.integrationId,
|
|
131
|
+
scope: "integrations:build",
|
|
132
|
+
});
|
|
133
|
+
const metadata = await new HttpOrchestratorClient(orchestratorUrl).getMetadata({
|
|
134
|
+
apiKey: principal.value.apiKey,
|
|
135
|
+
integrationId: input.integrationId,
|
|
136
|
+
profileKey,
|
|
137
|
+
scopedToken,
|
|
138
|
+
});
|
|
139
|
+
const sanitized = sanitizeAgentFacingValue(metadata);
|
|
140
|
+
const search = input.search?.trim().toLowerCase();
|
|
141
|
+
const matched = metadataEntries(sanitized).filter((entry) => !search ||
|
|
142
|
+
entry.path.toLowerCase().includes(search) ||
|
|
143
|
+
String(entry.value).toLowerCase().includes(search));
|
|
144
|
+
const limit = input.limit ?? DEFAULT_LIMIT;
|
|
145
|
+
const offset = input.offset ?? 0;
|
|
146
|
+
const entries = matched.slice(offset, offset + limit);
|
|
147
|
+
const nextOffset = offset + entries.length < matched.length ? offset + entries.length : null;
|
|
148
|
+
return {
|
|
149
|
+
kind: "ok",
|
|
150
|
+
value: {
|
|
151
|
+
...(applicationId && { applicationId }),
|
|
152
|
+
entries,
|
|
153
|
+
hasMore: nextOffset !== null,
|
|
154
|
+
integrationId: integration.id,
|
|
155
|
+
integrationName,
|
|
156
|
+
nextOffset,
|
|
157
|
+
offset,
|
|
158
|
+
pluginId,
|
|
159
|
+
returnedCount: entries.length,
|
|
160
|
+
totalCount: matched.length,
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
const detail = sanitizeAgentFacingText(error instanceof Error ? error.message : "Unknown metadata error").replaceAll("\u2014", "-");
|
|
166
|
+
return {
|
|
167
|
+
...(applicationId && { applicationId }),
|
|
168
|
+
kind: "error",
|
|
169
|
+
code: "integration_metadata_unavailable",
|
|
170
|
+
message: `Could not load metadata for ${label}: ${detail}`,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=integration-metadata.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integration-metadata.js","sourceRoot":"","sources":["../../src/capabilities/integration-metadata.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,7 +5,7 @@ import type { CapturePreviewScreenshot } from "../preview/capture-screenshot.js"
|
|
|
5
5
|
import type { CallerRef, ProgressStore, RecentAppStore, TurnStore } from "../resume/types.js";
|
|
6
6
|
import type { SessionPeer } from "../sabs/session-peer.js";
|
|
7
7
|
import type { SuperblocksServerClient } from "../server/client.js";
|
|
8
|
-
import type { CapabilityResult, CheckAppProgressInput, CheckAppProgressResult, EditAppInput, EditAppResult, GetAppInput, GetAppResult, ImportAppInput, ImportAppResult, PreviewAppInput, PreviewAppResult, Principal, ProgressEvent, PublishAppInput, PublishAppResult, StartAppInput, StartAppResult } from "./types.js";
|
|
8
|
+
import type { CapabilityResult, CheckAppProgressInput, CheckAppProgressResult, EditAppInput, EditAppResult, GetAppInput, GetAppResult, ImportAppInput, ImportAppResult, PreviewAppInput, PreviewAppResult, Principal, ProgressEvent, PublishAppInput, PublishAppResult, ResolvedPrincipal, StartAppInput, StartAppResult } from "./types.js";
|
|
9
9
|
export { IMPORT_ZIP_MAX_BYTES } from "./types.js";
|
|
10
10
|
export type CapabilityContext = {
|
|
11
11
|
/**
|
|
@@ -23,6 +23,7 @@ export type CapabilityContext = {
|
|
|
23
23
|
/** Injected so pacing and stall thresholds are testable without real time. */
|
|
24
24
|
now?: () => number;
|
|
25
25
|
onProgress?: (event: ProgressEvent) => void;
|
|
26
|
+
onPrincipalResolved?: (principal: ResolvedPrincipal) => void;
|
|
26
27
|
principal: Principal;
|
|
27
28
|
/** Where each caller's place in the event stream is kept between calls. */
|
|
28
29
|
progressCursors?: ProgressStore;
|
|
@@ -44,6 +45,16 @@ export type CapabilityContext = {
|
|
|
44
45
|
signal?: AbortSignal;
|
|
45
46
|
turns: TurnStore;
|
|
46
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* Resolves the Superblocks user behind the CLI session this Gateway was
|
|
50
|
+
* started with.
|
|
51
|
+
*
|
|
52
|
+
* Identity is the already-logged-in Superblocks CLI user. A rejection here
|
|
53
|
+
* means that personal API key is bad, not that the user needs a second login,
|
|
54
|
+
* so it surfaces as an error rather than an interactive-auth elicitation.
|
|
55
|
+
*/
|
|
56
|
+
export declare function ensurePrincipal(ctx: CapabilityContext): Promise<CapabilityResult<ResolvedPrincipal>>;
|
|
57
|
+
export declare function resolveApplicationId(ctx: CapabilityContext, principal: ResolvedPrincipal, applicationId: string | undefined): Promise<CapabilityResult<string>>;
|
|
47
58
|
export declare function startApp(ctx: CapabilityContext, input: StartAppInput): Promise<CapabilityResult<StartAppResult>>;
|
|
48
59
|
/**
|
|
49
60
|
* Creates a fullstack app, uploads the caller's archive as an app attachment,
|
|
@@ -56,6 +56,7 @@ function libraryCaptureInput(ctx, applicationId) {
|
|
|
56
56
|
? { agentUrls: context.bootstrapData.agentUrls }
|
|
57
57
|
: {}),
|
|
58
58
|
appId: context.bootstrapData.appId,
|
|
59
|
+
devServerAuthorization: auth,
|
|
59
60
|
...(context.bootstrapData.featureFlags
|
|
60
61
|
? { featureFlags: context.bootstrapData.featureFlags }
|
|
61
62
|
: {}),
|
|
@@ -227,19 +228,21 @@ function defaultAppName(prompt) {
|
|
|
227
228
|
* means that personal API key is bad, not that the user needs a second login,
|
|
228
229
|
* so it surfaces as an error rather than an interactive-auth elicitation.
|
|
229
230
|
*/
|
|
230
|
-
async function ensurePrincipal(ctx) {
|
|
231
|
+
export async function ensurePrincipal(ctx) {
|
|
231
232
|
const { principal } = ctx;
|
|
232
233
|
// MCP already has the CLI session key. Looking it up again would spend a
|
|
233
234
|
// second round trip on `/users/me`, the slowest endpoint the Superblocks
|
|
234
235
|
// API has, when the principal is already resolved (ENG-5806).
|
|
235
236
|
if (principal.organizationId && principal.userId) {
|
|
237
|
+
const resolved = {
|
|
238
|
+
...principal,
|
|
239
|
+
organizationId: principal.organizationId,
|
|
240
|
+
userId: principal.userId,
|
|
241
|
+
};
|
|
242
|
+
ctx.onPrincipalResolved?.(resolved);
|
|
236
243
|
return {
|
|
237
244
|
kind: "ok",
|
|
238
|
-
value:
|
|
239
|
-
...principal,
|
|
240
|
-
organizationId: principal.organizationId,
|
|
241
|
-
userId: principal.userId,
|
|
242
|
-
},
|
|
245
|
+
value: resolved,
|
|
243
246
|
};
|
|
244
247
|
}
|
|
245
248
|
let validated;
|
|
@@ -276,14 +279,16 @@ async function ensurePrincipal(ctx) {
|
|
|
276
279
|
message: "The Superblocks API rejected the credential issued for this caller.",
|
|
277
280
|
};
|
|
278
281
|
}
|
|
282
|
+
const resolved = {
|
|
283
|
+
...principal,
|
|
284
|
+
email: validated.email,
|
|
285
|
+
organizationId: validated.organizationId,
|
|
286
|
+
userId: validated.userId,
|
|
287
|
+
};
|
|
288
|
+
ctx.onPrincipalResolved?.(resolved);
|
|
279
289
|
return {
|
|
280
290
|
kind: "ok",
|
|
281
|
-
value:
|
|
282
|
-
...principal,
|
|
283
|
-
email: validated.email,
|
|
284
|
-
organizationId: validated.organizationId,
|
|
285
|
-
userId: validated.userId,
|
|
286
|
-
},
|
|
291
|
+
value: resolved,
|
|
287
292
|
};
|
|
288
293
|
}
|
|
289
294
|
/**
|
|
@@ -378,7 +383,7 @@ async function elicitStepUp(ctx, idempotencyKey, applicationId) {
|
|
|
378
383
|
},
|
|
379
384
|
};
|
|
380
385
|
}
|
|
381
|
-
async function resolveApplicationId(ctx, principal, applicationId) {
|
|
386
|
+
export async function resolveApplicationId(ctx, principal, applicationId) {
|
|
382
387
|
if (applicationId?.trim()) {
|
|
383
388
|
return { kind: "ok", value: applicationId.trim() };
|
|
384
389
|
}
|
|
@@ -512,7 +517,9 @@ function turnSummary(turn) {
|
|
|
512
517
|
return "Superblocks is still working, but only the editor can show it — open the edit URL";
|
|
513
518
|
}
|
|
514
519
|
if (turn.generationStatus === "live_edit_terminated") {
|
|
515
|
-
return
|
|
520
|
+
return turn.retryAction === "retry_import"
|
|
521
|
+
? "The Superblocks live-edit session terminated before anything was built — send the archive again with import_app"
|
|
522
|
+
: "The Superblocks live-edit session terminated before anything was built — send the prompt again with edit_app";
|
|
516
523
|
}
|
|
517
524
|
if (turn.checkpointCommitId) {
|
|
518
525
|
return `Application ready — checkpoint saved. ${NEXT_STEPS}`;
|
|
@@ -668,6 +675,7 @@ async function forwardClarkPrompt(ctx, input) {
|
|
|
668
675
|
...(forward.recentRuns && forward.recentRuns.length > 0
|
|
669
676
|
? { recentRuns: forward.recentRuns }
|
|
670
677
|
: {}),
|
|
678
|
+
...(forward.retryAction ? { retryAction: forward.retryAction } : {}),
|
|
671
679
|
viewStatus: forward.viewStatus,
|
|
672
680
|
},
|
|
673
681
|
};
|
|
@@ -757,17 +765,13 @@ function namingCreatedApplication(applicationId, capability, result) {
|
|
|
757
765
|
* The recovery to advertise when the machinery around a turn fails: `edit_app`
|
|
758
766
|
* with the same prompt, which is what `retry_build` means.
|
|
759
767
|
*
|
|
760
|
-
*
|
|
761
|
-
*
|
|
762
|
-
*
|
|
763
|
-
* cannot see. Advertising a recovery that cannot finish is worse than
|
|
764
|
-
* advertising none, so the import path gets `applicationId` and no hint.
|
|
765
|
-
* TODO(ENG-5917): let `import_app` resume into an application it already
|
|
766
|
-
* created, and give the import path a recovery of its own.
|
|
768
|
+
* A regular prompt is retried with `edit_app`. An import prompt carries an
|
|
769
|
+
* attachment, so it must be retried with `import_app` to upload the archive
|
|
770
|
+
* again and obtain a fresh signed URL.
|
|
767
771
|
*/
|
|
768
772
|
function retryIfReplayable(attachments) {
|
|
769
773
|
return attachments && attachments.length > 0
|
|
770
|
-
?
|
|
774
|
+
? { pendingAction: "retry_import" }
|
|
771
775
|
: { pendingAction: "retry_build" };
|
|
772
776
|
}
|
|
773
777
|
/**
|
|
@@ -809,7 +813,9 @@ function rememberFailedStart(ctx, input) {
|
|
|
809
813
|
organizationId: input.principal.organizationId,
|
|
810
814
|
// Reaches the caller as the dead start's reply on its next poll, so it
|
|
811
815
|
// has to stand on its own there rather than lean on this call's error.
|
|
812
|
-
reason:
|
|
816
|
+
reason: input.reason ??
|
|
817
|
+
`The Superblocks live-edit session for application ${input.applicationId} never opened, so the prompt never reached Superblocks and nothing was built. Nothing is running for that application; send the same instruction again to retry it. (${cause})`,
|
|
818
|
+
...(input.retryAction ? { retryAction: input.retryAction } : {}),
|
|
813
819
|
userId: input.principal.userId,
|
|
814
820
|
});
|
|
815
821
|
}
|
|
@@ -865,7 +871,12 @@ async function promptNewApplication(ctx, args) {
|
|
|
865
871
|
});
|
|
866
872
|
}
|
|
867
873
|
catch (error) {
|
|
868
|
-
rememberFailedStart(ctx, {
|
|
874
|
+
rememberFailedStart(ctx, {
|
|
875
|
+
applicationId,
|
|
876
|
+
error,
|
|
877
|
+
principal,
|
|
878
|
+
...(args.attachments?.length ? { retryAction: "retry_import" } : {}),
|
|
879
|
+
});
|
|
869
880
|
return liveEditUnavailable(error, args.attachments);
|
|
870
881
|
}
|
|
871
882
|
ctx.onProgress?.({
|
|
@@ -1150,14 +1161,19 @@ function normalizeImportSource(source) {
|
|
|
1150
1161
|
* wizard, without the sessionStorage hop.
|
|
1151
1162
|
*/
|
|
1152
1163
|
export async function importApp(ctx, input) {
|
|
1164
|
+
const resumedApplicationId = input.applicationId?.trim();
|
|
1153
1165
|
const archiveResult = await resolveImportArchive(input, ctx.config.importSearchDirs);
|
|
1154
1166
|
if (archiveResult.kind !== "ok") {
|
|
1155
|
-
return
|
|
1167
|
+
return resumedApplicationId
|
|
1168
|
+
? namingApplication(resumedApplicationId, archiveResult)
|
|
1169
|
+
: archiveResult;
|
|
1156
1170
|
}
|
|
1157
1171
|
const archive = archiveResult.value;
|
|
1158
1172
|
const principalResult = await ensurePrincipal(ctx);
|
|
1159
1173
|
if (principalResult.kind !== "ok") {
|
|
1160
|
-
return
|
|
1174
|
+
return resumedApplicationId
|
|
1175
|
+
? namingApplication(resumedApplicationId, principalResult)
|
|
1176
|
+
: principalResult;
|
|
1161
1177
|
}
|
|
1162
1178
|
const principal = principalResult.value;
|
|
1163
1179
|
const source = normalizeImportSource(input.source);
|
|
@@ -1168,25 +1184,26 @@ export async function importApp(ctx, input) {
|
|
|
1168
1184
|
});
|
|
1169
1185
|
const explicitName = input.name?.trim();
|
|
1170
1186
|
const name = explicitName || defaultAppName(migrationPrompt);
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1187
|
+
let applicationId = resumedApplicationId;
|
|
1188
|
+
if (!applicationId) {
|
|
1189
|
+
ctx.onProgress?.({
|
|
1190
|
+
message: "Creating application for import",
|
|
1191
|
+
type: "progress",
|
|
1192
|
+
});
|
|
1193
|
+
const created = await createApplicationForPrompt(ctx, {
|
|
1194
|
+
apiKey: principal.apiKey,
|
|
1195
|
+
name,
|
|
1196
|
+
organizationId: principal.organizationId,
|
|
1197
|
+
templateName: "app-fullstack",
|
|
1198
|
+
});
|
|
1199
|
+
if (created.kind !== "ok") {
|
|
1200
|
+
return created;
|
|
1201
|
+
}
|
|
1202
|
+
applicationId = created.value.id;
|
|
1183
1203
|
}
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
await rememberApplication(ctx, principal, app.id);
|
|
1188
|
-
return namingCreatedApplication(app.id, "import_app", await migrateArchiveIntoApplication(ctx, {
|
|
1189
|
-
applicationId: app.id,
|
|
1204
|
+
await rememberApplication(ctx, principal, applicationId);
|
|
1205
|
+
const result = await migrateArchiveIntoApplication(ctx, {
|
|
1206
|
+
applicationId,
|
|
1190
1207
|
archive,
|
|
1191
1208
|
explicitName,
|
|
1192
1209
|
input,
|
|
@@ -1194,7 +1211,10 @@ export async function importApp(ctx, input) {
|
|
|
1194
1211
|
name,
|
|
1195
1212
|
principal,
|
|
1196
1213
|
source,
|
|
1197
|
-
})
|
|
1214
|
+
});
|
|
1215
|
+
return resumedApplicationId
|
|
1216
|
+
? namingApplication(applicationId, result)
|
|
1217
|
+
: namingCreatedApplication(applicationId, "import_app", result);
|
|
1198
1218
|
}
|
|
1199
1219
|
/**
|
|
1200
1220
|
* Uploads the archive onto an application that already exists and hands
|
|
@@ -1218,10 +1238,18 @@ async function migrateArchiveIntoApplication(ctx, args) {
|
|
|
1218
1238
|
}
|
|
1219
1239
|
catch (error) {
|
|
1220
1240
|
const message = error instanceof Error ? error.message : String(error);
|
|
1241
|
+
rememberFailedStart(ctx, {
|
|
1242
|
+
applicationId,
|
|
1243
|
+
error,
|
|
1244
|
+
principal,
|
|
1245
|
+
reason: `The archive for application ${applicationId} could not be uploaded, so the migration never started and nothing was built. Send the same archive again with import_app to retry it. (${message})`,
|
|
1246
|
+
retryAction: "retry_import",
|
|
1247
|
+
});
|
|
1221
1248
|
return {
|
|
1222
1249
|
kind: "error",
|
|
1223
1250
|
code: "import_upload_failed",
|
|
1224
1251
|
message,
|
|
1252
|
+
pendingAction: "retry_import",
|
|
1225
1253
|
};
|
|
1226
1254
|
}
|
|
1227
1255
|
return promptNewApplication(ctx, {
|
|
@@ -1247,7 +1275,13 @@ async function migrateArchiveIntoApplication(ctx, args) {
|
|
|
1247
1275
|
prompt: args.migrationPrompt,
|
|
1248
1276
|
});
|
|
1249
1277
|
}
|
|
1250
|
-
/**
|
|
1278
|
+
/**
|
|
1279
|
+
* Machine-readable shape a cancelled wait answers with.
|
|
1280
|
+
*
|
|
1281
|
+
* Callers name the application alongside it. A cancelled wait says nothing
|
|
1282
|
+
* about the work, which carries on: a caller asked to resume it has to be able
|
|
1283
|
+
* to point at the application rather than start a second one.
|
|
1284
|
+
*/
|
|
1251
1285
|
function cancelled(capability) {
|
|
1252
1286
|
return {
|
|
1253
1287
|
kind: "error",
|
|
@@ -1336,7 +1370,7 @@ export async function checkAppProgress(ctx, input) {
|
|
|
1336
1370
|
// while the credential was being resolved should never reach the point of
|
|
1337
1371
|
// registering a waiter it will not read.
|
|
1338
1372
|
if (ctx.signal?.aborted) {
|
|
1339
|
-
return cancelled("check_app_progress");
|
|
1373
|
+
return namingApplication(applicationId, cancelled("check_app_progress"));
|
|
1340
1374
|
}
|
|
1341
1375
|
const cursorKey = cursorKeyFor(ctx, principal, applicationId);
|
|
1342
1376
|
const stored = cursorKey
|
|
@@ -1350,7 +1384,7 @@ export async function checkAppProgress(ctx, input) {
|
|
|
1350
1384
|
waitMs: hostCallBudgetMs(ctx, ctx.config.clarkProgressWaitMs),
|
|
1351
1385
|
});
|
|
1352
1386
|
if (ctx.signal?.aborted) {
|
|
1353
|
-
return cancelled("check_app_progress");
|
|
1387
|
+
return namingApplication(applicationId, cancelled("check_app_progress"));
|
|
1354
1388
|
}
|
|
1355
1389
|
const snapshot = await readSnapshot(ctx, {
|
|
1356
1390
|
applicationId,
|
|
@@ -1468,6 +1502,9 @@ export async function checkAppProgress(ctx, input) {
|
|
|
1468
1502
|
reply: update.reply,
|
|
1469
1503
|
...(update.decision ? { decision: update.decision } : {}),
|
|
1470
1504
|
generationStatus: update.generationStatus,
|
|
1505
|
+
...(update.retryAction && emptyTurnStatus(update.generationStatus)
|
|
1506
|
+
? { pendingAction: update.retryAction }
|
|
1507
|
+
: {}),
|
|
1471
1508
|
status,
|
|
1472
1509
|
viewStatus: update.viewStatus,
|
|
1473
1510
|
},
|
|
@@ -2052,7 +2089,13 @@ function localUnbuiltReason(turn, pending) {
|
|
|
2052
2089
|
return undefined;
|
|
2053
2090
|
}
|
|
2054
2091
|
const status = emptyTurnStatus(turn.generationStatus);
|
|
2055
|
-
return status
|
|
2092
|
+
return status
|
|
2093
|
+
? {
|
|
2094
|
+
kind: "empty_turn",
|
|
2095
|
+
retryAction: turn.retryAction ?? "retry_build",
|
|
2096
|
+
status,
|
|
2097
|
+
}
|
|
2098
|
+
: undefined;
|
|
2056
2099
|
}
|
|
2057
2100
|
/**
|
|
2058
2101
|
* The same question asked of what was persisted, for a caller with no live turn
|
|
@@ -2140,6 +2183,8 @@ function persistedTurnEnd(status) {
|
|
|
2140
2183
|
// plain question and cannot be approved.
|
|
2141
2184
|
case "needs_decision":
|
|
2142
2185
|
return { kind: "awaiting_decision" };
|
|
2186
|
+
// The stream never recorded whether the prompt carried an archive, so
|
|
2187
|
+
// this path cannot pick retry_build vs retry_import. Omit rather than guess.
|
|
2143
2188
|
case "live_edit_terminated":
|
|
2144
2189
|
return { kind: "empty_turn", status };
|
|
2145
2190
|
// A `cancelled` in the tail may still have a real app behind it: the
|
|
@@ -2211,7 +2256,11 @@ async function unbuiltApp(ctx, principal, applicationId) {
|
|
|
2211
2256
|
if (reason.kind === "empty_turn") {
|
|
2212
2257
|
return {
|
|
2213
2258
|
kind: "ok",
|
|
2214
|
-
value: {
|
|
2259
|
+
value: {
|
|
2260
|
+
...app,
|
|
2261
|
+
...(reason.retryAction ? { pendingAction: reason.retryAction } : {}),
|
|
2262
|
+
status: reason.status,
|
|
2263
|
+
},
|
|
2215
2264
|
};
|
|
2216
2265
|
}
|
|
2217
2266
|
return {
|
|
@@ -2345,7 +2394,7 @@ async function waitForBuild(ctx, input) {
|
|
|
2345
2394
|
let started = false;
|
|
2346
2395
|
for (;;) {
|
|
2347
2396
|
if (ctx.signal?.aborted) {
|
|
2348
|
-
return cancelled("get_app");
|
|
2397
|
+
return namingApplication(input.applicationId, cancelled("get_app"));
|
|
2349
2398
|
}
|
|
2350
2399
|
// Checked before the network call, not only after it: a budget already
|
|
2351
2400
|
// exhausted when this loop begins (or used up by the previous
|