@agent-team-foundation/first-tree-hub 0.12.3 → 0.12.4

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.
@@ -2,10 +2,10 @@ import { a as __toCommonJS, o as __toESM, t as __commonJSMin } from "./chunk-BSw
2
2
  import { A as FIRST_TREE_HUB_ATTR, C as stampOrgScope, D as untrustedAttrs, E as startWsConnectionSpan, M as require_pino, O as withSpan, S as stampChatResource, _ as rootLogger$1, a as buildRateLimitError, c as currentTraceId, g as reportErrorToRoot, i as bodyCaptureOnSendHook, j as redactUrl, k as withWsMessageSpan, l as decodeJwtForTrace, m as observabilityPlugin, n as applyLoggerConfig, o as classifyJoseError, r as attachRequestContext, s as createLogger$1, t as adapterAttrs, u as endWsConnectionSpan, x as stampAgentResource, y as setWsConnectionAttrs } from "./observability-BAScT_5S-BcW9HgkG.mjs";
3
3
  import { C as resetConfigMeta, E as setConfigValue, S as resetConfig, T as serverConfigSchema, b as migrateLegacyHome, c as resolveServerUrl, d as DEFAULT_CONFIG_DIR, f as DEFAULT_DATA_DIR$1, g as collectMissingPrompts, h as clientConfigSchema, i as ensureFreshAccessToken, l as saveAgentConfig, m as agentConfigSchema, o as loadCredentials, p as DEFAULT_HOME_DIR$1, u as saveCredentials, v as initConfig, w as resolveConfigReadonly, y as loadAgents } from "./bootstrap-C_K2CKXC.mjs";
4
4
  import { a as print, i as blank, n as CLI_USER_AGENT, r as COMMAND_VERSION, s as status, t as cliFetch } from "./cli-fetch--tiwKm5S.mjs";
5
- import { $ as loginSchema, A as createAgentSchema, At as updateTaskStatusSchema, B as githubCallbackQuerySchema, C as agentTypeSchema$1, Ct as updateAdapterConfigSchema, D as contextTreeSnapshotSchema, Dt as updateClientCapabilitiesSchema, E as connectTokenExchangeSchema, Et as updateChatSchema, F as createTaskSchema, G as inboxDeliverFrameSchema$1, H as githubStartQuerySchema, I as defaultRuntimeConfigPayload, J as isRedactedEnvValue, K as inboxPollQuerySchema, L as delegateFeishuUserSchema, M as createMeChatSchema, N as createMemberSchema, O as createAdapterConfigSchema, Ot as updateMemberSchema, P as createOrgFromMeSchema, Q as listMeChatsQuerySchema, R as dryRunAgentRuntimeConfigSchema, S as agentRuntimeConfigPayloadSchema$1, St as taskListQuerySchema, T as clientRegisterSchema, Tt as updateAgentSchema, U as imageInlineContentSchema, V as githubDevCallbackQuerySchema, W as inboxAckFrameSchema, X as joinByInvitationSchema, Y as isReservedAgentName$1, Z as linkTaskChatSchema, _ as addParticipantSchema, _t as sessionEventSchema$1, a as AGENT_STATUSES, b as agentBindRequestSchema, bt as stripCode, ct as refreshTokenSchema, d as TASK_CREATOR_TYPES, et as messageSourceSchema$1, f as TASK_HEALTH_SIGNALS, ft as selfServiceFeishuBotSchema, g as addMeChatParticipantsSchema, gt as sessionEventMessageSchema, h as WS_AUTH_FRAME_TIMEOUT_MS, ht as sessionCompletionMessageSchema, i as AGENT_SOURCES, it as patchOnboardingSchema, j as createChatSchema, jt as wsAuthFrameSchema, k as createAdapterMappingSchema, kt as updateOrganizationSchema, l as MENTION_REGEX, lt as runtimeStateMessageSchema, m as TASK_TERMINAL_STATUSES, mt as sendToAgentSchema, n as AGENT_NAME_REGEX$1, nt as onboardingEventSchema, o as AGENT_TYPES, p as TASK_STATUSES, pt as sendMessageSchema, q as isOrgSettingNamespace, r as AGENT_SELECTOR_HEADER$1, rt as paginationQuerySchema, s as AGENT_VISIBILITY, st as rebindAgentSchema, t as AGENT_BIND_REJECT_REASONS, tt as notificationQuerySchema, u as ORG_SETTINGS_NAMESPACES$1, ut as safeRedirectPath, v as adminCreateTaskSchema, vt as sessionReconcileRequestSchema, wt as updateAgentRuntimeConfigSchema, x as agentPinnedMessageSchema$1, xt as submitQuestionAnswerSchema, y as adminUpdateTaskSchema, yt as sessionStateMessageSchema } from "./dist-DHHd2dar.mjs";
5
+ import { $ as runtimeStateMessageSchema, B as isReservedAgentName$1, C as createChatSchema, D as defaultRuntimeConfigPayload, E as createOrgFromMeSchema, F as inboxAckFrameSchema, G as notificationQuerySchema, H as listMeChatsQuerySchema, I as inboxDeliverFrameSchema$1, J as patchOnboardingSchema, K as onboardingEventSchema, L as inboxPollQuerySchema, M as githubDevCallbackQuerySchema, N as githubStartQuerySchema, O as delegateFeishuUserSchema, P as imageInlineContentSchema, Q as refreshTokenSchema, R as isOrgSettingNamespace, S as createAgentSchema, T as createMemberSchema, U as loginSchema, V as joinByInvitationSchema, W as messageSourceSchema$1, Z as rebindAgentSchema, _ as clientRegisterSchema, _t as updateMemberSchema, a as AGENT_VISIBILITY, at as sessionCompletionMessageSchema, b as createAdapterConfigSchema, c as ORG_SETTINGS_NAMESPACES$1, ct as sessionReconcileRequestSchema, d as addParticipantSchema, dt as submitQuestionAnswerSchema, et as safeRedirectPath, f as agentBindRequestSchema, ft as updateAdapterConfigSchema, gt as updateClientCapabilitiesSchema, h as agentTypeSchema$1, ht as updateChatSchema, i as AGENT_STATUSES, it as sendToAgentSchema, j as githubCallbackQuerySchema, k as dryRunAgentRuntimeConfigSchema, l as WS_AUTH_FRAME_TIMEOUT_MS, lt as sessionStateMessageSchema, m as agentRuntimeConfigPayloadSchema$1, mt as updateAgentSchema, n as AGENT_NAME_REGEX$1, nt as selfServiceFeishuBotSchema, ot as sessionEventMessageSchema, p as agentPinnedMessageSchema$1, pt as updateAgentRuntimeConfigSchema, q as paginationQuerySchema, r as AGENT_SELECTOR_HEADER$1, rt as sendMessageSchema, s as MENTION_REGEX, st as sessionEventSchema$1, t as AGENT_BIND_REJECT_REASONS, u as addMeChatParticipantsSchema, ut as stripCode, v as connectTokenExchangeSchema, vt as updateOrganizationSchema, w as createMeChatSchema, x as createAdapterMappingSchema, y as contextTreeSnapshotSchema, yt as wsAuthFrameSchema, z as isRedactedEnvValue } from "./dist-CMhywpXB.mjs";
6
6
  import { a as ConflictError, c as UnauthorizedError, i as ClientUserMismatchError$1, l as organizations, n as BadRequestError, o as ForbiddenError, r as ClientOrgMismatchError$1, s as NotFoundError, t as AppError, u as users } from "./errors-CF5evtJt-B0NTIVPt.mjs";
7
7
  import { n as init_esm, r as trace, t as esm_exports } from "./esm-iadMkGbV.mjs";
