@standardagents/builder 0.30.2 → 0.31.0

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.
Files changed (53) hide show
  1. package/dist/built-in-routes.js +44 -3
  2. package/dist/built-in-routes.js.map +1 -1
  3. package/dist/client/{ApiKeysView-jhfg43UH.js → ApiKeysView-wfApkZad.js} +1 -1
  4. package/dist/client/CenteredContentView-JFIi6iAA.js +1 -0
  5. package/dist/client/{CompositionView-DhkLWmtE.js → CompositionView-CIm8Kt1I.js} +1 -1
  6. package/dist/client/{ConfirmDialog.vue_vue_type_script_setup_true_lang-D-WToRMy.js → ConfirmDialog.vue_vue_type_script_setup_true_lang-DO1fUxUR.js} +1 -1
  7. package/dist/client/{CopyButton.vue_vue_type_script_setup_true_lang-Dhq0t0dd.js → CopyButton.vue_vue_type_script_setup_true_lang-DfEFB4ze.js} +1 -1
  8. package/dist/client/{DataTable.vue_vue_type_script_setup_true_lang-D1MDCns-.js → DataTable.vue_vue_type_script_setup_true_lang-4XH4cxYM.js} +1 -1
  9. package/dist/client/{JsonViewer-BoHrE76S.js → JsonViewer-md2l5DdY.js} +1 -1
  10. package/dist/client/{LoginView-BpR2nMNf.js → LoginView-BJSfZpgw.js} +1 -1
  11. package/dist/client/{MarketplaceView-uUf1JYla.js → MarketplaceView-DXE7ys3s.js} +1 -1
  12. package/dist/client/{Modal.vue_vue_type_script_setup_true_lang-D-SDVFQI.js → Modal.vue_vue_type_script_setup_true_lang-FiL__HMg.js} +1 -1
  13. package/dist/client/{ModelModal.vue_vue_type_script_setup_true_lang-DE57Zr0r.js → ModelModal.vue_vue_type_script_setup_true_lang-BIBHjNB0.js} +1 -1
  14. package/dist/client/{ModelsView-CuM_OieH.js → ModelsView-BhtR_WPG.js} +1 -1
  15. package/dist/client/{PromptEditView-BWjrA2zr.js → PromptEditView-DadQ9eJ0.js} +1 -1
  16. package/dist/client/{PromptModal-BFxhuG0Z.js → PromptModal-BMBKSmJC.js} +1 -1
  17. package/dist/client/{PromptsView-AlGsRB6k.js → PromptsView-F1l2HocI.js} +1 -1
  18. package/dist/client/{ProvidersView-CbXBs3SH.js → ProvidersView-DFWGCJwN.js} +1 -1
  19. package/dist/client/ThreadInspectorPane.vue_vue_type_script_setup_true_lang-C7JV15r3.js +22 -0
  20. package/dist/client/{ThreadsView-rN9rzJi1.js → ThreadsView-CMb05n9d.js} +1 -1
  21. package/dist/client/{ToolsView-0Gpo_RFg.js → ToolsView-7F2ybb1x.js} +1 -1
  22. package/dist/client/{UiCodeBlock.vue_vue_type_script_setup_true_lang-CNncALQo.js → UiCodeBlock.vue_vue_type_script_setup_true_lang-QytRm7Ql.js} +1 -1
  23. package/dist/client/{UsersView-iXPbcguY.js → UsersView-Badppc4K.js} +1 -1
  24. package/dist/client/{VariablesView-C9BB9h34.js → VariablesView-E45rU9bc.js} +1 -1
  25. package/dist/client/assets/ThreadInspectorPane-Bha1aVZZ.css +1 -0
  26. package/dist/client/assets/index-C4KCxdCv.css +1 -0
  27. package/dist/client/{check-BdJVlUmI.js → check-pGlck8qX.js} +1 -1
  28. package/dist/client/{chevron-right-D49O3XLU.js → chevron-right-FpPE1Obh.js} +1 -1
  29. package/dist/client/{copy-40uDyTgY.js → copy-Dt1FLh3s.js} +1 -1
  30. package/dist/client/{formkit-Bl-sERnF.js → formkit-DcZQH9-p.js} +1 -1
  31. package/dist/client/{index-BCoE48be.js → index-BYdncvi0.js} +3 -3
  32. package/dist/client/index.html +4 -4
  33. package/dist/client/{package-1UgoERpx.js → package-DSNwOrcN.js} +1 -1
  34. package/dist/client/{plus-BzCjfYs0.js → plus-D0GkkdA7.js} +1 -1
  35. package/dist/client/{timeAgo-cFwldMu-.js → timeAgo-X3Xeq5Kz.js} +1 -1
  36. package/dist/client/{useActionFeedback-BVifffYQ.js → useActionFeedback-Dxh_Xp0q.js} +1 -1
  37. package/dist/client/vue-KXHVRwln.js +1 -0
  38. package/dist/{index-CcnGEkPh.d.ts → index-CL7q3PuW.d.ts} +24 -0
  39. package/dist/index.d.ts +1 -1
  40. package/dist/index.js +159 -16
  41. package/dist/index.js.map +1 -1
  42. package/dist/plugin.js +4 -0
  43. package/dist/plugin.js.map +1 -1
  44. package/dist/runtime.d.ts +45 -2
  45. package/dist/runtime.js +155 -16
  46. package/dist/runtime.js.map +1 -1
  47. package/dist/test.d.ts +1 -1
  48. package/package.json +5 -5
  49. package/dist/client/CenteredContentView-CS0mV2EJ.js +0 -1
  50. package/dist/client/ThreadInspectorPane.vue_vue_type_script_setup_true_lang-Dm3d37hz.js +0 -22
  51. package/dist/client/assets/ThreadInspectorPane-CfpdDnUQ.css +0 -1
  52. package/dist/client/assets/index-BTPsPULO.css +0 -1
  53. package/dist/client/vue-CmmJuziT.js +0 -1
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, DurableUserHub, FlowStateSdk, FlowStateWithSdk, HubClientInfo, ImageContextConfig, InjectMessageOptions, LLMProviderInterface, NamespaceResolutionError, PromptContent, PromptDefinition, PromptIncludePart, PromptPart, PromptToolConfig, Provider, ProviderContentPart, ProviderFilePart, ProviderImagePart, ProviderRegistry, ProviderTextPart, SessionToolBinding, SessionToolConfig, SideConfig, SubagentToolConfig, SubpromptConfig, ThreadRegistryEntry, ThreadWatchdog, ThreadWatchdogState, ToolConfig, UpdateThreadParams, User, WatchdogCheckin, WatchdogProbeResult, 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, x as AttachmentRef, f as BuilderThreadEndpointHandler, C as CLIENT_STREAM_CHUNK_BYTES, o as ClientDuplex, p as ClientForwarding, m as ClientStreamRequest, n as ClientStreamResponse, k as ClientToolRequest, l as ClientToolResponse, B as Controller, a as ControllerContext, E as Env, w as FileRecord, y as FileStats, u as FlowResult, F as FlowState, G as GrepResult, D as ImageContentPart, I as ImageMetadata, L as LLMResponse, M as Message, J as MessageContent, H as MultimodalContent, R as RequestContext, S as StorageBackend, v as TelemetryEvent, z as TextContentPart, b as ThreadEndpointContext, e as ThreadEnv, q as ThreadInstance, r as ThreadMetadata, T as ToolCall, t as ToolResult, g as callClient, h as callClientStream, c as createThreadEndpointHandler, j as decodeStreamChunk, d as defineController, i as encodeStreamChunk, s as supportsClientForwarding } from './index-CcnGEkPh.js';
4
+ export { A as Agent, x as AttachmentRef, f as BuilderThreadEndpointHandler, C as CLIENT_STREAM_CHUNK_BYTES, o as ClientDuplex, p as ClientForwarding, m as ClientStreamRequest, n as ClientStreamResponse, k as ClientToolRequest, l as ClientToolResponse, B as Controller, a as ControllerContext, E as Env, w as FileRecord, y as FileStats, u as FlowResult, F as FlowState, G as GrepResult, D as ImageContentPart, I as ImageMetadata, L as LLMResponse, M as Message, J as MessageContent, H as MultimodalContent, R as RequestContext, S as StorageBackend, v as TelemetryEvent, z as TextContentPart, b as ThreadEndpointContext, e as ThreadEnv, q as ThreadInstance, r as ThreadMetadata, T as ToolCall, t as ToolResult, g as callClient, h as callClientStream, c as createThreadEndpointHandler, j as decodeStreamChunk, d as defineController, i as encodeStreamChunk, s as supportsClientForwarding } from './index-CL7q3PuW.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
@@ -14891,6 +14891,10 @@ declare module 'virtual:@standardagents/builder' {
14891
14891
  parent: string | null;
14892
14892
  terminated: number | null;
14893
14893
  created_at: number;
14894
+ /** Unix seconds of the last message or execution on this thread (or a descendant). */
14895
+ last_activity_at?: number | null;
14896
+ /** True while the thread is executing a flow. */
14897
+ executing?: boolean;
14894
14898
  }
