@agent-native/core 0.85.3 → 0.85.5
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 +12 -0
- package/corpus/core/docs/content/tracking.mdx +1 -1
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/run-manager.ts +30 -10
- package/corpus/core/src/client/AssistantChat.tsx +87 -2
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +1 -1
- package/corpus/templates/design/AGENTS.md +6 -4
- package/corpus/templates/design/actions/present-design-variants.ts +10 -5
- package/corpus/templates/design/changelog/2026-07-03-variant-picks-build-the-selected-app.md +5 -0
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +27 -11
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +60 -3
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/docs/content/tracking.mdx +1 -1
- package/package.json +1 -1
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.85.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- bd7c040: Auto-continue active chat reconnects when action preparation stalls, and count first action-prep events as durable progress.
|
|
8
|
+
|
|
9
|
+
## 0.85.4
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 00c6f16: Keep chat restore and active-run polling on the durable background timeout budget so healthy long-running background turns do not look stale to the browser.
|
|
14
|
+
|
|
3
15
|
## 0.85.3
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -235,7 +235,7 @@ Privacy defaults are intentionally conservative but still useful for playback:
|
|
|
235
235
|
- URLs are scrubbed with the same `scrubUrl()` helper used by browser analytics.
|
|
236
236
|
- Replay capture is web-only and opt-in; it does not record native desktop screens.
|
|
237
237
|
|
|
238
|
-
While recording, session replay also captures browser console output (`log`, `info`, `warn`, `error`, `debug`, plus window `error` / `unhandledrejection`) and network request metadata (`fetch` and XHR) as tagged rrweb custom events, so agents and the replay viewer can debug user-reported issues. Capture is on by default when replay is enabled; tune or disable it with the `sessionReplay.console` and `sessionReplay.network` options, each accepting a boolean or an options object. Request/response bodies and headers are never captured, URLs are scrubbed, messages are truncated, the recorder's own ingest/tracking traffic is excluded, and per-session budgets (1000 console / 2000 network events) add a truncation notice when exceeded.
|
|
238
|
+
While recording, session replay also captures browser console output (`log`, `info`, `warn`, `error`, `debug`, plus window `error` / `unhandledrejection`) and network request metadata (`fetch` and XHR) as tagged rrweb custom events, so agents and the replay viewer can debug user-reported issues. Capture is on by default when replay is enabled; tune or disable it with the `sessionReplay.console` and `sessionReplay.network` options, each accepting a boolean or an options object (`{ maxEvents?: number }`). Request/response bodies and headers are never captured, URLs are scrubbed, messages are truncated, the recorder's own ingest/tracking traffic is excluded, and per-session budgets (1000 console / 2000 network events) add a truncation notice when exceeded.
|
|
239
239
|
|
|
240
240
|
The Analytics template stores replay metadata in SQL (`session_recordings`) and stores chunks through private blob refs (`session_replay_chunks`). Browsers and agents never receive provider URLs. Playback goes through scoped server routes and the default agent tools return summaries or bounded replay events, not raw chunk table access.
|
|
241
241
|
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.85.
|
|
3
|
+
"version": "0.85.5",
|
|
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": {
|
|
@@ -553,6 +553,16 @@ export function startRun(
|
|
|
553
553
|
preparingActivityBytes.get(activityKey) ?? 0,
|
|
554
554
|
restartHighWater,
|
|
555
555
|
);
|
|
556
|
+
if (
|
|
557
|
+
!preparingActivityBytes.has(activityKey) &&
|
|
558
|
+
progressBytes === 0 &&
|
|
559
|
+
!preparingActivityRestartHighWater.has(toolKey)
|
|
560
|
+
) {
|
|
561
|
+
preparingActivityTools.set(activityKey, toolKey);
|
|
562
|
+
preparingActivityBytes.set(activityKey, 0);
|
|
563
|
+
preparingActivityRestartHighWater.set(toolKey, 0);
|
|
564
|
+
return true;
|
|
565
|
+
}
|
|
556
566
|
if (progressBytes <= previousBytes) {
|
|
557
567
|
preparingActivityTools.set(activityKey, toolKey);
|
|
558
568
|
preparingActivityBytes.set(
|
|
@@ -1362,20 +1372,32 @@ export async function getActiveRunForThreadAsync(threadId: string): Promise<{
|
|
|
1362
1372
|
// the full conversation from completed runs via SSE.
|
|
1363
1373
|
const memRun = getActiveRunForThread(threadId);
|
|
1364
1374
|
if (memRun && (memRun.status === "running" || memRun.events.length > 0)) {
|
|
1375
|
+
const sqlSnapshot = await fetchRunThreadSnapshot(memRun.runId, threadId);
|
|
1376
|
+
const status = sqlSnapshot?.status ?? memRun.status;
|
|
1377
|
+
const heartbeatAt =
|
|
1378
|
+
status === "running"
|
|
1379
|
+
? Date.now()
|
|
1380
|
+
: (sqlSnapshot?.heartbeatAt ?? memRun.startedAt);
|
|
1365
1381
|
return {
|
|
1366
1382
|
runId: memRun.runId,
|
|
1367
1383
|
threadId: memRun.threadId,
|
|
1368
1384
|
turnId: memRun.turnId,
|
|
1369
|
-
status
|
|
1385
|
+
status,
|
|
1370
1386
|
// In-memory means this isolate is the producer. By definition, the
|
|
1371
|
-
// heartbeat is fresh as of "now"
|
|
1372
|
-
|
|
1387
|
+
// heartbeat is fresh as of "now" while the run is still running. Once
|
|
1388
|
+
// SQL has terminal truth, prefer that timestamp so a stale in-memory
|
|
1389
|
+
// buffer cannot keep the browser believing a finished background run is
|
|
1390
|
+
// still alive.
|
|
1391
|
+
heartbeatAt,
|
|
1373
1392
|
// For an in-memory run we don't have a separate "last event emit"
|
|
1374
1393
|
// timestamp tracked in JS — the SQL bump is throttled per-second.
|
|
1375
1394
|
// Read it back from SQL on demand. For the common case the SQL row
|
|
1376
1395
|
// is well under 1s old; if it isn't, the stuck-detector will pick
|
|
1377
1396
|
// it up on the next poll cycle.
|
|
1378
|
-
lastProgressAt:
|
|
1397
|
+
lastProgressAt: sqlSnapshot?.lastProgressAt ?? null,
|
|
1398
|
+
dispatchMode: sqlSnapshot?.dispatchMode ?? null,
|
|
1399
|
+
terminalReason: sqlSnapshot?.terminalReason ?? null,
|
|
1400
|
+
diagStage: sqlSnapshot?.diagStage ?? null,
|
|
1379
1401
|
};
|
|
1380
1402
|
}
|
|
1381
1403
|
// Fall back to SQL — also surface recently terminated runs so the client
|
|
@@ -1454,16 +1476,14 @@ export async function getActiveRunForThreadAsync(threadId: string): Promise<{
|
|
|
1454
1476
|
return null;
|
|
1455
1477
|
}
|
|
1456
1478
|
|
|
1457
|
-
async function
|
|
1479
|
+
async function fetchRunThreadSnapshot(runId: string, threadId: string) {
|
|
1458
1480
|
try {
|
|
1459
|
-
const run = await getRunById(runId);
|
|
1460
|
-
if (!run) return null;
|
|
1461
1481
|
// `getRunById` returns a narrow projection today; ask for the row via
|
|
1462
|
-
// the thread lookup which carries
|
|
1463
|
-
const byThread = await getRunByThread(
|
|
1482
|
+
// the thread lookup which carries dispatch/terminal/progress fields.
|
|
1483
|
+
const byThread = await getRunByThread(threadId, {
|
|
1464
1484
|
includeTerminal: true,
|
|
1465
1485
|
});
|
|
1466
|
-
if (byThread && byThread.id === runId) return byThread
|
|
1486
|
+
if (byThread && byThread.id === runId) return byThread;
|
|
1467
1487
|
return null;
|
|
1468
1488
|
} catch {
|
|
1469
1489
|
return null;
|
|
@@ -239,8 +239,12 @@ function createUserMessageRunConfig(
|
|
|
239
239
|
const PENDING_SELECTION_KEY = "pending-selection-context";
|
|
240
240
|
const ACTIVE_RUN_CLEAR_TIMEOUT_MS = 5_000;
|
|
241
241
|
const ACTIVE_RUN_STUCK_THRESHOLD_MS = 90_000;
|
|
242
|
+
const BACKGROUND_ACTIVE_RUN_STUCK_THRESHOLD_MS = 13 * 60_000;
|
|
242
243
|
const ACTIVE_RUN_POLL_INTERVAL_MS = 150;
|
|
243
244
|
const AUTO_RESUME_STATUS_TIMEOUT_MS = 30_000;
|
|
245
|
+
const MAX_RECONNECT_AUTO_RECOVERIES = 3;
|
|
246
|
+
const RECONNECT_NO_PROGRESS_CONTINUE_MESSAGE =
|
|
247
|
+
"Continue from where you stopped. Use the partial work above, verify what succeeded, and finish the original request. Do not rerun the exact same failed tool input unless the failure was transient or the user explicitly asked for an exact rerun. Prefer dedicated app actions over raw database edits when they exist.";
|
|
244
248
|
// How long a single activity (model call, tool prep, long tool) must stay
|
|
245
249
|
// in-flight before its label is surfaced in the running indicator. Below this
|
|
246
250
|
// the indicator stays a steady "Thinking" so normal fast turns don't flicker
|
|
@@ -261,6 +265,11 @@ type ActiveRunLookup = {
|
|
|
261
265
|
serverNow?: number;
|
|
262
266
|
};
|
|
263
267
|
|
|
268
|
+
type PendingReconnectRecovery = {
|
|
269
|
+
id: number;
|
|
270
|
+
message: string;
|
|
271
|
+
};
|
|
272
|
+
|
|
264
273
|
function isReplayableTerminalRun(runInfo: ActiveRunLookup): boolean {
|
|
265
274
|
const dispatchMode =
|
|
266
275
|
typeof runInfo.dispatchMode === "string" ? runInfo.dispatchMode : "";
|
|
@@ -271,15 +280,24 @@ function isReplayableTerminalRun(runInfo: ActiveRunLookup): boolean {
|
|
|
271
280
|
);
|
|
272
281
|
}
|
|
273
282
|
|
|
283
|
+
function activeRunStuckThresholdMs(runInfo: ActiveRunLookup): number {
|
|
284
|
+
const dispatchMode =
|
|
285
|
+
typeof runInfo.dispatchMode === "string" ? runInfo.dispatchMode : "";
|
|
286
|
+
return dispatchMode.startsWith("background")
|
|
287
|
+
? BACKGROUND_ACTIVE_RUN_STUCK_THRESHOLD_MS
|
|
288
|
+
: ACTIVE_RUN_STUCK_THRESHOLD_MS;
|
|
289
|
+
}
|
|
290
|
+
|
|
274
291
|
function activeRunLooksStale(runInfo: ActiveRunLookup): boolean {
|
|
275
292
|
const lastProgressAt =
|
|
276
293
|
typeof runInfo.lastProgressAt === "number" ? runInfo.lastProgressAt : null;
|
|
277
294
|
const nowMs =
|
|
278
295
|
typeof runInfo.serverNow === "number" ? runInfo.serverNow : Date.now();
|
|
296
|
+
const thresholdMs = activeRunStuckThresholdMs(runInfo);
|
|
279
297
|
return (
|
|
280
298
|
runInfo.status === "running" &&
|
|
281
299
|
lastProgressAt != null &&
|
|
282
|
-
nowMs - lastProgressAt >
|
|
300
|
+
nowMs - lastProgressAt > thresholdMs
|
|
283
301
|
);
|
|
284
302
|
}
|
|
285
303
|
|
|
@@ -1558,6 +1576,9 @@ const AssistantChatInner = forwardRef<
|
|
|
1558
1576
|
const reconnectTailOnlyRef = useRef(false);
|
|
1559
1577
|
const reconnectCanMaterializeRef = useRef(false);
|
|
1560
1578
|
const reconnectAbortRef = useRef<AbortController | null>(null);
|
|
1579
|
+
const reconnectAutoRecoveryCountRef = useRef(0);
|
|
1580
|
+
const [pendingReconnectRecovery, setPendingReconnectRecovery] =
|
|
1581
|
+
useState<PendingReconnectRecovery | null>(null);
|
|
1561
1582
|
// Nuclear stop: user clicked stop. Clears the stop button/indicator AND
|
|
1562
1583
|
// lets new submissions go through immediately — prevents the "stuck
|
|
1563
1584
|
// queueing forever" state where isReconnecting or isRuntimeRunning gets
|
|
@@ -1894,6 +1915,7 @@ const AssistantChatInner = forwardRef<
|
|
|
1894
1915
|
reconnectAbortRef.current = abortCtrl;
|
|
1895
1916
|
let reconnectTerminalReason: AgentAutoContinueSignal["reason"] | null =
|
|
1896
1917
|
null;
|
|
1918
|
+
const reconnectStuckThresholdMs = activeRunStuckThresholdMs(runInfo);
|
|
1897
1919
|
|
|
1898
1920
|
const watchdog = setInterval(async () => {
|
|
1899
1921
|
try {
|
|
@@ -1937,6 +1959,7 @@ const AssistantChatInner = forwardRef<
|
|
|
1937
1959
|
!reconnectProgressTimedOut({
|
|
1938
1960
|
lastProgressAt: lastReconnectProgressAt,
|
|
1939
1961
|
now: Date.now(),
|
|
1962
|
+
thresholdMs: reconnectStuckThresholdMs,
|
|
1940
1963
|
})
|
|
1941
1964
|
) {
|
|
1942
1965
|
return;
|
|
@@ -2122,6 +2145,38 @@ const AssistantChatInner = forwardRef<
|
|
|
2122
2145
|
setReconnectFrozen(latestContent.length > 0);
|
|
2123
2146
|
reconnectCanMaterializeRef.current = latestContent.length > 0;
|
|
2124
2147
|
}
|
|
2148
|
+
const canAutoRecoverReconnect =
|
|
2149
|
+
reconnectTerminalReason !== "run_timeout" &&
|
|
2150
|
+
reconnectAutoRecoveryCountRef.current <
|
|
2151
|
+
MAX_RECONNECT_AUTO_RECOVERIES;
|
|
2152
|
+
if (canAutoRecoverReconnect) {
|
|
2153
|
+
reconnectAutoRecoveryCountRef.current += 1;
|
|
2154
|
+
setRunErrorInfo(null);
|
|
2155
|
+
setDismissedRunErrorKey(null);
|
|
2156
|
+
clearActiveRunIfMatches(threadId, runId);
|
|
2157
|
+
reconnectAbortRef.current = null;
|
|
2158
|
+
setIsReconnecting(false);
|
|
2159
|
+
reconnectRunIdRef.current = null;
|
|
2160
|
+
reconnectTailOnlyRef.current = false;
|
|
2161
|
+
if (afterSeq > 0) {
|
|
2162
|
+
reconnectCanMaterializeRef.current = false;
|
|
2163
|
+
}
|
|
2164
|
+
window.dispatchEvent(
|
|
2165
|
+
new CustomEvent("agent-chat:auto-continue", {
|
|
2166
|
+
detail: { tabId: tabId || threadId },
|
|
2167
|
+
}),
|
|
2168
|
+
);
|
|
2169
|
+
setPendingReconnectRecovery({
|
|
2170
|
+
id: Date.now(),
|
|
2171
|
+
message: RECONNECT_NO_PROGRESS_CONTINUE_MESSAGE,
|
|
2172
|
+
});
|
|
2173
|
+
window.dispatchEvent(
|
|
2174
|
+
new CustomEvent("agentNative.chatRunning", {
|
|
2175
|
+
detail: { isRunning: false, tabId: tabId || threadId },
|
|
2176
|
+
}),
|
|
2177
|
+
);
|
|
2178
|
+
return;
|
|
2179
|
+
}
|
|
2125
2180
|
setRunErrorInfo({
|
|
2126
2181
|
message:
|
|
2127
2182
|
reconnectTerminalReason === "run_timeout"
|
|
@@ -2176,6 +2231,9 @@ const AssistantChatInner = forwardRef<
|
|
|
2176
2231
|
if (loaded || afterSeq > 0 || latestContent.length === 0) {
|
|
2177
2232
|
reconnectCanMaterializeRef.current = false;
|
|
2178
2233
|
}
|
|
2234
|
+
if (loaded) {
|
|
2235
|
+
reconnectAutoRecoveryCountRef.current = 0;
|
|
2236
|
+
}
|
|
2179
2237
|
window.dispatchEvent(
|
|
2180
2238
|
new CustomEvent("agentNative.chatRunning", {
|
|
2181
2239
|
detail: { isRunning: false, tabId: tabId || threadId },
|
|
@@ -3058,10 +3116,14 @@ const AssistantChatInner = forwardRef<
|
|
|
3058
3116
|
recoveryAction?: AgentRecoveryAction,
|
|
3059
3117
|
includeComposerContext = false,
|
|
3060
3118
|
trackInRunsTray = false,
|
|
3119
|
+
preserveReconnectAutoRecoveryBudget = false,
|
|
3061
3120
|
) => {
|
|
3062
3121
|
if (!(await ensureAgentEngineReadyForSubmit())) {
|
|
3063
3122
|
return;
|
|
3064
3123
|
}
|
|
3124
|
+
if (!preserveReconnectAutoRecoveryBudget) {
|
|
3125
|
+
reconnectAutoRecoveryCountRef.current = 0;
|
|
3126
|
+
}
|
|
3065
3127
|
materializeFrozenReconnectContent();
|
|
3066
3128
|
setShowContinue(false);
|
|
3067
3129
|
setLoopLimitInfo(null);
|
|
@@ -3284,6 +3346,29 @@ const AssistantChatInner = forwardRef<
|
|
|
3284
3346
|
],
|
|
3285
3347
|
);
|
|
3286
3348
|
|
|
3349
|
+
useEffect(() => {
|
|
3350
|
+
if (!pendingReconnectRecovery) return;
|
|
3351
|
+
const recovery = pendingReconnectRecovery;
|
|
3352
|
+
const timer = window.setTimeout(() => {
|
|
3353
|
+
setPendingReconnectRecovery((current) =>
|
|
3354
|
+
current?.id === recovery.id ? null : current,
|
|
3355
|
+
);
|
|
3356
|
+
addToQueue(
|
|
3357
|
+
recovery.message,
|
|
3358
|
+
undefined,
|
|
3359
|
+
undefined,
|
|
3360
|
+
undefined,
|
|
3361
|
+
undefined,
|
|
3362
|
+
"queued",
|
|
3363
|
+
"continue",
|
|
3364
|
+
false,
|
|
3365
|
+
false,
|
|
3366
|
+
true,
|
|
3367
|
+
);
|
|
3368
|
+
}, 0);
|
|
3369
|
+
return () => window.clearTimeout(timer);
|
|
3370
|
+
}, [addToQueue, pendingReconnectRecovery]);
|
|
3371
|
+
|
|
3287
3372
|
// Expose imperative handle
|
|
3288
3373
|
useImperativeHandle(
|
|
3289
3374
|
ref,
|
|
@@ -3859,7 +3944,7 @@ const AssistantChatInner = forwardRef<
|
|
|
3859
3944
|
onContinue={() => {
|
|
3860
3945
|
setRunErrorInfo(null);
|
|
3861
3946
|
addToQueue(
|
|
3862
|
-
|
|
3947
|
+
RECONNECT_NO_PROGRESS_CONTINUE_MESSAGE,
|
|
3863
3948
|
undefined,
|
|
3864
3949
|
undefined,
|
|
3865
3950
|
undefined,
|
|
@@ -46,7 +46,7 @@ agent answers about browser recordings in the Analytics template.
|
|
|
46
46
|
custom events tagged `agent-native.console` and `agent-native.network`.
|
|
47
47
|
- Capture is on by default whenever session replay is enabled. Tune or disable
|
|
48
48
|
it with the `console` / `network` options on the session replay config; each
|
|
49
|
-
accepts a boolean or an options object.
|
|
49
|
+
accepts a boolean or an options object (`{ maxEvents?: number }`).
|
|
50
50
|
- Privacy bounds: request/response bodies and headers are never captured, URLs
|
|
51
51
|
are scrubbed, messages are truncated, and recorder self-traffic (the replay
|
|
52
52
|
ingest and tracking endpoints) is excluded.
|
|
@@ -243,10 +243,12 @@ patterns live in `.agents/skills/`.
|
|
|
243
243
|
wait for the user to pick one in chat, delete each other generated variant
|
|
244
244
|
screen with `delete-file` at most once, call `get-design-snapshot` exactly
|
|
245
245
|
once with the selected screen's `fileId`, then call `edit-design` exactly once
|
|
246
|
-
on that same `fileId` for follow-up refinement.
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
a variant
|
|
246
|
+
on that same `fileId` for follow-up refinement. The kept variant screen is a
|
|
247
|
+
representative direction, not the final deliverable: use `mode:
|
|
248
|
+
"replace-file"` to replace it with the actual requested app/product UI in the
|
|
249
|
+
chosen visual style. Do not leave a direction board, variant brief, summary
|
|
250
|
+
card, or prose description as the final screen. Do not repeat delete/snapshot
|
|
251
|
+
cycles, and do not call `generate-design` after a variant pick.
|
|
250
252
|
- If inline chat choice buttons are unavailable, the user can tell you the
|
|
251
253
|
preferred screen name. Do not show a separate variant picker or ask them to
|
|
252
254
|
paste a copyable handoff summary.
|
|
@@ -42,13 +42,18 @@ const FALLBACK_INSTRUCTIONS =
|
|
|
42
42
|
"a screen by name if the inline buttons are not available; after they pick, " +
|
|
43
43
|
"delete each other variant screen at most once, call get-design-snapshot with fileId for " +
|
|
44
44
|
"the kept screen once, then call edit-design on that same fileId in a bounded pass. " +
|
|
45
|
-
'Use mode "replace-file"
|
|
45
|
+
'Use mode "replace-file" to replace the representative direction screen with ' +
|
|
46
|
+
"the actual requested product UI; do not leave a direction board, summary card, " +
|
|
47
|
+
"or variant brief as the final result. " +
|
|
46
48
|
"Do not call generate-design after a variant pick.";
|
|
47
49
|
|
|
48
50
|
const VARIANT_PICK_SUBMIT_MESSAGE =
|
|
49
51
|
"Use this design direction. Keep the selected screen, clean up each other " +
|
|
50
52
|
"variant screen at most once, read only the kept screen, then update that " +
|
|
51
|
-
"same screen in one bounded pass
|
|
53
|
+
"same screen in one bounded pass into the full requested app/product UI. " +
|
|
54
|
+
"The selected screen is only a representative direction; the final saved " +
|
|
55
|
+
"screen must not be a direction board, variant brief, or summary card. " +
|
|
56
|
+
"If a cleanup action reports a screen was " +
|
|
52
57
|
"already missing, continue. Use the exact file ids and tool instructions in " +
|
|
53
58
|
"the selected answer below. Do not repeat cleanup/read cycles, do not create " +
|
|
54
59
|
"a new index.html, and stop after the first successful screen update.";
|
|
@@ -580,7 +585,7 @@ export default defineAction({
|
|
|
580
585
|
await writeAppStateForCurrentTab("guided-questions", {
|
|
581
586
|
title: prompt ?? "Pick a direction",
|
|
582
587
|
description:
|
|
583
|
-
"All options are on the board. Choose one to keep; I will delete the others, read only the kept screen, and
|
|
588
|
+
"All options are on the board. Choose one to keep; I will delete the others, read only the kept screen, and turn that direction into the final requested screen.",
|
|
584
589
|
submitLabel: "Use selected direction",
|
|
585
590
|
submitMessage: VARIANT_PICK_SUBMIT_MESSAGE,
|
|
586
591
|
skipLabel: "Show another set",
|
|
@@ -608,7 +613,7 @@ export default defineAction({
|
|
|
608
613
|
value:
|
|
609
614
|
`Keep "${screen.label}" (${screen.filename}, file id ${screen.id}) ` +
|
|
610
615
|
`from variant set ${variantSetId}. Delete each other variant screen at most once: ${otherScreens}. If delete-file says a screen is already missing, continue. ` +
|
|
611
|
-
`Then call get-design-snapshot exactly once with designId ${designId} and fileId ${screen.id} (filename ${screen.filename}), then call edit-design with fileId ${screen.id} on that same kept file in a bounded single-file pass. Use mode "replace-file"
|
|
616
|
+
`Then call get-design-snapshot exactly once with designId ${designId} and fileId ${screen.id} (filename ${screen.filename}), then call edit-design with fileId ${screen.id} on that same kept file in a bounded single-file pass. Use mode "replace-file" to replace the representative direction screen with the full requested app/product UI in the chosen visual style. The final saved screen must be the actual usable UI requested by the user, not a direction board, variant brief, summary card, or description of the direction. Do not call generate-design after this variant pick, do not repeat delete/snapshot cycles, do not create index.html, and do not resend a huge payload. Stop after the first successful edit-design save.`,
|
|
612
617
|
};
|
|
613
618
|
}),
|
|
614
619
|
},
|
|
@@ -626,7 +631,7 @@ export default defineAction({
|
|
|
626
631
|
embed: true,
|
|
627
632
|
fallbackInstructions: FALLBACK_INSTRUCTIONS,
|
|
628
633
|
nextRequiredAction:
|
|
629
|
-
'Wait for the user to pick a screen in chat. Then delete each unchosen variant screen with delete-file at most once, call get-design-snapshot exactly once with fileId for the chosen screen, and call edit-design with that same fileId in a bounded pass. Use mode "replace-file"
|
|
634
|
+
'Wait for the user to pick a screen in chat. Then delete each unchosen variant screen with delete-file at most once, call get-design-snapshot exactly once with fileId for the chosen screen, and call edit-design with that same fileId in a bounded pass. Use mode "replace-file" to replace the representative direction screen with the full requested app/product UI in the chosen visual style. Do not leave a direction board, variant brief, or summary card as the final result. Do not repeat delete/snapshot cycles. Do not call generate-design after a variant pick. Stop after the first successful edit-design save.',
|
|
630
635
|
};
|
|
631
636
|
},
|
|
632
637
|
link: ({ result }) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-manager.d.ts","sourceRoot":"","sources":["../../src/agent/run-manager.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEtE,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB,MAAM,EAAE,SAAS,CAAC;IAClB,WAAW,EAAE,GAAG,CAAC,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC,CAAC;IAC5C,KAAK,EAAE,eAAe,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAQD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,kCAAkC,QAAS,CAAC;AAEzD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,8BAA8B,QAAS,CAAC;AAErD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,kCAAkC,QAAc,CAAC;AAE9D;;;;;GAKG;AACH,eAAO,MAAM,sCAAsC,QACf,CAAC;AAErC;;;;;;;GAOG;AACH,eAAO,MAAM,yCAAyC,QACT,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,+BAA+B,SAAU,CAAC;AAEvD,qEAAqE;AACrE,eAAO,MAAM,kCAAkC,QAAsB,CAAC;AAEtE;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,QAA0B,CAAC;AAExE;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,QAAiB,CAAC;AAE/D,wFAAwF;AACxF,eAAO,MAAM,+BAA+B,MAAM,CAAC;AAEnD,iEAAiE;AACjE,eAAO,MAAM,6BAA6B,MAAM,CAAC;AAEjD;;;GAGG;AACH,eAAO,MAAM,gCAAgC,OAAQ,CAAC;AAEtD,8EAA8E;AAC9E,eAAO,MAAM,+BAA+B,MAAM,CAAC;AAEnD,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE,MAAM,EACX,eAAe,EAAE,MAAM,GACtB,MAAM,CAIR;AAmDD,MAAM,WAAW,eAAe;IAC9B;;2CAEuC;IACvC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;4DACwD;IACxD,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC;;;+EAG2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;OAQG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,4BAA4B;IAC3C,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AA0BD,wBAAgB,uBAAuB,CACrC,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,4BAA4B,GACrC,MAAM,CAkCR;AAED,wBAAgB,8BAA8B,IAAI,MAAM,CAOvD;AAED,wBAAgB,4BAA4B,IAAI,MAAM,CAOrD;AAiDD;;;;;;GAMG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,CACL,IAAI,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,EACrC,MAAM,EAAE,WAAW,KAChB,OAAO,CAAC,IAAI,CAAC,EAClB,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,SAAS,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EACrD,OAAO,CAAC,EAAE,eAAe,GACxB,SAAS,
|
|
1
|
+
{"version":3,"file":"run-manager.d.ts","sourceRoot":"","sources":["../../src/agent/run-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -406,6 +406,14 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
|
|
|
406
406
|
return progressBytes > 0;
|
|
407
407
|
}
|
|
408
408
|
const previousBytes = Math.max(preparingActivityBytes.get(activityKey) ?? 0, restartHighWater);
|
|
409
|
+
if (!preparingActivityBytes.has(activityKey) &&
|
|
410
|
+
progressBytes === 0 &&
|
|
411
|
+
!preparingActivityRestartHighWater.has(toolKey)) {
|
|
412
|
+
preparingActivityTools.set(activityKey, toolKey);
|
|
413
|
+
preparingActivityBytes.set(activityKey, 0);
|
|
414
|
+
preparingActivityRestartHighWater.set(toolKey, 0);
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
409
417
|
if (progressBytes <= previousBytes) {
|
|
410
418
|
preparingActivityTools.set(activityKey, toolKey);
|
|
411
419
|
preparingActivityBytes.set(activityKey, Math.max(previousBytes, progressBytes));
|
|
@@ -1118,20 +1126,31 @@ export async function getActiveRunForThreadAsync(threadId) {
|
|
|
1118
1126
|
// the full conversation from completed runs via SSE.
|
|
1119
1127
|
const memRun = getActiveRunForThread(threadId);
|
|
1120
1128
|
if (memRun && (memRun.status === "running" || memRun.events.length > 0)) {
|
|
1129
|
+
const sqlSnapshot = await fetchRunThreadSnapshot(memRun.runId, threadId);
|
|
1130
|
+
const status = sqlSnapshot?.status ?? memRun.status;
|
|
1131
|
+
const heartbeatAt = status === "running"
|
|
1132
|
+
? Date.now()
|
|
1133
|
+
: (sqlSnapshot?.heartbeatAt ?? memRun.startedAt);
|
|
1121
1134
|
return {
|
|
1122
1135
|
runId: memRun.runId,
|
|
1123
1136
|
threadId: memRun.threadId,
|
|
1124
1137
|
turnId: memRun.turnId,
|
|
1125
|
-
status
|
|
1138
|
+
status,
|
|
1126
1139
|
// In-memory means this isolate is the producer. By definition, the
|
|
1127
|
-
// heartbeat is fresh as of "now"
|
|
1128
|
-
|
|
1140
|
+
// heartbeat is fresh as of "now" while the run is still running. Once
|
|
1141
|
+
// SQL has terminal truth, prefer that timestamp so a stale in-memory
|
|
1142
|
+
// buffer cannot keep the browser believing a finished background run is
|
|
1143
|
+
// still alive.
|
|
1144
|
+
heartbeatAt,
|
|
1129
1145
|
// For an in-memory run we don't have a separate "last event emit"
|
|
1130
1146
|
// timestamp tracked in JS — the SQL bump is throttled per-second.
|
|
1131
1147
|
// Read it back from SQL on demand. For the common case the SQL row
|
|
1132
1148
|
// is well under 1s old; if it isn't, the stuck-detector will pick
|
|
1133
1149
|
// it up on the next poll cycle.
|
|
1134
|
-
lastProgressAt:
|
|
1150
|
+
lastProgressAt: sqlSnapshot?.lastProgressAt ?? null,
|
|
1151
|
+
dispatchMode: sqlSnapshot?.dispatchMode ?? null,
|
|
1152
|
+
terminalReason: sqlSnapshot?.terminalReason ?? null,
|
|
1153
|
+
diagStage: sqlSnapshot?.diagStage ?? null,
|
|
1135
1154
|
};
|
|
1136
1155
|
}
|
|
1137
1156
|
// Fall back to SQL — also surface recently terminated runs so the client
|
|
@@ -1211,18 +1230,15 @@ export async function getActiveRunForThreadAsync(threadId) {
|
|
|
1211
1230
|
}
|
|
1212
1231
|
return null;
|
|
1213
1232
|
}
|
|
1214
|
-
async function
|
|
1233
|
+
async function fetchRunThreadSnapshot(runId, threadId) {
|
|
1215
1234
|
try {
|
|
1216
|
-
const run = await getRunById(runId);
|
|
1217
|
-
if (!run)
|
|
1218
|
-
return null;
|
|
1219
1235
|
// `getRunById` returns a narrow projection today; ask for the row via
|
|
1220
|
-
// the thread lookup which carries
|
|
1221
|
-
const byThread = await getRunByThread(
|
|
1236
|
+
// the thread lookup which carries dispatch/terminal/progress fields.
|
|
1237
|
+
const byThread = await getRunByThread(threadId, {
|
|
1222
1238
|
includeTerminal: true,
|
|
1223
1239
|
});
|
|
1224
1240
|
if (byThread && byThread.id === runId)
|
|
1225
|
-
return byThread
|
|
1241
|
+
return byThread;
|
|
1226
1242
|
return null;
|
|
1227
1243
|
}
|
|
1228
1244
|
catch {
|