8
- import { $ as pendingQuestions, A as heartbeatClient, B as listAgentsWithRuntime, C as findOrCreateDirectChat, D as getClient, E as getChatDetail, F as joinChat, G as listClientsForOrgAdmin, H as listChats, I as leaveAsParticipant, J as markStaleAgents, K as listMessages, L as leaveChat, M as inboxEntries, N as invalidateChatAudience, O as getOnlineCount, P as joinAsParticipant, Q as notifyRecipients, R as listActiveAgentsPinnedToClient, S as ensureParticipant$1, T as getCachedAudience, U as listChatsForMember, V as listChatParticipantsWithNames, W as listClients, X as members, Y as markSupersededByChat, Z as messages, _ as createNotifier, _t as updateClientCapabilities, a as agents, at as removeParticipant, b as editMessage, c as bindAgent, ct as retireClient, d as chats, dt as serverInstances, et as recomputeChatWatchers, f as claimClient, ft as setOffline, g as createChat, gt as unbindAgent, h as clients, ht as touchAgent, i as agentVisibilityCondition, it as registerClient, j as heartbeatInstance, k as getPresence, l as chatParticipants, lt as sendMessage, m as cleanupStalePresence, mt as submitAnswer, n as agentChatSessions, nt as recomputeWatchersForMember, o as assertClientOwner, ot as resetActivity, p as cleanupStaleClients, pt as setRuntimeState, r as agentPresence, rt as registerChatMessageDispatcher, s as assertParticipant, st as resolveChatMembership, t as addParticipant, tt as recomputeWatchersForAgent, u as chatSubscriptions, ut as sendToAgent$1, v as deriveAuthState, vt as upsertSessionState, w as getActivityOverview, x as ensureCanJoin, y as disconnectClient, z as listAgentsManagedByUser } from "./client-D1TDiik_-gxtXN9bj.mjs";
8
+ import { $ as pendingQuestions, A as heartbeatClient, B as listAgentsWithRuntime, C as findOrCreateDirectChat, D as getClient, E as getChatDetail, F as joinChat, G as listClientsForOrgAdmin, H as listChats, I as leaveAsParticipant, J as markStaleAgents, K as listMessages, L as leaveChat, M as inboxEntries, N as invalidateChatAudience, O as getOnlineCount, P as joinAsParticipant, Q as notifyRecipients, R as listActiveAgentsPinnedToClient, S as ensureParticipant$1, T as getCachedAudience, U as listChatsForMember, V as listChatParticipantsWithNames, W as listClients, X as members, Y as markSupersededByChat, Z as messages, _ as createNotifier, _t as updateClientCapabilities, a as agents, at as removeParticipant, b as editMessage, c as bindAgent, ct as retireClient, d as chats, dt as serverInstances, et as recomputeChatWatchers, f as claimClient, ft as setOffline, g as createChat, gt as unbindAgent, h as clients, ht as touchAgent, i as agentVisibilityCondition, it as registerClient, j as heartbeatInstance, k as getPresence, l as chatParticipants, lt as sendMessage, m as cleanupStalePresence, mt as submitAnswer, n as agentChatSessions, nt as recomputeWatchersForMember, o as assertClientOwner, ot as resetActivity, p as cleanupStaleClients, pt as setRuntimeState, r as agentPresence, rt as registerChatMessageDispatcher, s as assertParticipant, st as resolveChatMembership, t as addParticipant, tt as recomputeWatchersForAgent, u as chatSubscriptions, ut as sendToAgent$1, v as deriveAuthState, vt as upsertSessionState, w as getActivityOverview, x as ensureCanJoin, y as disconnectClient, z as listAgentsManagedByUser } from "./client-D1TDiik_-NV_lkhfI.mjs";
9
9
  import { a as invitationRedemptions, c as recordRedemption, i as getActiveInvitation, l as rotateInvitation, n as ensureActiveInvitation, o as invitations, r as findActiveByToken, t as buildInviteUrl, u as uuidv7 } from "./invitation-Bg0TRiyx-BsZH4GCS.mjs";
10
10
  import { createRequire } from "node:module";
11
11
  import { ZodError, z } from "zod";
@@ -1015,7 +1015,6 @@ const messageFormatSchema = z.enum([
1015
1015
  "card",
1016
1016
  "reference",
1017
1017
  "file",
1018
- "task",
1019
1018
  "question",
1020
1019
  "question_answer"
1021
1020
  ]);
@@ -1199,9 +1198,7 @@ const meChatRowSchema = z.object({
1199
1198
  lastMessageAt: z.string().nullable(),
1200
1199
  lastMessagePreview: z.string().nullable(),
1201
1200
  unreadMentionCount: z.number().int(),
1202
- canReply: z.boolean(),
1203
- taskId: z.string().nullable(),
1204
- taskStatus: z.string().nullable()
1201
+ canReply: z.boolean()
1205
1202
  });
