@agent-native/core 0.101.3 → 0.101.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 +14 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/action.ts +15 -0
- package/corpus/core/src/agent/production-agent.ts +215 -19
- package/corpus/core/src/client/RunStuckBanner.tsx +12 -5
- package/corpus/core/src/client/sse-event-processor.ts +36 -1
- package/corpus/core/src/client/use-run-stuck-detection.ts +162 -25
- package/corpus/core/src/coding-tools/run-code.ts +11 -5
- package/corpus/core/src/deploy/build.ts +78 -25
- package/corpus/core/src/extensions/actions.ts +15 -0
- package/corpus/core/src/extensions/url-safety.ts +11 -1
- package/corpus/core/src/notifications/channels.ts +61 -30
- package/corpus/core/src/server/action-discovery.ts +4 -1
- package/corpus/core/src/server/agent-chat/context-tools.ts +4 -0
- package/corpus/templates/chat/changelog/2026-07-14-chat-opens-reliably-on-hosted-deployments-instead-of-failing.md +6 -0
- package/corpus/templates/clips/app/components/player/media-duration.ts +24 -0
- package/corpus/templates/clips/app/components/player/video-player.tsx +9 -18
- package/corpus/templates/clips/changelog/2026-07-14-paused-time-no-longer-counts-toward-chrome-extension-recordi.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +30 -4
- package/corpus/templates/clips/chrome-extension/src/recording-duration.ts +32 -0
- package/corpus/templates/slides/actions/_uploaded-files.ts +13 -2
- package/corpus/templates/slides/actions/import-docx.ts +3 -9
- package/corpus/templates/slides/actions/import-file.ts +11 -14
- package/corpus/templates/slides/actions/import-pptx.ts +3 -9
- package/corpus/templates/slides/changelog/2026-07-14-powerpoint-template-uploads-now-work-in-hosted-slides-deploy.md +6 -0
- package/corpus/templates/slides/server/handlers/uploads.ts +100 -50
- package/corpus/templates/slides/server/lib/tenant-files.ts +38 -11
- package/corpus/templates/slides/server/lib/uploaded-reference-storage.ts +108 -0
- package/dist/action.d.ts +12 -0
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +2 -0
- package/dist/action.js.map +1 -1
- package/dist/agent/production-agent.d.ts +24 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +175 -18
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/client/RunStuckBanner.d.ts.map +1 -1
- package/dist/client/RunStuckBanner.js +9 -5
- package/dist/client/RunStuckBanner.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +33 -1
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/client/use-run-stuck-detection.d.ts +4 -4
- package/dist/client/use-run-stuck-detection.d.ts.map +1 -1
- package/dist/client/use-run-stuck-detection.js +104 -19
- package/dist/client/use-run-stuck-detection.js.map +1 -1
- package/dist/coding-tools/run-code.d.ts +5 -2
- package/dist/coding-tools/run-code.d.ts.map +1 -1
- package/dist/coding-tools/run-code.js +11 -5
- package/dist/coding-tools/run-code.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.d.ts +15 -14
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +64 -23
- package/dist/deploy/build.js.map +1 -1
- package/dist/extensions/actions.d.ts.map +1 -1
- package/dist/extensions/actions.js +14 -0
- package/dist/extensions/actions.js.map +1 -1
- package/dist/extensions/url-safety.d.ts +6 -0
- package/dist/extensions/url-safety.d.ts.map +1 -1
- package/dist/extensions/url-safety.js +6 -0
- package/dist/extensions/url-safety.js.map +1 -1
- package/dist/notifications/channels.d.ts.map +1 -1
- package/dist/notifications/channels.js +38 -22
- package/dist/notifications/channels.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +4 -1
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/agent-chat/context-tools.d.ts.map +1 -1
- package/dist/server/agent-chat/context-tools.js +4 -0
- package/dist/server/agent-chat/context-tools.js.map +1 -1
- package/package.json +3 -3
|
@@ -28,6 +28,89 @@ export function useRunStuckDetection({ threadId, enabled = true, stuckThresholdM
|
|
|
28
28
|
const base = apiUrl ?? agentNativePath("/_agent-native/agent-chat");
|
|
29
29
|
let cancelled = false;
|
|
30
30
|
let timer = null;
|
|
31
|
+
let snapshotTransitionTimer = null;
|
|
32
|
+
let snapshotVersion = 0;
|
|
33
|
+
const effectiveThresholdFor = (dispatchMode, heartbeatSinceMs) => {
|
|
34
|
+
const liveBackgroundWorker = dispatchMode === "background-processing" &&
|
|
35
|
+
heartbeatSinceMs != null &&
|
|
36
|
+
heartbeatSinceMs >= 0 &&
|
|
37
|
+
heartbeatSinceMs < FRESH_BACKGROUND_HEARTBEAT_MS;
|
|
38
|
+
const serverContinued = dispatchMode === "foreground-self-chain" ||
|
|
39
|
+
dispatchMode?.startsWith("background") === true;
|
|
40
|
+
return liveBackgroundWorker
|
|
41
|
+
? Math.min(backgroundStuckThresholdMs, liveBackgroundStuckThresholdMs)
|
|
42
|
+
: serverContinued
|
|
43
|
+
? backgroundStuckThresholdMs
|
|
44
|
+
: stuckThresholdMs;
|
|
45
|
+
};
|
|
46
|
+
const scheduleSnapshotTransition = (snapshot, observedAtMs, version) => {
|
|
47
|
+
if (snapshotTransitionTimer)
|
|
48
|
+
clearTimeout(snapshotTransitionTimer);
|
|
49
|
+
snapshotTransitionTimer = null;
|
|
50
|
+
if (cancelled ||
|
|
51
|
+
version !== snapshotVersion ||
|
|
52
|
+
!snapshot.active ||
|
|
53
|
+
snapshot.status !== "running" ||
|
|
54
|
+
snapshot.runId == null) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const currentElapsedMs = Math.max(0, Date.now() - observedAtMs);
|
|
58
|
+
const currentHeartbeatSinceMs = snapshot.heartbeatSinceMs == null
|
|
59
|
+
? null
|
|
60
|
+
: snapshot.heartbeatSinceMs + currentElapsedMs;
|
|
61
|
+
const currentStuckSinceMs = snapshot.stuckSinceMs == null
|
|
62
|
+
? null
|
|
63
|
+
: snapshot.stuckSinceMs + currentElapsedMs;
|
|
64
|
+
const effectiveThresholdMs = effectiveThresholdFor(snapshot.dispatchMode, currentHeartbeatSinceMs);
|
|
65
|
+
const currentlyStuck = Boolean(currentStuckSinceMs != null &&
|
|
66
|
+
currentStuckSinceMs > effectiveThresholdMs);
|
|
67
|
+
const transitionDelaysMs = [];
|
|
68
|
+
if (currentHeartbeatSinceMs != null &&
|
|
69
|
+
currentHeartbeatSinceMs >= 0 &&
|
|
70
|
+
currentHeartbeatSinceMs < FRESH_BACKGROUND_HEARTBEAT_MS) {
|
|
71
|
+
transitionDelaysMs.push(FRESH_BACKGROUND_HEARTBEAT_MS - currentHeartbeatSinceMs + 1);
|
|
72
|
+
}
|
|
73
|
+
if (currentStuckSinceMs != null && !currentlyStuck) {
|
|
74
|
+
transitionDelaysMs.push(Math.max(1, effectiveThresholdMs - currentStuckSinceMs + 1));
|
|
75
|
+
}
|
|
76
|
+
if (transitionDelaysMs.length === 0)
|
|
77
|
+
return;
|
|
78
|
+
// Both ages originate from the server clock. Advance those snapshots by
|
|
79
|
+
// only a local elapsed duration, which remains safe when client/server
|
|
80
|
+
// wall clocks differ. Wake at the earlier semantic boundary, then
|
|
81
|
+
// reschedule if the other boundary is still ahead.
|
|
82
|
+
const delayMs = Math.max(1, Math.min(...transitionDelaysMs));
|
|
83
|
+
snapshotTransitionTimer = setTimeout(() => {
|
|
84
|
+
snapshotTransitionTimer = null;
|
|
85
|
+
if (cancelled || version !== snapshotVersion)
|
|
86
|
+
return;
|
|
87
|
+
const elapsedSinceObservationMs = Math.max(0, Date.now() - observedAtMs);
|
|
88
|
+
const nextHeartbeatSinceMs = snapshot.heartbeatSinceMs == null
|
|
89
|
+
? null
|
|
90
|
+
: snapshot.heartbeatSinceMs + elapsedSinceObservationMs;
|
|
91
|
+
const nextStuckSinceMs = snapshot.stuckSinceMs == null
|
|
92
|
+
? null
|
|
93
|
+
: snapshot.stuckSinceMs + elapsedSinceObservationMs;
|
|
94
|
+
const nextEffectiveThresholdMs = effectiveThresholdFor(snapshot.dispatchMode, nextHeartbeatSinceMs);
|
|
95
|
+
const nextIsStuck = Boolean(nextStuckSinceMs != null &&
|
|
96
|
+
nextStuckSinceMs > nextEffectiveThresholdMs);
|
|
97
|
+
setState((current) => {
|
|
98
|
+
if (version !== snapshotVersion ||
|
|
99
|
+
current.runId !== snapshot.runId ||
|
|
100
|
+
current.lastProgressAt !== snapshot.lastProgressAt ||
|
|
101
|
+
current.heartbeatAt !== snapshot.heartbeatAt) {
|
|
102
|
+
return current;
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
...current,
|
|
106
|
+
isStuck: nextIsStuck,
|
|
107
|
+
stuckSinceMs: nextStuckSinceMs,
|
|
108
|
+
heartbeatSinceMs: nextHeartbeatSinceMs,
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
scheduleSnapshotTransition(snapshot, observedAtMs, version);
|
|
112
|
+
}, delayMs);
|
|
113
|
+
};
|
|
31
114
|
const poll = async () => {
|
|
32
115
|
if (cancelled)
|
|
33
116
|
return;
|
|
@@ -49,30 +132,29 @@ export function useRunStuckDetection({ threadId, enabled = true, stuckThresholdM
|
|
|
49
132
|
const heartbeatAt = data.heartbeatAt ?? null;
|
|
50
133
|
const heartbeatSinceMs = heartbeatAt != null ? nowMs - heartbeatAt : null;
|
|
51
134
|
const dispatchMode = typeof data.dispatchMode === "string" ? data.dispatchMode : null;
|
|
52
|
-
// Server-continued runs get the wider threshold: the server's own
|
|
53
|
-
// recovery (150s no-progress backstop + chained continuations) must
|
|
54
|
-
// get its chance before the user sees a "stuck" affordance.
|
|
55
|
-
const serverContinued = dispatchMode === "foreground-self-chain" ||
|
|
56
|
-
dispatchMode?.startsWith("background") === true;
|
|
57
135
|
// A claimed durable worker with a fresh heartbeat can legitimately
|
|
58
|
-
// be waiting on a bounded long-running tool/sub-agent call.
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
const
|
|
64
|
-
heartbeatSinceMs != null &&
|
|
65
|
-
heartbeatSinceMs >= 0 &&
|
|
66
|
-
heartbeatSinceMs < FRESH_BACKGROUND_HEARTBEAT_MS;
|
|
67
|
-
const effectiveThresholdMs = liveBackgroundWorker
|
|
68
|
-
? liveBackgroundStuckThresholdMs
|
|
69
|
-
: serverContinued
|
|
70
|
-
? backgroundStuckThresholdMs
|
|
71
|
-
: stuckThresholdMs;
|
|
136
|
+
// be waiting on a bounded long-running tool/sub-agent call. Still
|
|
137
|
+
// surface informational status at the normal background threshold;
|
|
138
|
+
// RunStuckBanner uses the fresh heartbeat to withhold Retry while
|
|
139
|
+
// keeping an explicit Cancel available. The legacy live-worker
|
|
140
|
+
// threshold may make that notice earlier, but never later.
|
|
141
|
+
const effectiveThresholdMs = effectiveThresholdFor(dispatchMode, heartbeatSinceMs);
|
|
72
142
|
const isStuck = Boolean(data.active &&
|
|
73
143
|
data.status === "running" &&
|
|
74
144
|
stuckSinceMs != null &&
|
|
75
145
|
stuckSinceMs > effectiveThresholdMs);
|
|
146
|
+
const observedAtMs = Date.now();
|
|
147
|
+
const version = ++snapshotVersion;
|
|
148
|
+
scheduleSnapshotTransition({
|
|
149
|
+
active: data.active,
|
|
150
|
+
runId: data.runId ?? null,
|
|
151
|
+
status: data.status ?? null,
|
|
152
|
+
lastProgressAt,
|
|
153
|
+
stuckSinceMs,
|
|
154
|
+
heartbeatAt,
|
|
155
|
+
heartbeatSinceMs,
|
|
156
|
+
dispatchMode,
|
|
157
|
+
}, observedAtMs, version);
|
|
76
158
|
setState({
|
|
77
159
|
isStuck,
|
|
78
160
|
runId: data.runId ?? null,
|
|
@@ -107,8 +189,11 @@ export function useRunStuckDetection({ threadId, enabled = true, stuckThresholdM
|
|
|
107
189
|
timer = setTimeout(poll, 2_000);
|
|
108
190
|
return () => {
|
|
109
191
|
cancelled = true;
|
|
192
|
+
snapshotVersion += 1;
|
|
110
193
|
if (timer)
|
|
111
194
|
clearTimeout(timer);
|
|
195
|
+
if (snapshotTransitionTimer)
|
|
196
|
+
clearTimeout(snapshotTransitionTimer);
|
|
112
197
|
};
|
|
113
198
|
}, [
|
|
114
199
|
threadId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-run-stuck-detection.js","sourceRoot":"","sources":["../../src/client/use-run-stuck-detection.ts"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useState, useCallback } from \"react\";\n\nimport { agentNativePath } from \"./api-path.js\";\n\n/**\n * Per-thread chat run health, derived from the durable `last_progress_at`\n * timestamp on the server. Drives the user-visible \"this chat looks stuck\"\n * affordance — distinct from the silent reconnect logic in\n * `agent-chat-adapter.ts`, which keeps trying in the background. When\n * automatic recovery isn't making progress (for whatever reason), this\n * hook surfaces a Retry / Cancel button to the user instead of leaving\n * them staring at a frozen spinner.\n */\nexport interface RunStuckState {\n /** True when an active run hasn't emitted an event for `stuckThresholdMs`. */\n isStuck: boolean;\n /** ID of the active run, or null when nothing is in flight. */\n runId: string | null;\n /** Server-side run status (\"running\" / \"completed\" / \"errored\" / etc.). */\n status: string | null;\n /** Server timestamp (ms) of the last emitted event, or null if none yet. */\n lastProgressAt: number | null;\n /** Milliseconds since `lastProgressAt`, or null. */\n stuckSinceMs: number | null;\n /** Server timestamp (ms) of the last process-alive heartbeat. */\n heartbeatAt: number | null;\n /** Milliseconds since `heartbeatAt`, computed against the server clock. */\n heartbeatSinceMs: number | null;\n /** How the run was dispatched/continued, e.g. foreground-self-chain or background-processing. */\n dispatchMode: string | null;\n /**\n * Server-authoritative: true when the run holds an open tool call or A2A\n * `agent_call` delegation (`in_flight_since` marker set). Preferred over the\n * client-side proxy for deciding whether Retry (which aborts the run) is\n * safe to offer. Null when the server bundle predates this field.\n */\n hasInFlightWork: boolean | null;\n}\n\nexport interface UseRunStuckDetectionOptions {\n /** The thread to monitor. Pass null/undefined to disable polling. */\n threadId: string | null | undefined;\n /**\n * Set false to skip scheduling the poll loop entirely — used to gate\n * polling to only the active chat tab when multiple tabs are mounted\n * (inactive tabs are kept alive via display:none, not unmounted).\n * Defaults to true.\n */\n enabled?: boolean;\n /**\n * Threshold above which an in-flight FOREGROUND run is considered stuck.\n * The default sits comfortably above the adapter's 75s no-progress\n * reconnect — by then automatic recovery has already had its chance.\n */\n stuckThresholdMs?: number;\n /**\n * Threshold for BACKGROUND-dispatched runs (dispatchMode starts with\n * \"background\"). The server owns recovery for these — its run-manager\n * no-progress backstop (150s) and unclaimed-run sweep act first — so the\n * user-facing \"stuck\" affordance is a late fallback, not a race against\n * them. Selected inside the hook because the dispatch mode is only known\n * from the same poll response that computes the elapsed time.\n */\n backgroundStuckThresholdMs?: number;\n /**\n * Threshold for a claimed durable background worker that is still sending\n * fresh process heartbeats. These workers can legitimately spend up to the\n * 12-minute tool/no-progress window on large Design, Plan, or Assets work.\n * Default 13 minutes, matching the durable chunk handoff boundary.\n */\n liveBackgroundStuckThresholdMs?: number;\n /** Poll interval. Default 5_000ms. */\n pollIntervalMs?: number;\n /** API base path. Default `/_agent-native/agent-chat`. */\n apiUrl?: string;\n}\n\nconst DEFAULT_STUCK_THRESHOLD_MS = 90_000;\nexport const DEFAULT_BACKGROUND_STUCK_THRESHOLD_MS = 180_000;\nexport const DEFAULT_LIVE_BACKGROUND_STUCK_THRESHOLD_MS = 13 * 60_000;\nconst DEFAULT_POLL_INTERVAL_MS = 5_000;\nconst IDLE_BACKOFF_INTERVAL_MS = 15_000;\nconst FRESH_BACKGROUND_HEARTBEAT_MS = 30_000;\n\ninterface ActiveRunResponse {\n active: boolean;\n runId?: string;\n status?: string;\n heartbeatAt: number | null;\n lastProgressAt?: number | null;\n dispatchMode?: string | null;\n /** Server clock at response time, used to compute elapsed server-relative. */\n serverNow?: number;\n /** True when the run holds an open tool/A2A call (in_flight_since marker). */\n hasInFlightWork?: boolean;\n}\n\nconst EMPTY_STATE: RunStuckState = {\n isStuck: false,\n runId: null,\n status: null,\n lastProgressAt: null,\n stuckSinceMs: null,\n heartbeatAt: null,\n heartbeatSinceMs: null,\n dispatchMode: null,\n hasInFlightWork: null,\n};\n\nexport function useRunStuckDetection({\n threadId,\n enabled = true,\n stuckThresholdMs = DEFAULT_STUCK_THRESHOLD_MS,\n backgroundStuckThresholdMs = DEFAULT_BACKGROUND_STUCK_THRESHOLD_MS,\n liveBackgroundStuckThresholdMs = DEFAULT_LIVE_BACKGROUND_STUCK_THRESHOLD_MS,\n pollIntervalMs = DEFAULT_POLL_INTERVAL_MS,\n apiUrl,\n}: UseRunStuckDetectionOptions): RunStuckState {\n const [state, setState] = useState<RunStuckState>(EMPTY_STATE);\n\n useEffect(() => {\n // Reset on every thread change so the previous thread's stuck banner\n // doesn't bleed onto the new one before the first poll completes.\n setState(EMPTY_STATE);\n if (!threadId || !enabled) return;\n\n const base = apiUrl ?? agentNativePath(\"/_agent-native/agent-chat\");\n let cancelled = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const poll = async () => {\n if (cancelled) return;\n let nextDelay = pollIntervalMs;\n try {\n const res = await fetch(\n `${base}/runs/active?threadId=${encodeURIComponent(threadId)}`,\n { credentials: \"same-origin\" },\n );\n if (cancelled) return;\n if (res.ok) {\n const data = (await res.json()) as ActiveRunResponse;\n const lastProgressAt = data.lastProgressAt ?? null;\n // Measure elapsed against the SERVER clock (serverNow) rather than the\n // client's Date.now(). lastProgressAt is a server timestamp, so client\n // clock skew of more than stuckThresholdMs would otherwise mark every\n // run stuck (clock ahead) or never stuck (clock behind). Fall back to\n // the client clock for older bundles that don't send serverNow.\n const nowMs = data.serverNow ?? Date.now();\n const stuckSinceMs =\n lastProgressAt != null ? nowMs - lastProgressAt : null;\n const heartbeatAt = data.heartbeatAt ?? null;\n const heartbeatSinceMs =\n heartbeatAt != null ? nowMs - heartbeatAt : null;\n const dispatchMode =\n typeof data.dispatchMode === \"string\" ? data.dispatchMode : null;\n // Server-continued runs get the wider threshold: the server's own\n // recovery (150s no-progress backstop + chained continuations) must\n // get its chance before the user sees a \"stuck\" affordance.\n const serverContinued =\n dispatchMode === \"foreground-self-chain\" ||\n dispatchMode?.startsWith(\"background\") === true;\n // A claimed durable worker with a fresh heartbeat can legitimately\n // be waiting on a bounded long-running tool/sub-agent call. Showing\n // Retry at the generic 3-minute continuation threshold aborts healthy\n // work and starts the same call again. Let the worker's own 12-minute\n // watchdog act first; a dead/stale heartbeat still gets the earlier\n // background fallback below.\n const liveBackgroundWorker =\n dispatchMode === \"background-processing\" &&\n heartbeatSinceMs != null &&\n heartbeatSinceMs >= 0 &&\n heartbeatSinceMs < FRESH_BACKGROUND_HEARTBEAT_MS;\n const effectiveThresholdMs = liveBackgroundWorker\n ? liveBackgroundStuckThresholdMs\n : serverContinued\n ? backgroundStuckThresholdMs\n : stuckThresholdMs;\n const isStuck = Boolean(\n data.active &&\n data.status === \"running\" &&\n stuckSinceMs != null &&\n stuckSinceMs > effectiveThresholdMs,\n );\n setState({\n isStuck,\n runId: data.runId ?? null,\n status: data.status ?? null,\n lastProgressAt,\n stuckSinceMs,\n heartbeatAt,\n heartbeatSinceMs,\n dispatchMode,\n hasInFlightWork:\n typeof data.hasInFlightWork === \"boolean\"\n ? data.hasInFlightWork\n : null,\n });\n // Back off polling when nothing is in flight — there's no point\n // hammering the endpoint while the chat is idle. We still poll\n // occasionally so a fresh run started in another tab is picked up.\n if (!data.active || data.status !== \"running\") {\n nextDelay = IDLE_BACKOFF_INTERVAL_MS;\n }\n }\n } catch {\n // Network blip — leave previous state. Next tick will retry.\n }\n if (!cancelled) {\n timer = setTimeout(poll, nextDelay);\n }\n };\n\n // Stagger the first poll so a freshly-started run isn't immediately\n // classified as stuck before the server has had a chance to record\n // any progress events.\n timer = setTimeout(poll, 2_000);\n\n return () => {\n cancelled = true;\n if (timer) clearTimeout(timer);\n };\n }, [\n threadId,\n enabled,\n stuckThresholdMs,\n backgroundStuckThresholdMs,\n liveBackgroundStuckThresholdMs,\n pollIntervalMs,\n apiUrl,\n ]);\n\n return state;\n}\n\n/**\n * POST `/runs/:id/abort` so the server flips the run to \"aborted\" and the\n * adapter's reconnect loop exits cleanly. Returns the run id that was\n * aborted (or null on failure) so callers can correlate observability\n * events. Best-effort — failures are swallowed, since the user's intent\n * is already captured locally.\n */\nexport function useAbortRun(apiUrl?: string) {\n return useCallback(\n async (runId: string, reason: string = \"user\"): Promise<string | null> => {\n const base = apiUrl ?? agentNativePath(\"/_agent-native/agent-chat\");\n try {\n const res = await fetch(\n `${base}/runs/${encodeURIComponent(runId)}/abort`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"same-origin\",\n body: JSON.stringify({ reason }),\n },\n );\n if (!res.ok) return null;\n return runId;\n } catch {\n return null;\n }\n },\n [apiUrl],\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"use-run-stuck-detection.js","sourceRoot":"","sources":["../../src/client/use-run-stuck-detection.ts"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useState, useCallback } from \"react\";\n\nimport { agentNativePath } from \"./api-path.js\";\n\n/**\n * Per-thread chat run health, derived from the durable `last_progress_at`\n * timestamp on the server. Drives the user-visible \"this chat looks stuck\"\n * affordance — distinct from the silent reconnect logic in\n * `agent-chat-adapter.ts`, which keeps trying in the background. When\n * automatic recovery isn't making progress (for whatever reason), this\n * hook surfaces a Retry / Cancel button to the user instead of leaving\n * them staring at a frozen spinner.\n */\nexport interface RunStuckState {\n /** True when an active run hasn't emitted an event for `stuckThresholdMs`. */\n isStuck: boolean;\n /** ID of the active run, or null when nothing is in flight. */\n runId: string | null;\n /** Server-side run status (\"running\" / \"completed\" / \"errored\" / etc.). */\n status: string | null;\n /** Server timestamp (ms) of the last emitted event, or null if none yet. */\n lastProgressAt: number | null;\n /** Milliseconds since `lastProgressAt`, or null. */\n stuckSinceMs: number | null;\n /** Server timestamp (ms) of the last process-alive heartbeat. */\n heartbeatAt: number | null;\n /** Milliseconds since `heartbeatAt`, computed against the server clock. */\n heartbeatSinceMs: number | null;\n /** How the run was dispatched/continued, e.g. foreground-self-chain or background-processing. */\n dispatchMode: string | null;\n /**\n * Server-authoritative: true when the run holds an open tool call or A2A\n * `agent_call` delegation (`in_flight_since` marker set). Preferred over the\n * client-side proxy for deciding whether Retry (which aborts the run) is\n * safe to offer. Null when the server bundle predates this field.\n */\n hasInFlightWork: boolean | null;\n}\n\nexport interface UseRunStuckDetectionOptions {\n /** The thread to monitor. Pass null/undefined to disable polling. */\n threadId: string | null | undefined;\n /**\n * Set false to skip scheduling the poll loop entirely — used to gate\n * polling to only the active chat tab when multiple tabs are mounted\n * (inactive tabs are kept alive via display:none, not unmounted).\n * Defaults to true.\n */\n enabled?: boolean;\n /**\n * Threshold above which an in-flight FOREGROUND run is considered stuck.\n * The default sits comfortably above the adapter's 75s no-progress\n * reconnect — by then automatic recovery has already had its chance.\n */\n stuckThresholdMs?: number;\n /**\n * Threshold for BACKGROUND-dispatched runs (dispatchMode starts with\n * \"background\"). The server owns recovery for these — its run-manager\n * no-progress backstop (150s) and unclaimed-run sweep act first — so the\n * user-facing \"stuck\" affordance is a late fallback, not a race against\n * them. Selected inside the hook because the dispatch mode is only known\n * from the same poll response that computes the elapsed time.\n */\n backgroundStuckThresholdMs?: number;\n /**\n * Legacy upper bound for a claimed durable background worker that is still\n * sending fresh process heartbeats. Informational quiet-run UI is never\n * delayed past `backgroundStuckThresholdMs`; this option can only request an\n * earlier notice for live workers. Default 13 minutes.\n */\n liveBackgroundStuckThresholdMs?: number;\n /** Poll interval. Default 5_000ms. */\n pollIntervalMs?: number;\n /** API base path. Default `/_agent-native/agent-chat`. */\n apiUrl?: string;\n}\n\nconst DEFAULT_STUCK_THRESHOLD_MS = 90_000;\nexport const DEFAULT_BACKGROUND_STUCK_THRESHOLD_MS = 180_000;\nexport const DEFAULT_LIVE_BACKGROUND_STUCK_THRESHOLD_MS = 13 * 60_000;\nconst DEFAULT_POLL_INTERVAL_MS = 5_000;\nconst IDLE_BACKOFF_INTERVAL_MS = 15_000;\nconst FRESH_BACKGROUND_HEARTBEAT_MS = 30_000;\n\ninterface ActiveRunResponse {\n active: boolean;\n runId?: string;\n status?: string;\n heartbeatAt: number | null;\n lastProgressAt?: number | null;\n dispatchMode?: string | null;\n /** Server clock at response time, used to compute elapsed server-relative. */\n serverNow?: number;\n /** True when the run holds an open tool/A2A call (in_flight_since marker). */\n hasInFlightWork?: boolean;\n}\n\nconst EMPTY_STATE: RunStuckState = {\n isStuck: false,\n runId: null,\n status: null,\n lastProgressAt: null,\n stuckSinceMs: null,\n heartbeatAt: null,\n heartbeatSinceMs: null,\n dispatchMode: null,\n hasInFlightWork: null,\n};\n\nexport function useRunStuckDetection({\n threadId,\n enabled = true,\n stuckThresholdMs = DEFAULT_STUCK_THRESHOLD_MS,\n backgroundStuckThresholdMs = DEFAULT_BACKGROUND_STUCK_THRESHOLD_MS,\n liveBackgroundStuckThresholdMs = DEFAULT_LIVE_BACKGROUND_STUCK_THRESHOLD_MS,\n pollIntervalMs = DEFAULT_POLL_INTERVAL_MS,\n apiUrl,\n}: UseRunStuckDetectionOptions): RunStuckState {\n const [state, setState] = useState<RunStuckState>(EMPTY_STATE);\n\n useEffect(() => {\n // Reset on every thread change so the previous thread's stuck banner\n // doesn't bleed onto the new one before the first poll completes.\n setState(EMPTY_STATE);\n if (!threadId || !enabled) return;\n\n const base = apiUrl ?? agentNativePath(\"/_agent-native/agent-chat\");\n let cancelled = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n let snapshotTransitionTimer: ReturnType<typeof setTimeout> | null = null;\n let snapshotVersion = 0;\n\n type RunHealthSnapshot = {\n active: boolean;\n runId: string | null;\n status: string | null;\n lastProgressAt: number | null;\n stuckSinceMs: number | null;\n heartbeatAt: number | null;\n heartbeatSinceMs: number | null;\n dispatchMode: string | null;\n };\n\n const effectiveThresholdFor = (\n dispatchMode: string | null,\n heartbeatSinceMs: number | null,\n ) => {\n const liveBackgroundWorker =\n dispatchMode === \"background-processing\" &&\n heartbeatSinceMs != null &&\n heartbeatSinceMs >= 0 &&\n heartbeatSinceMs < FRESH_BACKGROUND_HEARTBEAT_MS;\n const serverContinued =\n dispatchMode === \"foreground-self-chain\" ||\n dispatchMode?.startsWith(\"background\") === true;\n return liveBackgroundWorker\n ? Math.min(backgroundStuckThresholdMs, liveBackgroundStuckThresholdMs)\n : serverContinued\n ? backgroundStuckThresholdMs\n : stuckThresholdMs;\n };\n\n const scheduleSnapshotTransition = (\n snapshot: RunHealthSnapshot,\n observedAtMs: number,\n version: number,\n ) => {\n if (snapshotTransitionTimer) clearTimeout(snapshotTransitionTimer);\n snapshotTransitionTimer = null;\n if (\n cancelled ||\n version !== snapshotVersion ||\n !snapshot.active ||\n snapshot.status !== \"running\" ||\n snapshot.runId == null\n ) {\n return;\n }\n\n const currentElapsedMs = Math.max(0, Date.now() - observedAtMs);\n const currentHeartbeatSinceMs =\n snapshot.heartbeatSinceMs == null\n ? null\n : snapshot.heartbeatSinceMs + currentElapsedMs;\n const currentStuckSinceMs =\n snapshot.stuckSinceMs == null\n ? null\n : snapshot.stuckSinceMs + currentElapsedMs;\n const effectiveThresholdMs = effectiveThresholdFor(\n snapshot.dispatchMode,\n currentHeartbeatSinceMs,\n );\n const currentlyStuck = Boolean(\n currentStuckSinceMs != null &&\n currentStuckSinceMs > effectiveThresholdMs,\n );\n const transitionDelaysMs: number[] = [];\n if (\n currentHeartbeatSinceMs != null &&\n currentHeartbeatSinceMs >= 0 &&\n currentHeartbeatSinceMs < FRESH_BACKGROUND_HEARTBEAT_MS\n ) {\n transitionDelaysMs.push(\n FRESH_BACKGROUND_HEARTBEAT_MS - currentHeartbeatSinceMs + 1,\n );\n }\n if (currentStuckSinceMs != null && !currentlyStuck) {\n transitionDelaysMs.push(\n Math.max(1, effectiveThresholdMs - currentStuckSinceMs + 1),\n );\n }\n if (transitionDelaysMs.length === 0) return;\n\n // Both ages originate from the server clock. Advance those snapshots by\n // only a local elapsed duration, which remains safe when client/server\n // wall clocks differ. Wake at the earlier semantic boundary, then\n // reschedule if the other boundary is still ahead.\n const delayMs = Math.max(1, Math.min(...transitionDelaysMs));\n snapshotTransitionTimer = setTimeout(() => {\n snapshotTransitionTimer = null;\n if (cancelled || version !== snapshotVersion) return;\n const elapsedSinceObservationMs = Math.max(\n 0,\n Date.now() - observedAtMs,\n );\n const nextHeartbeatSinceMs =\n snapshot.heartbeatSinceMs == null\n ? null\n : snapshot.heartbeatSinceMs + elapsedSinceObservationMs;\n const nextStuckSinceMs =\n snapshot.stuckSinceMs == null\n ? null\n : snapshot.stuckSinceMs + elapsedSinceObservationMs;\n const nextEffectiveThresholdMs = effectiveThresholdFor(\n snapshot.dispatchMode,\n nextHeartbeatSinceMs,\n );\n const nextIsStuck = Boolean(\n nextStuckSinceMs != null &&\n nextStuckSinceMs > nextEffectiveThresholdMs,\n );\n setState((current) => {\n if (\n version !== snapshotVersion ||\n current.runId !== snapshot.runId ||\n current.lastProgressAt !== snapshot.lastProgressAt ||\n current.heartbeatAt !== snapshot.heartbeatAt\n ) {\n return current;\n }\n return {\n ...current,\n isStuck: nextIsStuck,\n stuckSinceMs: nextStuckSinceMs,\n heartbeatSinceMs: nextHeartbeatSinceMs,\n };\n });\n scheduleSnapshotTransition(snapshot, observedAtMs, version);\n }, delayMs);\n };\n\n const poll = async () => {\n if (cancelled) return;\n let nextDelay = pollIntervalMs;\n try {\n const res = await fetch(\n `${base}/runs/active?threadId=${encodeURIComponent(threadId)}`,\n { credentials: \"same-origin\" },\n );\n if (cancelled) return;\n if (res.ok) {\n const data = (await res.json()) as ActiveRunResponse;\n const lastProgressAt = data.lastProgressAt ?? null;\n // Measure elapsed against the SERVER clock (serverNow) rather than the\n // client's Date.now(). lastProgressAt is a server timestamp, so client\n // clock skew of more than stuckThresholdMs would otherwise mark every\n // run stuck (clock ahead) or never stuck (clock behind). Fall back to\n // the client clock for older bundles that don't send serverNow.\n const nowMs = data.serverNow ?? Date.now();\n const stuckSinceMs =\n lastProgressAt != null ? nowMs - lastProgressAt : null;\n const heartbeatAt = data.heartbeatAt ?? null;\n const heartbeatSinceMs =\n heartbeatAt != null ? nowMs - heartbeatAt : null;\n const dispatchMode =\n typeof data.dispatchMode === \"string\" ? data.dispatchMode : null;\n // A claimed durable worker with a fresh heartbeat can legitimately\n // be waiting on a bounded long-running tool/sub-agent call. Still\n // surface informational status at the normal background threshold;\n // RunStuckBanner uses the fresh heartbeat to withhold Retry while\n // keeping an explicit Cancel available. The legacy live-worker\n // threshold may make that notice earlier, but never later.\n const effectiveThresholdMs = effectiveThresholdFor(\n dispatchMode,\n heartbeatSinceMs,\n );\n const isStuck = Boolean(\n data.active &&\n data.status === \"running\" &&\n stuckSinceMs != null &&\n stuckSinceMs > effectiveThresholdMs,\n );\n const observedAtMs = Date.now();\n const version = ++snapshotVersion;\n scheduleSnapshotTransition(\n {\n active: data.active,\n runId: data.runId ?? null,\n status: data.status ?? null,\n lastProgressAt,\n stuckSinceMs,\n heartbeatAt,\n heartbeatSinceMs,\n dispatchMode,\n },\n observedAtMs,\n version,\n );\n setState({\n isStuck,\n runId: data.runId ?? null,\n status: data.status ?? null,\n lastProgressAt,\n stuckSinceMs,\n heartbeatAt,\n heartbeatSinceMs,\n dispatchMode,\n hasInFlightWork:\n typeof data.hasInFlightWork === \"boolean\"\n ? data.hasInFlightWork\n : null,\n });\n // Back off polling when nothing is in flight — there's no point\n // hammering the endpoint while the chat is idle. We still poll\n // occasionally so a fresh run started in another tab is picked up.\n if (!data.active || data.status !== \"running\") {\n nextDelay = IDLE_BACKOFF_INTERVAL_MS;\n }\n }\n } catch {\n // Network blip — leave previous state. Next tick will retry.\n }\n if (!cancelled) {\n timer = setTimeout(poll, nextDelay);\n }\n };\n\n // Stagger the first poll so a freshly-started run isn't immediately\n // classified as stuck before the server has had a chance to record\n // any progress events.\n timer = setTimeout(poll, 2_000);\n\n return () => {\n cancelled = true;\n snapshotVersion += 1;\n if (timer) clearTimeout(timer);\n if (snapshotTransitionTimer) clearTimeout(snapshotTransitionTimer);\n };\n }, [\n threadId,\n enabled,\n stuckThresholdMs,\n backgroundStuckThresholdMs,\n liveBackgroundStuckThresholdMs,\n pollIntervalMs,\n apiUrl,\n ]);\n\n return state;\n}\n\n/**\n * POST `/runs/:id/abort` so the server flips the run to \"aborted\" and the\n * adapter's reconnect loop exits cleanly. Returns the run id that was\n * aborted (or null on failure) so callers can correlate observability\n * events. Best-effort — failures are swallowed, since the user's intent\n * is already captured locally.\n */\nexport function useAbortRun(apiUrl?: string) {\n return useCallback(\n async (runId: string, reason: string = \"user\"): Promise<string | null> => {\n const base = apiUrl ?? agentNativePath(\"/_agent-native/agent-chat\");\n try {\n const res = await fetch(\n `${base}/runs/${encodeURIComponent(runId)}/abort`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"same-origin\",\n body: JSON.stringify({ reason }),\n },\n );\n if (!res.ok) return null;\n return runId;\n } catch {\n return null;\n }\n },\n [apiUrl],\n );\n}\n"]}
|
|
@@ -76,8 +76,11 @@ export declare function executeSandboxCode(options: ExecuteSandboxCodeOptions):
|
|
|
76
76
|
/**
|
|
77
77
|
* Standalone, access-scoped poll tool for background executions. Behaviorally
|
|
78
78
|
* identical to calling `run-code` with only `executionId`; hosts that register
|
|
79
|
-
* it as `get-code-execution` give the model a dedicated read tool (and
|
|
80
|
-
* enqueue guidance automatically points at it when present in the
|
|
79
|
+
* it as `get-code-execution` give the model a dedicated volatile read tool (and
|
|
80
|
+
* the enqueue guidance automatically points at it when present in the
|
|
81
|
+
* registry). Keep the opt-out here rather than on `run-code`: repeated normal
|
|
82
|
+
* run-code calls may execute writes or outbound requests and must retain the
|
|
83
|
+
* agent loop's default duplicate-call protection.
|
|
81
84
|
*/
|
|
82
85
|
export declare function createGetCodeExecutionEntry(): ActionEntry;
|
|
83
86
|
//# sourceMappingURL=run-code.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-code.d.ts","sourceRoot":"","sources":["../../src/coding-tools/run-code.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAKH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAmChE,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EAC7C,IAAI,GAAE,cAAmB,GACxB,WAAW,CA2Jb;AAMD,MAAM,WAAW,yBAAyB;IACxC,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,SAAS,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,UAAU,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC9C,mDAAmD;IACnD,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC/B,iEAAiE;IACjE,OAAO,CAAC,EAAE,gBAAgB,CAAC;CAC5B;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,wBAAwB,CAAC,CA2DnC;AAkPD
|
|
1
|
+
{"version":3,"file":"run-code.d.ts","sourceRoot":"","sources":["../../src/coding-tools/run-code.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAKH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAmChE,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EAC7C,IAAI,GAAE,cAAmB,GACxB,WAAW,CA2Jb;AAMD,MAAM,WAAW,yBAAyB;IACxC,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,SAAS,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,UAAU,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC9C,mDAAmD;IACnD,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC/B,iEAAiE;IACjE,OAAO,CAAC,EAAE,gBAAgB,CAAC;CAC5B;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,wBAAwB,CAAC,CA2DnC;AAkPD;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,IAAI,WAAW,CAmCzD"}
|
|
@@ -104,7 +104,7 @@ export function createRunCodeEntry(getActions, opts = {}) {
|
|
|
104
104
|
" - `workspaceList(prefix?)` — list workspace files, returns [{ path, sizeBytes, contentType, updatedAt }].",
|
|
105
105
|
"Print results with `console.log()`; only stdout+stderr are returned.",
|
|
106
106
|
"Timeout defaults to 120 s (max 600 s). Output is truncated to 50 000 chars by default (max 200 000).",
|
|
107
|
-
'For LONG compute — big cross-source joins, corpus-wide sweeps, scripts that could exceed ~30 s, or anything at risk of dying with the current chat run — pass `background: true`. That enqueues a durable execution and returns `{ executionId, status: "queued" }` immediately; the code runs out-of-band with a generous budget (default 10 min) and its result survives run timeouts. Continue other work, then poll
|
|
107
|
+
'For LONG compute — big cross-source joins, corpus-wide sweeps, scripts that could exceed ~30 s, or anything at risk of dying with the current chat run — pass `background: true`. That enqueues a durable execution and returns `{ executionId, status: "queued" }` immediately; the code runs out-of-band with a generous budget (default 10 min) and its result survives run timeouts. Continue other work, then poll with `get-code-execution` when that dedicated tool is available; legacy hosts can call run-code again with just `{ executionId }`. Keep quick scripts in the default foreground mode.',
|
|
108
108
|
].join(" "),
|
|
109
109
|
parameters: {
|
|
110
110
|
type: "object",
|
|
@@ -123,11 +123,11 @@ export function createRunCodeEntry(getActions, opts = {}) {
|
|
|
123
123
|
},
|
|
124
124
|
background: {
|
|
125
125
|
type: "boolean",
|
|
126
|
-
description: 'Run as a durable background execution: returns { executionId, status: "queued" } immediately and the code executes out-of-band, surviving chat-run timeouts. Use for long compute (large joins, multi-page provider sweeps, heavy analysis). Poll with
|
|
126
|
+
description: 'Run as a durable background execution: returns { executionId, status: "queued" } immediately and the code executes out-of-band, surviving chat-run timeouts. Use for long compute (large joins, multi-page provider sweeps, heavy analysis). Poll with get-code-execution when available.',
|
|
127
127
|
},
|
|
128
128
|
executionId: {
|
|
129
129
|
type: "string",
|
|
130
|
-
description: "
|
|
130
|
+
description: "Legacy polling fallback for hosts without get-code-execution: pass the executionId alone (no code) to get status and output.",
|
|
131
131
|
},
|
|
132
132
|
},
|
|
133
133
|
required: [],
|
|
@@ -419,12 +419,18 @@ function formatTerminalSandboxExecution(row) {
|
|
|
419
419
|
/**
|
|
420
420
|
* Standalone, access-scoped poll tool for background executions. Behaviorally
|
|
421
421
|
* identical to calling `run-code` with only `executionId`; hosts that register
|
|
422
|
-
* it as `get-code-execution` give the model a dedicated read tool (and
|
|
423
|
-
* enqueue guidance automatically points at it when present in the
|
|
422
|
+
* it as `get-code-execution` give the model a dedicated volatile read tool (and
|
|
423
|
+
* the enqueue guidance automatically points at it when present in the
|
|
424
|
+
* registry). Keep the opt-out here rather than on `run-code`: repeated normal
|
|
425
|
+
* run-code calls may execute writes or outbound requests and must retain the
|
|
426
|
+
* agent loop's default duplicate-call protection.
|
|
424
427
|
*/
|
|
425
428
|
export function createGetCodeExecutionEntry() {
|
|
426
429
|
return {
|
|
427
430
|
readOnly: true,
|
|
431
|
+
// Polling with an identical executionId is the intended usage — the
|
|
432
|
+
// status changes over time, so this must not be deduped.
|
|
433
|
+
dedupe: false,
|
|
428
434
|
tool: {
|
|
429
435
|
description: "Check a background run-code execution: returns its status (queued | running | succeeded | failed | timed_out) and, once finished, its stdout/stderr output. Executions are scoped to the user who started them. While one is queued or running, continue other useful work and poll every ~15-30 seconds instead of busy-waiting.",
|
|
430
436
|
parameters: {
|