@standardagents/builder 0.30.2 → 0.32.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 +45 -3
  2. package/dist/built-in-routes.js.map +1 -1
  3. package/dist/client/{ApiKeysView-jhfg43UH.js → ApiKeysView-CxNw8yvZ.js} +1 -1
  4. package/dist/client/CenteredContentView-D4G91Thq.js +1 -0
  5. package/dist/client/{CompositionView-DhkLWmtE.js → CompositionView-BaWfyUW6.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-CVzMoEOi.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-Cz_ZrOAS.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-CQwcIXyb.js} +1 -1
  9. package/dist/client/{JsonViewer-BoHrE76S.js → JsonViewer-Dsl-e2IH.js} +1 -1
  10. package/dist/client/{LoginView-BpR2nMNf.js → LoginView-CX0lmKUS.js} +1 -1
  11. package/dist/client/{MarketplaceView-uUf1JYla.js → MarketplaceView-BJAG7kej.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-B2Yd_pvh.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-CCH8VxwI.js} +1 -1
  14. package/dist/client/{ModelsView-CuM_OieH.js → ModelsView-CGgNBkY-.js} +1 -1
  15. package/dist/client/{PromptEditView-BWjrA2zr.js → PromptEditView-CL2LEH80.js} +1 -1
  16. package/dist/client/{PromptModal-BFxhuG0Z.js → PromptModal-DHHy53-v.js} +1 -1
  17. package/dist/client/{PromptsView-AlGsRB6k.js → PromptsView-BcTfvJdH.js} +1 -1
  18. package/dist/client/{ProvidersView-CbXBs3SH.js → ProvidersView-B2yy_Kbe.js} +1 -1
  19. package/dist/client/ThreadInspectorPane.vue_vue_type_script_setup_true_lang-BCiZyZu-.js +22 -0
  20. package/dist/client/{ThreadsView-rN9rzJi1.js → ThreadsView-BKSAJipV.js} +1 -1
  21. package/dist/client/{ToolsView-0Gpo_RFg.js → ToolsView-DBkare-6.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-yBnliXIU.js} +1 -1
  24. package/dist/client/{VariablesView-C9BB9h34.js → VariablesView-N-VgGqPg.js} +1 -1
  25. package/dist/client/assets/ThreadInspectorPane-DSQsKyG6.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-DANZMMwO.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-1AMgutkG.d.ts} +27 -0
  39. package/dist/index.d.ts +1 -1
  40. package/dist/index.js +203 -16
  41. package/dist/index.js.map +1 -1
  42. package/dist/plugin.js +6 -0
  43. package/dist/plugin.js.map +1 -1
  44. package/dist/runtime.d.ts +48 -2
  45. package/dist/runtime.js +197 -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-1AMgutkG.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-1AMgutkG.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,12 @@ 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;
1816
+ /** Short excerpt of the most recent user message, for thread-list rows. */
1817
+ last_message_preview?: string | null;
1805
1818
  }
1806
1819
  interface PendingSubagentAttachmentRef {
1807
1820
  path: string;
@@ -1910,6 +1923,8 @@ interface Provider$1 {
1910
1923
  sdk: string;
1911
1924
  api_key: string;
1912
1925
  }
1926
+ /** Sort orders supported by `listThreads`. */
1927
+ type ThreadListSort = 'activity' | 'created' | 'oldest';
1913
1928
  declare class DurableAgentBuilder<Env extends AgentBuilderEnv = AgentBuilderEnv> extends DurableObject<Env> {
1914
1929
  private migratedToVersion;
1915
1930
  private eventSockets;
@@ -2024,10 +2039,41 @@ declare class DurableAgentBuilder<Env extends AgentBuilderEnv = AgentBuilderEnv>
2024
2039
  limit?: number;
2025
2040
  offset?: number;
2026
2041
  include_children?: boolean;
2042
+ /** Default `activity`: most recently worked-on threads first. */
2043
+ sort?: ThreadListSort;
2044
+ /** `true` → only threads currently executing; `false` → only idle ones. */
2045
+ executing?: boolean;
2046
+ /** `false` hides terminated threads; `true` shows only terminated ones. */
2047
+ terminated?: boolean;
2027
2048
  }): Promise<{
2028
2049
  threads: ThreadRegistryEntry[];
2029
2050
  total: number;
2030
2051
  }>;