14895
14899
 
14896
14900
  export interface UpdateThreadParams {
@@ -26192,9 +26196,9 @@ var DurableThread = class _DurableThread extends DurableObject {
26192
26196
  * Each migration is run in order, starting from the current version + 1.
26193
26197
  */
26194
26198
  async runMigrations(fromVersion) {
26195
- for (const migration47 of migrations) {
26196
- if (migration47.version > fromVersion) {
26197
- await migration47.up(this.ctx.storage.sql);
26199
+ for (const migration48 of migrations) {
26200
+ if (migration48.version > fromVersion) {
26201
+ await migration48.up(this.ctx.storage.sql);
26198
26202
  }
26199
26203
  }
26200
26204
  }
@@ -29588,6 +29592,25 @@ ${resultContent}${attachmentSummary}`;
29588
29592
  if (!namespace || typeof namespace.idFromName !== "function") return null;
29589
29593
  return namespace.get(namespace.idFromName(threadId));
29590
29594
  }
29595
+ /**
29596
+ * Stamp thread activity on the registry so thread lists can sort by recency
29597
+ * of real work and show which threads are executing right now. Registry
29598
+ * bookkeeping is presentation state: a failure here must never take down the
29599
+ * turn that triggered it.
29600
+ */
29601
+ async recordRegistryActivity(threadId, executing) {
29602
+ try {
29603
+ const namespace = this.env.AGENT_BUILDER;
29604
+ if (!namespace || typeof namespace.idFromName !== "function") return;
29605
+ const agentBuilder = namespace.get(namespace.idFromName("singleton"));
29606
+ await agentBuilder.recordThreadActivity?.(threadId, { executing });
29607
+ } catch (error) {
29608
+ console.error(
29609
+ "[DurableThread] Failed to record thread activity (non-fatal):",
29610
+ error
29611
+ );
29612
+ }
29613
+ }
29591
29614
  /** Arm the standing watchdog for an execution start. One pending row max. */
29592
29615
  async armExecutionWatchdog(threadId) {
29593
29616
  try {
@@ -29774,6 +29797,7 @@ ${resultContent}${attachmentSummary}`;
29774
29797
  }
29775
29798
  this.isExecuting = false;
29776
29799
  this.currentAbortController = null;
29800
+ await this.recordRegistryActivity(threadId, false);
29777
29801
  tookOver = true;
29778
29802
  } else if (incomplete.length > 0 && silenceMs <= ceiling) {
29779
29803
  return report("observing", "none");
@@ -29977,6 +30001,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
29977
30001
  this.isExecuting = true;
29978
30002
  this.currentAbortController = new AbortController();
29979
30003
  await this.armExecutionWatchdog(threadId);
30004
+ await this.recordRegistryActivity(threadId, true);
29980
30005
  try {
29981
30006
  const agentDef = await this.loadAgent(agentName);
29982
30007
  const packageId = agentDef.__package?.packageId;
@@ -30108,6 +30133,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
30108
30133
  this.isExecuting = false;
30109
30134
  this.currentAbortController = null;
30110
30135
  await this.disarmExecutionWatchdog(threadId);
30136
+ await this.recordRegistryActivity(threadId, false);
30111
30137
  }
30112
30138
  }
30113
30139
  /**
@@ -30168,6 +30194,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
30168
30194
  this.isExecuting = true;
30169
30195
  this.currentAbortController = new AbortController();
30170
30196
  await this.armExecutionWatchdog(threadId);
30197
+ await this.recordRegistryActivity(threadId, true);
30171
30198
  try {
30172
30199
  const agentBuilderId = this.env.AGENT_BUILDER.idFromName("singleton");
30173
30200
  const agentBuilder = this.env.AGENT_BUILDER.get(agentBuilderId);
@@ -30307,6 +30334,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
30307
30334
  this.isExecuting = false;
30308
30335
  this.currentAbortController = null;
30309
30336
  await this.disarmExecutionWatchdog(threadId);
30337
+ await this.recordRegistryActivity(threadId, false);
30310
30338
  }
30311
30339
  }
30312
30340
  /**
@@ -30324,6 +30352,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
30324
30352
  this.isExecuting = true;
30325
30353
  this.currentAbortController = new AbortController();
30326
30354
  await this.armExecutionWatchdog(threadId);
30355
+ await this.recordRegistryActivity(threadId, true);
30327
30356
  try {
30328
30357
  const agentBuilderId = this.env.AGENT_BUILDER.idFromName("singleton");
30329
30358
  const agentBuilder = this.env.AGENT_BUILDER.get(agentBuilderId);
@@ -30433,6 +30462,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
30433
30462
  this.isExecuting = false;
30434
30463
  this.currentAbortController = null;
30435
30464
  await this.disarmExecutionWatchdog(threadId);
30465
+ await this.recordRegistryActivity(threadId, false);
30436
30466
  const latestQueuedSide = await this.getLatestQueuedMessageSide();
30437
30467
  if (latestQueuedSide) {
30438
30468
  await this.ensureQueuedMessageExecution(threadId, latestQueuedSide);
@@ -31526,9 +31556,25 @@ var migration46 = {
31526
31556
  }
31527
31557
  };
31528
31558
 
31559
+ // src/durable-objects/agentbuilder-migrations/0013_add_thread_activity.ts
31560
+ var migration47 = {
31561
+ version: 13,
31562
+ async up(sql) {
31563
+ sql.exec(`ALTER TABLE threads ADD COLUMN last_activity_at INTEGER`);
31564
+ sql.exec(`ALTER TABLE threads ADD COLUMN executing_since INTEGER`);
31565
+ sql.exec(
31566
+ `CREATE INDEX IF NOT EXISTS idx_threads_last_activity_at ON threads(last_activity_at)`
31567
+ );
31568
+ sql.exec(`UPDATE threads SET last_activity_at = created_at`);
31569
+ sql.exec(`
31570
+ INSERT OR REPLACE INTO _metadata (key, value) VALUES ('schema_version', '13')
31571
+ `);
31572
+ }
31573
+ };
31574
+
31529
31575
  // src/durable-objects/agentbuilder-migrations/index.ts
31530
- var migrations2 = [migration35, migration36, migration37, migration38, migration39, migration40, migration41, migration42, migration43, migration44, migration45, migration46];
31531
- var LATEST_SCHEMA_VERSION2 = 12;
31576
+ var migrations2 = [migration35, migration36, migration37, migration38, migration39, migration40, migration41, migration42, migration43, migration44, migration45, migration46, migration47];
31577
+ var LATEST_SCHEMA_VERSION2 = 13;
31532
31578
 
31533
31579
  // src/utils/crypto.ts
31534
31580
  var CryptoUtil = class {
@@ -31727,6 +31773,8 @@ function parseStoredPermissions(value) {
31727
31773
  return null;
31728
31774
  }
31729
31775
  }
31776
+ var EXECUTING_STALE_AFTER_SECONDS = 15 * 60;
31777
+ var isThreadExecuting = (executingSince, nowSeconds) => executingSince != null && nowSeconds - executingSince < EXECUTING_STALE_AFTER_SECONDS;
31730
31778
  var DurableAgentBuilder = class extends DurableObject {
31731
31779
  migratedToVersion = null;
31732
31780
  eventSockets = /* @__PURE__ */ new Set();
@@ -32295,9 +32343,9 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
32295
32343
  }
32296
32344
  }
