@agent-team-foundation/first-tree-hub 0.9.11 → 0.10.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.
@@ -0,0 +1,31 @@
1
+ -- Phase 2 of the agent-naming refactor (docs/agent-naming-design.md §4).
2
+ --
3
+ -- Before this migration, `display_name` was nullable and the web layer
4
+ -- silently fell back to `name` for rendering. CLI, server logs, and IM
5
+ -- bridges saw raw NULLs. This migration closes the gap in three steps:
6
+ --
7
+ -- 1. Backfill every NULL row with a non-empty label — the agent's `name`
8
+ -- when available, else the tombstone literal "[deleted agent]".
9
+ -- Important: the third branch must NOT be `uuid`, because UUIDs would
10
+ -- surface as human-visible strings in the chat roster and IM bridge.
11
+ -- Only `status = 'deleted'` rows can have both `name` and
12
+ -- `display_name` NULL (see `deleteAgent` in services/agent.ts), which
13
+ -- is why the literal refers to deletion.
14
+ --
15
+ -- 2. Add a temporary empty-string DEFAULT so any old server instance
16
+ -- still running during a rolling deploy — i.e. code that doesn't yet
17
+ -- default `display_name` in `createAgent` — can INSERT without
18
+ -- violating the upcoming NOT NULL. The application code treats an
19
+ -- empty string as "no display name" (the Zod read schema accepts it);
20
+ -- a follow-up migration can drop the default once the code is fully
21
+ -- rolled.
22
+ --
23
+ -- 3. Promote the column to NOT NULL. Combined with the service-level
24
+ -- default this closes the hole for new rows too.
25
+
26
+ UPDATE "agents"
27
+ SET "display_name" = COALESCE("display_name", "name", '[deleted agent]')
28
+ WHERE "display_name" IS NULL;
29
+
30
+ ALTER TABLE "agents" ALTER COLUMN "display_name" SET DEFAULT '';
31
+ ALTER TABLE "agents" ALTER COLUMN "display_name" SET NOT NULL;
@@ -0,0 +1,53 @@
1
+ -- Silent inbox entries for missed group-chat context.
2
+ --
3
+ -- Adds `inbox_entries.notify`, defaulting to true so existing rows continue to
4
+ -- behave as "active" deliverables. Group-chat fan-out now writes a row for
5
+ -- every non-sender participant — for `mention_only` participants who aren't
6
+ -- explicitly @mentioned in the triggering message we set `notify = false` so
7
+ -- the row is silently parked. Subsequent active deliveries to the same chat
8
+ -- pick up these silent rows and replay them as preceding context, then
9
+ -- bulk-ack them so they don't get re-replayed.
10
+ --
11
+ -- See proposals/group-chat-ux-improvements §1 (silent inbox).
12
+ --
13
+ -- ──────────────── Indexes ────────────────
14
+ --
15
+ -- idx_inbox_pending_notify — partial index used by pollInbox's claim.
16
+ -- The query is "WHERE inbox_id = ? AND status = 'pending' AND notify = true
17
+ -- ORDER BY created_at LIMIT N FOR UPDATE SKIP LOCKED". Without `notify` in
18
+ -- the index, a chat that accumulates silent rows (mention_only agent in a
19
+ -- chatty group) forces the planner to scan past them before finding the
20
+ -- next trigger. Partial index keeps it bounded.
21
+ --
22
+ -- idx_inbox_chat_silent — used by collectPrecedingContext to walk the
23
+ -- silent rows in a single (inbox, chat) bucket between two triggers.
24
+ --
25
+ -- ──────────────── Operator note ────────────────
26
+ --
27
+ -- Drizzle migrator wraps every migration file in a single transaction (see the
28
+ -- comment block in 0020_unified_user_token.sql), which means we can't use
29
+ -- `CREATE INDEX CONCURRENTLY` here — PG rejects it inside a tx. On a small
30
+ -- `inbox_entries` table the regular `CREATE INDEX` finishes in <1s and is
31
+ -- fine. For a large production table, the runbook is:
32
+ --
33
+ -- 1. Stop applying new migrations briefly.
34
+ -- 2. Manually run, OUTSIDE a transaction:
35
+ -- CREATE INDEX CONCURRENTLY idx_inbox_pending_notify
36
+ -- ON inbox_entries (inbox_id, created_at)
37
+ -- WHERE status = 'pending' AND notify = true;
38
+ -- CREATE INDEX CONCURRENTLY idx_inbox_chat_silent
39
+ -- ON inbox_entries (inbox_id, chat_id, notify, status);
40
+ -- 3. Re-run `pnpm db:migrate`. The `IF NOT EXISTS` clauses below detect
41
+ -- the pre-created indexes and skip them.
42
+
43
+ ALTER TABLE "inbox_entries"
44
+ ADD COLUMN "notify" boolean DEFAULT true NOT NULL;
45
+
46
+ --> statement-breakpoint
47
+ CREATE INDEX IF NOT EXISTS "idx_inbox_pending_notify"
48
+ ON "inbox_entries" ("inbox_id", "created_at")
49
+ WHERE status = 'pending' AND notify = true;
50
+
51
+ --> statement-breakpoint
52
+ CREATE INDEX IF NOT EXISTS "idx_inbox_chat_silent"
53
+ ON "inbox_entries" ("inbox_id", "chat_id", "notify", "status");
@@ -169,6 +169,20 @@
169
169
  "when": 1777248000000,
