@agent-native/core 0.84.29 → 0.84.32
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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +19 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/client/chat/tool-call-display.tsx +50 -8
- package/corpus/core/src/client/session-replay.ts +5 -0
- package/corpus/core/src/client/sse-event-processor.ts +17 -4
- package/corpus/core/src/server/ssr-handler.ts +152 -8
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +13 -1
- package/corpus/templates/analytics/changelog/2026-07-01-session-replays-keep-inlined-css-without-live-resource-loa.md +6 -0
- package/corpus/templates/design/AGENTS.md +18 -0
- package/corpus/templates/design/actions/apply-source-edit.ts +87 -0
- package/corpus/templates/design/actions/list-source-files.ts +52 -0
- package/corpus/templates/design/actions/navigate.ts +3 -2
- package/corpus/templates/design/actions/preview-source-edit.ts +85 -0
- package/corpus/templates/design/actions/read-source-file.ts +55 -0
- package/corpus/templates/design/actions/resolve-selection-source.ts +101 -0
- package/corpus/templates/design/actions/view-screen.ts +43 -1
- package/corpus/templates/design/app/components/design/CodeWorkbenchHost.tsx +630 -0
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +9 -6
- package/corpus/templates/design/app/pages/DesignEditor.tsx +189 -11
- package/corpus/templates/design/changelog/2026-07-01-design-code-workspace.md +6 -0
- package/corpus/templates/design/changelog/2026-07-01-design-previews-refresh-immediately-after-agent-screen-edits.md +6 -0
- package/corpus/templates/design/server/source-workspace.ts +215 -0
- package/corpus/templates/design/shared/design-source-capabilities.ts +5 -5
- package/corpus/templates/design/shared/source-workspace.ts +149 -0
- package/dist/client/chat/tool-call-display.d.ts +1 -0
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +22 -5
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/session-replay.d.ts +1 -0
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +2 -0
- package/dist/client/session-replay.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +13 -4
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/ssr-handler.d.ts.map +1 -1
- package/dist/server/ssr-handler.js +111 -8
- package/dist/server/ssr-handler.js.map +1 -1
- package/package.json +1 -1
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.84.32
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- da8ac0a: Keep tail-resume reconnect content display-only, recover zero-byte action-prep stalls, and polish Design screen tool labels.
|
|
8
|
+
|
|
9
|
+
## 0.84.31
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 3190dea: Inline rrweb stylesheet snapshots by default so Analytics session replay captures can play back styled pages without live CSS fetches.
|
|
14
|
+
|
|
15
|
+
## 0.84.30
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- 80e618a: Fix hosted Google Analytics / Tag Manager injection by baking the measurement id into Nitro server bundles and merging the required GA/GTM script, connect, and image hosts into existing stricter document CSPs.
|
|
20
|
+
- 80e618a: Improve chat tool-preparation UX by hiding zero-byte progress, using clearer preparation/writing copy, and showing a delayed long-running update hint.
|
|
21
|
+
|
|
3
22
|
## 0.84.29
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.84.
|
|
3
|
+
"version": "0.84.32",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -70,6 +70,43 @@ export const ApprovalContext = React.createContext<ApprovalContextValue | null>(
|
|
|
70
70
|
null,
|
|
71
71
|
);
|
|
72
72
|
|
|
73
|
+
export const TOOL_LONG_RUNNING_HINT_DELAY_MS = 45_000;
|
|
74
|
+
|
|
75
|
+
function ToolLongRunningHintShell({
|
|
76
|
+
toolName,
|
|
77
|
+
isRunning,
|
|
78
|
+
children,
|
|
79
|
+
}: {
|
|
80
|
+
toolName: string;
|
|
81
|
+
isRunning: boolean;
|
|
82
|
+
children: React.ReactNode;
|
|
83
|
+
}) {
|
|
84
|
+
const [showLongRunningHint, setShowLongRunningHint] = useState(false);
|
|
85
|
+
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (!isRunning) {
|
|
88
|
+
setShowLongRunningHint(false);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
setShowLongRunningHint(false);
|
|
92
|
+
const timeout = window.setTimeout(() => {
|
|
93
|
+
setShowLongRunningHint(true);
|
|
94
|
+
}, TOOL_LONG_RUNNING_HINT_DELAY_MS);
|
|
95
|
+
return () => window.clearTimeout(timeout);
|
|
96
|
+
}, [isRunning, toolName]);
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<>
|
|
100
|
+
{children}
|
|
101
|
+
{isRunning && showLongRunningHint && (
|
|
102
|
+
<div className="mt-0.5 px-2.5 text-[11px] leading-snug text-muted-foreground/80">
|
|
103
|
+
Still working. Large updates can take a minute or two.
|
|
104
|
+
</div>
|
|
105
|
+
)}
|
|
106
|
+
</>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
73
110
|
// ─── Tool-payload formatting ──────────────────────────────────────────────────
|
|
74
111
|
|
|
75
112
|
type ToolDetailSection = "input" | "result";
|
|
@@ -435,38 +472,43 @@ export function ToolCallDisplay({
|
|
|
435
472
|
// These must be separate components so hook order in ToolCallDisplayGeneric
|
|
436
473
|
// is always stable (no conditional hook calls).
|
|
437
474
|
const toolKind = structuredMeta?.toolKind as string | undefined;
|
|
475
|
+
const wrapToolDisplay = (children: React.ReactNode) => (
|
|
476
|
+
<ToolLongRunningHintShell toolName={toolName} isRunning={isRunning}>
|
|
477
|
+
{children}
|
|
478
|
+
</ToolLongRunningHintShell>
|
|
479
|
+
);
|
|
438
480
|
if (toolKind === "bash") {
|
|
439
|
-
return (
|
|
481
|
+
return wrapToolDisplay(
|
|
440
482
|
<BashCell
|
|
441
483
|
meta={
|
|
442
484
|
structuredMeta as unknown as Parameters<typeof BashCell>[0]["meta"]
|
|
443
485
|
}
|
|
444
486
|
output={result}
|
|
445
487
|
isRunning={isRunning}
|
|
446
|
-
|
|
488
|
+
/>,
|
|
447
489
|
);
|
|
448
490
|
}
|
|
449
491
|
if (toolKind === "edit") {
|
|
450
|
-
return (
|
|
492
|
+
return wrapToolDisplay(
|
|
451
493
|
<EditCell
|
|
452
494
|
meta={
|
|
453
495
|
structuredMeta as unknown as Parameters<typeof EditCell>[0]["meta"]
|
|
454
496
|
}
|
|
455
497
|
isRunning={isRunning}
|
|
456
|
-
|
|
498
|
+
/>,
|
|
457
499
|
);
|
|
458
500
|
}
|
|
459
501
|
if (toolKind === "write") {
|
|
460
|
-
return (
|
|
502
|
+
return wrapToolDisplay(
|
|
461
503
|
<WriteCell
|
|
462
504
|
meta={
|
|
463
505
|
structuredMeta as unknown as Parameters<typeof WriteCell>[0]["meta"]
|
|
464
506
|
}
|
|
465
507
|
isRunning={isRunning}
|
|
466
|
-
|
|
508
|
+
/>,
|
|
467
509
|
);
|
|
468
510
|
}
|
|
469
|
-
return (
|
|
511
|
+
return wrapToolDisplay(
|
|
470
512
|
<ToolCallDisplayGeneric
|
|
471
513
|
toolName={toolName}
|
|
472
514
|
argsText={argsText}
|
|
@@ -477,7 +519,7 @@ export function ToolCallDisplay({
|
|
|
477
519
|
isRunning={isRunning}
|
|
478
520
|
approval={approval}
|
|
479
521
|
repeatCount={repeatCount}
|
|
480
|
-
|
|
522
|
+
/>,
|
|
481
523
|
);
|
|
482
524
|
}
|
|
483
525
|
|
|
@@ -19,6 +19,7 @@ interface RrwebRecordOptions {
|
|
|
19
19
|
emit: (event: ReplayEvent) => void;
|
|
20
20
|
checkoutEveryNth?: number;
|
|
21
21
|
checkoutEveryNms?: number;
|
|
22
|
+
inlineStylesheet?: boolean;
|
|
22
23
|
blockClass?: string | RegExp;
|
|
23
24
|
blockSelector?: string;
|
|
24
25
|
ignoreClass?: string | RegExp;
|
|
@@ -82,6 +83,7 @@ export interface SessionReplayOptions {
|
|
|
82
83
|
maxBatchBytes?: number;
|
|
83
84
|
checkoutEveryNth?: number;
|
|
84
85
|
checkoutEveryNms?: number;
|
|
86
|
+
inlineStylesheet?: boolean;
|
|
85
87
|
blockSelector?: string;
|
|
86
88
|
ignoreSelector?: string;
|
|
87
89
|
maskTextClass?: string | RegExp;
|
|
@@ -135,6 +137,7 @@ interface NormalizedSessionReplayOptions {
|
|
|
135
137
|
maxBatchBytes: number;
|
|
136
138
|
checkoutEveryNth?: number;
|
|
137
139
|
checkoutEveryNms?: number;
|
|
140
|
+
inlineStylesheet: boolean;
|
|
138
141
|
blockSelector: string;
|
|
139
142
|
ignoreSelector: string;
|
|
140
143
|
maskTextClass: string | RegExp;
|
|
@@ -509,6 +512,7 @@ function normalizeOptions(
|
|
|
509
512
|
),
|
|
510
513
|
checkoutEveryNth: options.checkoutEveryNth,
|
|
511
514
|
checkoutEveryNms: options.checkoutEveryNms,
|
|
515
|
+
inlineStylesheet: options.inlineStylesheet ?? true,
|
|
512
516
|
blockSelector: options.blockSelector || DEFAULT_BLOCK_SELECTOR,
|
|
513
517
|
ignoreSelector: options.ignoreSelector || DEFAULT_IGNORE_SELECTOR,
|
|
514
518
|
maskTextClass: options.maskTextClass || DEFAULT_MASK_TEXT_CLASS,
|
|
@@ -1010,6 +1014,7 @@ async function startSessionReplayRecorder(
|
|
|
1010
1014
|
sampling: normalized.eventSampling,
|
|
1011
1015
|
checkoutEveryNth: normalized.checkoutEveryNth,
|
|
1012
1016
|
checkoutEveryNms: normalized.checkoutEveryNms,
|
|
1017
|
+
inlineStylesheet: normalized.inlineStylesheet,
|
|
1013
1018
|
blockSelector: normalized.blockSelector,
|
|
1014
1019
|
ignoreSelector: normalized.ignoreSelector,
|
|
1015
1020
|
maskTextClass: normalized.maskTextClass,
|
|
@@ -205,13 +205,26 @@ function baseActivityLabel(ev: SSEEvent, tool?: string): string {
|
|
|
205
205
|
return humanizeToolLabelText(ev.label ?? "Working", tool);
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
function preparationActivityLabel(
|
|
209
|
+
tool: string | undefined,
|
|
210
|
+
progressBytes: number | undefined,
|
|
211
|
+
): string {
|
|
212
|
+
const action = humanizeToolName(tool);
|
|
213
|
+
if (progressBytes === undefined) {
|
|
214
|
+
return `Starting ${action}...`;
|
|
215
|
+
}
|
|
216
|
+
if (progressBytes <= 0) {
|
|
217
|
+
return `Preparing ${action}...`;
|
|
218
|
+
}
|
|
219
|
+
return `Writing ${action}... (${formatProgressBytes(progressBytes)} prepared)`;
|
|
220
|
+
}
|
|
221
|
+
|
|
208
222
|
function visibleActivityLabel(ev: SSEEvent, tool?: string): string {
|
|
209
|
-
const label = baseActivityLabel(ev, tool);
|
|
210
223
|
const progressBytes = activityProgressBytes(ev);
|
|
211
|
-
if (
|
|
212
|
-
return
|
|
224
|
+
if (isPreparingActionActivity(ev)) {
|
|
225
|
+
return preparationActivityLabel(tool, progressBytes);
|
|
213
226
|
}
|
|
214
|
-
return
|
|
227
|
+
return baseActivityLabel(ev, tool);
|
|
215
228
|
}
|
|
216
229
|
|
|
217
230
|
function findPendingToolCallIndex(
|
|
@@ -277,6 +277,59 @@ type CspDirective = {
|
|
|
277
277
|
tokens: string[];
|
|
278
278
|
};
|
|
279
279
|
|
|
280
|
+
const CSP_DIRECTIVES_WITH_VALUE_TOKENS = new Set([
|
|
281
|
+
"base-uri",
|
|
282
|
+
"block-all-mixed-content",
|
|
283
|
+
"child-src",
|
|
284
|
+
"connect-src",
|
|
285
|
+
"default-src",
|
|
286
|
+
"fenced-frame-src",
|
|
287
|
+
"font-src",
|
|
288
|
+
"form-action",
|
|
289
|
+
"frame-ancestors",
|
|
290
|
+
"frame-src",
|
|
291
|
+
"img-src",
|
|
292
|
+
"manifest-src",
|
|
293
|
+
"media-src",
|
|
294
|
+
"navigate-to",
|
|
295
|
+
"object-src",
|
|
296
|
+
"plugin-types",
|
|
297
|
+
"prefetch-src",
|
|
298
|
+
"referrer",
|
|
299
|
+
"reflected-xss",
|
|
300
|
+
"require-sri-for",
|
|
301
|
+
"require-trusted-types-for",
|
|
302
|
+
"report-to",
|
|
303
|
+
"report-uri",
|
|
304
|
+
"sandbox",
|
|
305
|
+
"script-src",
|
|
306
|
+
"script-src-attr",
|
|
307
|
+
"script-src-elem",
|
|
308
|
+
"style-src",
|
|
309
|
+
"style-src-attr",
|
|
310
|
+
"style-src-elem",
|
|
311
|
+
"trusted-types",
|
|
312
|
+
"upgrade-insecure-requests",
|
|
313
|
+
"webrtc",
|
|
314
|
+
"worker-src",
|
|
315
|
+
]);
|
|
316
|
+
|
|
317
|
+
function hasCommaJoinedCspPolicies(policy: string): boolean {
|
|
318
|
+
let commaIndex = policy.indexOf(",");
|
|
319
|
+
while (commaIndex !== -1) {
|
|
320
|
+
const afterComma = policy.slice(commaIndex + 1);
|
|
321
|
+
const directive = /^\s+([a-z][a-z0-9-]*)(?=\s|;|$)/i.exec(afterComma)?.[1];
|
|
322
|
+
if (
|
|
323
|
+
directive &&
|
|
324
|
+
CSP_DIRECTIVES_WITH_VALUE_TOKENS.has(directive.toLowerCase())
|
|
325
|
+
) {
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
commaIndex = policy.indexOf(",", commaIndex + 1);
|
|
329
|
+
}
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
|
|
280
333
|
function parseCsp(policy: string): CspDirective[] {
|
|
281
334
|
return policy
|
|
282
335
|
.split(";")
|
|
@@ -349,13 +402,96 @@ function appendToExistingCspDirective(
|
|
|
349
402
|
existing.tokens = appendCspTokens(existing.tokens, additions);
|
|
350
403
|
}
|
|
351
404
|
|
|
352
|
-
function
|
|
405
|
+
function hasStrictNonceScriptPolicy(tokens: readonly string[]): boolean {
|
|
406
|
+
return tokens.some(
|
|
407
|
+
(token) => token === "'strict-dynamic'" || token.startsWith("'nonce-"),
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function appendToScriptCspDirective(
|
|
412
|
+
directives: CspDirective[],
|
|
413
|
+
name: string,
|
|
414
|
+
additions: readonly string[],
|
|
415
|
+
): boolean {
|
|
416
|
+
const existing = findCspDirective(directives, name);
|
|
417
|
+
if (existing) {
|
|
418
|
+
if (hasStrictNonceScriptPolicy(existing.tokens)) return false;
|
|
419
|
+
existing.tokens = appendCspTokens(existing.tokens, additions);
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const defaultSrc = findCspDirective(directives, "default-src");
|
|
424
|
+
if (!defaultSrc || hasStrictNonceScriptPolicy(defaultSrc.tokens)) {
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
directives.push({
|
|
428
|
+
name,
|
|
429
|
+
tokens: appendCspTokens([...defaultSrc.tokens], additions),
|
|
430
|
+
});
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function appendToEffectiveScriptElementCspDirective(
|
|
435
|
+
directives: CspDirective[],
|
|
436
|
+
additions: readonly string[],
|
|
437
|
+
): boolean {
|
|
438
|
+
const scriptSrcElem = findCspDirective(directives, "script-src-elem");
|
|
439
|
+
if (scriptSrcElem) {
|
|
440
|
+
if (hasStrictNonceScriptPolicy(scriptSrcElem.tokens)) return false;
|
|
441
|
+
scriptSrcElem.tokens = appendCspTokens(scriptSrcElem.tokens, additions);
|
|
442
|
+
return true;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return appendToScriptCspDirective(directives, "script-src", additions);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function augmentExistingEnforcedCspForFrameworkScripts(
|
|
449
|
+
policy: string,
|
|
450
|
+
options: {
|
|
451
|
+
gaScriptSrcTokens: readonly string[];
|
|
452
|
+
gaEnabled: boolean;
|
|
453
|
+
},
|
|
454
|
+
): string {
|
|
455
|
+
// Multiple CSP headers are surfaced by Headers.get() as one comma-joined
|
|
456
|
+
// string. CSP is not a comma-list header, so serializing a parsed combined
|
|
457
|
+
// value would turn two policies into one invalid policy. Leave those headers
|
|
458
|
+
// app-owned; a comma inside a source/report URL is still safe to parse.
|
|
459
|
+
if (hasCommaJoinedCspPolicies(policy)) return policy;
|
|
460
|
+
|
|
461
|
+
const directives = parseCsp(policy);
|
|
462
|
+
if (!directives.length) return policy;
|
|
463
|
+
|
|
464
|
+
if (options.gaEnabled) {
|
|
465
|
+
const addedScriptElement = appendToEffectiveScriptElementCspDirective(
|
|
466
|
+
directives,
|
|
467
|
+
options.gaScriptSrcTokens,
|
|
468
|
+
);
|
|
469
|
+
if (addedScriptElement) {
|
|
470
|
+
appendToExistingOrDefaultCspDirective(
|
|
471
|
+
directives,
|
|
472
|
+
"connect-src",
|
|
473
|
+
GA_CSP_CONNECT_HOSTS,
|
|
474
|
+
);
|
|
475
|
+
appendToExistingOrDefaultCspDirective(
|
|
476
|
+
directives,
|
|
477
|
+
"img-src",
|
|
478
|
+
GA_CSP_IMG_HOSTS,
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return serializeCsp(directives);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function augmentExistingReportOnlyCspForFrameworkScripts(
|
|
353
487
|
policy: string,
|
|
354
488
|
options: {
|
|
355
489
|
scriptSrcTokens: readonly string[];
|
|
356
490
|
gaEnabled: boolean;
|
|
357
491
|
},
|
|
358
492
|
): string {
|
|
493
|
+
if (hasCommaJoinedCspPolicies(policy)) return policy;
|
|
494
|
+
|
|
359
495
|
const directives = parseCsp(policy);
|
|
360
496
|
if (!directives.length) return policy;
|
|
361
497
|
|
|
@@ -364,7 +500,6 @@ function augmentExistingCspForFrameworkScripts(
|
|
|
364
500
|
"script-src",
|
|
365
501
|
options.scriptSrcTokens,
|
|
366
502
|
);
|
|
367
|
-
// `script-src-elem` overrides `script-src` for script tags when present.
|
|
368
503
|
appendToExistingCspDirective(
|
|
369
504
|
directives,
|
|
370
505
|
"script-src-elem",
|
|
@@ -409,10 +544,11 @@ function augmentExistingCspForFrameworkScripts(
|
|
|
409
544
|
* instead of reporting a violation on every page load.
|
|
410
545
|
*
|
|
411
546
|
* If an app or host already sends an enforced CSP with `script-src`,
|
|
412
|
-
* `script-src-elem`, `connect-src`, `img-src`, or `default-src`, we merge
|
|
413
|
-
*
|
|
414
|
-
*
|
|
415
|
-
*
|
|
547
|
+
* `script-src-elem`, `connect-src`, `img-src`, or `default-src`, we merge only
|
|
548
|
+
* GA-specific allowances into existing host/hash policies. Strict nonce or
|
|
549
|
+
* `strict-dynamic` script policies stay app-owned because blindly appending
|
|
550
|
+
* hashes or hosts would widen the policy without reliably loading our injected
|
|
551
|
+
* scripts.
|
|
416
552
|
*
|
|
417
553
|
* Templates additionally render a theme-init inline script whose exact content
|
|
418
554
|
* varies by template (default theme param, custom docs variant, etc.) and which
|
|
@@ -440,6 +576,7 @@ function applyDocumentCsp(headers: Headers, sentryScript: string | null): void {
|
|
|
440
576
|
const gaInlineBody = getGaInlineConfigScriptBody();
|
|
441
577
|
const gaHash = gaInlineBody ? computeInlineScriptHash(gaInlineBody) : null;
|
|
442
578
|
const gaHosts = gaInlineBody ? [...GA_CSP_SCRIPT_HOSTS] : [];
|
|
579
|
+
const gaScriptSrcTokens = [...(gaHash ? [gaHash] : []), ...gaHosts];
|
|
443
580
|
const scriptSrcTokens = [
|
|
444
581
|
"'self'",
|
|
445
582
|
...(sentryHash ? [sentryHash] : []),
|
|
@@ -449,6 +586,7 @@ function applyDocumentCsp(headers: Headers, sentryScript: string | null): void {
|
|
|
449
586
|
|
|
450
587
|
const cspAugmentOptions = {
|
|
451
588
|
scriptSrcTokens,
|
|
589
|
+
gaScriptSrcTokens,
|
|
452
590
|
gaEnabled: Boolean(gaInlineBody),
|
|
453
591
|
};
|
|
454
592
|
const existing = headers.get("content-security-policy") ?? "";
|
|
@@ -460,7 +598,10 @@ function applyDocumentCsp(headers: Headers, sentryScript: string | null): void {
|
|
|
460
598
|
} else {
|
|
461
599
|
headers.set(
|
|
462
600
|
"content-security-policy",
|
|
463
|
-
|
|
601
|
+
augmentExistingEnforcedCspForFrameworkScripts(
|
|
602
|
+
existing,
|
|
603
|
+
cspAugmentOptions,
|
|
604
|
+
),
|
|
464
605
|
);
|
|
465
606
|
}
|
|
466
607
|
|
|
@@ -471,7 +612,10 @@ function applyDocumentCsp(headers: Headers, sentryScript: string | null): void {
|
|
|
471
612
|
} else {
|
|
472
613
|
headers.set(
|
|
473
614
|
"content-security-policy-report-only",
|
|
474
|
-
|
|
615
|
+
augmentExistingReportOnlyCspForFrameworkScripts(
|
|
616
|
+
existingRo,
|
|
617
|
+
cspAugmentOptions,
|
|
618
|
+
),
|
|
475
619
|
);
|
|
476
620
|
}
|
|
477
621
|
}
|
|
@@ -1243,7 +1243,14 @@ function sanitizeCssText(value: string): string {
|
|
|
1243
1243
|
if (!containsStylesheetNetworkLoad(value)) return value;
|
|
1244
1244
|
return value
|
|
1245
1245
|
.replace(/@import\s+(?:url\s*\()?[^;{}]+;?/gi, "")
|
|
1246
|
-
.replace(/\burl\s*\((?:\\.|[^\\)])
|
|
1246
|
+
.replace(/\burl\s*\(\s*((?:\\.|[^\\)])*)\)/gi, sanitizeCssUrlToken);
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
function sanitizeCssUrlToken(match: string, rawValue: string): string {
|
|
1250
|
+
const urlValue = rawValue.trim();
|
|
1251
|
+
const unquoted = urlValue.replace(/^(['"])(.*)\1$/, "$2").trim();
|
|
1252
|
+
if (/^(?:data:|blob:|#)/i.test(unquoted)) return match;
|
|
1253
|
+
return "none";
|
|
1247
1254
|
}
|
|
1248
1255
|
|
|
1249
1256
|
function sanitizeAttributes(attributes: AnyRecord): AnyRecord {
|
|
@@ -1258,6 +1265,11 @@ function sanitizeAttributes(attributes: AnyRecord): AnyRecord {
|
|
|
1258
1265
|
if (style.trim()) next[key] = style;
|
|
1259
1266
|
continue;
|
|
1260
1267
|
}
|
|
1268
|
+
if (normalized === "_csstext") {
|
|
1269
|
+
const cssText = sanitizeCssText(String(value));
|
|
1270
|
+
if (cssText.trim()) next[key] = cssText;
|
|
1271
|
+
continue;
|
|
1272
|
+
}
|
|
1261
1273
|
next[key] = value;
|
|
1262
1274
|
}
|
|
1263
1275
|
return next;
|
|
@@ -191,6 +191,24 @@ patterns live in `.agents/skills/`.
|
|
|
191
191
|
mode can list and resolve local routes now, but file reads/writes remain a
|
|
192
192
|
bridge contract until explicit permission controls are hardened.
|
|
193
193
|
|
|
194
|
+
## Code Workspace
|
|
195
|
+
|
|
196
|
+
- The editor left rail has a wide `code` workspace panel. Open it with
|
|
197
|
+
`navigate --view editor --designId <id> --leftPanel code` and optionally pass
|
|
198
|
+
`fileId`, `filename`, or `screen` to focus a file.
|
|
199
|
+
- Use `list-source-files` to inspect the source workspace. For the MVP the
|
|
200
|
+
backend is `virtual-inline` over SQL-backed `design_files`, exposed as
|
|
201
|
+
`designfs://<designId>/`.
|
|
202
|
+
- Use `read-source-file` for file contents and preserve its `versionHash` before
|
|
203
|
+
writing. Do not return full file content from `view-screen`; it reports only
|
|
204
|
+
active code file metadata and dirty state.
|
|
205
|
+
- Use `preview-source-edit` to show a diff without saving, then
|
|
206
|
+
`apply-source-edit` with the prior `versionHash` to save either a full replace
|
|
207
|
+
or exact replace. These actions update the same inline file state as the UI.
|
|
208
|
+
- Use `resolve-selection-source` when the user has a canvas element selected and
|
|
209
|
+
you need the best matching inline file location/snippet. Localhost/container
|
|
210
|
+
source reads and writes remain future backend work.
|
|
211
|
+
|
|
194
212
|
## Localhost Source Actions
|
|
195
213
|
|
|
196
214
|
- `connect-localhost`: registers or refreshes a localhost source connection
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
findSourceWorkspaceFile,
|
|
6
|
+
readLiveSourceFile,
|
|
7
|
+
resolveSourceWorkspace,
|
|
8
|
+
writeInlineSourceFile,
|
|
9
|
+
} from "../server/source-workspace.js";
|
|
10
|
+
import {
|
|
11
|
+
applySourceEdit,
|
|
12
|
+
previewSourceDiff,
|
|
13
|
+
} from "../shared/source-workspace.js";
|
|
14
|
+
|
|
15
|
+
const sourceEditSchema = z.discriminatedUnion("kind", [
|
|
16
|
+
z.object({
|
|
17
|
+
kind: z.literal("full-replace"),
|
|
18
|
+
content: z.string().describe("Complete replacement file content"),
|
|
19
|
+
}),
|
|
20
|
+
z.object({
|
|
21
|
+
kind: z.literal("exact-replace"),
|
|
22
|
+
search: z.string().min(1).describe("Unique exact text to replace"),
|
|
23
|
+
replace: z.string().describe("Replacement text"),
|
|
24
|
+
}),
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
export default defineAction({
|
|
28
|
+
description:
|
|
29
|
+
"Apply a source-file edit through the shared Design source surface. In the " +
|
|
30
|
+
"MVP this writes inline design_files only and rejects stale version hashes.",
|
|
31
|
+
schema: z
|
|
32
|
+
.object({
|
|
33
|
+
designId: z.string().describe("Design project ID"),
|
|
34
|
+
path: z
|
|
35
|
+
.string()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe("Source path/filename, such as index.html"),
|
|
38
|
+
fileId: z.string().optional().describe("Design file ID"),
|
|
39
|
+
edit: sourceEditSchema,
|
|
40
|
+
expectedVersionHash: z
|
|
41
|
+
.string()
|
|
42
|
+
.optional()
|
|
43
|
+
.describe("Hash returned by read-source-file or preview-source-edit"),
|
|
44
|
+
})
|
|
45
|
+
.refine((args) => args.path || args.fileId, {
|
|
46
|
+
message: "Provide either path or fileId.",
|
|
47
|
+
path: ["path"],
|
|
48
|
+
}),
|
|
49
|
+
run: async ({ designId, path, fileId, edit, expectedVersionHash }) => {
|
|
50
|
+
const workspace = await resolveSourceWorkspace(designId, {
|
|
51
|
+
includeContent: true,
|
|
52
|
+
});
|
|
53
|
+
if (workspace.sourceType !== "inline") {
|
|
54
|
+
throw new Error("Only inline Design files are editable in this MVP.");
|
|
55
|
+
}
|
|
56
|
+
const file = findSourceWorkspaceFile(workspace.files, { fileId, path });
|
|
57
|
+
const live = await readLiveSourceFile(file);
|
|
58
|
+
if (
|
|
59
|
+
expectedVersionHash !== undefined &&
|
|
60
|
+
expectedVersionHash !== live.versionHash
|
|
61
|
+
) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
"Source file changed since it was read. Re-read the file and retry.",
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const next = applySourceEdit(live.content, edit);
|
|
68
|
+
const write = await writeInlineSourceFile({
|
|
69
|
+
designId,
|
|
70
|
+
file,
|
|
71
|
+
content: next.content,
|
|
72
|
+
expectedVersionHash: expectedVersionHash ?? live.versionHash,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
designId,
|
|
77
|
+
path: file.filename,
|
|
78
|
+
fileId: file.id,
|
|
79
|
+
backendKind: "virtual-inline",
|
|
80
|
+
changed: write.changed,
|
|
81
|
+
editsApplied: next.editsApplied,
|
|
82
|
+
versionHash: write.versionHash,
|
|
83
|
+
updatedAt: write.updatedAt,
|
|
84
|
+
diff: previewSourceDiff(live.content, next.content),
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
import { resolveSourceWorkspace } from "../server/source-workspace.js";
|
|
5
|
+
|
|
6
|
+
export default defineAction({
|
|
7
|
+
description:
|
|
8
|
+
"List source files for a Design code workspace. In the current MVP this " +
|
|
9
|
+
"returns inline SQL-backed design_files by filename; future localhost and " +
|
|
10
|
+
"container source backends will use the same shape.",
|
|
11
|
+
schema: z.object({
|
|
12
|
+
designId: z.string().describe("Design project ID"),
|
|
13
|
+
}),
|
|
14
|
+
readOnly: true,
|
|
15
|
+
http: { method: "GET" },
|
|
16
|
+
run: async ({ designId }) => {
|
|
17
|
+
const workspace = await resolveSourceWorkspace(designId);
|
|
18
|
+
const readonly = !workspace.canEdit || workspace.sourceType !== "inline";
|
|
19
|
+
return {
|
|
20
|
+
designId,
|
|
21
|
+
backend: {
|
|
22
|
+
kind: "virtual-inline" as const,
|
|
23
|
+
workspaceUri: `designfs://${designId}/`,
|
|
24
|
+
designId,
|
|
25
|
+
capabilities: {
|
|
26
|
+
readFile: true,
|
|
27
|
+
writeFile: workspace.canEdit && workspace.sourceType === "inline",
|
|
28
|
+
diff: true,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
sourceType: workspace.sourceType,
|
|
32
|
+
files: workspace.files.map((file) => ({
|
|
33
|
+
path: file.filename,
|
|
34
|
+
displayName: file.filename,
|
|
35
|
+
kind: "file" as const,
|
|
36
|
+
sourceType: workspace.sourceType,
|
|
37
|
+
fileId: file.id,
|
|
38
|
+
readonly,
|
|
39
|
+
reason: readonly
|
|
40
|
+
? workspace.canEdit
|
|
41
|
+
? "Only inline Design files are editable in this MVP."
|
|
42
|
+
: "You need editor access to change this file."
|
|
43
|
+
: undefined,
|
|
44
|
+
language:
|
|
45
|
+
file.fileType === "html" || file.fileType === "css"
|
|
46
|
+
? file.fileType
|
|
47
|
+
: undefined,
|
|
48
|
+
updatedAt: file.updatedAt,
|
|
49
|
+
})),
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
});
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* --designId Design ID (for editor/present views)
|
|
19
19
|
* --editorView Editor mode for designs: single or overview
|
|
20
20
|
* --inspectorTab Inspector tab for designs: design or tweaks (extensions opens Tools for compatibility)
|
|
21
|
-
* --leftPanel Left editor panel: file, agent, assets, tools, or
|
|
21
|
+
* --leftPanel Left editor panel: file, agent, assets, tools, tokens, or code
|
|
22
22
|
* --fileId Screen/file id to focus in the design editor
|
|
23
23
|
* --filename Screen filename to focus in the design editor
|
|
24
24
|
* --tool Design editor tool to activate
|
|
@@ -53,11 +53,12 @@ const designLeftPanelSchema = z.enum([
|
|
|
53
53
|
"assets",
|
|
54
54
|
"tools",
|
|
55
55
|
"tokens",
|
|
56
|
+
"code",
|
|
56
57
|
]);
|
|
57
58
|
|
|
58
59
|
export default defineAction({
|
|
59
60
|
description:
|
|
60
|
-
"Navigate the UI to a specific view or path. Views: list, editor, design-systems, present, settings. Use --designId with editor/present views and --designSystemId with design-systems. For designs, use editorView=overview to show the infinite screens canvas, or editorView=single with fileId/filename/screen to focus a screen. Use leftPanel=file|agent|assets|tools|tokens to focus the
|
|
61
|
+
"Navigate the UI to a specific view or path. Views: list, editor, design-systems, present, settings. Use --designId with editor/present views and --designSystemId with design-systems. For designs, use editorView=overview to show the infinite screens canvas, or editorView=single with fileId/filename/screen to focus a screen. Use leftPanel=file|agent|assets|tools|tokens|code to focus the left rail, including the wide Code workspace. Legacy inspectorTab=extensions opens Tools. Use tool to activate a design editor tool.",
|
|
61
62
|
schema: z
|
|
62
63
|
.object({
|
|
63
64
|
view: z
|