32297
32345
  async runMigrations(fromVersion) {
32298
- for (const migration47 of migrations2) {
32299
- if (migration47.version > fromVersion) {
32300
- await migration47.up(this.ctx.storage.sql);
32346
+ for (const migration48 of migrations2) {
32347
+ if (migration48.version > fromVersion) {
32348
+ await migration48.up(this.ctx.storage.sql);
32301
32349
  }
32302
32350
  }
32303
32351
  }
@@ -32339,8 +32387,8 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
32339
32387
  const encryptedEnv = await this.encryptEnvRecord(env);
32340
32388
  const properties = this.withEnvTypeRecord(params.properties ?? null, envTypes);
32341
32389
  await this.ctx.storage.sql.exec(
32342
- `INSERT INTO threads (id, agent_name, user_id, tags, env, tenvs, properties, parent, terminated, created_at)
32343
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, ?)`,
32390
+ `INSERT INTO threads (id, agent_name, user_id, tags, env, tenvs, properties, parent, terminated, created_at, last_activity_at)
32391
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?)`,
32344
32392
  id,
32345
32393
  params.agent_name,
32346
32394
  params.user_id || null,
@@ -32349,6 +32397,7 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
32349
32397
  encryptedEnv,
32350
32398
  properties ? JSON.stringify(properties) : null,
32351
32399
  params.parent ?? null,
32400
+ now,
32352
32401
  now
32353
32402
  );
32354
32403
  const thread = {
@@ -32362,7 +32411,9 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
32362
32411
  properties,
32363
32412
  parent: params.parent ?? null,
32364
32413
  terminated: null,
32365
- created_at: now
32414
+ created_at: now,
32415
+ last_activity_at: now,
32416
+ executing: false
32366
32417
  };
32367
32418
  const eventThread = await this.toThreadEventPayload(thread);
32368
32419
  this.broadcastEvent({ type: "thread_created", thread: eventThread });
@@ -32418,7 +32469,10 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
32418
32469
  */
32419
32470
  async getThread(id) {
32420
32471
  await this.ensureMigrated();
32421
- const cursor = await this.ctx.storage.sql.exec(`SELECT id, agent_name, user_id, tags, env, tenvs, properties, parent, terminated, created_at FROM threads WHERE id = ?`, id);
32472
+ const cursor = await this.ctx.storage.sql.exec(
32473
+ `SELECT id, agent_name, user_id, tags, env, tenvs, properties, parent, terminated, created_at, last_activity_at, executing_since FROM threads WHERE id = ?`,
32474
+ id
32475
+ );
32422
32476
  const rows = cursor.toArray();
32423
32477
  if (rows.length === 0) {
32424
32478
  return null;
@@ -32438,7 +32492,12 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
32438
32492
  properties,
32439
32493
  parent: row.parent,
32440
32494
  terminated: row.terminated,
32441
- created_at: row.created_at
32495
+ created_at: row.created_at,
32496
+ last_activity_at: row.last_activity_at ?? row.created_at,
32497
+ executing: isThreadExecuting(
32498
+ row.executing_since,
32499
+ Math.floor(Date.now() / 1e3)
32500
+ )
32442
32501
  };
32443
32502
  }
32444
32503
  /**
@@ -32479,6 +32538,7 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
32479
32538
  */
32480
32539
  async listThreads(params) {
32481
32540
  await this.ensureMigrated();
32541
+ const nowSeconds = Math.floor(Date.now() / 1e3);
32482
32542
  const whereClauses = [];
32483
32543
  const args = [];
32484
32544
  if (params?.agent_name) {
@@ -32514,6 +32574,18 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
32514
32574
  whereClauses.push("created_at <= ?");
32515
32575
  args.push(params.endDate);
32516
32576
  }
32577
+ if (params?.executing === true) {
32578
+ whereClauses.push("executing_since IS NOT NULL AND executing_since > ?");
32579
+ args.push(nowSeconds - EXECUTING_STALE_AFTER_SECONDS);
32580
+ } else if (params?.executing === false) {
32581
+ whereClauses.push("(executing_since IS NULL OR executing_since <= ?)");
32582
+ args.push(nowSeconds - EXECUTING_STALE_AFTER_SECONDS);
32583
+ }
32584
+ if (params?.terminated === true) {
32585
+ whereClauses.push("terminated IS NOT NULL");
32586
+ } else if (params?.terminated === false) {
32587
+ whereClauses.push("terminated IS NULL");
32588
+ }
32517
32589
  const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
32518
32590
  const countCursor = await this.ctx.storage.sql.exec(
32519
32591
  `SELECT COUNT(*) as count FROM threads ${whereClause}`,
@@ -32522,10 +32594,11 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
32522
32594
  const total = countCursor.one().count;
32523
32595
  const limit = params?.limit ?? 100;
32524
32596
  const offset = params?.offset ?? 0;
32597
+ const orderByClause = params?.sort === "created" ? "created_at DESC" : params?.sort === "oldest" ? "created_at ASC" : "COALESCE(last_activity_at, created_at) DESC, created_at DESC";
32525
32598
  const cursor = await this.ctx.storage.sql.exec(
32526
- `SELECT id, agent_name, user_id, tags, properties, parent, terminated, created_at FROM threads
32599
+ `SELECT id, agent_name, user_id, tags, properties, parent, terminated, created_at, last_activity_at, executing_since FROM threads
32527
32600
  ${whereClause}
32528
- ORDER BY created_at DESC
32601
+ ORDER BY ${orderByClause}
32529
32602
  LIMIT ? OFFSET ?`,
32530
32603
  ...args,
32531
32604
  limit,
@@ -32545,10 +32618,80 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
32545
32618
  properties: row.properties ? JSON.parse(row.properties) : null,
32546
32619
  parent: row.parent,
32547
32620
  terminated: row.terminated,
32548
- created_at: row.created_at
32621
+ created_at: row.created_at,
32622
+ last_activity_at: row.last_activity_at ?? row.created_at,
32623
+ executing: isThreadExecuting(row.executing_since, nowSeconds)
32549
32624
  }));
32550
32625
  return { threads, total };
32551
32626
  }
32627
+ /**
32628
+ * Distinct owners of threads visible to the caller, with counts, so a thread
32629
+ * list can offer an "owner" filter without exposing the admin-only user
32630
+ * directory (which is unavailable on hosted instances).
32631
+ */
32632
+ async listThreadOwners(params) {
32633
+ await this.ensureMigrated();
32634
+ const whereClause = params?.user_id ? "WHERE t.user_id = ?" : "";
32635
+ const args = params?.user_id ? [params.user_id] : [];
32636
+ const cursor = await this.ctx.storage.sql.exec(
32637
+ `SELECT t.user_id AS user_id,
32638
+ u.username AS username,
32639
+ u.display_name AS display_name,
32640
+ u.email AS email,
32641
+ COUNT(*) AS thread_count
32642
+ FROM threads t
32643
+ LEFT JOIN users u ON u.id = t.user_id
32644
+ ${whereClause}
32645
+ GROUP BY t.user_id
32646
+ ORDER BY thread_count DESC`,
32647
+ ...args
32648
+ );
32649
+ return cursor.toArray();
32650
+ }
32651
+ /**
32652
+ * Record that a thread did work: stamp `last_activity_at` and flip the
32653
+ * executing marker. Activity bubbles up the parent chain so a root thread
32654
+ * sorts by the freshest work anywhere in its subagent tree — otherwise a
32655
+ * long-running subagent would let its parent sink down an activity-sorted
32656
+ * list.
32657
+ */
32658
+ async recordThreadActivity(threadId, options) {
32659
+ await this.ensureMigrated();
32660
+ const now = Math.floor(Date.now() / 1e3);
32661
+ const executing = options?.executing;
32662
+ await this.ctx.storage.sql.exec(
32663
+ `UPDATE threads
32664
+ SET last_activity_at = ?
32665
+ WHERE id IN (
32666
+ WITH RECURSIVE ancestry(id, parent) AS (
32667
+ SELECT id, parent FROM threads WHERE id = ?
32668
+ UNION ALL
32669
+ SELECT t.id, t.parent FROM threads t JOIN ancestry a ON t.id = a.parent
32670
+ )
32671
+ SELECT id FROM ancestry
32672
+ )`,
32673
+ now,
32674
+ threadId
32675
+ );
32676
+ if (executing === true) {
32677
+ await this.ctx.storage.sql.exec(
32678
+ `UPDATE threads SET executing_since = ? WHERE id = ? AND executing_since IS NULL`,
32679
+ now,
32680
+ threadId
32681
+ );
32682
+ } else if (executing === false) {
32683
+ await this.ctx.storage.sql.exec(
32684
+ `UPDATE threads SET executing_since = NULL WHERE id = ?`,
32685
+ threadId
32686
+ );
32687
+ }
32688
+ this.broadcastEvent({
32689
+ type: "thread_activity",
32690
+ threadId,
32691
+ last_activity_at: now,
32692
+ executing: executing === true
32693
+ });
32694
+ }
32552
32695
  /**
32553
32696
  * Delete a thread from the registry.
32554
32697
  */