1206
1203
  z.object({
1207
1204
  rows: z.array(meChatRowSchema),
@@ -1677,94 +1674,6 @@ z.object({
1677
1674
  totalMessages: z.number(),
1678
1675
  byOrganization: z.array(orgStatsSchema)
1679
1676
  });
1680
- const taskStatusSchema = z.enum([
1681
- "pending",
1682
- "assigned",
1683
- "working",
1684
- "completed",
1685
- "failed",
1686
- "cancelled"
1687
- ]);
1688
- const taskCreatorTypeSchema = z.enum(["agent", "admin"]);
1689
- const taskMessageEventSchema = z.enum([
1690
- "assigned",
1691
- "status_changed",
1692
- "cancelled"
1693
- ]);
1694
- z.object({
1695
- taskId: z.string(),
1696
- event: taskMessageEventSchema,
1697
- title: z.string(),
1698
- body: z.string().default(""),
1699
- status: taskStatusSchema,
1700
- fromStatus: taskStatusSchema.optional(),
1701
- originRef: z.string().nullable().optional()
1702
- });
1703
- z.object({
1704
- title: z.string().min(1).max(500),
1705
- body: z.string().optional(),
1706
- assigneeAgentId: z.string().optional(),
1707
- originRef: z.string().max(500).optional(),
1708
- metadata: z.record(z.string(), z.unknown()).optional()
1709
- }).extend({ organizationId: z.string().optional() });
1710
- z.object({
1711
- status: z.enum([
1712
- "working",
1713
- "completed",
1714
- "failed"
1715
- ]),
1716
- result: z.string().optional()
1717
- });
1718
- z.object({
1719
- assigneeAgentId: z.string().nullable().optional(),
1720
- status: taskStatusSchema.optional(),
1721
- result: z.string().optional()
1722
- });
1723
- z.object({ chatId: z.string().min(1) });
1724
- const taskSchema = z.object({
1725
- id: z.string(),
1726
- organizationId: z.string(),
1727
- title: z.string(),
1728
- body: z.string(),
1729
- status: taskStatusSchema,
1730
- assigneeAgentId: z.string().nullable(),
1731
- createdByType: taskCreatorTypeSchema,
1732
- createdById: z.string(),
1733
- originRef: z.string().nullable(),
1734
- result: z.string().nullable(),
1735
- metadata: z.record(z.string(), z.unknown()),
1736
- createdAt: z.string(),
1737
- updatedAt: z.string(),
1738
- cancelledAt: z.string().nullable(),
1739
- cancelledByType: taskCreatorTypeSchema.nullable(),
1740
- cancelledById: z.string().nullable()
1741
- });
1742
- const taskChatLinkSchema = z.object({
1743
- taskId: z.string(),
1744
- chatId: z.string(),
1745
- linkedByAgentId: z.string().nullable(),
1746
- linkedAt: z.string()
1747
- });
1748
- taskSchema.extend({ chats: z.array(taskChatLinkSchema) });
1749
- z.object({
1750
- status: taskStatusSchema.optional(),
1751
- assigneeAgentId: z.string().optional(),
1752
- originRef: z.string().optional(),
1753
- limit: z.coerce.number().int().min(1).max(100).default(20),
1754
- cursor: z.string().optional()
1755
- });
1756
- const taskHealthSignalSchema = z.enum([
1757
- "normal",
1758
- "idle_island",
1759
- "awaiting_reply",
1760
- "no_chat",
1761
- "not_applicable"
1762
- ]);
1763
- z.object({
1764
- taskId: z.string(),
1765
- signal: taskHealthSignalSchema,
1766
- reason: z.string()
1767
- });
1768
1677
  const userStatusSchema = z.enum(["active", "suspended"]);
1769
1678
  z.object({
1770
1679
  id: z.string(),
@@ -2876,7 +2785,126 @@ function getHandlerFactory(type) {
2876
2785
  }
2877
2786
  return factory;
2878
2787
  }
2879
- join(DEFAULT_DATA_DIR, "context-tree");
2788
+ const CONTEXT_TREE_DIR = join(DEFAULT_DATA_DIR, "context-tree");
2789
+ /**
2790
+ * Sync the shared Context Tree git clone.
2791
+ *
2792
+ * Clones on first run, pulls on subsequent runs.
2793
+ * Returns the binding on success, null on failure (graceful degradation).
2794
+ */
2795
+ async function syncContextTree(serverUrl, getAccessToken, log, userAgent) {
2796
+ try {
2797
+ execFileSync("git", ["--version"], { stdio: "ignore" });
2798
+ } catch {
2799
+ log("Context Tree sync skipped: git is not installed");
2800
+ return null;
2801
+ }
2802
+ let repo;
2803
+ let branch;
2804
+ try {
2805
+ const config = await new FirstTreeHubSDK({
2806
+ serverUrl,
2807
+ getAccessToken,
2808
+ userAgent
2809
+ }).getContextTreeConfig();
2810
+ if (!config.repo) {
2811
+ log("Context Tree sync skipped: not configured on server");
2812
+ return null;
2813
+ }
2814
+ repo = config.repo;
2815
+ branch = config.branch ?? "main";
2816
+ } catch (err) {
2817
+ log(`Context Tree sync skipped: failed to fetch config from server (${err instanceof Error ? err.message : String(err)})`);
2818
+ return null;
2819
+ }
2820
+ try {
2821
+ if (existsSync(join(CONTEXT_TREE_DIR, ".git"))) {
2822
+ if (execFileSync("git", [
2823
+ "rev-parse",
2824
+ "--abbrev-ref",
2825
+ "HEAD"
2826
+ ], {
2827
+ cwd: CONTEXT_TREE_DIR,
2828
+ encoding: "utf-8",
2829
+ timeout: 5e3
2830
+ }).trim() !== branch) {
2831
+ execFileSync("git", ["checkout", branch], {
2832
+ cwd: CONTEXT_TREE_DIR,
2833
+ stdio: "pipe",
2834
+ timeout: 1e4
2835
+ });
2836
+ log(`Context Tree switched to branch ${branch}`);
2837
+ }
2838
+ execFileSync("git", ["pull", "--ff-only"], {
2839
+ cwd: CONTEXT_TREE_DIR,
2840
+ stdio: "pipe",
2841
+ timeout: 3e4
2842
+ });
2843
+ log(`Context Tree updated (pull)`);
2844
+ } else {
2845
+ mkdirSync(CONTEXT_TREE_DIR, { recursive: true });
2846
+ execFileSync("git", [
2847
+ "clone",
2848
+ "--branch",
2849
+ branch,
2850
+ "--single-branch",
2851
+ repo,
2852
+ CONTEXT_TREE_DIR
2853
+ ], {
2854
+ stdio: "pipe",
2855
+ timeout: 6e4
2856
+ });
2857
+ log(`Context Tree cloned from ${repo} (branch: ${branch})`);
2858
+ }
2859
+ return {
2860
+ path: CONTEXT_TREE_DIR,
2861
+ repoUrl: repo,
2862
+ branch
2863
+ };
2864
+ } catch (err) {
2865
+ const msg = err instanceof Error ? err.message : String(err);
2866
+ log(`Context Tree sync failed: ${msg}`);
2867
+ log("Check that git credentials (SSH key or credential helper) are configured for this repo");
2868
+ if ((msg.includes("cannot fast-forward") || msg.includes("not possible to fast-forward") || msg.includes("CONFLICT")) && existsSync(join(CONTEXT_TREE_DIR, ".git"))) {
2869
+ log("Diverged history detected, attempting fresh clone...");
2870
+ try {
2871
+ rmSync(CONTEXT_TREE_DIR, {
2872
+ recursive: true,
2873
+ force: true
2874
+ });
2875
+ mkdirSync(CONTEXT_TREE_DIR, { recursive: true });
2876
+ execFileSync("git", [
2877
+ "clone",
2878
+ "--branch",
2879
+ branch,
2880
+ "--single-branch",
2881
+ repo,
2882
+ CONTEXT_TREE_DIR
2883
+ ], {
2884
+ stdio: "pipe",
2885
+ timeout: 6e4
2886
+ });
2887
+ log("Context Tree re-cloned successfully");
2888
+ return {
2889
+ path: CONTEXT_TREE_DIR,
2890
+ repoUrl: repo,
2891
+ branch
2892
+ };
2893
+ } catch {
2894
+ log("Context Tree re-clone also failed, continuing without context");
2895
+ }
2896
+ }
2897
+ if (existsSync(join(CONTEXT_TREE_DIR, ".git"))) {
2898
+ log("Using existing Context Tree clone despite sync failure");
2899
+ return {
2900
+ path: CONTEXT_TREE_DIR,
2901
+ repoUrl: repo,
2902
+ branch
2903
+ };
2904
+ }
2905
+ return null;
2906
+ }
2907
+ }
2880
2908
  /**
2881
2909
  * Marker file written into every workspace so the Codex CLI's project-root
2882
2910
  * detection (configured via `project_root_markers: ["first-tree-workspace"]`)
@@ -7213,6 +7241,14 @@ var ClientRuntime = class {
7213
7241
  watcher = null;
7214
7242
  debounceTimer = null;
7215
7243
  /**
7244
+ * Per-org Context Tree binding resolved at `start()`. Threaded through every
7245
+ * `slot.start()` so handlers can copy `AGENT.md` / root `NODE.md` into the
7246
+ * agent workspace's `.agent/context/` and install the first-tree skill.
7247
+ * `null` when the user has no primary org, the org has no tree configured,
7248
+ * or git sync failed — handlers degrade gracefully (empty context dir).
7249
+ */
7250
+ contextTreeBinding = null;
7251
+ /**
7216
7252
  * Directory we write auto-registered agent configs into (same path that
7217
7253
  * `first-tree-hub agent add` uses). Set by `watchAgentsDir` so the
7218
7254
  * `agent:pinned` handler knows where to materialise new configs.
@@ -7272,6 +7308,7 @@ var ClientRuntime = class {
7272
7308
  this.agentIds.add(config.agentId);
7273
7309
  }
7274
7310
  async start() {
7311
+ this.contextTreeBinding = await syncContextTree(this.serverUrl, (opts) => ensureFreshAccessToken(opts), (msg) => print.status("[context-tree]", msg), CLI_USER_AGENT);
7275
7312
  if (this.options.currentVersion && this.options.update) this.updateManager = UpdateManager.attach(this.connection, {
7276
7313
  currentVersion: this.options.currentVersion,
7277
7314
  ...this.options.update,
@@ -7290,7 +7327,7 @@ var ClientRuntime = class {
7290
7327
  }
7291
7328
  await Promise.allSettled(this.agents.map(async (agent) => {
7292
7329
  try {
7293
- const identity = await agent.slot.start();
7330
+ const identity = await agent.slot.start(this.contextTreeBinding);
7294
7331
  print.check(true, `${agent.name}: connected`, `agent: ${identity.displayName ?? identity.agentId}`);
7295
7332
  } catch (error) {
7296
7333
  const msg = error instanceof Error ? error.message : String(error);
@@ -7417,7 +7454,7 @@ var ClientRuntime = class {
7417
7454
  startAgent(name) {
7418
7455
  const entry = this.agents.find((a) => a.name === name);
7419
7456
  if (!entry) return;
7420
- entry.slot.start().then((identity) => {
7457
+ entry.slot.start(this.contextTreeBinding).then((identity) => {
7421
7458
  print.check(true, `${name}: connected`, `agent: ${identity.displayName ?? identity.agentId}`);
7422
7459
  }).catch((err) => {
7423
7460
  const msg = err instanceof Error ? err.message : String(err);
@@ -9054,7 +9091,7 @@ async function onboardCreate(args) {
9054
9091
  }
9055
9092
  const runtimeAgent = args.type === "human" ? args.assistant : args.id;
9056
9093
  if (args.feishuBotAppId && args.feishuBotAppSecret) {
9057
- const { bindFeishuBot } = await import("./feishu-fLnwqCOs.mjs").then((n) => n.r);
9094
+ const { bindFeishuBot } = await import("./feishu-tkZS0vvL.mjs").then((n) => n.r);
9058
9095
  const targetAgentUuid = args.type === "human" ? assistantUuid : primary.uuid;
9059
9096
  if (!targetAgentUuid) print.line(`Warning: Cannot bind Feishu bot — no runtime agent available for "${args.id}".\n`);
9060
9097
  else {
@@ -10267,7 +10304,7 @@ function createFeedbackHandler(config) {
10267
10304
  return { handle };
10268
10305
  }
10269
10306
  //#endregion
10270
- //#region ../server/dist/app-BXdU2BzM.mjs
10307
+ //#region ../server/dist/app-D4vCx0C0.mjs
10271
10308
  var import_fastify_opentelemetry = /* @__PURE__ */ __toESM(require_fastify_opentelemetry(), 1);
10272
10309
  init_esm();
10273
10310
  var __defProp = Object.defineProperty;
@@ -10292,50 +10329,6 @@ const adapterAgentMappings = pgTable("adapter_agent_mappings", {
10292
10329
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
10293
10330
  }, (table) => [unique("uq_adapter_agent_mapping").on(table.platform, table.externalUserId)]);
10294
10331
  /**
10295
- * Tasks — lightweight work units. Process descriptors, not tickets.
10296
- * Immutable status state machine: pending → assigned → working → (completed | failed | cancelled).
10297
- * Sub-tasks (parent_task_id) are deferred to a later phase.
10298
- *
10299
- * Referential integrity (org / assignee / chat) is enforced at the service layer,
10300
- * not via DB foreign keys — see `services/task.ts`.
10301
- */
10302
- const tasks = pgTable("tasks", {
10303
- id: text("id").primaryKey(),
10304
- organizationId: text("organization_id").notNull(),
10305
- title: text("title").notNull(),
10306
- body: text("body").notNull().default(""),
10307
- status: text("status").$type().notNull(),
10308
- assigneeAgentId: text("assignee_agent_id"),
10309
- createdByType: text("created_by_type").$type().notNull(),
10310
- createdById: text("created_by_id").notNull(),
10311
- originRef: text("origin_ref"),
10312
- result: text("result"),
10313
- metadata: jsonb("metadata").$type().notNull().default({}),
10314
- createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
10315
- updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
10316
- cancelledAt: timestamp("cancelled_at", { withTimezone: true }),
10317
- cancelledByType: text("cancelled_by_type").$type(),
10318
- cancelledById: text("cancelled_by_id")
10319
- }, (table) => [
10320
- index("idx_tasks_org_status").on(table.organizationId, table.status),
10321
- index("idx_tasks_assignee_status").on(table.assigneeAgentId, table.status),
10322
- index("idx_tasks_origin_ref").on(table.originRef),
10323
- index("idx_tasks_org_created_at").on(table.organizationId, table.createdAt)
10324
- ]);
10325
- /**
10326
- * Task ↔ Chat association (M:N). A task may be executed across multiple chats;
10327
- * a chat may host work for multiple tasks over its lifetime.
10328
- *
10329
- * No FK constraints — when a task or chat is deleted, the service layer is
10330
- * responsible for deleting linked rows here first.
10331
- */
10332
- const taskChats = pgTable("task_chats", {
10333
- taskId: text("task_id").notNull(),
10334
- chatId: text("chat_id").notNull(),
10335
- linkedByAgentId: text("linked_by_agent_id"),
10336
- linkedAt: timestamp("linked_at", { withTimezone: true }).notNull().defaultNow()
10337
- }, (table) => [primaryKey({ columns: [table.taskId, table.chatId] }), index("idx_task_chats_chat").on(table.chatId)]);
10338
- /**
10339
10332
  * Pull the JWT-verified `UserScope` off the request. The `userAuthHook`
10340
10333
  * middleware populates `request.user` synchronously before any handler
10341
10334
  * runs; this helper just narrows the optional and throws a clean 401 if
@@ -10470,31 +10463,6 @@ async function assertAgentManageableByUser(db, userId, agentUuid) {
10470
10463
  return scope;
10471
10464
  }
10472
10465
  /**
10473
- * Gate access to a task. Allowed for any active member of the task's org —
10474
- * mirrors the original inline gate in `api/tasks.ts` that this helper
10475
- * replaces. Returns both the task's org row and the caller's resolved
10476
- * `OrgScope`, so handlers can read `scope.memberId` for audit fields.
10477
- */
10478
- async function requireTaskAccess(request, db) {
10479
- const { userId } = requireUser(request);
10480
- const { taskId } = request.params;
10481
- const [task] = await db.select({ organizationId: tasks.organizationId }).from(tasks).where(eq(tasks.id, taskId)).limit(1);
10482
- if (!task) throw new NotFoundError(`Task "${taskId}" not found`);
10483
- const caller = await resolveCallerInOrg(db, userId, task.organizationId);
10484
- const scope = {
10485
- userId,
10486
- organizationId: task.organizationId,
10487
- memberId: caller.memberId,
10488
- role: caller.role,
10489
- humanAgentId: caller.humanAgentId
10490
- };
10491
- stampOrgScope(request, scope);
10492
- return {
10493
- task,
10494
- scope
10495
- };
10496
- }
10497
- /**
10498
10466
  * Assert every agent in `agentIds` is visible to `scope` and lives in
10499
10467
  * `scope.organizationId`. Used by chat-create to keep visibility rules out of
10500
10468
  * the service layer's signature.
@@ -10930,10 +10898,9 @@ async function ensureDefaultOrganization(db) {
10930
10898
  return org ?? existing;
10931
10899
  }
10932
10900
  /**
10933
- * Names beginning with `__` are reserved for Hub-internal pseudo agents
10934
- * (e.g. the task notifier). User-facing creation must not be able to
10935
- * squat on them, otherwise internal traffic could be routed through a
10936
- * real account.
10901
+ * Names beginning with `__` are reserved for Hub-internal pseudo agents.
10902
+ * User-facing creation must not be able to squat on them, otherwise
10903
+ * internal traffic could be routed through a real account.
10937
10904
  */
10938
10905
  const RESERVED_AGENT_NAME_PREFIX = "__";
10939
10906
  /**
@@ -12143,462 +12110,6 @@ async function agentSendToAgentRoutes(app) {
12143
12110
  });
12144
12111
  });
12145
12112
  }
12146
- /** Legal status transitions. Service enforces; API maps violations to 400. */
12147
- const STATUS_TRANSITIONS = {
12148
- pending: ["assigned", "cancelled"],
12149
- assigned: ["working", "cancelled"],
12150
- working: [
12151
- "completed",
12152
- "failed",
12153
- "cancelled"
12154
- ],
12155
- completed: [],
12156
- failed: [],
12157
- cancelled: []
12158
- };
12159
- function isLegalTransition(from, to) {
12160
- return STATUS_TRANSITIONS[from]?.includes(to) ?? false;
12161
- }
12162
- function isTerminal(status) {
12163
- return TASK_TERMINAL_STATUSES.includes(status);
12164
- }
12165
- /**
12166
- * Reserved name for the hub-owned task notifier pseudo agent. The `__` prefix
12167
- * is rejected by `createAgent`, so real users cannot squat on this identity.
12168
- */
12169
- const SYSTEM_TASKS_AGENT_NAME = "__hub_system_tasks";
12170
- /**
12171
- * Ensure a task-notifier pseudo agent exists in the given organization and
12172
- * return its UUID. Used as the sender for task notification messages so they
12173
- * flow through the normal chat/inbox pipeline. Idempotent under concurrent
12174
- * creation via the unique `(organization_id, name)` constraint.
12175
- */
12176
- async function ensureSystemTasksAgent(db, organizationId) {
12177
- const [existing] = await db.select({ uuid: agents.uuid }).from(agents).where(and(eq(agents.organizationId, organizationId), eq(agents.name, SYSTEM_TASKS_AGENT_NAME), ne(agents.status, AGENT_STATUSES.DELETED))).limit(1);
12178
- if (existing) return existing.uuid;
12179
- const uuid = uuidv7();
12180
- const inboxId = `inbox_${uuid}`;
12181
- const [adminMember] = await db.select({ id: members.id }).from(members).where(and(eq(members.organizationId, organizationId), eq(members.role, "admin"))).orderBy(asc(members.createdAt)).limit(1);
12182
- if (!adminMember) throw new ConflictError(`Cannot create system tasks agent in organization "${organizationId}" — no admin member exists.`);
12183
- try {
12184
- const [created] = await db.insert(agents).values({
12185
- uuid,
12186
- name: SYSTEM_TASKS_AGENT_NAME,
12187
- organizationId,
12188
- type: AGENT_TYPES.AUTONOMOUS_AGENT,
12189
- displayName: "System · Tasks",
12190
- inboxId,
12191
- status: AGENT_STATUSES.ACTIVE,
12192
- source: AGENT_SOURCES.ADMIN_API,
12193
- metadata: {
12194
- system: true,
12195
- role: "task-notifier"
12196
- },
12197
- managerId: adminMember.id
12198
- }).returning({ uuid: agents.uuid });
12199
- if (created) return created.uuid;
12200
- } catch (err) {
12201
- if ((err?.code ?? err?.cause?.code ?? "") !== "23505") throw err;
12202
- }
12203
- const [row] = await db.select({ uuid: agents.uuid }).from(agents).where(and(eq(agents.organizationId, organizationId), eq(agents.name, SYSTEM_TASKS_AGENT_NAME))).limit(1);
12204
- if (!row) throw new Error("ensureSystemTasksAgent: agent missing after conflict");
12205
- return row.uuid;
12206
- }
12207
- function resolveCreator(actor) {
12208
- if (actor.type === "agent") return {
12209
- type: TASK_CREATOR_TYPES.AGENT,
12210
- id: actor.agentId
12211
- };
12212
- return {
12213
- type: TASK_CREATOR_TYPES.ADMIN,
12214
- id: actor.adminId
12215
- };
12216
- }
12217
- /**
12218
- * Assert the task allows the given agent actor to mutate its chat associations.
12219
- * Only the creator or assignee (for agents) or any admin may do so.
12220
- */
12221
- function assertCanMutateTaskChats(task, actor) {
12222
- if (actor.type === "admin") return;
12223
- const isAssignee = task.assigneeAgentId === actor.agentId;
12224
- const isCreator = task.createdByType === TASK_CREATOR_TYPES.AGENT && task.createdById === actor.agentId;
12225
- if (!isAssignee && !isCreator) throw new ForbiddenError("Only the task creator or assignee may modify its chat associations");
12226
- }
12227
- async function loadAssigneeOrThrow(db, assigneeAgentId, expectedOrgId) {
12228
- const [assignee] = await db.select({
12229
- uuid: agents.uuid,
12230
- organizationId: agents.organizationId,
12231
- status: agents.status
12232
- }).from(agents).where(eq(agents.uuid, assigneeAgentId)).limit(1);
12233
- if (!assignee || assignee.status === AGENT_STATUSES.DELETED) throw new BadRequestError(`Assignee agent "${assigneeAgentId}" not found`);
12234
- if (assignee.organizationId !== expectedOrgId) throw new BadRequestError("Assignee agent belongs to a different organization");
12235
- if (assignee.status === AGENT_STATUSES.SUSPENDED) throw new BadRequestError(`Assignee agent "${assigneeAgentId}" is suspended`);
12236
- return assignee;
12237
- }
12238
- /**
12239
- * Create a task.
12240
- *
12241
- * Initial status is determined by assignee:
12242
- * - no assignee → "pending"
12243
- * - assignee is an agent and equals the creator → "working" (work-first; no notification)
12244
- * - assignee set and differs from creator → "assigned" (task-first; notification dispatched)
12245
- *
12246
- * Task-first notifications go through the regular message+inbox pipeline via a per-org
12247
- * task-notifier pseudo agent. The caller is responsible for triggering notifier fan-out
12248
- * using the returned notification recipients.
12249
- */
12250
- async function createTask(db, actor, input) {
12251
- const [org] = await db.select({ id: organizations.id }).from(organizations).where(eq(organizations.id, input.organizationId)).limit(1);
12252
- if (!org) throw new NotFoundError(`Organization "${input.organizationId}" not found`);
12253
- if (input.assigneeAgentId) await loadAssigneeOrThrow(db, input.assigneeAgentId, input.organizationId);
12254
- if (actor.type === "agent" && actor.organizationId !== input.organizationId) throw new ForbiddenError("Cannot create tasks in a different organization");
12255
- const creator = resolveCreator(actor);
12256
- const selfAssigned = input.assigneeAgentId !== void 0 && actor.type === "agent" && input.assigneeAgentId === actor.agentId;
12257
- let initialStatus;
12258
- if (!input.assigneeAgentId) initialStatus = TASK_STATUSES.PENDING;
12259
- else if (selfAssigned) initialStatus = TASK_STATUSES.WORKING;
12260
- else initialStatus = TASK_STATUSES.ASSIGNED;
12261
- const taskId = uuidv7();
12262
- const [task] = await db.insert(tasks).values({
12263
- id: taskId,
12264
- organizationId: input.organizationId,
12265
- title: input.title,
12266
- body: input.body ?? "",
12267
- status: initialStatus,
12268
- assigneeAgentId: input.assigneeAgentId ?? null,
12269
- createdByType: creator.type,
12270
- createdById: creator.id,
12271
- originRef: input.originRef ?? null,
12272
- metadata: input.metadata ?? {}
12273
- }).returning();
12274
- if (!task) throw new Error("Unexpected: INSERT RETURNING produced no row");
12275
- let notification;
12276
- if (initialStatus === TASK_STATUSES.ASSIGNED && task.assigneeAgentId) notification = await dispatchTaskSystemMessage(db, task, "assigned");
12277
- return {
12278
- task,
12279
- notification
12280
- };
12281
- }
12282
- /** Compose and send a system message describing a task state change to the assignee's chat. */
12283
- async function dispatchTaskSystemMessage(db, task, event, fromStatus) {
12284
- if (!task.assigneeAgentId) return void 0;
12285
- const systemAgentId = await ensureSystemTasksAgent(db, task.organizationId);
12286
- if (systemAgentId === task.assigneeAgentId) return void 0;
12287
- const chat = await findOrCreateDirectChat(db, systemAgentId, task.assigneeAgentId);
12288
- const content = {
12289
- taskId: task.id,
12290
- event,
12291
- title: task.title,
12292
- body: task.body,
12293
- status: task.status,
12294
- ...fromStatus ? { fromStatus } : {},
12295
- originRef: task.originRef
12296
- };
12297
- return sendMessage(db, chat.id, systemAgentId, {
12298
- format: "task",
12299
- content,
12300
- metadata: {
12301
- taskId: task.id,
12302
- event,
12303
- mentions: [task.assigneeAgentId]
12304
- }
12305
- });
12306
- }
12307
- /**
12308
- * Fetch a task, optionally asserting it belongs to `expectedOrgId`. Cross-org
12309
- * access is reported as NotFound so we don't leak existence across tenants.
12310
- */
12311
- async function getTask(db, taskId, expectedOrgId) {
12312
- const [task] = await db.select().from(tasks).where(eq(tasks.id, taskId)).limit(1);
12313
- if (!task) throw new NotFoundError(`Task "${taskId}" not found`);
12314
- if (expectedOrgId !== void 0 && task.organizationId !== expectedOrgId) throw new NotFoundError(`Task "${taskId}" not found`);
12315
- return task;
12316
- }
12317
- async function getTaskDetail(db, taskId, expectedOrgId) {
12318
- const task = await getTask(db, taskId, expectedOrgId);
12319
- const links = await db.select().from(taskChats).where(eq(taskChats.taskId, taskId));
12320
- return {
12321
- ...task,
12322
- chats: links.map((c) => ({
12323
- taskId: c.taskId,
12324
- chatId: c.chatId,
12325
- linkedByAgentId: c.linkedByAgentId,
12326
- linkedAt: c.linkedAt.toISOString()
12327
- }))
12328
- };
12329
- }
12330
- async function listTasks(db, organizationId, query) {
12331
- const conditions = [eq(tasks.organizationId, organizationId)];
12332
- if (query.status) conditions.push(eq(tasks.status, query.status));
12333
- if (query.assigneeAgentId) conditions.push(eq(tasks.assigneeAgentId, query.assigneeAgentId));
12334
- if (query.originRef) conditions.push(eq(tasks.originRef, query.originRef));
12335
- if (query.cursor) conditions.push(lt(tasks.createdAt, new Date(query.cursor)));
12336
- const rows = await db.select().from(tasks).where(and(...conditions)).orderBy(desc(tasks.createdAt)).limit(query.limit + 1);
12337
- const hasMore = rows.length > query.limit;
12338
- const items = hasMore ? rows.slice(0, query.limit) : rows;
12339
- const last = items[items.length - 1];
12340
- return {
12341
- items,
12342
- nextCursor: hasMore && last ? last.createdAt.toISOString() : null
12343
- };
12344
- }
12345
- /** Agent self-report: working / completed / failed. */
12346
- async function updateTaskStatus(db, taskId, actor, data) {
12347
- const existing = await getTask(db, taskId);
12348
- if (actor.type !== "agent") throw new ForbiddenError("updateTaskStatus is for agent self-report; use adminUpdateTask for admin actions");
12349
- if (existing.assigneeAgentId !== actor.agentId) throw new ForbiddenError("Only the assignee may update this task");
12350
- const from = existing.status;
12351
- const to = data.status;
12352
- if (!isLegalTransition(from, to)) throw new BadRequestError(`Illegal status transition: ${from} → ${to}`);
12353
- if (to === TASK_STATUSES.COMPLETED && data.result === void 0) throw new BadRequestError("Completion requires a result (may be an empty string)");
12354
- const updates = {
12355
- status: to,
12356
- updatedAt: /* @__PURE__ */ new Date()
12357
- };
12358
- if (data.result !== void 0) updates.result = data.result;
12359
- const [updated] = await db.update(tasks).set(updates).where(eq(tasks.id, taskId)).returning();
12360
- if (!updated) throw new Error("Unexpected: UPDATE RETURNING produced no row");
12361
- return { task: updated };
12362
- }
12363
- /** Admin-facing update: may re-assign while pending, or force a status transition (still gated by state machine). */
12364
- async function adminUpdateTask(db, taskId, actor, data) {
12365
- if (actor.type !== "admin") throw new ForbiddenError("adminUpdateTask requires admin actor");
12366
- const existing = await getTask(db, taskId);
12367
- if (data.status === TASK_STATUSES.CANCELLED) {
12368
- if (isTerminal(existing.status)) throw new ConflictError(`Task is already in terminal state "${existing.status}"`);
12369
- return cancelTask(db, taskId, actor);
12370
- }
12371
- const updates = { updatedAt: /* @__PURE__ */ new Date() };
12372
- let notify = false;
12373
- if (data.assigneeAgentId !== void 0) {
12374
- if (existing.status !== TASK_STATUSES.PENDING && data.assigneeAgentId !== existing.assigneeAgentId) throw new BadRequestError("Cannot reassign a task that is not pending");
12375
- if (data.assigneeAgentId !== null) {
12376
- await loadAssigneeOrThrow(db, data.assigneeAgentId, existing.organizationId);
12377
- updates.assigneeAgentId = data.assigneeAgentId;
12378
- updates.status = TASK_STATUSES.ASSIGNED;
12379
- notify = true;
12380
- } else {
12381
- updates.assigneeAgentId = null;
12382
- updates.status = TASK_STATUSES.PENDING;
12383
- }
12384
- }
12385
- if (data.status !== void 0 && data.status !== existing.status) {
12386
- const from = updates.status ?? existing.status;
12387
- if (!isLegalTransition(from, data.status)) throw new BadRequestError(`Illegal status transition: ${from} → ${data.status}`);
12388
- updates.status = data.status;
12389
- }
12390
- if (data.result !== void 0) updates.result = data.result;
12391
- const resolvedStatus = updates.status ?? existing.status;
12392
- const resolvedAssignee = updates.assigneeAgentId === void 0 ? existing.assigneeAgentId : updates.assigneeAgentId;
12393
- if (resolvedStatus === TASK_STATUSES.ASSIGNED && !resolvedAssignee) throw new BadRequestError("Cannot set status to \"assigned\" without an assignee");
12394
- const [updated] = await db.update(tasks).set(updates).where(eq(tasks.id, taskId)).returning();
12395
- if (!updated) throw new Error("Unexpected: UPDATE RETURNING produced no row");
12396
- let notification;
12397
- if (notify && updated.assigneeAgentId) notification = await dispatchTaskSystemMessage(db, updated, "assigned");
12398
- return {
12399
- task: updated,
12400
- notification
12401
- };
12402
- }
12403
- async function cancelTask(db, taskId, actor) {
12404
- const existing = await getTask(db, taskId);
12405
- if (isTerminal(existing.status)) throw new ConflictError(`Task is already in terminal state "${existing.status}"`);
12406
- if (actor.type === "agent") {
12407
- const isAssignee = existing.assigneeAgentId === actor.agentId;
12408
- const isCreator = existing.createdByType === TASK_CREATOR_TYPES.AGENT && existing.createdById === actor.agentId;
12409
- if (!isAssignee && !isCreator) throw new ForbiddenError("Only the assignee or creator may cancel this task");
12410
- }
12411
- const now = /* @__PURE__ */ new Date();
12412
- const { type: cancelType, id: cancelId } = resolveCreator(actor);
12413
- const [updated] = await db.update(tasks).set({
12414
- status: TASK_STATUSES.CANCELLED,
12415
- cancelledAt: now,
12416
- cancelledByType: cancelType,
12417
- cancelledById: cancelId,
12418
- updatedAt: now
12419
- }).where(eq(tasks.id, taskId)).returning();
12420
- if (!updated) throw new Error("Unexpected: UPDATE RETURNING produced no row");
12421
- let notification;
12422
- if (updated.assigneeAgentId && !(actor.type === "agent" && actor.agentId === updated.assigneeAgentId)) notification = await dispatchTaskSystemMessage(db, updated, "cancelled", existing.status);
12423
- return {
12424
- task: updated,
12425
- notification
12426
- };
12427
- }
12428
- async function linkChatToTask(db, taskId, chatId, actor) {
12429
- const task = await getTask(db, taskId);
12430
- if (actor.type === "agent" && task.organizationId !== actor.organizationId) throw new NotFoundError(`Task "${taskId}" not found`);
12431
- assertCanMutateTaskChats(task, actor);
12432
- const [chat] = await db.select({ organizationId: chats.organizationId }).from(chats).where(eq(chats.id, chatId)).limit(1);
12433
- if (!chat) throw new NotFoundError(`Chat "${chatId}" not found`);
12434
- if (chat.organizationId !== task.organizationId) throw new BadRequestError("Chat belongs to a different organization");
12435
- if (actor.type === "agent") await assertParticipant(db, chatId, actor.agentId);
12436
- const linkedBy = actor.type === "agent" ? actor.agentId : null;
12437
- await db.insert(taskChats).values({
12438
- taskId,
12439
- chatId,
12440
- linkedByAgentId: linkedBy
12441
- }).onConflictDoNothing();
12442
- }
12443
- async function unlinkChatFromTask(db, taskId, chatId, actor) {
12444
- const task = await getTask(db, taskId);
12445
- if (actor.type === "agent" && task.organizationId !== actor.organizationId) throw new NotFoundError(`Task "${taskId}" not found`);
12446
- assertCanMutateTaskChats(task, actor);
12447
- if ((await db.delete(taskChats).where(and(eq(taskChats.taskId, taskId), eq(taskChats.chatId, chatId))).returning({ chatId: taskChats.chatId })).length === 0) throw new NotFoundError(`Chat "${chatId}" is not linked to task "${taskId}"`);
12448
- }
12449
- /**
12450
- * Derive a health signal for a task. Only meaningful for `working` tasks.
12451
- * See hub-task-design Section 9 for the rules this implements.
12452
- *
12453
- * Algorithm (per linked chat for the assignee):
12454
- * 1. No session row OR state != 'active' → idle_island candidate
12455
- * 2. Session active, last message from assignee → awaiting_reply candidate
12456
- * 3. Session active, last message from other → normal candidate
12457
- * Across all linked chats, normal wins over awaiting_reply, which wins over idle_island.
12458
- */
12459
- async function getTaskHealth(db, taskId, expectedOrgId) {
12460
- const task = await getTask(db, taskId, expectedOrgId);
12461
- if (task.status !== TASK_STATUSES.WORKING) return {
12462
- taskId,
12463
- signal: TASK_HEALTH_SIGNALS.NOT_APPLICABLE,
12464
- reason: `Task status is "${task.status}" — health is only computed for working tasks`
12465
- };
12466
- if (!task.assigneeAgentId) return {
12467
- taskId,
12468
- signal: TASK_HEALTH_SIGNALS.NO_CHAT,
12469
- reason: "Task has no assignee"
12470
- };
12471
- const linked = await db.select({
12472
- chatId: taskChats.chatId,
12473
- sessionState: agentChatSessions.state
12474
- }).from(taskChats).leftJoin(agentChatSessions, and(eq(agentChatSessions.chatId, taskChats.chatId), eq(agentChatSessions.agentId, task.assigneeAgentId))).where(eq(taskChats.taskId, taskId));
12475
- if (linked.length === 0) return {
12476
- taskId,
12477
- signal: TASK_HEALTH_SIGNALS.NO_CHAT,
12478
- reason: "Task has no linked chats"
12479
- };
12480
- const chatSignals = [];
12481
- for (const row of linked) {
12482
- if (row.sessionState !== "active") {
12483
- chatSignals.push(TASK_HEALTH_SIGNALS.IDLE_ISLAND);
12484
- continue;
12485
- }
12486
- const [last] = await db.select({ senderId: messages.senderId }).from(messages).where(eq(messages.chatId, row.chatId)).orderBy(desc(messages.createdAt)).limit(1);
12487
- if (!last) {
12488
- chatSignals.push(TASK_HEALTH_SIGNALS.IDLE_ISLAND);
12489
- continue;
12490
- }
12491
- if (last.senderId === task.assigneeAgentId) chatSignals.push(TASK_HEALTH_SIGNALS.AWAITING_REPLY);
12492
- else chatSignals.push(TASK_HEALTH_SIGNALS.NORMAL);
12493
- }
12494
- if (chatSignals.includes(TASK_HEALTH_SIGNALS.NORMAL)) return {
12495
- taskId,
12496
- signal: TASK_HEALTH_SIGNALS.NORMAL,
12497
- reason: "At least one linked chat is actively progressing"
12498
- };
12499
- if (chatSignals.includes(TASK_HEALTH_SIGNALS.AWAITING_REPLY)) return {
12500
- taskId,
12501
- signal: TASK_HEALTH_SIGNALS.AWAITING_REPLY,
12502
- reason: "Assignee sent the last message and is waiting for a reply"
12503
- };
12504
- return {
12505
- taskId,
12506
- signal: TASK_HEALTH_SIGNALS.IDLE_ISLAND,
12507
- reason: "No active session found for the assignee in any linked chat"
12508
- };
12509
- }
12510
- /** Serialize a task row for API output. */
12511
- function serializeTask(task) {
12512
- return {
12513
- ...task,
12514
- createdAt: task.createdAt.toISOString(),
12515
- updatedAt: task.updatedAt.toISOString(),
12516
- cancelledAt: task.cancelledAt ? task.cancelledAt.toISOString() : null
12517
- };
12518
- }
12519
- function dispatch$2(notifier, result) {
12520
- if (!result) return;
12521
- notifyRecipients(notifier, result.recipients, result.message.id);
12522
- }
12523
- async function agentTaskRoutes(app) {
12524
- /** Create a task. Agent creator; assignee defaults to self (work-first) if omitted. */
12525
- app.post("/", async (request, reply) => {
12526
- const identity = requireAgent(request);
12527
- const body = createTaskSchema.parse(request.body);
12528
- const { task, notification } = await createTask(app.db, {
12529
- type: "agent",
12530
- agentId: identity.uuid,
12531
- organizationId: identity.organizationId
12532
- }, {
12533
- ...body,
12534
- organizationId: identity.organizationId
12535
- });
12536
- dispatch$2(app.notifier, notification);
12537
- return reply.status(201).send(serializeTask(task));
12538
- });
12539
- app.get("/", async (request) => {
12540
- const identity = requireAgent(request);
12541
- const query = taskListQuerySchema.parse(request.query);
12542
- const result = await listTasks(app.db, identity.organizationId, query);
12543
- return {
12544
- items: result.items.map((t) => serializeTask(t)),
12545
- nextCursor: result.nextCursor
12546
- };
12547
- });
12548
- app.get("/:taskId", async (request) => {
12549
- const identity = requireAgent(request);
12550
- const detail = await getTaskDetail(app.db, request.params.taskId, identity.organizationId);
12551
- return {
12552
- ...serializeTask(detail),
12553
- chats: detail.chats
12554
- };
12555
- });
12556
- /** Agent self-report: working / completed / failed. */
12557
- app.patch("/:taskId", async (request) => {
12558
- const identity = requireAgent(request);
12559
- const body = updateTaskStatusSchema.parse(request.body);
12560
- const { task } = await updateTaskStatus(app.db, request.params.taskId, {
12561
- type: "agent",
12562
- agentId: identity.uuid,
12563
- organizationId: identity.organizationId
12564
- }, body);
12565
- return serializeTask(task);
12566
- });
12567
- app.post("/:taskId/cancel", async (request) => {
12568
- const identity = requireAgent(request);
12569
- const { task, notification } = await cancelTask(app.db, request.params.taskId, {
12570
- type: "agent",
12571
- agentId: identity.uuid,
12572
- organizationId: identity.organizationId
12573
- });
12574
- dispatch$2(app.notifier, notification);
12575
- return serializeTask(task);
12576
- });
12577
- app.post("/:taskId/chats", async (request, reply) => {
12578
- const identity = requireAgent(request);
12579
- const body = linkTaskChatSchema.parse(request.body);
12580
- await linkChatToTask(app.db, request.params.taskId, body.chatId, {
12581
- type: "agent",
12582
- agentId: identity.uuid,
12583
- organizationId: identity.organizationId
12584
- });
12585
- return reply.status(204).send();
12586
- });
12587
- app.delete("/:taskId/chats/:chatId", async (request, reply) => {
12588
- const identity = requireAgent(request);
12589
- await unlinkChatFromTask(app.db, request.params.taskId, request.params.chatId, {
12590
- type: "agent",
12591
- agentId: identity.uuid,
12592
- organizationId: identity.organizationId
12593
- });
12594
- return reply.status(204).send();
12595
- });
12596
- /** Task health signal — only meaningful while task.status === "working". */
12597
- app.get("/:taskId/health", async (request) => {
12598
- const identity = requireAgent(request);
12599
- return getTaskHealth(app.db, request.params.taskId, identity.organizationId);
12600
- });
12601
- }
12602
12113
  /** WS close code: agent already connected from another client. */
12603
12114
  const WS_CLOSE_ALREADY_CONNECTED = 4009;
12604
12115
  /** Track active WS connections per agentId. At most one entry per agent. */
@@ -15174,9 +14685,7 @@ async function listMeChats(db, humanAgentId, organizationId, query) {
15174
14685
  lastMessageAt: toDate(r.last_message_at)?.toISOString() ?? null,
15175
14686
  lastMessagePreview: r.last_message_preview,
15176
14687
  unreadMentionCount: r.unread_mention_count,
15177
- canReply: r.membership_kind === "participant",
15178
- taskId: null,
15179
- taskStatus: null
14688
+ canReply: r.membership_kind === "participant"
15180
14689
  };
15181
14690
  }),
15182
14691
  nextCursor
@@ -16894,7 +16403,7 @@ async function healthzRoutes(app) {
16894
16403
  * `api/orgs/invitations.ts` (Class B, admin-gated).
16895
16404
  */
16896
16405
  async function publicInvitationRoutes(app) {
16897
- const { previewInvitation } = await import("./invitation-C299fxkP-B89eqDos.mjs");
16406
+ const { previewInvitation } = await import("./invitation-C299fxkP-CZRV665C.mjs");
16898
16407
  app.get("/:token/preview", async (request, reply) => {
16899
16408
  if (!request.params.token) throw new UnauthorizedError("Token required");
16900
16409
  const preview = await previewInvitation(app.db, request.params.token);
@@ -17074,7 +16583,7 @@ async function meRoutes(app) {
17074
16583
  */
17075
16584
  app.get("/me/pinned-agents", async (request) => {
17076
16585
  const { userId } = requireUser(request);
17077
- const { listMyPinnedAgents } = await import("./client-0RrgrMjR-DPyuu6Ls.mjs");
16586
+ const { listMyPinnedAgents } = await import("./client-0RrgrMjR-CylTJGEb.mjs");
17078
16587
  return listMyPinnedAgents(app.db, { userId });
17079
16588
  });
17080
16589
  /**
@@ -17887,39 +17396,6 @@ function enrichOutput(namespace, out, orgId, publicUrl) {
17887
17396
  }
17888
17397
  return out;
17889
17398
  }
17890
- function dispatch$1(notifier, result) {
17891
- if (!result) return;
17892
- notifyRecipients(notifier, result.recipients, result.message.id);
17893
- }
17894
- /** Class B — `/api/v1/orgs/:orgId/tasks`. Per-task ops live in api/tasks.ts. */
17895
- async function orgTaskRoutes(app) {
17896
- app.get("/", async (request) => {
17897
- const scope = await requireOrgMembership(request, app.db);
17898
- const query = taskListQuerySchema.parse(request.query);
17899
- const result = await listTasks(app.db, scope.organizationId, query);
17900
- return {
17901
- items: result.items.map((t) => serializeTask(t)),
17902
- nextCursor: result.nextCursor
17903
- };
17904
- });
17905
- app.post("/", async (request, reply) => {
17906
- const scope = await requireOrgMembership(request, app.db);
17907
- const body = adminCreateTaskSchema.parse(request.body);
17908
- const { task, notification } = await createTask(app.db, {
17909
- type: "admin",
17910
- adminId: scope.memberId
17911
- }, {
17912
- title: body.title,
17913
- body: body.body,
17914
- ...body.assigneeAgentId !== void 0 ? { assigneeAgentId: body.assigneeAgentId } : {},
17915
- ...body.originRef !== void 0 ? { originRef: body.originRef } : {},
17916
- ...body.metadata !== void 0 ? { metadata: body.metadata } : {},
17917
- organizationId: scope.organizationId
17918
- });
17919
- dispatch$1(app.notifier, notification);
17920
- return reply.status(201).send(serializeTask(task));
17921
- });
17922
- }
17923
17399
  async function loadVisibleAgentIds(db, organizationId, memberId) {
17924
17400
  const rows = await db.select({ id: agents.uuid }).from(agents).where(and(eq(agents.organizationId, organizationId), ne(agents.status, AGENT_STATUSES.DELETED), or(eq(agents.visibility, AGENT_VISIBILITY.ORGANIZATION), eq(agents.managerId, memberId))));
17925
17401
  return new Set(rows.map((r) => r.id));
@@ -18140,44 +17616,6 @@ async function sessionRoutes(app) {
18140
17616
  });
18141
17617
  });
18142
17618
  }
18143
- function dispatch(notifier, result) {
18144
- if (!result) return;
18145
- notifyRecipients(notifier, result.recipients, result.message.id);
18146
- }
18147
- /** Class C — `/api/v1/tasks/:taskId`. The task's `organizationId` locates the org. */
18148
- async function taskRoutes(app) {
18149
- app.get("/:taskId", async (request) => {
18150
- await requireTaskAccess(request, app.db);
18151
- const detail = await getTaskDetail(app.db, request.params.taskId);
18152
- return {
18153
- ...serializeTask(detail),
18154
- chats: detail.chats
18155
- };
18156
- });
18157
- app.patch("/:taskId", async (request) => {
18158
- const { scope } = await requireTaskAccess(request, app.db);
18159
- const body = adminUpdateTaskSchema.parse(request.body);
18160
- const { task, notification } = await adminUpdateTask(app.db, request.params.taskId, {
18161
- type: "admin",
18162
- adminId: scope.memberId
18163
- }, body);
18164
- dispatch(app.notifier, notification);
18165
- return serializeTask(task);
18166
- });
18167
- app.post("/:taskId/cancel", async (request) => {
18168
- const { scope } = await requireTaskAccess(request, app.db);
18169
- const { task, notification } = await cancelTask(app.db, request.params.taskId, {
18170
- type: "admin",
18171
- adminId: scope.memberId
18172
- });
18173
- dispatch(app.notifier, notification);
18174
- return serializeTask(task);
18175
- });
18176
- app.get("/:taskId/health", async (request) => {
18177
- await requireTaskAccess(request, app.db);
18178
- return getTaskHealth(app.db, request.params.taskId);
18179
- });
18180
- }
18181
17619
  const log$1 = createLogger$1("GithubWebhook");
18182
17620
  const GITHUB_ADAPTER_ID = "github-adapter";
18183
17621
  function verifySignature(secret, rawBody, signatureHeader) {
@@ -18764,8 +18202,6 @@ var schema_exports = /* @__PURE__ */ __exportAll({
18764
18202
  pendingQuestions: () => pendingQuestions,
18765
18203
  serverInstances: () => serverInstances,
18766
18204
  sessionEvents: () => sessionEvents,
18767
- taskChats: () => taskChats,
18768
- tasks: () => tasks,
18769
18205
  users: () => users
18770
18206
  });
18771
18207
  function connectDatabase(url) {
@@ -20258,7 +19694,6 @@ async function buildApp(config) {
20258
19694
  await scope.register(orgAdapterStatusRoutes, { prefix: "/adapters/status" });
20259
19695
  await scope.register(orgOverviewRoutes, { prefix: "/overview" });
20260
19696
  await scope.register(orgActivityRoutes, { prefix: "/activity" });
20261
- await scope.register(orgTaskRoutes, { prefix: "/tasks" });
20262
19697
  await scope.register(orgSessionRoutes, { prefix: "/sessions" });
20263
19698
  await scope.register(orgNotificationRoutes, { prefix: "/notifications" });
20264
19699
  await scope.register(orgClientRoutes, { prefix: "/clients" });
@@ -20274,7 +19709,6 @@ async function buildApp(config) {
20274
19709
  await scope.register(agentActivityRoutes, { prefix: "/agents" });
20275
19710
  await scope.register(sessionRoutes, { prefix: "/agents" });
20276
19711
  await scope.register(chatRoutes, { prefix: "/chats" });
20277
- await scope.register(taskRoutes, { prefix: "/tasks" });
20278
19712
  await scope.register(adapterRoutes, { prefix: "/adapters" });
20279
19713
  await scope.register(adapterMappingRoutes, { prefix: "/adapter-mappings" });
20280
19714
  await scope.register(clientRoutes, { prefix: "/clients" });
@@ -20286,7 +19720,6 @@ async function buildApp(config) {
20286
19720
  await scope.register(agentSendToAgentRoutes, { prefix: "/agents" });
20287
19721
  await scope.register(agentInboxRoutes, { prefix: "/inbox" });
20288
19722
  await scope.register(agentConfigRoutes$1);
20289
- await scope.register(agentTaskRoutes, { prefix: "/tasks" });
20290
19723
  await scope.register(agentFeishuBotRoutes);
20291
19724
  await scope.register(agentFeishuUserRoutes, { prefix: "/delegated" });
20292
19725
  }), { prefix: "/agent" });