@standardagents/builder 0.25.4 → 0.25.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.
@@ -257,6 +257,7 @@ interface ThreadInstance {
257
257
  error?: string;
258
258
  }>;
259
259
  shouldStop(): Promise<boolean>;
260
+ noteExecutionProgress?(): void;
260
261
  tools(): Record<string, () => Promise<NativeToolModule>>;
261
262
  hooks(): HookRegistry;
262
263
  getValue<T = unknown>(key: string): Promise<T | null>;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ModelDefinition, AgentDefinition } from './runtime.js';
2
2
  export { AgentBuilderEnv, AuthContext, AuthUser, BroadcastOptions, CodeExecutionBridge, DurableAgentBuilder, DurableThread, FlowStateSdk, FlowStateWithSdk, ImageContextConfig, InjectMessageOptions, LLMProviderInterface, NamespaceResolutionError, PromptContent, PromptDefinition, PromptIncludePart, PromptPart, PromptToolConfig, Provider, ProviderContentPart, ProviderFilePart, ProviderImagePart, ProviderRegistry, ProviderTextPart, SessionToolBinding, SessionToolConfig, SideConfig, SubagentToolConfig, SubpromptConfig, ThreadRegistryEntry, ToolConfig, UpdateThreadParams, User, authenticate, buildImageDescription, cat, createNamespaceContext, emitThreadEvent, enhanceFlowState, exists, find, forceTurn, generateImageDescription, getFileStats, getMessages, getMessagesToSummarize, getShortName, getThumbnail, getUnsummarizedImageAttachments, getVisibleAgentNames, getVisibleModelNames, getVisiblePromptNames, getVisibleToolNames, grep, hasImageAttachments, head, injectMessage, isQualifiedName, linkFile, mkdir, move, optimizeImageContext, parseQualifiedName, qualifyName, queueTool, readFile, readdir, reloadHistory, rename, replaceImagesWithDescriptions, requireAdmin, requireAuth, resolveAgent, resolveHook, resolveModel, resolvePrompt, resolveTool, rmdir, stat, tail, unlink, updateThread, writeFile, writeImage } from './runtime.js';
3
3
  export { AgentPluginOptions, agentbuilder, builder } from './plugin.js';
4
- export { A as Agent, m as AttachmentRef, f as BuilderThreadEndpointHandler, B as Controller, a as ControllerContext, E as Env, l as FileRecord, n as FileStats, j as FlowResult, F as FlowState, G as GrepResult, p as ImageContentPart, I as ImageMetadata, L as LLMResponse, M as Message, r as MessageContent, q as MultimodalContent, R as RequestContext, S as StorageBackend, k as TelemetryEvent, o as TextContentPart, b as ThreadEndpointContext, e as ThreadEnv, g as ThreadInstance, h as ThreadMetadata, T as ToolCall, i as ToolResult, c as createThreadEndpointHandler, d as defineController } from './index-BsS0cHzL.js';
4
+ export { A as Agent, m as AttachmentRef, f as BuilderThreadEndpointHandler, B as Controller, a as ControllerContext, E as Env, l as FileRecord, n as FileStats, j as FlowResult, F as FlowState, G as GrepResult, p as ImageContentPart, I as ImageMetadata, L as LLMResponse, M as Message, r as MessageContent, q as MultimodalContent, R as RequestContext, S as StorageBackend, k as TelemetryEvent, o as TextContentPart, b as ThreadEndpointContext, e as ThreadEnv, g as ThreadInstance, h as ThreadMetadata, T as ToolCall, i as ToolResult, c as createThreadEndpointHandler, d as defineController } from './index-CX9Ti98s.js';
5
5
  export { AgentType, DefinitionLoader, GlobalNamespaceContext, HookSignatures, ImageContent, ModelCapabilities, ModelProvider, NamespaceContext, PackageSignature, PackedExports, PackedMeta, PackedMetadata, PackedNamespaceContext, PromptInput, PromptTextPart, ProviderAssistantMessage, ProviderConfigSlot, ProviderConfigSlots, ProviderConfigValueSource, ProviderDefinition, ProviderError, ProviderErrorCode, ProviderFactory, ProviderFactoryConfig, ProviderFinishReason, ProviderGeneratedImage, ProviderMessage, ProviderMessageContent, ProviderMethodOverrides, ModelCapabilities as ProviderModelCapabilities, ProviderOverride, ProviderOverrideContext, ProviderReasoningDetail, ProviderRequest, ProviderResponse, ProviderStreamChunk, ProviderSyncOverride, ProviderSystemMessage, ProviderTool, ProviderToolCallPart, ProviderToolMessage, ProviderToolResultContent, ProviderUsage, ProviderUserMessage, ReasoningConfig, StructuredPrompt, TextContent, Tool, ToolArgs, ToolArgsNode, ToolArgsRawShape, ToolContent, belongsToPackage, defineAgent, defineHook, defineModel, definePrompt, defineProvider, defineTool, isPacked, isVisibleInNamespace, mapReasoningLevel, providerConst, providerEnv, providerValue } from '@standardagents/spec';
