@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/runtime.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { e as ThreadEnv, A as Agent, r as ThreadMetadata, t as ToolResult, M as Message, k as ClientToolRequest, l as ClientToolResponse, m as ClientStreamRequest, n as ClientStreamResponse, w as FileRecord, F as FlowState, y as FileStats, G as GrepResult, x as AttachmentRef, J as MessageContent, E as Env, q as ThreadInstance } from './index-CcnGEkPh.js';
2
- export { f as BuilderThreadEndpointHandler, C as CLIENT_STREAM_CHUNK_BYTES, o as ClientDuplex, p as ClientForwarding, B as Controller, a as ControllerContext, u as FlowResult, D as ImageContentPart, I as ImageMetadata, L as LLMResponse, H as MultimodalContent, R as RequestContext, S as StorageBackend, v as TelemetryEvent, z as TextContentPart, b as ThreadEndpointContext, T as ToolCall, 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';
1
+ import { e as ThreadEnv, A as Agent, r as ThreadMetadata, t as ToolResult, M as Message, k as ClientToolRequest, l as ClientToolResponse, m as ClientStreamRequest, n as ClientStreamResponse, w as FileRecord, F as FlowState, y as FileStats, G as GrepResult, x as AttachmentRef, J as MessageContent, E as Env, q as ThreadInstance } from './index-CL7q3PuW.js';
2
+ export { f as BuilderThreadEndpointHandler, C as CLIENT_STREAM_CHUNK_BYTES, o as ClientDuplex, p as ClientForwarding, B as Controller, a as ControllerContext, u as FlowResult, D as ImageContentPart, I as ImageMetadata, L as LLMResponse, H as MultimodalContent, R as RequestContext, S as StorageBackend, v as TelemetryEvent, z as TextContentPart, b as ThreadEndpointContext, T as ToolCall, 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';
3
3
  import { CodeExecutionOptions, CodeExecutionResult, SubagentRegistryEntry, InjectMessageInput, QueueMessageInput, CredentialGrant, SkillMetadata, SkillFileInput, ModelDefinition as ModelDefinition$1, ToolArgs, PromptTextPart, PromptEnvPart, VariableDefinition, SubpromptConfig as SubpromptConfig$1, PromptToolConfig as PromptToolConfig$1, SubagentToolConfig as SubagentToolConfig$1, ReasoningConfig, SideConfig as SideConfig$1, LLMProviderInterface, ContentPart, TextPart, ImagePart, FilePart, ThreadState, NamespaceContext, DefinitionLoader } from '@standardagents/spec';
4
4
  export { AgentType, DefinitionLoader, GlobalNamespaceContext, HookSignatures, ImageContent, ModelCapabilities, ModelProvider, NamespaceContext, PackageSignature, PackedExports, PackedMeta, PackedMetadata, PackedNamespaceContext, PromptInput, PromptTextPart, ProviderAssistantMessage, ProviderError, ProviderErrorCode, ProviderFactory, ProviderFactoryConfig, ProviderFinishReason, ProviderGeneratedImage, ProviderMessage, ProviderMessageContent, ModelCapabilities as ProviderModelCapabilities, ProviderReasoningDetail, ProviderRequest, ProviderResponse, ProviderStreamChunk, ProviderSystemMessage, ProviderTool, ProviderToolCallPart, ProviderToolMessage, ProviderToolResultContent, ProviderUsage, ProviderUserMessage, ReasoningConfig, StructuredPrompt, TextContent, Tool, ToolArgs, ToolArgsNode, ToolArgsRawShape, ToolContent, belongsToPackage, defineAgent, defineHook, defineModel, definePrompt, defineTool, isPacked, isVisibleInNamespace, mapReasoningLevel } from '@standardagents/spec';
5
5
  import { DurableObject, WorkerEntrypoint } from 'cloudflare:workers';
@@ -1241,6 +1241,13 @@ declare class DurableThread<Env extends ThreadEnv = ThreadEnv> extends DurableOb
1241
1241
  * Returns null on deployments whose generated config predates the binding.
1242
1242
  */
1243
1243
  private watchdogCompanion;
1244
+ /**
1245
+ * Stamp thread activity on the registry so thread lists can sort by recency
1246
+ * of real work and show which threads are executing right now. Registry
1247
+ * bookkeeping is presentation state: a failure here must never take down the
1248
+ * turn that triggered it.
1249
+ */
1250
+ private recordRegistryActivity;
1244
1251
  /** Arm the standing watchdog for an execution start. One pending row max. */
1245
1252
  private armExecutionWatchdog;
1246
1253
  /** Disarm on clean execution end. Completed check rows are noise, not history. */
@@ -1802,6 +1809,10 @@ interface ThreadRegistryEntry {
1802
1809
  parent: string | null;
1803
1810
  terminated: number | null;
1804
1811
  created_at: number;
1812
+ /** Unix seconds of the last message or execution on this thread (or a descendant). */
1813
+ last_activity_at?: number | null;
1814
+ /** True while the thread is executing a flow (stale markers read as false). */
1815
+ executing?: boolean;
1805
1816
  }
1806
1817
  interface PendingSubagentAttachmentRef {
1807
1818
  path: string;
@@ -1910,6 +1921,8 @@ interface Provider$1 {
1910
1921
  sdk: string;
1911
1922
  api_key: string;
1912
1923
  }
1924
+ /** Sort orders supported by `listThreads`. */
1925
+ type ThreadListSort = 'activity' | 'created' | 'oldest';
1913
1926
  declare class DurableAgentBuilder<Env extends AgentBuilderEnv = AgentBuilderEnv> extends DurableObject<Env> {
1914
1927
  private migratedToVersion;
1915
1928
  private eventSockets;
@@ -2024,10 +2037,40 @@ declare class DurableAgentBuilder<Env extends AgentBuilderEnv = AgentBuilderEnv>
2024
2037
  limit?: number;
2025
2038
  offset?: number;
2026
2039
  include_children?: boolean;
2040
+ /** Default `activity`: most recently worked-on threads first. */
2041
+ sort?: ThreadListSort;
2042
+ /** `true` → only threads currently executing; `false` → only idle ones. */
2043
+ executing?: boolean;
2044
+ /** `false` hides terminated threads; `true` shows only terminated ones. */
2045
+ terminated?: boolean;
2027
2046
  }): Promise<{
2028
2047
  threads: ThreadRegistryEntry[];
2029
2048
  total: number;
2030
2049
  }>;
2050
+ /**
2051
+ * Distinct owners of threads visible to the caller, with counts, so a thread
2052
+ * list can offer an "owner" filter without exposing the admin-only user
2053
+ * directory (which is unavailable on hosted instances).
2054
+ */
2055
+ listThreadOwners(params?: {
2056
+ user_id?: string;
2057
+ }): Promise<Array<{
2058
+ user_id: string | null;
2059
+ username: string | null;
2060
+ display_name: string | null;
2061
+ email: string | null;
2062
+ thread_count: number;
2063
+ }>>;
2064
+ /**
2065
+ * Record that a thread did work: stamp `last_activity_at` and flip the
2066
+ * executing marker. Activity bubbles up the parent chain so a root thread
2067
+ * sorts by the freshest work anywhere in its subagent tree — otherwise a
2068
+ * long-running subagent would let its parent sink down an activity-sorted
2069
+ * list.
2070
+ */
2071
+ recordThreadActivity(threadId: string, options?: {
2072
+ executing?: boolean;
2073
+ }): Promise<void>;
2031
2074
  /**
2032
2075
  * Delete a thread from the registry.
2033
2076
  */
package/dist/runtime.js CHANGED
@@ -17790,9 +17790,9 @@ var DurableThread = class _DurableThread extends DurableObject {
17790
17790
  * Each migration is run in order, starting from the current version + 1.
17791
17791
  */
17792
17792
  async runMigrations(fromVersion) {
17793
- for (const migration47 of migrations) {
17794
- if (migration47.version > fromVersion) {
17795
- await migration47.up(this.ctx.storage.sql);
17793
+ for (const migration48 of migrations) {
17794
+ if (migration48.version > fromVersion) {
17795
+ await migration48.up(this.ctx.storage.sql);
17796
17796
  }
17797
17797
  }
17798
17798
  }
@@ -21186,6 +21186,25 @@ ${resultContent}${attachmentSummary}`;
21186
21186
  if (!namespace || typeof namespace.idFromName !== "function") return null;
21187
21187
  return namespace.get(namespace.idFromName(threadId));
21188
21188
  }
21189
+ /**
21190
+ * Stamp thread activity on the registry so thread lists can sort by recency
21191
+ * of real work and show which threads are executing right now. Registry
21192
+ * bookkeeping is presentation state: a failure here must never take down the
21193
+ * turn that triggered it.
21194
+ */
21195
+ async recordRegistryActivity(threadId, executing) {
21196
+ try {
21197
+ const namespace = this.env.AGENT_BUILDER;
21198
+ if (!namespace || typeof namespace.idFromName !== "function") return;
21199
+ const agentBuilder = namespace.get(namespace.idFromName("singleton"));
21200
+ await agentBuilder.recordThreadActivity?.(threadId, { executing });
21201
+ } catch (error) {
21202
+ console.error(
21203
+ "[DurableThread] Failed to record thread activity (non-fatal):",
21204
+ error
21205
+ );
21206
+ }
21207
+ }
21189
21208
  /** Arm the standing watchdog for an execution start. One pending row max. */
21190
21209
  async armExecutionWatchdog(threadId) {
21191
21210
  try {
@@ -21372,6 +21391,7 @@ ${resultContent}${attachmentSummary}`;
21372
21391
  }
21373
21392
  this.isExecuting = false;
21374
21393
  this.currentAbortController = null;
21394
+ await this.recordRegistryActivity(threadId, false);
21375
21395
  tookOver = true;
21376
21396
  } else if (incomplete.length > 0 && silenceMs <= ceiling) {
21377
21397
  return report("observing", "none");
@@ -21575,6 +21595,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
21575
21595
  this.isExecuting = true;
21576
21596
  this.currentAbortController = new AbortController();
21577
21597
  await this.armExecutionWatchdog(threadId);
21598
+ await this.recordRegistryActivity(threadId, true);
21578
21599
  try {
21579
21600
  const agentDef = await this.loadAgent(agentName);
21580
21601
  const packageId = agentDef.__package?.packageId;
@@ -21706,6 +21727,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
21706
21727
  this.isExecuting = false;
21707
21728
  this.currentAbortController = null;
21708
21729
  await this.disarmExecutionWatchdog(threadId);
21730
+ await this.recordRegistryActivity(threadId, false);
21709
21731
  }
21710
21732
  }
21711
21733
  /**
@@ -21766,6 +21788,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
21766
21788
  this.isExecuting = true;
21767
21789
  this.currentAbortController = new AbortController();
21768
21790
  await this.armExecutionWatchdog(threadId);
21791
+ await this.recordRegistryActivity(threadId, true);
21769
21792
  try {
21770
21793
  const agentBuilderId = this.env.AGENT_BUILDER.idFromName("singleton");
21771
21794
  const agentBuilder = this.env.AGENT_BUILDER.get(agentBuilderId);
@@ -21905,6 +21928,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
21905
21928
  this.isExecuting = false;
21906
21929
  this.currentAbortController = null;
21907
21930
  await this.disarmExecutionWatchdog(threadId);
21931
+ await this.recordRegistryActivity(threadId, false);
21908
21932
  }
21909
21933
  }
21910
21934
  /**
@@ -21922,6 +21946,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
21922
21946
  this.isExecuting = true;
21923
21947
  this.currentAbortController = new AbortController();
21924
21948
  await this.armExecutionWatchdog(threadId);
21949
+ await this.recordRegistryActivity(threadId, true);
21925
21950
  try {
21926
21951
  const agentBuilderId = this.env.AGENT_BUILDER.idFromName("singleton");
21927
21952
  const agentBuilder = this.env.AGENT_BUILDER.get(agentBuilderId);
@@ -22031,6 +22056,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
22031
22056
  this.isExecuting = false;
22032
22057
  this.currentAbortController = null;
22033
22058
  await this.disarmExecutionWatchdog(threadId);
22059
+ await this.recordRegistryActivity(threadId, false);
22034
22060
  const latestQueuedSide = await this.getLatestQueuedMessageSide();
22035
22061
  if (latestQueuedSide) {
22036
22062
  await this.ensureQueuedMessageExecution(threadId, latestQueuedSide);
@@ -23124,9 +23150,25 @@ var migration46 = {
23124
23150
  }
23125
23151
  };
23126
23152
 
23153
+ // src/durable-objects/agentbuilder-migrations/0013_add_thread_activity.ts
23154
+ var migration47 = {
23155
+ version: 13,
23156
+ async up(sql) {
23157
+ sql.exec(`ALTER TABLE threads ADD COLUMN last_activity_at INTEGER`);
23158
+ sql.exec(`ALTER TABLE threads ADD COLUMN executing_since INTEGER`);
23159
+ sql.exec(
23160
+ `CREATE INDEX IF NOT EXISTS idx_threads_last_activity_at ON threads(last_activity_at)`
23161
+ );
23162
+ sql.exec(`UPDATE threads SET last_activity_at = created_at`);
23163
+ sql.exec(`
23164
+ INSERT OR REPLACE INTO _metadata (key, value) VALUES ('schema_version', '13')
23165
+ `);
23166
+ }
23167
+ };
23168
+
23127
23169
  // src/durable-objects/agentbuilder-migrations/index.ts
23128
- var migrations2 = [migration35, migration36, migration37, migration38, migration39, migration40, migration41, migration42, migration43, migration44, migration45, migration46];
23129
- var LATEST_SCHEMA_VERSION2 = 12;
23170
+ var migrations2 = [migration35, migration36, migration37, migration38, migration39, migration40, migration41, migration42, migration43, migration44, migration45, migration46, migration47];
23171
+ var LATEST_SCHEMA_VERSION2 = 13;
23130
23172
 
23131
23173
  // src/utils/crypto.ts
23132
23174
  var CryptoUtil = class {
@@ -23325,6 +23367,8 @@ function parseStoredPermissions(value) {
23325
23367
  return null;
23326
23368
  }
23327
23369
  }
23370
+ var EXECUTING_STALE_AFTER_SECONDS = 15 * 60;
23371
+ var isThreadExecuting = (executingSince, nowSeconds) => executingSince != null && nowSeconds - executingSince < EXECUTING_STALE_AFTER_SECONDS;
23328
23372
  var DurableAgentBuilder = class extends DurableObject {
23329
23373
  migratedToVersion = null;
23330
23374
  eventSockets = /* @__PURE__ */ new Set();
@@ -23893,9 +23937,9 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
23893
23937
  }
23894
23938
  }
23895
23939
  async runMigrations(fromVersion) {
23896
- for (const migration47 of migrations2) {
23897
- if (migration47.version > fromVersion) {
23898
- await migration47.up(this.ctx.storage.sql);
23940
+ for (const migration48 of migrations2) {
23941
+ if (migration48.version > fromVersion) {
23942
+ await migration48.up(this.ctx.storage.sql);
23899
23943
  }
23900
23944
  }
23901
23945
  }
@@ -23937,8 +23981,8 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
23937
23981
  const encryptedEnv = await this.encryptEnvRecord(env);
23938
23982
  const properties = this.withEnvTypeRecord(params.properties ?? null, envTypes);
23939
23983
  await this.ctx.storage.sql.exec(
23940
- `INSERT INTO threads (id, agent_name, user_id, tags, env, tenvs, properties, parent, terminated, created_at)
23941
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, ?)`,
23984
+ `INSERT INTO threads (id, agent_name, user_id, tags, env, tenvs, properties, parent, terminated, created_at, last_activity_at)
23985
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?)`,
23942
23986
  id,
23943
23987
  params.agent_name,
23944
23988
  params.user_id || null,
@@ -23947,6 +23991,7 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
23947
23991
  encryptedEnv,
23948
23992
  properties ? JSON.stringify(properties) : null,
23949
23993
  params.parent ?? null,
23994
+ now,
23950
23995
  now
23951
23996
  );
23952
23997
  const thread = {
@@ -23960,7 +24005,9 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
23960
24005
  properties,
23961
24006
  parent: params.parent ?? null,
23962
24007
  terminated: null,
23963
- created_at: now
24008
+ created_at: now,
24009
+ last_activity_at: now,
24010
+ executing: false
23964
24011
  };
23965
24012
  const eventThread = await this.toThreadEventPayload(thread);
23966
24013
  this.broadcastEvent({ type: "thread_created", thread: eventThread });
@@ -24016,7 +24063,10 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
24016
24063
  */
24017
24064
  async getThread(id) {
24018
24065
  await this.ensureMigrated();
24019
- 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);
24066
+ const cursor = await this.ctx.storage.sql.exec(
24067
+ `SELECT id, agent_name, user_id, tags, env, tenvs, properties, parent, terminated, created_at, last_activity_at, executing_since FROM threads WHERE id = ?`,
24068
+ id
24069
+ );
24020
24070
  const rows = cursor.toArray();
24021
24071
  if (rows.length === 0) {
24022
24072
  return null;
@@ -24036,7 +24086,12 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
24036
24086
  properties,
24037
24087
  parent: row.parent,
24038
24088
  terminated: row.terminated,
24039
- created_at: row.created_at
24089
+ created_at: row.created_at,
24090
+ last_activity_at: row.last_activity_at ?? row.created_at,
24091
+ executing: isThreadExecuting(
24092
+ row.executing_since,
24093
+ Math.floor(Date.now() / 1e3)
24094
+ )
24040
24095
  };
24041
24096
  }
24042
24097
  /**
@@ -24077,6 +24132,7 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
24077
24132
  */
24078
24133
  async listThreads(params) {
24079
24134
  await this.ensureMigrated();
24135
+ const nowSeconds = Math.floor(Date.now() / 1e3);
24080
24136
  const whereClauses = [];
24081
24137
  const args = [];
24082
24138
  if (params?.agent_name) {
@@ -24112,6 +24168,18 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
24112
24168
  whereClauses.push("created_at <= ?");
24113
24169
  args.push(params.endDate);
24114
24170
  }
24171
+ if (params?.executing === true) {
24172
+ whereClauses.push("executing_since IS NOT NULL AND executing_since > ?");
24173
+ args.push(nowSeconds - EXECUTING_STALE_AFTER_SECONDS);
24174
+ } else if (params?.executing === false) {
24175
+ whereClauses.push("(executing_since IS NULL OR executing_since <= ?)");
24176
+ args.push(nowSeconds - EXECUTING_STALE_AFTER_SECONDS);
24177
+ }
24178
+ if (params?.terminated === true) {
24179
+ whereClauses.push("terminated IS NOT NULL");
24180
+ } else if (params?.terminated === false) {
24181
+ whereClauses.push("terminated IS NULL");
24182
+ }
24115
24183
  const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
24116
24184
  const countCursor = await this.ctx.storage.sql.exec(
24117
24185
  `SELECT COUNT(*) as count FROM threads ${whereClause}`,
@@ -24120,10 +24188,11 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
24120
24188
  const total = countCursor.one().count;
24121
24189
  const limit = params?.limit ?? 100;
24122
24190
  const offset = params?.offset ?? 0;
24191
+ const orderByClause = params?.sort === "created" ? "created_at DESC" : params?.sort === "oldest" ? "created_at ASC" : "COALESCE(last_activity_at, created_at) DESC, created_at DESC";
24123
24192
  const cursor = await this.ctx.storage.sql.exec(
24124
- `SELECT id, agent_name, user_id, tags, properties, parent, terminated, created_at FROM threads
24193
+ `SELECT id, agent_name, user_id, tags, properties, parent, terminated, created_at, last_activity_at, executing_since FROM threads
24125
24194
  ${whereClause}
24126
- ORDER BY created_at DESC
24195
+ ORDER BY ${orderByClause}
24127
24196
  LIMIT ? OFFSET ?`,
24128
24197
  ...args,
24129
24198
  limit,
@@ -24143,10 +24212,80 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
24143
24212
  properties: row.properties ? JSON.parse(row.properties) : null,
24144
24213
  parent: row.parent,
24145
24214
  terminated: row.terminated,
24146
- created_at: row.created_at
24215
+ created_at: row.created_at,
24216
+ last_activity_at: row.last_activity_at ?? row.created_at,
24217
+ executing: isThreadExecuting(row.executing_since, nowSeconds)
24147
24218
  }));
24148
24219
  return { threads, total };
24149
24220
  }
24221
+ /**
24222
+ * Distinct owners of threads visible to the caller, with counts, so a thread
24223
+ * list can offer an "owner" filter without exposing the admin-only user
24224
+ * directory (which is unavailable on hosted instances).
24225
+ */
24226
+ async listThreadOwners(params) {
24227
+ await this.ensureMigrated();
24228
+ const whereClause = params?.user_id ? "WHERE t.user_id = ?" : "";
24229
+ const args = params?.user_id ? [params.user_id] : [];
24230
+ const cursor = await this.ctx.storage.sql.exec(
24231
+ `SELECT t.user_id AS user_id,
24232
+ u.username AS username,
24233
+ u.display_name AS display_name,
24234
+ u.email AS email,
24235
+ COUNT(*) AS thread_count
24236
+ FROM threads t
24237
+ LEFT JOIN users u ON u.id = t.user_id
24238
+ ${whereClause}
24239
+ GROUP BY t.user_id
24240
+ ORDER BY thread_count DESC`,
24241
+ ...args
24242
+ );
24243
+ return cursor.toArray();
24244
+ }
24245
+ /**
24246
+ * Record that a thread did work: stamp `last_activity_at` and flip the
24247
+ * executing marker. Activity bubbles up the parent chain so a root thread
24248
+ * sorts by the freshest work anywhere in its subagent tree — otherwise a
24249
+ * long-running subagent would let its parent sink down an activity-sorted
24250
+ * list.
24251
+ */
24252
+ async recordThreadActivity(threadId, options) {
24253
+ await this.ensureMigrated();
24254
+ const now = Math.floor(Date.now() / 1e3);
24255
+ const executing = options?.executing;
24256
+ await this.ctx.storage.sql.exec(
24257
+ `UPDATE threads
24258
+ SET last_activity_at = ?
24259
+ WHERE id IN (
24260
+ WITH RECURSIVE ancestry(id, parent) AS (
24261
+ SELECT id, parent FROM threads WHERE id = ?
24262
+ UNION ALL
24263
+ SELECT t.id, t.parent FROM threads t JOIN ancestry a ON t.id = a.parent
24264
+ )
24265
+ SELECT id FROM ancestry
24266
+ )`,
24267
+ now,
24268
+ threadId
24269
+ );
24270
+ if (executing === true) {
24271
+ await this.ctx.storage.sql.exec(
24272
+ `UPDATE threads SET executing_since = ? WHERE id = ? AND executing_since IS NULL`,
24273
+ now,
24274
+ threadId
24275
+ );
24276
+ } else if (executing === false) {
24277
+ await this.ctx.storage.sql.exec(
24278
+ `UPDATE threads SET executing_since = NULL WHERE id = ?`,
24279
+ threadId
24280
+ );
24281
+ }
24282
+ this.broadcastEvent({
24283
+ type: "thread_activity",
24284
+ threadId,
24285
+ last_activity_at: now,
24286
+ executing: executing === true
24287
+ });
24288
+ }
24150
24289
  /**
24151
24290
  * Delete a thread from the registry.
24152
24291
  */