170
170
  "tag": "0023_clients_org_scoping",
171
171
  "breakpoints": true
172
+ },
173
+ {
174
+ "idx": 24,
175
+ "version": "7",
176
+ "when": 1777334400000,
177
+ "tag": "0024_display_name_not_null",
178
+ "breakpoints": true
179
+ },
180
+ {
181
+ "idx": 25,
182
+ "version": "7",
183
+ "when": 1777420800000,
184
+ "tag": "0025_inbox_silent_entries",
185
+ "breakpoints": true
172
186
  }
173
187
  ]
174
188
  }
@@ -23,6 +23,21 @@ function extractMentions(content, participants) {
23
23
  }
24
24
  return [...hits];
25
25
  }
26
+ /**
27
+ * Return every `@<name>` token that survives the code-stripping / word-
28
+ * boundary gates, regardless of whether it matches a participant. The
29
+ * caller uses this to log unmatched tokens (typos, renamed agents) without
30
+ * polluting the authoritative mention list.
31
+ */
32
+ function scanMentionTokens(content) {
33
+ const stripped = stripCode(content);
34
+ const tokens = [];
35
+ for (const m of stripped.matchAll(MENTION_REGEX)) {
36
+ const token = m[1];
37
+ if (token) tokens.push(token.toLowerCase());
38
+ }
39
+ return tokens;
40
+ }
26
41
  const adapterPlatformSchema = z.enum([
27
42
  "feishu",
28
43
  "slack",
@@ -204,7 +219,7 @@ function isReservedAgentName(name) {
204
219
  const createAgentSchema = z.object({
205
220
  name: z.string().min(1).max(64).regex(AGENT_NAME_REGEX, "Must start with a letter or digit and contain only lowercase letters, digits, hyphens (-), and underscores (_). Max 64 chars.").refine((n) => !isReservedAgentName(n), { message: "That agent name is reserved — pick a different one." }).optional(),
206
221
  type: agentTypeSchema,
207
- displayName: z.string().max(200).optional(),
222
+ displayName: z.string().min(1).max(200).optional(),
208
223
  delegateMention: z.string().max(100).optional(),
209
224
  organizationId: z.string().max(100).optional(),
210
225
  source: agentSourceSchema.optional(),
@@ -215,7 +230,7 @@ const createAgentSchema = z.object({
215
230
  });
216
231
  const updateAgentSchema = z.object({
217
232
  type: agentTypeSchema.optional(),
218
- displayName: z.string().max(200).nullable().optional(),
233
+ displayName: z.string().min(1).max(200).optional(),
219
234
  delegateMention: z.string().max(100).nullable().optional(),
220
235
  visibility: agentVisibilitySchema.optional(),
221
236
  metadata: z.record(z.string(), z.unknown()).optional(),
@@ -227,7 +242,7 @@ z.object({
227
242
  name: z.string().nullable(),
228
243
  organizationId: z.string(),
229
244
  type: agentTypeSchema,
230
- displayName: z.string().nullable(),
245
+ displayName: z.string(),
231
246
  delegateMention: z.string().nullable(),
232
247
  inboxId: z.string(),
233
248
  status: z.string(),
@@ -254,7 +269,7 @@ const agentPinnedMessageSchema = z.object({
254
269
  type: z.literal("agent:pinned"),
255
270
  agentId: z.string(),
256
271
  name: z.string().nullable(),
257
- displayName: z.string().nullable(),
272
+ displayName: z.string(),
258
273
  agentType: agentTypeSchema
259
274
  });
260
275
  /**
@@ -450,7 +465,7 @@ const chatParticipantSchema = z.object({
450
465
  });
451
466
  chatParticipantSchema.extend({
452
467
  name: z.string().nullable(),
453
- displayName: z.string().nullable(),
468
+ displayName: z.string(),
454
469
  type: z.string()
455
470
  });
456
471
  z.object({
@@ -590,6 +605,23 @@ const inReplyToSnapshotSchema = z.object({
590
605
  /** Per-chat participation mode exposed to the recipient runtime. */
591
606
  const participantModeSchema = z.enum(["full", "mention_only"]);
592
607
  /**
608
+ * Lightweight snapshot of an earlier message in the same chat that the
609
+ * recipient missed (because it was `mention_only` + not @mentioned). Server
610
+ * attaches a list of these to the next active delivery in the chat so the
611
+ * agent's prompt carries enough context to reply meaningfully.
612
+ *
613
+ * Smaller than `messageSchema` on purpose — drops fields that don't help the
614
+ * LLM (replyTo envelopes, source) and aren't safe to leak across recipients.
615
+ */
616
+ const precedingMessageSchema = z.object({
617
+ id: z.string(),
618
+ senderId: z.string(),
619
+ format: z.string(),
620
+ content: z.unknown(),
621
+ metadata: z.record(z.string(), z.unknown()).default({}),
622
+ createdAt: z.string()
623
+ });
624
+ /**
593
625
  * Wire format for messages routed FROM the Hub TO a client runtime.
594
626
  *
595
627
  * Adds `configVersion` so the client can compare against its locally cached
@@ -605,11 +637,17 @@ const participantModeSchema = z.enum(["full", "mention_only"]);
605
637
  *
606
638
  * `inReplyToSnapshot` is populated when `inReplyTo` resolves to an existing
607
639
  * message; runtime uses it to suppress self-reply echo on direct chats.
640
+ *
641
+ * `precedingMessages` is a (possibly empty) list of older messages in the
642
+ * same chat that this recipient did not previously receive (silent inbox
643
+ * context). The runtime renders them as "earlier in chat" before the
644
+ * triggering message — see proposals/group-chat-ux-improvements §1.
608
645
  */
609
646
  const clientMessageSchema = messageSchema.extend({
610
647
  configVersion: z.number().int().positive(),
611
648
  recipientMode: participantModeSchema.default("full"),
612
- inReplyToSnapshot: inReplyToSnapshotSchema.default(null)
649
+ inReplyToSnapshot: inReplyToSnapshotSchema.default(null),
650
+ precedingMessages: z.array(precedingMessageSchema).default([])
613
651
  });
614
652
  z.enum([
615
653
  "pending",
@@ -1072,4 +1110,4 @@ async function bindFeishuUser(serverUrl, accessToken, agentId, humanAgentId, fei
1072
1110
  }
1073
1111
  }
1074
1112
  //#endregion
1075
- export { sessionEventSchema as $, createChatSchema as A, isReservedAgentName as B, agentRuntimeConfigPayloadSchema as C, createAdapterConfigSchema as D, connectTokenExchangeSchema as E, dryRunAgentRuntimeConfigSchema as F, paginationQuerySchema as G, loginSchema as H, extractMentions as I, selfServiceFeishuBotSchema as J, refreshTokenSchema as K, imageInlineContentSchema as L, createOrganizationSchema as M, createTaskSchema as N, createAdapterMappingSchema as O, delegateFeishuUserSchema as P, sessionEventMessageSchema as Q, inboxPollQuerySchema as R, agentPinnedMessageSchema as S, clientRegisterSchema as T, messageSourceSchema as U, linkTaskChatSchema as V, notificationQuerySchema as W, sendToAgentSchema as X, sendMessageSchema as Y, sessionCompletionMessageSchema as Z, WS_AUTH_FRAME_TIMEOUT_MS as _, AGENT_NAME_REGEX as a, updateAgentSchema as at, adminUpdateTaskSchema as b, AGENT_STATUSES as c, updateOrganizationSchema as ct, DEFAULT_AGENT_RUNTIME_CONFIG_PAYLOAD as d, wsAuthFrameSchema as dt, sessionReconcileRequestSchema as et, SYSTEM_CONFIG_DEFAULTS as f, TASK_TERMINAL_STATUSES as g, TASK_STATUSES as h, AGENT_BIND_REJECT_REASONS as i, updateAgentRuntimeConfigSchema as it, createMemberSchema as j, createAgentSchema as k, AGENT_TYPES as l, updateSystemConfigSchema as lt, TASK_HEALTH_SIGNALS as m, bindFeishuUser as n, taskListQuerySchema as nt, AGENT_SELECTOR_HEADER as o, updateChatSchema as ot, TASK_CREATOR_TYPES as p, runtimeStateMessageSchema as q, feishu_exports as r, updateAdapterConfigSchema as rt, AGENT_SOURCES as s, updateMemberSchema as st, bindFeishuBot as t, sessionStateMessageSchema as tt, AGENT_VISIBILITY as u, updateTaskStatusSchema as ut, addParticipantSchema as v, agentTypeSchema as w, agentBindRequestSchema as x, adminCreateTaskSchema as y, isRedactedEnvValue as z };
1113
+ export { sessionEventMessageSchema as $, createChatSchema as A, isReservedAgentName as B, agentRuntimeConfigPayloadSchema as C, createAdapterConfigSchema as D, connectTokenExchangeSchema as E, dryRunAgentRuntimeConfigSchema as F, paginationQuerySchema as G, loginSchema as H, extractMentions as I, scanMentionTokens as J, refreshTokenSchema as K, imageInlineContentSchema as L, createOrganizationSchema as M, createTaskSchema as N, createAdapterMappingSchema as O, delegateFeishuUserSchema as P, sessionCompletionMessageSchema as Q, inboxPollQuerySchema as R, agentPinnedMessageSchema as S, clientRegisterSchema as T, messageSourceSchema as U, linkTaskChatSchema as V, notificationQuerySchema as W, sendMessageSchema as X, selfServiceFeishuBotSchema as Y, sendToAgentSchema as Z, WS_AUTH_FRAME_TIMEOUT_MS as _, AGENT_NAME_REGEX as a, updateAgentRuntimeConfigSchema as at, adminUpdateTaskSchema as b, AGENT_STATUSES as c, updateMemberSchema as ct, DEFAULT_AGENT_RUNTIME_CONFIG_PAYLOAD as d, updateTaskStatusSchema as dt, sessionEventSchema as et, SYSTEM_CONFIG_DEFAULTS as f, wsAuthFrameSchema as ft, TASK_TERMINAL_STATUSES as g, TASK_STATUSES as h, AGENT_BIND_REJECT_REASONS as i, updateAdapterConfigSchema as it, createMemberSchema as j, createAgentSchema as k, AGENT_TYPES as l, updateOrganizationSchema as lt, TASK_HEALTH_SIGNALS as m, bindFeishuUser as n, sessionStateMessageSchema as nt, AGENT_SELECTOR_HEADER as o, updateAgentSchema as ot, TASK_CREATOR_TYPES as p, runtimeStateMessageSchema as q, feishu_exports as r, taskListQuerySchema as rt, AGENT_SOURCES as s, updateChatSchema as st, bindFeishuBot as t, sessionReconcileRequestSchema as tt, AGENT_VISIBILITY as u, updateSystemConfigSchema as ut, addParticipantSchema as v, agentTypeSchema as w, agentBindRequestSchema as x, adminCreateTaskSchema as y, isRedactedEnvValue as z };
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import "./observability-DV_fQKqV-oxfXX6Z2.mjs";
2
- import { A as resolveCliInvocation, C as checkServerHealth, D as getClientServiceStatus, E as printResults, F as ClientRuntime, I as handleClientOrgMismatch, K as FirstTreeHubSDK, L as rotateClientIdWithBackup, M as ensurePostgres, N as isDockerAvailable, O as installClientService, P as stopPostgres, R as createOwner, S as checkServerConfig, T as checkWebSocket, V as blank, W as status, b as checkDocker, c as promptMissingFields, d as onboardCheck, f as onboardCreate, g as checkAgentConfigs, h as runMigrations, i as startServer, j as uninstallClientService, k as isServiceSupported, l as formatCheckReport, m as runHomeMigration, o as isInteractive, q as SdkError, s as promptAddAgent, v as checkClientConfig, w as checkServerReachable, x as checkNodeVersion, y as checkDatabase, z as hasUser } from "./core-CuSIXoof.mjs";
2
+ import { A as installClientService, B as createOwner, C as checkNodeVersion, D as checkWebSocket, E as checkServerReachable, F as isDockerAvailable, I as stopPostgres, J as FirstTreeHubSDK, K as status, L as ClientRuntime, M as resolveCliInvocation, N as uninstallClientService, O as printResults, P as ensurePostgres, R as handleClientOrgMismatch, S as checkDocker, T as checkServerHealth, U as blank, V as hasUser, Y as SdkError, _ as runMigrations, b as checkClientConfig, c as promptMissingFields, d as onboardCheck, f as onboardCreate, i as startServer, j as isServiceSupported, k as getClientServiceStatus, l as formatCheckReport, m as runHomeMigration, o as isInteractive, s as promptAddAgent, v as checkAgentConfigs, w as checkServerConfig, x as checkDatabase, z as rotateClientIdWithBackup } from "./core-BgiFGT7Y.mjs";
3
3
  import "./logger-core-BTmvdflj-DjW8FM4T.mjs";
4
4
  import { a as resolveAccessToken, n as ensureFreshAccessToken, o as resolveServerUrl, r as ensureFreshAdminToken } from "./bootstrap-CtVqQA8a.mjs";
5
- import { n as bindFeishuUser, t as bindFeishuBot } from "./feishu-B2sjp6Z6.mjs";
5
+ import { n as bindFeishuUser, t as bindFeishuBot } from "./feishu-DEmwoNn_.mjs";
6
6
  export { ClientRuntime, FirstTreeHubSDK, SdkError, bindFeishuBot, bindFeishuUser, blank, checkAgentConfigs, checkClientConfig, checkDatabase, checkDocker, checkNodeVersion, checkServerConfig, checkServerHealth, checkServerReachable, checkWebSocket, createOwner, ensureFreshAccessToken, ensureFreshAdminToken, ensurePostgres, formatCheckReport, getClientServiceStatus, handleClientOrgMismatch, hasUser, installClientService, isDockerAvailable, isInteractive, isServiceSupported, onboardCheck, onboardCreate, printResults, promptAddAgent, promptMissingFields, resolveAccessToken, resolveCliInvocation, resolveServerUrl, rotateClientIdWithBackup, runHomeMigration, runMigrations, startServer, status, stopPostgres, uninstallClientService };