6
6
  export { C as ConstituentItem, D as DiscoveredPackage, N as NamespacedRegistry, P as PackingAnalysis, a as PackingOptions, b as PackingResult, c as UnpackOptions, d as UnpackResult } from './types-Bpe7IANZ.js';
7
7
  export { D as DiscoveryConfig, P as PackageDiscoveryService, d as discoverPackages } from './discovery-DiMJWisl.js';
package/dist/index.js CHANGED
@@ -1066,6 +1066,19 @@ function resolveStreamTimeoutMs(env, key, fallback) {
1066
1066
  const parsed = typeof raw === "number" ? raw : typeof raw === "string" ? Number.parseInt(raw, 10) : NaN;
1067
1067
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
1068
1068
  }
1069
+ function resolveWatchdogStaleCeilingMs(env) {
1070
+ const firstChunk = resolveStreamTimeoutMs(
1071
+ env,
1072
+ "STANDARD_AGENTS_STREAM_FIRST_CHUNK_TIMEOUT_MS",
1073
+ STREAM_FIRST_CHUNK_TIMEOUT_MS
1074
+ );
1075
+ const stall = resolveStreamTimeoutMs(
1076
+ env,
1077
+ "STANDARD_AGENTS_STREAM_STALL_TIMEOUT_MS",
1078
+ STREAM_STALL_TIMEOUT_MS
1079
+ );
1080
+ return Math.max(firstChunk, stall) + 6e4;
1081
+ }
1069
1082
  function modelSupportsImageInput(modelDef) {
1070
1083
  const supportsImages = modelDef.capabilities?.supportsImages;
1071
1084
  if (typeof supportsImages === "boolean") {
@@ -1578,6 +1591,7 @@ var init_LLMRequest = __esm({
1578
1591
  attempts++;
1579
1592
  try {
1580
1593
  logId = await this.logRequest(state, actualModelId, context, modelDef, previousLogId);
1594
+ state.thread.instance.noteExecutionProgress?.();
1581
1595
  state.emitTelemetry?.({
1582
1596
  type: "llm_request",
1583
1597
  model: actualModelId,
@@ -1844,6 +1858,7 @@ var init_LLMRequest = __esm({
1844
1858
  }
1845
1859
  if (next.done) break;
1846
1860
  const chunk = next.value;
1861
+ state.thread.instance.noteExecutionProgress?.();
1847
1862
  if (firstChunkAt === 0) {
1848
1863
  firstChunkAt = Date.now();
1849
1864
  emitLogPatch({ time_to_first_token_ms: firstChunkAt - requestSentAt });
@@ -23578,6 +23593,7 @@ var AlarmQueue = class {
23578
23593
 
23579
23594
  // src/durable-objects/DurableThread.ts
23580
23595
  init_StreamManager();
23596
+ init_LLMRequest();
23581
23597
 
23582
23598
  // src/packing/resolver.ts
23583
23599
  var NamespaceResolutionError = class extends Error {
@@ -24065,6 +24081,18 @@ var DurableThread = class _DurableThread extends DurableObject {
24065
24081
  alarmQueueRearmed = false;
24066
24082
  currentAbortController = null;
24067
24083
  isExecuting = false;
24084
+ // ── Execution watchdog state (see watchdogCheck) ─────────────────────────
24085
+ // In-memory heartbeat: bumped by noteExecutionProgress() on every streamed
24086
+ // chunk and request attempt. Deliberately NOT persisted — after a DO restart
24087
+ // the isExecuting flag alone proves the executor is gone, and while the DO
24088
+ // is alive the in-memory value is authoritative. Zero hot-path storage cost.
24089
+ lastProgressAt = 0;
24090
+ // Consecutive watchdog firings that found a live-but-silent executor. Strike
24091
+ // one aborts the in-flight request; strike two forces takeover.
24092
+ watchdogWedgeStrikes = 0;
24093
+ watchdogIntervalMs = 3e4;
24094
+ watchdogResumeWindowMs = 15 * 60 * 1e3;
24095
+ watchdogMaxResumesPerWindow = 2;
24068
24096
  codeExecutionBridges = /* @__PURE__ */ new Map();
24069
24097
  constructor(ctx, env) {
24070
24098
  super(ctx, env);
@@ -24100,6 +24128,9 @@ var DurableThread = class _DurableThread extends DurableObject {
24100
24128
  case "continueExecution":
24101
24129
  await this.doContinueExecution(args.threadId, args.side);
24102
24130
  break;
24131
+ case "watchdogCheck":
24132
+ await this.watchdogCheck(args.threadId);
24133
+ break;
24103
24134
  default:
24104
24135
  throw new Error(`Unknown method: ${method}`);
24105
24136
  }
@@ -27852,6 +27883,190 @@ ${resultContent}${attachmentSummary}`;
27852
27883
  console.error("[DurableThread] Alarm handler failed:", error);
27853
27884
  }
27854
27885
  }
27886
+ // ══════════════════════════════════════════════════════════════════════════
27887
+ // Execution watchdog
27888
+ //
27889
+ // The in-flight stream guards (LLMRequest's first-chunk/stall timeouts) live
27890
+ // inside the execution's promise chain — they die with the DO. A deploy or
27891
+ // eviction mid-request therefore used to leave a durable log row stuck at
27892
+ // is_complete = 0 forever: the thread looked "working", queued messages
27893
+ // never played, and nothing woke it (finalizeOrphanedLogs only runs at the
27894
+ // START of the next execution). Persisted DO alarms are the one timer that
27895
+ // survives restarts and fires without inbound traffic, so a standing
27896
+ // per-thread watchdog alarm re-checks the thread's CURRENT state every 30s
27897
+ // while work is active.
27898
+ //
27899
+ // Design invariants (do not weaken):
27900
+ // - Progress, never duration: only silence ages. A response can stream for
27901
+ // ten minutes; every chunk bumps the in-memory heartbeat.
27902
+ // - Per-thread, not per-request: the check carries no request identity. By
27903
+ // the time it fires the thread may be several requests further along —
27904
+ // it always evaluates fresh SQL + live flags, never a stale snapshot.
27905
+ // - Strictly second line: the staleness ceiling is DERIVED from the stream
27906
+ // timeouts (resolveWatchdogStaleCeilingMs = max(first-chunk, stall) + 60s)
27907
+ // so the in-flight guards always get to act first. The watchdog only
27908
+ // moves when the guard that should have fired didn't.
27909
+ // - Restart proof: isExecuting is volatile; an incomplete log with
27910
+ // isExecuting === false is PROOF the executor died (executions cannot
27911
+ // survive a restart). No heartbeat needed for that branch.
27912
+ // - Nonce re-read: every await inside the check is a yield point; state is
27913
+ // re-read immediately before acting so a healthy thread racing the
27914
+ // watchdog always wins.
27915
+ // ══════════════════════════════════════════════════════════════════════════
27916
+ /** Heartbeat: called by LLMRequest on each attempt and streamed chunk. */
27917
+ noteExecutionProgress() {
27918
+ this.lastProgressAt = Date.now();
27919
+ }
27920
+ /** Arm the standing watchdog for an execution start. One pending row max. */
27921
+ async armExecutionWatchdog(threadId) {
27922
+ try {
27923
+ this.lastProgressAt = Date.now();
27924
+ this.watchdogWedgeStrikes = 0;
27925
+ const pending = (await this.ctx.storage.sql.exec(
27926
+ `SELECT id FROM alarm_queue WHERE method = 'watchdogCheck' AND status = 'pending' LIMIT 1`
27927
+ )).toArray();
27928
+ if (pending.length > 0) return;
27929
+ await this.alarmQueue.enqueue("watchdogCheck", { threadId }, this.watchdogIntervalMs);
27930
+ } catch (error) {
27931
+ console.error("[DurableThread] Failed to arm execution watchdog (non-fatal):", error);
27932
+ }
27933
+ }
27934
+ /** Disarm on clean execution end. Completed check rows are noise, not history. */
27935
+ async disarmExecutionWatchdog() {
27936
+ try {
27937
+ await this.ctx.storage.sql.exec(
27938
+ `DELETE FROM alarm_queue WHERE method = 'watchdogCheck' AND status IN ('pending', 'completed')`
27939
+ );
27940
+ await this.alarmQueue.ensureAlarmScheduled();
27941
+ } catch (error) {
27942
+ console.error("[DurableThread] Failed to disarm execution watchdog (non-fatal):", error);
27943
+ }
27944
+ }
27945
+ /**
27946
+ * The standing check. Evaluates the thread's state AT FIRE TIME — it knows
27947
+ * nothing about whichever request was in flight when it was armed.
27948
+ */
27949
+ async watchdogCheck(threadId) {
27950
+ const rearm = () => this.alarmQueue.enqueue("watchdogCheck", { threadId }, this.watchdogIntervalMs);
27951
+ try {
27952
+ await this.ctx.storage.sql.exec(
27953
+ `DELETE FROM alarm_queue WHERE method = 'watchdogCheck' AND status = 'completed'`
27954
+ );
27955
+ const readIncomplete = async () => (await this.ctx.storage.sql.exec(
27956
+ `SELECT id FROM logs WHERE is_complete = 0`
27957
+ )).toArray();
27958
+ let incomplete = await readIncomplete();
27959
+ if (this.isExecuting) {
27960
+ if (incomplete.length === 0) {
27961
+ this.watchdogWedgeStrikes = 0;
27962
+ await rearm();
27963
+ return;
27964
+ }
27965
+ const ceiling = resolveWatchdogStaleCeilingMs(this.env);
27966
+ const silenceMs = Date.now() - this.lastProgressAt;
27967
+ if (silenceMs <= ceiling) {
27968
+ this.watchdogWedgeStrikes = 0;
27969
+ await rearm();
27970
+ return;
27971
+ }
27972
+ this.watchdogWedgeStrikes++;
27973
+ if (this.watchdogWedgeStrikes < 2) {
27974
+ console.warn(
27975
+ `[DurableThread] Watchdog: execution silent for ${Math.round(silenceMs / 1e3)}s (ceiling ${Math.round(ceiling / 1e3)}s) \u2014 aborting in-flight request`
27976
+ );
27977
+ try {
27978
+ this.currentAbortController?.abort();
27979
+ } catch {
27980
+ }
27981
+ await rearm();
27982
+ return;
27983
+ }
27984
+ console.error(
27985
+ "[DurableThread] Watchdog: wedged execution did not unwind after abort \u2014 forcing takeover"
27986
+ );
27987
+ this.isExecuting = false;
27988
+ this.currentAbortController = null;
27989
+ }
27990
+ incomplete = await readIncomplete();
27991
+ if (this.isExecuting) {
27992
+ await rearm();
27993
+ return;
27994
+ }
27995
+ if (incomplete.length > 0) {
27996
+ await this.finalizeLostExecutionLogs(incomplete.map((row) => row.id));
27997
+ }
27998
+ const stopped = await this.shouldStop();
27999
+ const queued = (await this.ctx.storage.sql.exec(
28000
+ `SELECT COUNT(*) AS c FROM message_queue`
28001
+ )).toArray()[0]?.c ?? 0;
28002
+ if (stopped || incomplete.length === 0 && queued === 0) {
28003
+ await this.disarmExecutionWatchdog();
28004
+ return;
28005
+ }
28006
+ if (await this.consumeWatchdogResumeBudget()) {
28007
+ const side = await this.getLatestQueuedMessageSide() ?? "a";
28008
+ await this.alarmQueue.enqueue("continueExecution", { threadId, side }, 0);
28009
+ console.warn(
28010
+ `[DurableThread] Watchdog: lost execution recovered \u2014 resuming thread ${threadId}`
28011
+ );
28012
+ } else {
28013
+ console.error(
28014
+ `[DurableThread] Watchdog: resume budget exhausted for thread ${threadId} \u2014 leaving thread stopped`
28015
+ );
28016
+ }
28017
+ await this.disarmExecutionWatchdog();
28018
+ } catch (error) {
28019
+ console.error("[DurableThread] Watchdog check failed (non-fatal):", error);
28020
+ try {
28021
+ await rearm();
28022
+ } catch {
28023
+ }
28024
+ }
28025
+ }
28026
+ /** Mark logs the dead executor abandoned, and tell connected clients. */
28027
+ async finalizeLostExecutionLogs(logIds) {
28028
+ const error = "Request lost \u2014 the executor restarted (deploy or eviction) or hung before finishing. The thread watchdog finalized it.";
28029
+ const errorType = "execution_lost";
28030
+ await this.ctx.storage.sql.exec(
28031
+ `UPDATE logs SET is_complete = 1, error = ?1, error_type = ?2 WHERE is_complete = 0`,
28032
+ error,
28033
+ errorType
28034
+ );
28035
+ for (const id of logIds) {
28036
+ this.broadcastLog({
28037
+ type: "log_data",
28038
+ log_id: id,
28039
+ data: { id, is_complete: true, error, error_type: errorType }
28040
+ });
28041
+ }
28042
+ }
28043
+ /** At most N auto-resumes per rolling window, tracked durably. */
28044
+ async consumeWatchdogResumeBudget() {
28045
+ const now = Date.now();
28046
+ let count = 0;
28047
+ let windowStart = now;
28048
+ const row = (await this.ctx.storage.sql.exec(
28049
+ `SELECT value FROM execution_state WHERE key = 'watchdog_resume_state'`
28050
+ )).toArray()[0];
28051
+ if (row) {
28052
+ try {
28053
+ const parsed = JSON.parse(row.value);
28054
+ if (typeof parsed.count === "number" && typeof parsed.windowStart === "number" && now - parsed.windowStart < this.watchdogResumeWindowMs) {
28055
+ count = parsed.count;
28056
+ windowStart = parsed.windowStart;
28057
+ }
28058
+ } catch {
28059
+ }
28060
+ }
28061
+ if (count >= this.watchdogMaxResumesPerWindow) return false;
28062
+ const value = JSON.stringify({ count: count + 1, windowStart });
28063
+ await this.ctx.storage.sql.exec(
28064
+ `INSERT INTO execution_state (key, value) VALUES ('watchdog_resume_state', ?1)
28065
+ ON CONFLICT(key) DO UPDATE SET value = ?1`,
28066
+ value
28067
+ );
28068
+ return true;
28069
+ }
27855
28070
  normalizePlainRecord(value) {
27856
28071
  if (!value || typeof value !== "object" || Array.isArray(value)) {
27857
28072
  return null;
@@ -27877,6 +28092,7 @@ ${resultContent}${attachmentSummary}`;
27877
28092
  await this.assertNotTerminated();
27878
28093
  this.isExecuting = true;
27879
28094
  this.currentAbortController = new AbortController();
28095
+ await this.armExecutionWatchdog(threadId);
27880
28096
  try {
27881
28097
  const agentDef = await this.loadAgent(agentName);
27882
28098
  const packageId = agentDef.__package?.packageId;
@@ -28007,6 +28223,7 @@ ${resultContent}${attachmentSummary}`;
28007
28223
  } finally {
28008
28224
  this.isExecuting = false;
28009
28225
  this.currentAbortController = null;
28226
+ await this.disarmExecutionWatchdog();
28010
28227
  }
28011
28228
  }
28012
28229
  /**
@@ -28053,6 +28270,7 @@ ${resultContent}${attachmentSummary}`;
28053
28270
  }
28054
28271
  this.isExecuting = true;
28055
28272
  this.currentAbortController = new AbortController();
28273
+ await this.armExecutionWatchdog(threadId);
28056
28274
  try {
28057
28275
  const agentBuilderId = this.env.AGENT_BUILDER.idFromName("singleton");
28058
28276
  const agentBuilder = this.env.AGENT_BUILDER.get(agentBuilderId);
@@ -28191,6 +28409,7 @@ ${resultContent}${attachmentSummary}`;
28191
28409
  } finally {
28192
28410
  this.isExecuting = false;
28193
28411
  this.currentAbortController = null;
28412
+ await this.disarmExecutionWatchdog();
28194
28413
  }
28195
28414
  }
28196
28415
  /**
@@ -28201,6 +28420,7 @@ ${resultContent}${attachmentSummary}`;
28201
28420
  await this.assertNotTerminated();
28202
28421
  this.isExecuting = true;
28203
28422
  this.currentAbortController = new AbortController();
28423
+ await this.armExecutionWatchdog(threadId);
28204
28424
  try {
28205
28425
  const agentBuilderId = this.env.AGENT_BUILDER.idFromName("singleton");
28206
28426
  const agentBuilder = this.env.AGENT_BUILDER.get(agentBuilderId);
@@ -28309,6 +28529,7 @@ ${resultContent}${attachmentSummary}`;
28309
28529
  } finally {
28310
28530
  this.isExecuting = false;
28311
28531
  this.currentAbortController = null;
28532
+ await this.disarmExecutionWatchdog();
28312
28533
  const latestQueuedSide = await this.getLatestQueuedMessageSide();
28313
28534
  if (latestQueuedSide) {
28314
28535
  await this.ensureQueuedMessageExecution(threadId, latestQueuedSide);