@agent-native/core 0.80.9 → 0.80.11
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 +13 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +108 -15
- package/corpus/core/src/cli/design-connect.ts +28 -2
- package/corpus/core/src/cli/skills.ts +45 -24
- package/corpus/core/src/client/AssistantChat.tsx +123 -26
- package/corpus/core/src/client/sse-event-processor.ts +9 -0
- package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +9 -2
- package/corpus/templates/assets/AGENTS.md +4 -0
- package/corpus/templates/assets/actions/_tool-activity.ts +46 -0
- package/corpus/templates/assets/actions/generate-image-batch.ts +26 -5
- package/corpus/templates/assets/actions/generate-image.ts +118 -67
- package/corpus/templates/assets/actions/list-draft-assets.ts +50 -0
- package/corpus/templates/assets/actions/rerun-generation-run.ts +52 -31
- package/corpus/templates/assets/app/components/create/RecentDraftsSection.tsx +100 -0
- package/corpus/templates/assets/app/i18n/zh-TW.ts +7 -0
- package/corpus/templates/assets/app/i18n-data.ts +61 -0
- package/corpus/templates/assets/app/routes/_index.tsx +4 -0
- package/corpus/templates/assets/app/routes/library.tsx +145 -38
- package/corpus/templates/assets/changelog/2026-06-29-image-generation-can-now-render-requested-text-and-respect-b.md +6 -0
- package/corpus/templates/assets/changelog/2026-06-30-image-generation-no-longer-looks-stuck-while-the-provider-is-still-working.md +6 -0
- package/corpus/templates/assets/changelog/2026-06-30-tagged-presets-now-control-the-image-aspect-ratio.md +6 -0
- package/corpus/templates/assets/server/lib/generation.ts +135 -11
- package/corpus/templates/assets/shared/api.ts +4 -0
- package/corpus/templates/clips/actions/list-ai-requests.ts +60 -0
- package/corpus/templates/clips/app/hooks/use-auto-title.ts +24 -25
- package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +717 -0
- package/corpus/templates/design/actions/add-breakpoint.ts +143 -0
- package/corpus/templates/design/actions/add-localhost-screens.ts +5 -0
- package/corpus/templates/design/actions/apply-a11y-fix.ts +317 -0
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +441 -0
- package/corpus/templates/design/actions/apply-design-state.ts +213 -0
- package/corpus/templates/design/actions/apply-design-token-edit.ts +202 -0
- package/corpus/templates/design/actions/apply-motion-edit.ts +413 -0
- package/corpus/templates/design/actions/apply-shader-fill.ts +404 -0
- package/corpus/templates/design/actions/apply-visual-edit.ts +191 -5
- package/corpus/templates/design/actions/capture-design-state.ts +224 -0
- package/corpus/templates/design/actions/connect-builder-app.ts +162 -0
- package/corpus/templates/design/actions/create-component.ts +447 -0
- package/corpus/templates/design/actions/create-design-branch.ts +263 -0
- package/corpus/templates/design/actions/create-design-state.ts +162 -0
- package/corpus/templates/design/actions/delete-design-state.ts +55 -0
- package/corpus/templates/design/actions/delete-design.ts +20 -0
- package/corpus/templates/design/actions/deploy-design-preview.ts +275 -0
- package/corpus/templates/design/actions/get-component-details.ts +251 -0
- package/corpus/templates/design/actions/get-design-branch-diff.ts +362 -0
- package/corpus/templates/design/actions/get-design-review.ts +314 -0
- package/corpus/templates/design/actions/get-design-surface-index.ts +582 -0
- package/corpus/templates/design/actions/get-motion-timeline.ts +99 -0
- package/corpus/templates/design/actions/index-components.ts +258 -0
- package/corpus/templates/design/actions/index-design-tokens.ts +277 -0
- package/corpus/templates/design/actions/list-design-extensions.ts +309 -0
- package/corpus/templates/design/actions/list-design-source-capabilities.ts +153 -0
- package/corpus/templates/design/actions/list-design-states.ts +90 -0
- package/corpus/templates/design/actions/migrate-inline-design-to-app.ts +298 -0
- package/corpus/templates/design/actions/open-component-source.ts +242 -0
- package/corpus/templates/design/actions/preview-component-prop-edit.ts +260 -0
- package/corpus/templates/design/actions/preview-design-token-edit.ts +113 -0
- package/corpus/templates/design/actions/preview-shader-fill.ts +187 -0
- package/corpus/templates/design/actions/remove-breakpoint.ts +103 -0
- package/corpus/templates/design/actions/remove-motion-timeline.ts +196 -0
- package/corpus/templates/design/actions/run-design-audit.ts +436 -0
- package/corpus/templates/design/actions/run-design-extension-action.ts +284 -0
- package/corpus/templates/design/actions/set-active-breakpoint.ts +39 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +1550 -82
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +649 -43
- package/corpus/templates/design/app/components/design/DrawOverlay.tsx +1 -1
- package/corpus/templates/design/app/components/design/EditPanel.tsx +1788 -52
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +114 -33
- package/corpus/templates/design/app/components/design/LocalSourceEditBanner.tsx +157 -0
- package/corpus/templates/design/app/components/design/MotionDock.tsx +1071 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1053 -99
- package/corpus/templates/design/app/components/design/ReviewPanel.tsx +766 -0
- package/corpus/templates/design/app/components/design/StatesPanel.tsx +581 -0
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +578 -0
- package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +241 -0
- package/corpus/templates/design/app/components/design/index.ts +16 -0
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +15 -6
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +66 -3
- package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +145 -0
- package/corpus/templates/design/app/components/design/inspector/SHADER_INTEGRATION.md +42 -12
- package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +1 -1
- package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +131 -0
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +3 -1
- package/corpus/templates/design/app/i18n/zh-TW.ts +32 -0
- package/corpus/templates/design/app/i18n-data.ts +280 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +3087 -490
- package/corpus/templates/design/changelog/2026-06-29-added-a-review-panel-in-the-design-editor-s-inspector-with-a.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-copying-and-pasting-layers-no-longer-shows-success-notificat.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-device-presets-in-all-screens-view-resize-the-selected-previ.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-layer-and-canvas-drags-keep-the-layer-list-stable-while-chan.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-layer-moves-can-be-undone-and-redone-without-flashing-the-ca.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-previews-use-a-single-blue-hover-border-in-all-screen.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-selected-containers-show-draggable-padding-and-gap-guides-on.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-the-design-editor-adds-a-studio-layer-with-tokens-respon.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-accessibility-findings-now-offer-a-one-click-fix.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-component-instances-now-have-editable-props-in-the-inspe.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-token-edits-now-stay-visible-in-previews-and-token-li.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-inspect-code-now-shows-the-elements-opening-tag-at-a-gla.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-shader-fill-presets-can-now-be-applied-to-an-element.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-motion-timeline-can-now-add-a-track-to-any-element-a.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-visual-editor-selection-layer-paint-and-drawing-controls-are-more-reliable.md +6 -0
- package/corpus/templates/design/e2e/global-setup.ts +94 -2
- package/corpus/templates/design/e2e/helpers.ts +10 -4
- package/corpus/templates/design/server/db/schema.ts +123 -0
- package/corpus/templates/design/server/plugins/db.ts +90 -0
- package/corpus/templates/design/shared/builder-app.ts +297 -0
- package/corpus/templates/design/shared/capability-resolver.ts +123 -0
- package/corpus/templates/design/shared/capture-sanitize.ts +70 -0
- package/corpus/templates/design/shared/code-layer.ts +1016 -71
- package/corpus/templates/design/shared/component-model.ts +274 -0
- package/corpus/templates/design/shared/design-review.ts +275 -0
- package/corpus/templates/design/shared/design-source-capabilities.ts +286 -0
- package/corpus/templates/design/shared/design-state.ts +112 -0
- package/corpus/templates/design/shared/design-surface-index.ts +258 -0
- package/corpus/templates/design/shared/motion-compiler.ts +349 -0
- package/corpus/templates/design/shared/motion-timeline.ts +193 -0
- package/corpus/templates/design/shared/resolve-tweaks.ts +32 -9
- package/corpus/templates/design/shared/responsive-classes.ts +452 -0
- package/corpus/templates/design/shared/shader-fill.ts +323 -0
- package/corpus/templates/design/shared/source-mode.ts +245 -0
- package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +6 -2
- package/corpus/templates/plan/changelog/2026-06-29-plan-canvases-open-without-an-initial-pan-and-zoom-flicker.md +6 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +94 -10
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +28 -2
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +37 -21
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +14 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +115 -22
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +10 -0
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +8 -8
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.80.11
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- abf0681: Stop chat reconnect from falsely reporting "stopped before finishing" on long but healthy runs. The active-run reconnect now treats the stuck threshold as an idle deadline that resets on every streamed event, instead of a one-shot cap on total reconnect duration — so a long-running tool (e.g. image generation) that keeps emitting activity heartbeats is never aborted with a no-progress error just for running longer than the threshold. Also surfaces active tool progress while reconnecting, and waits for interrupted write-tool results before re-running them so long-running tools do not appear stopped or duplicate work.
|
|
8
|
+
|
|
9
|
+
## 0.80.10
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- d26a679: Design connect: document the per-element provenance contract (`data-source-file` / `data-source-line` / `data-source-column` / `data-component-name`, plus `data-loc` shorthand) used to map a selected element back to its source location, and surface it in `design connect --help`. The `resolveNodeToFile` bridge capability now carries a reason string describing this contract.
|
|
14
|
+
- d26a679: Harden the app-backed skill installer for visual-plan feedback: built-in skill folders now stage writes before replacing existing installs, include first-time install command metadata, and make `skills update` point first-time users to `skills add` for setup and MCP registration.
|
|
15
|
+
|
|
3
16
|
## 0.80.9
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.80.
|
|
3
|
+
"version": "0.80.11",
|
|
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": {
|
|
@@ -2171,6 +2171,63 @@ function toolCallCacheKey(toolName: string, input: unknown): string {
|
|
|
2171
2171
|
return `${toolName}:${stableStringify(normalizeToolCallInputForHistory(input))}`;
|
|
2172
2172
|
}
|
|
2173
2173
|
|
|
2174
|
+
const INTERRUPTED_TOOL_LEDGER_POLL_MS =
|
|
2175
|
+
process.env.NODE_ENV === "test" ? 0 : 2_000;
|
|
2176
|
+
|
|
2177
|
+
async function waitForInterruptedToolLedgerEntry(opts: {
|
|
2178
|
+
threadId: string;
|
|
2179
|
+
toolKey: string;
|
|
2180
|
+
toolName: string;
|
|
2181
|
+
timeoutMs: number;
|
|
2182
|
+
signal: AbortSignal;
|
|
2183
|
+
send: (event: AgentChatEvent) => void;
|
|
2184
|
+
}): Promise<string | null> {
|
|
2185
|
+
const pollMs = INTERRUPTED_TOOL_LEDGER_POLL_MS;
|
|
2186
|
+
// Wait up to the tool's OWN declared timeout — the abandoned zombie can keep
|
|
2187
|
+
// running that long (e.g. a 12-minute image generation, whose provider keeps
|
|
2188
|
+
// generating after the run aborts), and giving up early re-runs the same
|
|
2189
|
+
// write tool while the original is still in flight, duplicating work and
|
|
2190
|
+
// double-charging. A flat sub-tool-timeout cap (previously 5 min) silently
|
|
2191
|
+
// truncated long tools. The run's AbortSignal still bounds this to the run's
|
|
2192
|
+
// remaining budget: when the run is cut off, the poll returns null and the
|
|
2193
|
+
// re-dispatch hits the already-aborted signal instead of launching a real
|
|
2194
|
+
// second call, so the wait never outlives the run.
|
|
2195
|
+
const maxWaitMs =
|
|
2196
|
+
process.env.NODE_ENV === "test" ? 1 : Math.max(0, opts.timeoutMs);
|
|
2197
|
+
const maxPolls =
|
|
2198
|
+
process.env.NODE_ENV === "test"
|
|
2199
|
+
? 3
|
|
2200
|
+
: Math.max(1, Math.ceil(maxWaitMs / Math.max(1, pollMs)) + 1);
|
|
2201
|
+
|
|
2202
|
+
for (let attempt = 0; attempt < maxPolls; attempt++) {
|
|
2203
|
+
if (opts.signal.aborted) return null;
|
|
2204
|
+
const ledgerResult = await readLedgerEntry(opts.threadId, opts.toolKey);
|
|
2205
|
+
if (ledgerResult !== null) return ledgerResult;
|
|
2206
|
+
|
|
2207
|
+
if (attempt >= maxPolls - 1) break;
|
|
2208
|
+
opts.send({
|
|
2209
|
+
type: "activity",
|
|
2210
|
+
tool: opts.toolName,
|
|
2211
|
+
label: `Waiting for previous ${opts.toolName} result.`,
|
|
2212
|
+
});
|
|
2213
|
+
if (pollMs <= 0) continue;
|
|
2214
|
+
await new Promise<void>((resolve) => {
|
|
2215
|
+
let settled = false;
|
|
2216
|
+
const done = () => {
|
|
2217
|
+
if (settled) return;
|
|
2218
|
+
settled = true;
|
|
2219
|
+
clearTimeout(timer);
|
|
2220
|
+
opts.signal.removeEventListener("abort", done);
|
|
2221
|
+
resolve();
|
|
2222
|
+
};
|
|
2223
|
+
const timer = setTimeout(done, pollMs);
|
|
2224
|
+
opts.signal.addEventListener("abort", done, { once: true });
|
|
2225
|
+
});
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
return null;
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2174
2231
|
function normalizeToolErrorForBreaker(error: string): string {
|
|
2175
2232
|
return error.replace(/\s+/g, " ").trim();
|
|
2176
2233
|
}
|
|
@@ -3241,6 +3298,17 @@ export async function runAgentLoop(opts: {
|
|
|
3241
3298
|
};
|
|
3242
3299
|
}
|
|
3243
3300
|
|
|
3301
|
+
const DEFAULT_TOOL_RESULT_CHARS = 50_000;
|
|
3302
|
+
const DEFAULT_TOOL_TIMEOUT_MS = 60_000;
|
|
3303
|
+
const toolTimeoutMs =
|
|
3304
|
+
actionEntry.timeoutMs ??
|
|
3305
|
+
opts.toolLimits?.timeoutMs ??
|
|
3306
|
+
DEFAULT_TOOL_TIMEOUT_MS;
|
|
3307
|
+
const toolMaxResultChars =
|
|
3308
|
+
actionEntry.maxResultChars ??
|
|
3309
|
+
opts.toolLimits?.maxResultChars ??
|
|
3310
|
+
DEFAULT_TOOL_RESULT_CHARS;
|
|
3311
|
+
|
|
3244
3312
|
// TOOL-CALL JOURNAL HARD-BLOCK (resume safety, tool-layer enforcement).
|
|
3245
3313
|
// The prompt-level resume journal already TELLS a resuming model not to
|
|
3246
3314
|
// re-run completed tool calls; this enforces it at the tool layer so a
|
|
@@ -3291,17 +3359,23 @@ export async function runAgentLoop(opts: {
|
|
|
3291
3359
|
// previous invocation's zombie actually completed and wrote its result to
|
|
3292
3360
|
// the durable ledger. If so, return the ledger result without re-executing
|
|
3293
3361
|
// (prevents the duplicate side effect) and skip counting it toward the
|
|
3294
|
-
// interruption budget.
|
|
3362
|
+
// interruption budget. A just-abandoned long tool may need a short grace
|
|
3363
|
+
// period before its detached promise writes the ledger, so wait while the
|
|
3364
|
+
// current run still has budget instead of immediately re-running it.
|
|
3295
3365
|
if (!actionEntry.readOnly) {
|
|
3296
3366
|
const writeCacheKey = toolCallCacheKey(toolCall.name, toolCall.input);
|
|
3297
3367
|
const priorInterruptions =
|
|
3298
3368
|
writeToolInterruptions.get(writeCacheKey) ?? 0;
|
|
3299
3369
|
|
|
3300
3370
|
if (priorInterruptions > 0 && opts.threadId) {
|
|
3301
|
-
const ledgerResult = await
|
|
3302
|
-
opts.threadId,
|
|
3303
|
-
writeCacheKey,
|
|
3304
|
-
|
|
3371
|
+
const ledgerResult = await waitForInterruptedToolLedgerEntry({
|
|
3372
|
+
threadId: opts.threadId,
|
|
3373
|
+
toolKey: writeCacheKey,
|
|
3374
|
+
toolName: toolCall.name,
|
|
3375
|
+
timeoutMs: toolTimeoutMs,
|
|
3376
|
+
signal,
|
|
3377
|
+
send,
|
|
3378
|
+
});
|
|
3305
3379
|
if (ledgerResult !== null) {
|
|
3306
3380
|
// Zombie completed — recover the real result without re-executing.
|
|
3307
3381
|
const result =
|
|
@@ -3359,6 +3433,25 @@ export async function runAgentLoop(opts: {
|
|
|
3359
3433
|
}
|
|
3360
3434
|
}
|
|
3361
3435
|
|
|
3436
|
+
// Stop BEFORE emitting tool_start if the run was already aborted —
|
|
3437
|
+
// typically because the ledger wait above polled for minutes and the soft
|
|
3438
|
+
// timeout fired meanwhile. Emitting tool_start/tool_done here would leave a
|
|
3439
|
+
// bogus interrupted pair in the transcript for a tool that never re-ran,
|
|
3440
|
+
// and re-invoking would spawn a duplicate zombie. Return the interrupted
|
|
3441
|
+
// marker (no events) so the next continuation recovers via the ledger.
|
|
3442
|
+
// (A second guard inside the try below still covers an abort that lands in
|
|
3443
|
+
// the tiny sync window between here and the action invocation.)
|
|
3444
|
+
if (signal.aborted) {
|
|
3445
|
+
recordToolResult(INTERRUPTED_TOOL_RESULT_MARKER, false);
|
|
3446
|
+
return {
|
|
3447
|
+
type: "tool-result" as const,
|
|
3448
|
+
toolCallId: toolCall.id,
|
|
3449
|
+
toolName: toolCall.name,
|
|
3450
|
+
toolInput: wireToolInput,
|
|
3451
|
+
content: INTERRUPTED_TOOL_RESULT_MARKER,
|
|
3452
|
+
};
|
|
3453
|
+
}
|
|
3454
|
+
|
|
3362
3455
|
send({
|
|
3363
3456
|
type: "tool_start",
|
|
3364
3457
|
tool: toolCall.name,
|
|
@@ -3427,22 +3520,22 @@ export async function runAgentLoop(opts: {
|
|
|
3427
3520
|
};
|
|
3428
3521
|
}
|
|
3429
3522
|
|
|
3430
|
-
const DEFAULT_TOOL_RESULT_CHARS = 50_000;
|
|
3431
|
-
const DEFAULT_TOOL_TIMEOUT_MS = 60_000;
|
|
3432
|
-
const toolTimeoutMs =
|
|
3433
|
-
actionEntry.timeoutMs ??
|
|
3434
|
-
opts.toolLimits?.timeoutMs ??
|
|
3435
|
-
DEFAULT_TOOL_TIMEOUT_MS;
|
|
3436
|
-
const toolMaxResultChars =
|
|
3437
|
-
actionEntry.maxResultChars ??
|
|
3438
|
-
opts.toolLimits?.maxResultChars ??
|
|
3439
|
-
DEFAULT_TOOL_RESULT_CHARS;
|
|
3440
3523
|
let result: string;
|
|
3441
3524
|
let isError = false;
|
|
3442
3525
|
let mcpApp:
|
|
3443
3526
|
| import("../mcp-client/app-result.js").AgentMcpAppPayload
|
|
3444
3527
|
| undefined;
|
|
3445
3528
|
try {
|
|
3529
|
+
// The run may have been aborted while we waited above for an
|
|
3530
|
+
// interrupted tool's ledger result (the wait can poll for minutes).
|
|
3531
|
+
// Re-check before invoking the action: starting it now would spawn a
|
|
3532
|
+
// fresh zombie execution — a duplicate side effect / double charge —
|
|
3533
|
+
// which the ledger-recovery path exists to prevent. The Promise.race
|
|
3534
|
+
// "Run aborted" leg below only rejects AFTER the action is invoked, so
|
|
3535
|
+
// it cannot guard this. Throw here instead, handled like any abort.
|
|
3536
|
+
if (signal.aborted) {
|
|
3537
|
+
throw new Error("Run aborted");
|
|
3538
|
+
}
|
|
3446
3539
|
const timeoutSignal = AbortSignal.timeout(toolTimeoutMs);
|
|
3447
3540
|
const actionUserEmail = opts.ownerEmail ?? getRequestUserEmail();
|
|
3448
3541
|
const actionOrgId = opts.orgId ?? getRequestOrgId() ?? null;
|
|
@@ -367,7 +367,14 @@ export async function prepareDesignConnectManifest(
|
|
|
367
367
|
reason:
|
|
368
368
|
operation === "writeFile"
|
|
369
369
|
? "The bridge advertises the contract before enabling local file writes."
|
|
370
|
-
:
|
|
370
|
+
: operation === "resolveNodeToFile"
|
|
371
|
+
? // resolveNodeToFile maps a runtime DOM node id (from the editor's
|
|
372
|
+
// 'select' payload) to { file, line, component } provenance. The
|
|
373
|
+
// bridge endpoint exists; per-element provenance data must be
|
|
374
|
+
// emitted by the connected app at build time — see the provenance
|
|
375
|
+
// note in the help text below.
|
|
376
|
+
"Requires the connected app to emit data-source-file / data-source-line / data-component-name attributes (e.g. via @vitejs/plugin-react jsxDEV or a Babel source plugin)."
|
|
377
|
+
: undefined,
|
|
371
378
|
})),
|
|
372
379
|
};
|
|
373
380
|
}
|
|
@@ -442,7 +449,26 @@ Options:
|
|
|
442
449
|
--route-manifest <path> Non-destructive route manifest output path
|
|
443
450
|
--json Print the manifest JSON and exit
|
|
444
451
|
--once Prepare/scaffold the manifest and exit
|
|
445
|
-
--dry-run Print what would be exposed without writing files
|
|
452
|
+
--dry-run Print what would be exposed without writing files
|
|
453
|
+
|
|
454
|
+
Element provenance (resolveNodeToFile):
|
|
455
|
+
The design editor can map a selected DOM element back to its source file,
|
|
456
|
+
line, and React component name when the connected app emits provenance
|
|
457
|
+
attributes at build time. Add one of the following to your app's build:
|
|
458
|
+
|
|
459
|
+
• @vitejs/plugin-react with jsxDEV enabled (development mode default):
|
|
460
|
+
Sets data-source-file and data-source-line on each JSX element
|
|
461
|
+
automatically when using the Babel transform.
|
|
462
|
+
|
|
463
|
+
• A Babel source plugin (e.g. babel-plugin-react-source or a custom plugin):
|
|
464
|
+
Emits data-source-file="src/Button.tsx" data-source-line="12"
|
|
465
|
+
data-source-column="4" data-component-name="Button" on each element.
|
|
466
|
+
|
|
467
|
+
• data-loc="src/Button.tsx:12:4" shorthand attribute (Babel source convention):
|
|
468
|
+
The bridge parses this as { sourceFile, line, column } automatically.
|
|
469
|
+
|
|
470
|
+
Without these attributes the editor still works; provenance is simply absent.
|
|
471
|
+
Cross-origin localhost iframes cannot be read regardless of attributes (CSP).`);
|
|
446
472
|
}
|
|
447
473
|
|
|
448
474
|
export async function runDesign(argv: string[]) {
|
|
@@ -3073,6 +3073,7 @@ interface SkillInstallMetadata {
|
|
|
3073
3073
|
contentHash: string;
|
|
3074
3074
|
mcpUrl: string;
|
|
3075
3075
|
installedAt: string;
|
|
3076
|
+
installCommand: string;
|
|
3076
3077
|
updateCommand: string;
|
|
3077
3078
|
planMode?: PlanInstallMode;
|
|
3078
3079
|
}
|
|
@@ -3489,30 +3490,48 @@ function writeSkillFolder(
|
|
|
3489
3490
|
bundle: SkillFolderBundle,
|
|
3490
3491
|
installedAt = new Date().toISOString(),
|
|
3491
3492
|
): void {
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
3497
|
-
fs.writeFileSync(target, content, "utf-8");
|
|
3498
|
-
}
|
|
3499
|
-
const metadata: SkillInstallMetadata = {
|
|
3500
|
-
schemaVersion: 1,
|
|
3501
|
-
source: "agent-native",
|
|
3502
|
-
appSkillId: bundle.appSkillId,
|
|
3503
|
-
displayName: bundle.displayName,
|
|
3504
|
-
skillName: bundle.skillName,
|
|
3505
|
-
contentHash: bundle.contentHash,
|
|
3506
|
-
mcpUrl: bundle.mcpUrl,
|
|
3507
|
-
installedAt,
|
|
3508
|
-
updateCommand: `npx @agent-native/core@latest skills update ${bundle.skillName}`,
|
|
3509
|
-
...(bundle.planMode ? { planMode: bundle.planMode } : {}),
|
|
3510
|
-
};
|
|
3511
|
-
fs.writeFileSync(
|
|
3512
|
-
path.join(dir, AGENT_NATIVE_SKILL_METADATA_FILE),
|
|
3513
|
-
`${JSON.stringify(metadata, null, 2)}\n`,
|
|
3514
|
-
"utf-8",
|
|
3493
|
+
const parent = path.dirname(dir);
|
|
3494
|
+
const tempDir = path.join(
|
|
3495
|
+
parent,
|
|
3496
|
+
`.${path.basename(dir)}.${process.pid}.${Date.now()}.tmp`,
|
|
3515
3497
|
);
|
|
3498
|
+
try {
|
|
3499
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
3500
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
3501
|
+
for (const [rel, content] of Object.entries(bundle.files)) {
|
|
3502
|
+
const target = path.join(tempDir, rel);
|
|
3503
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
3504
|
+
fs.writeFileSync(target, content, "utf-8");
|
|
3505
|
+
}
|
|
3506
|
+
const metadata: SkillInstallMetadata = {
|
|
3507
|
+
schemaVersion: 1,
|
|
3508
|
+
source: "agent-native",
|
|
3509
|
+
appSkillId: bundle.appSkillId,
|
|
3510
|
+
displayName: bundle.displayName,
|
|
3511
|
+
skillName: bundle.skillName,
|
|
3512
|
+
contentHash: bundle.contentHash,
|
|
3513
|
+
mcpUrl: bundle.mcpUrl,
|
|
3514
|
+
installedAt,
|
|
3515
|
+
installCommand: `npx @agent-native/core@latest skills add ${bundle.skillName}`,
|
|
3516
|
+
updateCommand: `npx @agent-native/core@latest skills update ${bundle.skillName}`,
|
|
3517
|
+
...(bundle.planMode ? { planMode: bundle.planMode } : {}),
|
|
3518
|
+
};
|
|
3519
|
+
fs.writeFileSync(
|
|
3520
|
+
path.join(tempDir, AGENT_NATIVE_SKILL_METADATA_FILE),
|
|
3521
|
+
`${JSON.stringify(metadata, null, 2)}\n`,
|
|
3522
|
+
"utf-8",
|
|
3523
|
+
);
|
|
3524
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
3525
|
+
fs.renameSync(tempDir, dir);
|
|
3526
|
+
} catch (error: any) {
|
|
3527
|
+
try {
|
|
3528
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
3529
|
+
} catch {}
|
|
3530
|
+
throw new Error(
|
|
3531
|
+
`Cannot write Agent Native skill folder ${dir}: ${error?.message ?? error}`,
|
|
3532
|
+
{ cause: error },
|
|
3533
|
+
);
|
|
3534
|
+
}
|
|
3516
3535
|
}
|
|
3517
3536
|
|
|
3518
3537
|
function isJsonRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -6228,7 +6247,9 @@ function runSkillsStatusOrUpdate(
|
|
|
6228
6247
|
const target = parsed.target ? ` for ${parsed.target}` : "";
|
|
6229
6248
|
const hint = isScaffoldGuidanceTarget(parsed.target)
|
|
6230
6249
|
? `Run this from a generated Agent Native app or workspace root.\n`
|
|
6231
|
-
:
|
|
6250
|
+
: update
|
|
6251
|
+
? `The update command only refreshes skill folders that already exist; it does not do first-time install, MCP registration, or auth. Run "npx @agent-native/core@latest skills add ${parsed.target ?? "visual-plan"}" for one-step setup.\n`
|
|
6252
|
+
: `Run "npx @agent-native/core@latest skills add ${parsed.target ?? "visual-plan"}" to install one.\n`;
|
|
6232
6253
|
process.stdout.write(
|
|
6233
6254
|
`No installed Agent Native skill copies found${target}.\n${hint}`,
|
|
6234
6255
|
);
|
|
@@ -230,6 +230,12 @@ const PENDING_SELECTION_KEY = "pending-selection-context";
|
|
|
230
230
|
const ACTIVE_RUN_CLEAR_TIMEOUT_MS = 5_000;
|
|
231
231
|
const ACTIVE_RUN_STUCK_THRESHOLD_MS = 90_000;
|
|
232
232
|
const ACTIVE_RUN_POLL_INTERVAL_MS = 150;
|
|
233
|
+
// How long a single activity (model call, tool prep, long tool) must stay
|
|
234
|
+
// in-flight before its label is surfaced in the running indicator. Below this
|
|
235
|
+
// the indicator stays a steady "Thinking" so normal fast turns don't flicker
|
|
236
|
+
// through transient labels ("Contacting model", "Preparing X action"); past it
|
|
237
|
+
// the live label appears so a genuinely slow step reads as working, not hung.
|
|
238
|
+
const ACTIVITY_LABEL_REVEAL_DELAY_MS = 6_000;
|
|
233
239
|
const SUBMIT_ENGINE_STATUS_TIMEOUT_MS = 1000;
|
|
234
240
|
|
|
235
241
|
type ActiveRunLookup = {
|
|
@@ -254,6 +260,24 @@ function activeRunLooksStale(runInfo: ActiveRunLookup): boolean {
|
|
|
254
260
|
);
|
|
255
261
|
}
|
|
256
262
|
|
|
263
|
+
/**
|
|
264
|
+
* Decide whether a reconnect should give up with "no progress". The signal is
|
|
265
|
+
* *time since the last streamed event*, NOT total reconnect duration: a healthy
|
|
266
|
+
* long-running tool (e.g. image generation, which emits `activity` heartbeats
|
|
267
|
+
* every few seconds) makes continuous progress and must never be aborted just
|
|
268
|
+
* for running longer than the threshold. Only genuine silence for the full
|
|
269
|
+
* stuck threshold counts as stuck. Pure + exported so the decision is unit
|
|
270
|
+
* testable without driving the whole reconnect lifecycle.
|
|
271
|
+
*/
|
|
272
|
+
export function reconnectProgressTimedOut(args: {
|
|
273
|
+
lastProgressAt: number;
|
|
274
|
+
now: number;
|
|
275
|
+
thresholdMs?: number;
|
|
276
|
+
}): boolean {
|
|
277
|
+
const threshold = args.thresholdMs ?? ACTIVE_RUN_STUCK_THRESHOLD_MS;
|
|
278
|
+
return args.now - args.lastProgressAt >= threshold;
|
|
279
|
+
}
|
|
280
|
+
|
|
257
281
|
function isAssistantUiDuplicateMessageIdError(error: unknown): boolean {
|
|
258
282
|
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
259
283
|
return (
|
|
@@ -1323,6 +1347,24 @@ const AssistantChatInner = forwardRef<
|
|
|
1323
1347
|
// True during the 250ms continuation window and startup of the next chunk
|
|
1324
1348
|
// (adapter's auto-continue delay before POSTing the next chunk).
|
|
1325
1349
|
const [isAutoResuming, setIsAutoResuming] = useState(false);
|
|
1350
|
+
const [runningActivityLabel, setRunningActivityLabel] = useState<
|
|
1351
|
+
string | null
|
|
1352
|
+
>(null);
|
|
1353
|
+
// Delayed-reveal state for the activity label (see ACTIVITY_LABEL_REVEAL_DELAY_MS).
|
|
1354
|
+
// `latest` holds the most recent activity label; `surfaced` flips true once the
|
|
1355
|
+
// reveal timer fires; `timer` is the pending one-shot reveal.
|
|
1356
|
+
const activityLabelTimerRef = useRef<number | null>(null);
|
|
1357
|
+
const latestActivityLabelRef = useRef<string | null>(null);
|
|
1358
|
+
const activityLabelSurfacedRef = useRef(false);
|
|
1359
|
+
const resetRunningActivity = useCallback(() => {
|
|
1360
|
+
if (activityLabelTimerRef.current !== null) {
|
|
1361
|
+
window.clearTimeout(activityLabelTimerRef.current);
|
|
1362
|
+
activityLabelTimerRef.current = null;
|
|
1363
|
+
}
|
|
1364
|
+
latestActivityLabelRef.current = null;
|
|
1365
|
+
activityLabelSurfacedRef.current = false;
|
|
1366
|
+
setRunningActivityLabel(null);
|
|
1367
|
+
}, []);
|
|
1326
1368
|
const [reconnectContent, setReconnectContent] = useState<ContentPart[]>([]);
|
|
1327
1369
|
// When stop is clicked during reconnect, keep content visible (don't wipe it)
|
|
1328
1370
|
const [reconnectFrozen, setReconnectFrozen] = useState(false);
|
|
@@ -1341,6 +1383,19 @@ const AssistantChatInner = forwardRef<
|
|
|
1341
1383
|
const textStreaming = isRunning || externalStreaming;
|
|
1342
1384
|
// UI-only running state — drives the stop button and thinking indicator.
|
|
1343
1385
|
const showRunningInUI = isRunning;
|
|
1386
|
+
// A revealed activity label wins; otherwise stay a steady "Thinking" by
|
|
1387
|
+
// default. We only surface "Reconnecting" while we are actively replaying
|
|
1388
|
+
// recovered content (reconnectContent populated). A bare reconnect with no
|
|
1389
|
+
// replayed content — e.g. a tail-resume after the serverless wall, where
|
|
1390
|
+
// `scheduleUpdate` intentionally leaves reconnectContent empty — is normal
|
|
1391
|
+
// ongoing work, so it must read as "Thinking", never a perpetual "Working".
|
|
1392
|
+
const runningStatusLabel = runningActivityLabel
|
|
1393
|
+
? runningActivityLabel
|
|
1394
|
+
: isAutoResuming
|
|
1395
|
+
? "Resuming"
|
|
1396
|
+
: isReconnecting && reconnectContent.length > 0
|
|
1397
|
+
? "Reconnecting"
|
|
1398
|
+
: "Thinking";
|
|
1344
1399
|
const lastBroadcastRunningRef = useRef(isRunning);
|
|
1345
1400
|
const tiptapRef = useRef<TiptapComposerHandle>(null);
|
|
1346
1401
|
// Stable ref to the "stop active run" action so addToQueue can abort
|
|
@@ -1608,11 +1663,32 @@ const AssistantChatInner = forwardRef<
|
|
|
1608
1663
|
}, 1000);
|
|
1609
1664
|
|
|
1610
1665
|
let reconnectTimedOut = false;
|
|
1611
|
-
|
|
1666
|
+
// Idle deadline, NOT a total-duration cap. A long-but-healthy run (image
|
|
1667
|
+
// generation emits `activity` heartbeats every few seconds) keeps
|
|
1668
|
+
// advancing `lastReconnectProgressAt`, so it is never aborted for simply
|
|
1669
|
+
// taking longer than the threshold. Only true silence for the full stuck
|
|
1670
|
+
// threshold trips it — the same semantics as `activeRunLooksStale` and the
|
|
1671
|
+
// SSE-level no-progress timeout. `markReconnectProgress` resets it on every
|
|
1672
|
+
// streamed event (see the `onSeq` callback below, which fires for every
|
|
1673
|
+
// event including activity, for both fresh and tail-resume reconnects).
|
|
1674
|
+
let lastReconnectProgressAt = Date.now();
|
|
1675
|
+
const markReconnectProgress = () => {
|
|
1676
|
+
lastReconnectProgressAt = Date.now();
|
|
1677
|
+
};
|
|
1678
|
+
const idleCheck = setInterval(() => {
|
|
1679
|
+
if (
|
|
1680
|
+
!reconnectProgressTimedOut({
|
|
1681
|
+
lastProgressAt: lastReconnectProgressAt,
|
|
1682
|
+
now: Date.now(),
|
|
1683
|
+
})
|
|
1684
|
+
) {
|
|
1685
|
+
return;
|
|
1686
|
+
}
|
|
1612
1687
|
reconnectTimedOut = true;
|
|
1613
1688
|
abortCtrl.abort();
|
|
1614
1689
|
clearInterval(watchdog);
|
|
1615
|
-
|
|
1690
|
+
clearInterval(idleCheck);
|
|
1691
|
+
}, 1000);
|
|
1616
1692
|
|
|
1617
1693
|
const streamReconnect = async () => {
|
|
1618
1694
|
let noProgressDuringReconnect = false;
|
|
@@ -1653,7 +1729,10 @@ const AssistantChatInner = forwardRef<
|
|
|
1653
1729
|
toolCallCounter,
|
|
1654
1730
|
tabId,
|
|
1655
1731
|
scheduleUpdate,
|
|
1656
|
-
(seq) =>
|
|
1732
|
+
(seq) => {
|
|
1733
|
+
markReconnectProgress();
|
|
1734
|
+
updateActiveRunSeq(seq);
|
|
1735
|
+
},
|
|
1657
1736
|
);
|
|
1658
1737
|
if (afterSeq === 0) {
|
|
1659
1738
|
setReconnectContent([...content]);
|
|
@@ -1677,7 +1756,7 @@ const AssistantChatInner = forwardRef<
|
|
|
1677
1756
|
window.clearInterval(threadPollInterval);
|
|
1678
1757
|
}
|
|
1679
1758
|
clearInterval(watchdog);
|
|
1680
|
-
|
|
1759
|
+
clearInterval(idleCheck);
|
|
1681
1760
|
}
|
|
1682
1761
|
|
|
1683
1762
|
if (noProgressDuringReconnect && reconnectRunIdRef.current === runId) {
|
|
@@ -2235,11 +2314,9 @@ const AssistantChatInner = forwardRef<
|
|
|
2235
2314
|
return () => window.removeEventListener("agent-chat:run-error", handler);
|
|
2236
2315
|
}, [tabId]);
|
|
2237
2316
|
|
|
2238
|
-
// Real activity means the next chunk has started
|
|
2239
|
-
//
|
|
2240
|
-
//
|
|
2241
|
-
// indicator stays a steady "Thinking" rather than flipping through
|
|
2242
|
-
// transient step labels.
|
|
2317
|
+
// Real activity means the next chunk has started. Surface longer-lived
|
|
2318
|
+
// activity such as "Still generating image" so active-run reconnects do not
|
|
2319
|
+
// look like failures while the server is still making progress.
|
|
2243
2320
|
useEffect(() => {
|
|
2244
2321
|
const handler = (e: Event) => {
|
|
2245
2322
|
const detail = (e as CustomEvent).detail as {
|
|
@@ -2248,13 +2325,43 @@ const AssistantChatInner = forwardRef<
|
|
|
2248
2325
|
tabId?: string;
|
|
2249
2326
|
};
|
|
2250
2327
|
if (tabId && detail?.tabId && detail.tabId !== tabId) return;
|
|
2251
|
-
|
|
2252
|
-
|
|
2328
|
+
const label =
|
|
2329
|
+
typeof detail?.label === "string" ? detail.label.trim() : "";
|
|
2330
|
+
if (!label) return;
|
|
2331
|
+
setIsAutoResuming(false);
|
|
2332
|
+
latestActivityLabelRef.current = label;
|
|
2333
|
+
// Already past the delay → keep the visible label current.
|
|
2334
|
+
if (activityLabelSurfacedRef.current) {
|
|
2335
|
+
setRunningActivityLabel(label);
|
|
2336
|
+
return;
|
|
2337
|
+
}
|
|
2338
|
+
// Not yet surfaced → arm a single reveal timer on the FIRST activity of
|
|
2339
|
+
// this in-flight period. A burst of quick steps that all finish (clear)
|
|
2340
|
+
// before the timer fires never surfaces anything, so normal fast turns
|
|
2341
|
+
// stay a steady "Thinking". A step still in-flight at the deadline reveals
|
|
2342
|
+
// the latest label so a slow operation reads as working, not hung.
|
|
2343
|
+
if (activityLabelTimerRef.current === null) {
|
|
2344
|
+
activityLabelTimerRef.current = window.setTimeout(() => {
|
|
2345
|
+
activityLabelTimerRef.current = null;
|
|
2346
|
+
activityLabelSurfacedRef.current = true;
|
|
2347
|
+
if (latestActivityLabelRef.current) {
|
|
2348
|
+
setRunningActivityLabel(latestActivityLabelRef.current);
|
|
2349
|
+
}
|
|
2350
|
+
}, ACTIVITY_LABEL_REVEAL_DELAY_MS);
|
|
2253
2351
|
}
|
|
2254
2352
|
};
|
|
2353
|
+
const clear = (e: Event) => {
|
|
2354
|
+
const detail = (e as CustomEvent).detail as { tabId?: string };
|
|
2355
|
+
if (tabId && detail?.tabId && detail.tabId !== tabId) return;
|
|
2356
|
+
resetRunningActivity();
|
|
2357
|
+
};
|
|
2255
2358
|
window.addEventListener("agent-chat:activity", handler);
|
|
2256
|
-
|
|
2257
|
-
|
|
2359
|
+
window.addEventListener("agent-chat:activity-clear", clear);
|
|
2360
|
+
return () => {
|
|
2361
|
+
window.removeEventListener("agent-chat:activity", handler);
|
|
2362
|
+
window.removeEventListener("agent-chat:activity-clear", clear);
|
|
2363
|
+
};
|
|
2364
|
+
}, [resetRunningActivity, tabId]);
|
|
2258
2365
|
|
|
2259
2366
|
// Show "Resuming…" during the adapter's auto-continuation window (the
|
|
2260
2367
|
// ~250ms gap between the end of one serverless chunk and the POST for the
|
|
@@ -2274,8 +2381,9 @@ const AssistantChatInner = forwardRef<
|
|
|
2274
2381
|
useEffect(() => {
|
|
2275
2382
|
if (!isRunning) {
|
|
2276
2383
|
setIsAutoResuming(false);
|
|
2384
|
+
resetRunningActivity();
|
|
2277
2385
|
}
|
|
2278
|
-
}, [isRunning]);
|
|
2386
|
+
}, [isRunning, resetRunningActivity]);
|
|
2279
2387
|
|
|
2280
2388
|
// Auto-dequeue: when the agent is idle, send the next queued message. This
|
|
2281
2389
|
// intentionally does not depend on observing the running -> idle transition:
|
|
@@ -3336,18 +3444,7 @@ const AssistantChatInner = forwardRef<
|
|
|
3336
3444
|
<ReconnectStreamMessage content={reconnectContent} />
|
|
3337
3445
|
)}
|
|
3338
3446
|
{showRunningInUI && (
|
|
3339
|
-
<RunningActivityStatus
|
|
3340
|
-
label={
|
|
3341
|
-
isReconnecting
|
|
3342
|
-
? "Reconnecting"
|
|
3343
|
-
: isAutoResuming
|
|
3344
|
-
? "Resuming"
|
|
3345
|
-
: // Keep a steady "Thinking" while the model works —
|
|
3346
|
-
// never flip through transient activity labels
|
|
3347
|
-
// (e.g. "Contacting model", "Preparing X action").
|
|
3348
|
-
"Thinking"
|
|
3349
|
-
}
|
|
3350
|
-
/>
|
|
3447
|
+
<RunningActivityStatus label={runningStatusLabel} />
|
|
3351
3448
|
)}
|
|
3352
3449
|
{queuedMessages.map((msg) => {
|
|
3353
3450
|
const displayText = msg.text
|
|
@@ -352,6 +352,11 @@ export function processEvent(
|
|
|
352
352
|
}
|
|
353
353
|
|
|
354
354
|
if (ev.type === "text") {
|
|
355
|
+
// Visible output means the run is plainly not hanging — drop any running
|
|
356
|
+
// activity label so a transient "Contacting model" / "Still generating
|
|
357
|
+
// image" doesn't linger beside streamed text. Idempotent (clears once, then
|
|
358
|
+
// no-ops) so per-token text deltas stay cheap.
|
|
359
|
+
if (ev.text) dispatchActivityClear(tabId);
|
|
355
360
|
const lastPart = content[content.length - 1];
|
|
356
361
|
if (lastPart && lastPart.type === "text") {
|
|
357
362
|
lastPart.text += ev.text ?? "";
|
|
@@ -492,6 +497,10 @@ export function processEvent(
|
|
|
492
497
|
}),
|
|
493
498
|
);
|
|
494
499
|
}
|
|
500
|
+
// Clear any sticky running-activity label (e.g. "Still generating image"):
|
|
501
|
+
// the tool that was emitting activity heartbeats has finished, so the label
|
|
502
|
+
// must not linger while the model streams its follow-up text or reasoning.
|
|
503
|
+
dispatchActivityClear(tabId);
|
|
495
504
|
// Use id-based lookup when available so parallel same-name tool calls
|
|
496
505
|
// get their results correctly assigned; fall back to name-matching.
|
|
497
506
|
const doneIdx = findPendingToolCallIndex(content, doneTool, ev.id);
|
|
@@ -15,8 +15,15 @@ Use this skill before calling `generate-image`, `generate-image-batch`, or
|
|
|
15
15
|
`presetId`, and `media-type` references choose image generation versus video
|
|
16
16
|
generation. Call `view-screen` when the user says "this library" or "this
|
|
17
17
|
image" and you need fresh IDs. The image model may default from the composer
|
|
18
|
-
image-model picker
|
|
19
|
-
|
|
18
|
+
image-model picker.
|
|
19
|
+
- A tagged `@preset` (presetId) owns `aspectRatio`, `imageSize`, `model`,
|
|
20
|
+
`tier`, and `category`. When a preset is set, do NOT pass those args yourself —
|
|
21
|
+
leave them out so the preset's saved values are used. You cannot see the
|
|
22
|
+
preset's settings from the presetId, so passing your own guess silently
|
|
23
|
+
overrides the preset (this is the usual cause of a preset's aspect ratio being
|
|
24
|
+
ignored). Pass one of these args alongside a preset ONLY when the user
|
|
25
|
+
explicitly asks for a value that differs from the preset. When there is no
|
|
26
|
+
preset, set them explicitly or rely on the action schema defaults.
|
|
20
27
|
- Use category-tagged references. Blog heroes should prefer `hero`; diagrams
|
|
21
28
|
should prefer `diagram`; product imagery should include `product` and `logo`
|
|
22
29
|
references.
|
|
@@ -47,6 +47,10 @@ prompt, aspectRatio }`.
|
|
|
47
47
|
composer is empty. The image model is the only remaining composer-side
|
|
48
48
|
default; the image-model picker writes `imageGenerationModel`, which image
|
|
49
49
|
generation actions may use when `model` is omitted.
|
|
50
|
+
- For exact visible copy inside a generated image, pass `embeddedText` and
|
|
51
|
+
optional `textPlacement` to `generate-image` or each `generate-image-batch`
|
|
52
|
+
slot. Keep the general `prompt` for creative direction; the structured text
|
|
53
|
+
fields are what allow the pipeline to render copy instead of suppressing it.
|
|
50
54
|
- `asset-variants` is the shared live generation tray state. New image
|
|
51
55
|
candidates should appear there through `generate-image` or
|
|
52
56
|
`generate-image-batch`; do not invent page-local progress surfaces.
|