2052
+ /**
2053
+ * Distinct owners of threads visible to the caller, with counts, so a thread
2054
+ * list can offer an "owner" filter without exposing the admin-only user
2055
+ * directory (which is unavailable on hosted instances).
2056
+ */
2057
+ listThreadOwners(params?: {
2058
+ user_id?: string;
2059
+ }): Promise<Array<{
2060
+ user_id: string | null;
2061
+ username: string | null;
2062
+ display_name: string | null;
2063
+ email: string | null;
2064
+ thread_count: number;
2065
+ }>>;
2066
+ /**
2067
+ * Record that a thread did work: stamp `last_activity_at` and flip the
2068
+ * executing marker. Activity bubbles up the parent chain so a root thread
2069
+ * sorts by the freshest work anywhere in its subagent tree — otherwise a
2070
+ * long-running subagent would let its parent sink down an activity-sorted
2071
+ * list.
2072
+ */
2073
+ recordThreadActivity(threadId: string, options?: {
2074
+ executing?: boolean;
2075
+ messagePreview?: string | null;
2076
+ }): Promise<void>;
2031
2077
  /**
2032
2078
  * Delete a thread from the registry.
2033
2079
  */
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 migration49 of migrations) {
17794
+ if (migration49.version > fromVersion) {
17795
+ await migration49.up(this.ctx.storage.sql);
17796
17796
  }
17797
17797
  }
17798
17798
  }
@@ -21186,6 +21186,28 @@ ${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, messagePreview) {
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, {
21201
+ executing,
21202
+ ...messagePreview !== void 0 ? { messagePreview } : {}
21203
+ });
21204
+ } catch (error) {
21205
+ console.error(
21206
+ "[DurableThread] Failed to record thread activity (non-fatal):",
21207
+ error
21208
+ );
21209
+ }
21210
+ }
21189
21211
  /** Arm the standing watchdog for an execution start. One pending row max. */
21190
21212
  async armExecutionWatchdog(threadId) {
21191
21213
  try {
@@ -21372,6 +21394,7 @@ ${resultContent}${attachmentSummary}`;
21372
21394
  }
21373
21395
  this.isExecuting = false;
21374
21396
  this.currentAbortController = null;
21397
+ await this.recordRegistryActivity(threadId, false);
21375
21398
  tookOver = true;
21376
21399
  } else if (incomplete.length > 0 && silenceMs <= ceiling) {
21377
21400
  return report("observing", "none");
@@ -21575,6 +21598,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
21575
21598
  this.isExecuting = true;
21576
21599
  this.currentAbortController = new AbortController();
21577
21600
  await this.armExecutionWatchdog(threadId);
21601
+ await this.recordRegistryActivity(threadId, true);
21578
21602
  try {
21579
21603
  const agentDef = await this.loadAgent(agentName);
21580
21604
  const packageId = agentDef.__package?.packageId;
@@ -21706,6 +21730,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
21706
21730
  this.isExecuting = false;
21707
21731
  this.currentAbortController = null;
21708
21732
  await this.disarmExecutionWatchdog(threadId);
21733
+ await this.recordRegistryActivity(threadId, false);
21709
21734
  }
21710
21735
  }
21711
21736
  /**
@@ -21766,6 +21791,11 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
21766
21791
  this.isExecuting = true;
21767
21792
  this.currentAbortController = new AbortController();
21768
21793
  await this.armExecutionWatchdog(threadId);
21794
+ await this.recordRegistryActivity(
21795
+ threadId,
21796
+ true,
21797
+ role === "user" ? content : void 0
21798
+ );
21769
21799
  try {
21770
21800
  const agentBuilderId = this.env.AGENT_BUILDER.idFromName("singleton");
21771
21801
  const agentBuilder = this.env.AGENT_BUILDER.get(agentBuilderId);
@@ -21905,6 +21935,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
21905
21935
  this.isExecuting = false;
21906
21936
  this.currentAbortController = null;
21907
21937
  await this.disarmExecutionWatchdog(threadId);
21938
+ await this.recordRegistryActivity(threadId, false);
21908
21939
  }
21909
21940
  }
21910
21941
  /**
@@ -21922,6 +21953,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
21922
21953
  this.isExecuting = true;
21923
21954
  this.currentAbortController = new AbortController();
21924
21955
  await this.armExecutionWatchdog(threadId);
21956
+ await this.recordRegistryActivity(threadId, true);
21925
21957
  try {
21926
21958
  const agentBuilderId = this.env.AGENT_BUILDER.idFromName("singleton");
21927
21959
  const agentBuilder = this.env.AGENT_BUILDER.get(agentBuilderId);
@@ -22031,6 +22063,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
22031
22063
  this.isExecuting = false;
22032
22064
  this.currentAbortController = null;
22033
22065
  await this.disarmExecutionWatchdog(threadId);
22066
+ await this.recordRegistryActivity(threadId, false);
22034
22067
  const latestQueuedSide = await this.getLatestQueuedMessageSide();
22035
22068
  if (latestQueuedSide) {
22036
22069
  await this.ensureQueuedMessageExecution(threadId, latestQueuedSide);
@@ -23124,9 +23157,43 @@ var migration46 = {
23124
23157
  }
23125
23158
  };
23126
23159
 
23160
+ // src/durable-objects/agentbuilder-migrations/0013_add_thread_activity.ts
23161
+ var migration47 = {
23162
+ version: 13,
23163
+ async up(sql) {
23164
+ sql.exec(`ALTER TABLE threads ADD COLUMN last_activity_at INTEGER`);
23165
+ sql.exec(`ALTER TABLE threads ADD COLUMN executing_since INTEGER`);
23166
+ sql.exec(
23167
+ `CREATE INDEX IF NOT EXISTS idx_threads_last_activity_at ON threads(last_activity_at)`
23168
+ );
23169
+ sql.exec(`UPDATE threads SET last_activity_at = created_at`);
23170
+ sql.exec(`
23171
+ INSERT OR REPLACE INTO _metadata (key, value) VALUES ('schema_version', '13')
23172
+ `);
23173
+ }
23174
+ };
23175
+
23176
+ // src/durable-objects/agentbuilder-migrations/0014_thread_activity_scale.ts
23177
+ var migration48 = {
23178
+ version: 14,
23179
+ async up(sql) {
23180
+ sql.exec(`ALTER TABLE threads ADD COLUMN last_message_preview TEXT`);
23181
+ sql.exec(
23182
+ `CREATE INDEX IF NOT EXISTS idx_threads_executing_since
23183
+ ON threads(executing_since) WHERE executing_since IS NOT NULL`
23184
+ );
23185
+ sql.exec(
23186
+ `UPDATE threads SET last_activity_at = created_at WHERE last_activity_at IS NULL`
23187
+ );
23188
+ sql.exec(`
23189
+ INSERT OR REPLACE INTO _metadata (key, value) VALUES ('schema_version', '14')
23190
+ `);
23191
+ }
23192
+ };
23193
+
23127
23194
  // 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;
23195
+ var migrations2 = [migration35, migration36, migration37, migration38, migration39, migration40, migration41, migration42, migration43, migration44, migration45, migration46, migration47, migration48];
23196
+ var LATEST_SCHEMA_VERSION2 = 14;
23130
23197
 
23131
23198
  // src/utils/crypto.ts
23132
23199
  var CryptoUtil = class {
@@ -23325,6 +23392,14 @@ function parseStoredPermissions(value) {
23325
23392
  return null;
23326
23393
  }
23327
23394
  }
23395
+ var EXECUTING_STALE_AFTER_SECONDS = 15 * 60;
23396
+ var THREAD_PREVIEW_MAX_CHARS = 140;
23397
+ var buildThreadPreview = (content) => {
23398
+ const collapsed = content.replace(/\s+/g, " ").trim();
23399
+ if (!collapsed) return null;
23400
+ return collapsed.length > THREAD_PREVIEW_MAX_CHARS ? `${collapsed.slice(0, THREAD_PREVIEW_MAX_CHARS - 1)}\u2026` : collapsed;
23401
+ };
23402
+ var isThreadExecuting = (executingSince, nowSeconds) => executingSince != null && nowSeconds - executingSince < EXECUTING_STALE_AFTER_SECONDS;
23328
23403
  var DurableAgentBuilder = class extends DurableObject {
23329
23404
  migratedToVersion = null;
23330
23405
  eventSockets = /* @__PURE__ */ new Set();
@@ -23893,9 +23968,9 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
23893
23968
  }
23894
23969
  }
23895
23970
  async runMigrations(fromVersion) {
23896
- for (const migration47 of migrations2) {
23897
- if (migration47.version > fromVersion) {
23898
- await migration47.up(this.ctx.storage.sql);
23971
+ for (const migration49 of migrations2) {
23972
+ if (migration49.version > fromVersion) {
23973
+ await migration49.up(this.ctx.storage.sql);
23899
23974
  }
23900
23975
  }
23901
23976
  }
@@ -23937,8 +24012,8 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
23937
24012
  const encryptedEnv = await this.encryptEnvRecord(env);
23938
24013
  const properties = this.withEnvTypeRecord(params.properties ?? null, envTypes);
23939
24014
  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, ?)`,
24015
+ `INSERT INTO threads (id, agent_name, user_id, tags, env, tenvs, properties, parent, terminated, created_at, last_activity_at)
24016
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?)`,
23942
24017
  id,
23943
24018
  params.agent_name,
23944
24019
  params.user_id || null,
@@ -23947,6 +24022,7 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
23947
24022
  encryptedEnv,
23948
24023
  properties ? JSON.stringify(properties) : null,
23949
24024
  params.parent ?? null,
24025
+ now,
23950
24026
  now
23951
24027
  );
23952
24028
  const thread = {
@@ -23960,7 +24036,9 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
23960
24036
  properties,
23961
24037
  parent: params.parent ?? null,
23962
24038
  terminated: null,
23963
- created_at: now
24039
+ created_at: now,
24040
+ last_activity_at: now,
24041
+ executing: false
23964
24042
  };
23965
24043
  const eventThread = await this.toThreadEventPayload(thread);
23966
24044
  this.broadcastEvent({ type: "thread_created", thread: eventThread });
@@ -24016,7 +24094,10 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
24016
24094
  */
24017
24095
  async getThread(id) {
24018
24096
  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);
24097
+ const cursor = await this.ctx.storage.sql.exec(
24098
+ `SELECT id, agent_name, user_id, tags, env, tenvs, properties, parent, terminated, created_at, last_activity_at, executing_since, last_message_preview FROM threads WHERE id = ?`,
24099
+ id
24100
+ );
24020
24101
  const rows = cursor.toArray();
24021
24102
  if (rows.length === 0) {
24022
24103
  return null;
@@ -24036,7 +24117,13 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
24036
24117
  properties,
24037
24118
  parent: row.parent,
24038
24119
  terminated: row.terminated,
24039
- created_at: row.created_at
24120
+ created_at: row.created_at,
24121
+ last_activity_at: row.last_activity_at ?? row.created_at,
24122
+ executing: isThreadExecuting(
24123
+ row.executing_since,
24124
+ Math.floor(Date.now() / 1e3)
24125
+ ),
24126
+ last_message_preview: row.last_message_preview
24040
24127
  };
24041
24128
  }
24042
24129
  /**
@@ -24077,6 +24164,7 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
24077
24164
  */
24078
24165
  async listThreads(params) {
24079
24166
  await this.ensureMigrated();
24167
+ const nowSeconds = Math.floor(Date.now() / 1e3);
24080
24168
  const whereClauses = [];
24081
24169
  const args = [];
24082
24170
  if (params?.agent_name) {
@@ -24112,6 +24200,18 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
24112
24200
  whereClauses.push("created_at <= ?");
24113
24201
  args.push(params.endDate);
24114
24202
  }
24203
+ if (params?.executing === true) {
24204
+ whereClauses.push("executing_since IS NOT NULL AND executing_since > ?");
24205
+ args.push(nowSeconds - EXECUTING_STALE_AFTER_SECONDS);
24206
+ } else if (params?.executing === false) {
24207
+ whereClauses.push("(executing_since IS NULL OR executing_since <= ?)");
24208
+ args.push(nowSeconds - EXECUTING_STALE_AFTER_SECONDS);
24209
+ }
24210
+ if (params?.terminated === true) {
24211
+ whereClauses.push("terminated IS NOT NULL");
24212
+ } else if (params?.terminated === false) {
24213
+ whereClauses.push("terminated IS NULL");
24214
+ }
24115
24215
  const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
24116
24216
  const countCursor = await this.ctx.storage.sql.exec(
24117
24217
  `SELECT COUNT(*) as count FROM threads ${whereClause}`,
@@ -24120,10 +24220,11 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
24120
24220
  const total = countCursor.one().count;
24121
24221
  const limit = params?.limit ?? 100;
24122
24222
  const offset = params?.offset ?? 0;
24223
+ const orderByClause = params?.sort === "created" ? "created_at DESC" : params?.sort === "oldest" ? "created_at ASC" : "last_activity_at DESC, created_at DESC";
24123
24224
  const cursor = await this.ctx.storage.sql.exec(
24124
- `SELECT id, agent_name, user_id, tags, properties, parent, terminated, created_at FROM threads
24225
+ `SELECT id, agent_name, user_id, tags, properties, parent, terminated, created_at, last_activity_at, executing_since, last_message_preview FROM threads
24125
24226
  ${whereClause}
24126
- ORDER BY created_at DESC
24227
+ ORDER BY ${orderByClause}
24127
24228
  LIMIT ? OFFSET ?`,
24128
24229
  ...args,
24129
24230
  limit,
@@ -24143,10 +24244,90 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
24143
24244
  properties: row.properties ? JSON.parse(row.properties) : null,
24144
24245
  parent: row.parent,
24145
24246
  terminated: row.terminated,
24146
- created_at: row.created_at
24247
+ created_at: row.created_at,
24248
+ last_activity_at: row.last_activity_at ?? row.created_at,
24249
+ executing: isThreadExecuting(row.executing_since, nowSeconds),
24250
+ last_message_preview: row.last_message_preview
24147
24251
  }));
24148
24252
  return { threads, total };
24149
24253
  }
24254
+ /**
24255
+ * Distinct owners of threads visible to the caller, with counts, so a thread
24256
+ * list can offer an "owner" filter without exposing the admin-only user
24257
+ * directory (which is unavailable on hosted instances).
24258
+ */
24259
+ async listThreadOwners(params) {
24260
+ await this.ensureMigrated();
24261
+ const whereClause = params?.user_id ? "WHERE t.user_id = ?" : "";
24262
+ const args = params?.user_id ? [params.user_id] : [];
24263
+ const cursor = await this.ctx.storage.sql.exec(
24264
+ `SELECT t.user_id AS user_id,
24265
+ u.username AS username,
24266
+ u.display_name AS display_name,
24267
+ u.email AS email,
24268
+ COUNT(*) AS thread_count
24269
+ FROM threads t
24270
+ LEFT JOIN users u ON u.id = t.user_id
24271
+ ${whereClause}
24272
+ GROUP BY t.user_id
24273
+ ORDER BY thread_count DESC`,
24274
+ ...args
24275
+ );
24276
+ return cursor.toArray();
24277
+ }
24278
+ /**
24279
+ * Record that a thread did work: stamp `last_activity_at` and flip the
24280
+ * executing marker. Activity bubbles up the parent chain so a root thread
24281
+ * sorts by the freshest work anywhere in its subagent tree — otherwise a
24282
+ * long-running subagent would let its parent sink down an activity-sorted
24283
+ * list.
24284
+ */
24285
+ async recordThreadActivity(threadId, options) {
24286
+ await this.ensureMigrated();
24287
+ const now = Math.floor(Date.now() / 1e3);
24288
+ const executing = options?.executing;
24289
+ const preview = typeof options?.messagePreview === "string" ? buildThreadPreview(options.messagePreview) : void 0;
24290
+ await this.ctx.storage.sql.exec(
24291
+ `UPDATE threads
24292
+ SET last_activity_at = ?
24293
+ WHERE id IN (
24294
+ WITH RECURSIVE ancestry(id, parent) AS (
24295
+ SELECT id, parent FROM threads WHERE id = ?
24296
+ UNION ALL
24297
+ SELECT t.id, t.parent FROM threads t JOIN ancestry a ON t.id = a.parent
24298
+ )
24299
+ SELECT id FROM ancestry
24300
+ )`,
24301
+ now,
24302
+ threadId
24303
+ );
24304
+ if (executing === true) {
24305
+ await this.ctx.storage.sql.exec(
24306
+ `UPDATE threads SET executing_since = ? WHERE id = ? AND executing_since IS NULL`,
24307
+ now,
24308
+ threadId
24309
+ );
24310
+ } else if (executing === false) {
24311
+ await this.ctx.storage.sql.exec(
24312
+ `UPDATE threads SET executing_since = NULL WHERE id = ?`,
24313
+ threadId
24314
+ );
24315
+ }
24316
+ if (preview !== void 0) {
24317
+ await this.ctx.storage.sql.exec(
24318
+ `UPDATE threads SET last_message_preview = ? WHERE id = ?`,
24319
+ preview,
24320
+ threadId
24321
+ );
24322
+ }
24323
+ this.broadcastEvent({
24324
+ type: "thread_activity",
24325
+ threadId,
24326
+ last_activity_at: now,
24327
+ executing: executing === true,
24328
+ ...preview !== void 0 ? { last_message_preview: preview } : {}
24329
+ });
24330
+ }
24150
24331
  /**
24151
24332
  * Delete a thread from the registry.
24152
24333
  */