@ctliz/agent-intercom-codex 0.12.1 → 0.13.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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- process.stderr.write("[agent-intercom-build] package=@ctliz/agent-intercom-codex version=0.12.1 target=bridge-daemon sourceSha256=9e94589286ec909b8fd50e96f38fa52f7c21424081d18e8155e01cd3bffde8fb\n");
2
+ process.stderr.write("[agent-intercom-build] package=@ctliz/agent-intercom-codex version=0.13.0 target=bridge-daemon sourceSha256=04013522326f3042c72f6454a9c79e26d2d1c8de7af57769ed890e50fca514e3\n");
3
3
 
4
4
  // codex/bridge-daemon.ts
5
5
  import { once } from "node:events";
@@ -1566,6 +1566,7 @@ function isMessage(value) {
1566
1566
  if (typeof content.text !== "string") {
1567
1567
  return false;
1568
1568
  }
1569
+ if (content.team !== void 0 && (typeof content.team !== "string" || !/^[A-Za-z][A-Za-z0-9_-]{0,31}$/.test(content.team))) return false;
1569
1570
  return content.attachments === void 0 || Array.isArray(content.attachments) && content.attachments.every(isAttachment);
1570
1571
  }
1571
1572
  function isSessionInfo(value) {
@@ -2156,6 +2157,7 @@ var IntercomClient = class extends EventEmitter2 {
2156
2157
  expectsReply: options.expectsReply,
2157
2158
  content: {
2158
2159
  text: options.text,
2160
+ ...options.team === void 0 ? {} : { team: options.team },
2159
2161
  attachments: options.attachments
2160
2162
  }
2161
2163
  };
@@ -3064,6 +3066,213 @@ function formatIntercomTeam(team) {
3064
3066
  return lines.join("\n");
3065
3067
  }
3066
3068
 
3069
+ // codex/named-teams.ts
3070
+ import { randomBytes as randomBytes2 } from "node:crypto";
3071
+ import { readFileSync as readFileSync8 } from "node:fs";
3072
+ import { join as join8 } from "node:path";
3073
+ var NAMED_TEAMS_FILE = "named-teams.json";
3074
+ var NAMED_TEAMS_VERSION = 1;
3075
+ var NAMED_TEAM_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_-]{0,31}$/;
3076
+ function isNamedTeamScope(value) {
3077
+ if (value.length !== 48) return false;
3078
+ for (const char of value) {
3079
+ if (!(char >= "0" && char <= "9" || char >= "a" && char <= "f")) return false;
3080
+ }
3081
+ return true;
3082
+ }
3083
+ function teamsFilePath(agentDir) {
3084
+ return join8(getIntercomDirPath(agentDir ?? getAgentDirPath()), NAMED_TEAMS_FILE);
3085
+ }
3086
+ function genericReadError() {
3087
+ return new Error("Could not read the local named-team list.");
3088
+ }
3089
+ function genericWriteError() {
3090
+ return new Error("Could not create that named team.");
3091
+ }
3092
+ function isPlainObject(value) {
3093
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3094
+ }
3095
+ function parseStoredTeam(value) {
3096
+ if (!isPlainObject(value)) return void 0;
3097
+ if (typeof value.name !== "string" || !NAMED_TEAM_NAME_PATTERN.test(value.name)) return void 0;
3098
+ if (typeof value.scopeId !== "string" || !isNamedTeamScope(value.scopeId)) return void 0;
3099
+ if (typeof value.managerSessionId !== "string" || !value.managerSessionId.trim()) return void 0;
3100
+ if (value.managerSessionId !== value.managerSessionId.trim() || /[\u0000-\u001f\u007f]/.test(value.managerSessionId)) {
3101
+ return void 0;
3102
+ }
3103
+ if (typeof value.createdAt !== "number" || !Number.isSafeInteger(value.createdAt) || value.createdAt <= 0) {
3104
+ return void 0;
3105
+ }
3106
+ if (value.memberSessionIds !== void 0 && (!Array.isArray(value.memberSessionIds) || !value.memberSessionIds.every((id) => typeof id === "string" && id.trim() === id && id.length > 0 && !/[\u0000-\u001f\u007f]/.test(id)) || new Set(value.memberSessionIds).size !== value.memberSessionIds.length || !value.memberSessionIds.includes(value.managerSessionId))) return void 0;
3107
+ if (value.work !== void 0 && (typeof value.work !== "string" || !value.work.trim() || value.work.length > 2e3)) return void 0;
3108
+ return {
3109
+ name: value.name,
3110
+ scopeId: value.scopeId,
3111
+ managerSessionId: value.managerSessionId,
3112
+ createdAt: value.createdAt,
3113
+ ...value.memberSessionIds === void 0 ? {} : { memberSessionIds: value.memberSessionIds },
3114
+ ...value.work === void 0 ? {} : { work: value.work }
3115
+ };
3116
+ }
3117
+ function parseTeamName(raw) {
3118
+ const name = raw.trim();
3119
+ if (!name || name.includes(" ") || !NAMED_TEAM_NAME_PATTERN.test(name)) {
3120
+ throw new Error("Team names start with a letter and may include letters, numbers, hyphens, or underscores (max 32).");
3121
+ }
3122
+ return name;
3123
+ }
3124
+ function generateNamedTeamScope(existing = []) {
3125
+ const taken = new Set(existing);
3126
+ for (let attempt = 0; attempt < 8; attempt += 1) {
3127
+ const scopeId = randomBytes2(24).toString("hex");
3128
+ if (!taken.has(scopeId)) return scopeId;
3129
+ }
3130
+ throw genericWriteError();
3131
+ }
3132
+ function listNamedTeams(agentDir) {
3133
+ let raw;
3134
+ try {
3135
+ raw = readFileSync8(teamsFilePath(agentDir), "utf8");
3136
+ } catch (error) {
3137
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") return [];
3138
+ throw genericReadError();
3139
+ }
3140
+ let parsed;
3141
+ try {
3142
+ parsed = JSON.parse(raw);
3143
+ } catch {
3144
+ throw genericReadError();
3145
+ }
3146
+ if (!isPlainObject(parsed) || parsed.version !== NAMED_TEAMS_VERSION || !Array.isArray(parsed.teams)) {
3147
+ throw genericReadError();
3148
+ }
3149
+ const teams = [];
3150
+ const names = /* @__PURE__ */ new Set();
3151
+ const scopes = /* @__PURE__ */ new Set();
3152
+ for (const entry of parsed.teams) {
3153
+ const team = parseStoredTeam(entry);
3154
+ if (!team || names.has(team.name) || scopes.has(team.scopeId)) throw genericReadError();
3155
+ names.add(team.name);
3156
+ scopes.add(team.scopeId);
3157
+ teams.push(team);
3158
+ }
3159
+ return teams;
3160
+ }
3161
+ function formatJoinableNamedTeamList(teams) {
3162
+ if (teams.length === 0) {
3163
+ return 'No joinable named teams found.\nCreate one with intercom_join({ name: "billing", create: true }).';
3164
+ }
3165
+ return [
3166
+ "Joinable named teams:",
3167
+ ...teams.map((team, index) => ` ${index + 1}) ${team.name}`)
3168
+ ].join("\n");
3169
+ }
3170
+
3171
+ // codex/named-team-membership.ts
3172
+ import { mkdirSync as mkdirSync5, rmSync } from "node:fs";
3173
+ import { join as join9 } from "node:path";
3174
+ import { setTimeout as delay2 } from "node:timers/promises";
3175
+ function namedTeamMemberIds(team) {
3176
+ return team.memberSessionIds ?? [team.managerSessionId];
3177
+ }
3178
+ function sessionNamedTeams(sessionId, agentDir) {
3179
+ return listNamedTeams(agentDir).filter((team) => namedTeamMemberIds(team).includes(sessionId));
3180
+ }
3181
+ async function appendNamedTeamMembership(input) {
3182
+ const name = parseTeamName(input.name);
3183
+ const memberIds = [.../* @__PURE__ */ new Set([input.selfId, ...input.members ?? []])];
3184
+ if (memberIds.some((id) => !id || id.trim() !== id || /[\u0000-\u001f\u007f]/.test(id))) {
3185
+ throw new Error("Invalid team member session ID");
3186
+ }
3187
+ if (input.work !== void 0 && (!input.work.trim() || input.work.length > 2e3)) {
3188
+ throw new Error("Work must be a non-empty task description (max 2000 characters)");
3189
+ }
3190
+ const dir = getIntercomDirPath(input.agentDir ?? getAgentDirPath());
3191
+ ensureIntercomRuntimeDir(dir);
3192
+ const lock = join9(dir, "named-teams.lock");
3193
+ const deadline = Date.now() + 5e3;
3194
+ for (; ; ) {
3195
+ try {
3196
+ mkdirSync5(lock, { mode: 448 });
3197
+ break;
3198
+ } catch (error) {
3199
+ if (error.code !== "EEXIST") throw error;
3200
+ if (Date.now() >= deadline) throw new Error("Team registry is busy; retry. If a writer crashed, remove the stale named-teams.lock directory after verifying no writer is active.");
3201
+ await delay2(25);
3202
+ }
3203
+ }
3204
+ try {
3205
+ const teams = listNamedTeams(input.agentDir);
3206
+ let team = teams.find((entry) => entry.name === name);
3207
+ if (input.create) {
3208
+ if (team) throw new Error(`A named team called ${name} already exists; join it instead.`);
3209
+ team = { name, scopeId: generateNamedTeamScope(teams.map((entry) => entry.scopeId)), managerSessionId: input.selfId, createdAt: Date.now() };
3210
+ teams.push(team);
3211
+ }
3212
+ if (!team) throw new Error(`Team "${name}" does not exist; create it explicitly.`);
3213
+ if (memberIds.some((id) => id !== input.selfId) && team.managerSessionId !== input.selfId) {
3214
+ throw new Error(`Only the manager of team "${name}" may add other sessions`);
3215
+ }
3216
+ if (input.work !== void 0 && !input.create) {
3217
+ throw new Error("Work is set when creating a team; use a new team for a different task");
3218
+ }
3219
+ const updated = {
3220
+ ...team,
3221
+ memberSessionIds: [.../* @__PURE__ */ new Set([...namedTeamMemberIds(team), ...memberIds])],
3222
+ ...input.work === void 0 ? {} : { work: input.work.trim() }
3223
+ };
3224
+ writeDurableJson(join9(dir, NAMED_TEAMS_FILE), {
3225
+ version: NAMED_TEAMS_VERSION,
3226
+ teams: teams.map((entry) => entry.name === name ? updated : entry)
3227
+ });
3228
+ return updated;
3229
+ } finally {
3230
+ rmSync(lock, { recursive: true });
3231
+ }
3232
+ }
3233
+ function requireNamedTeamMembers(name, selfId, peerId, agentDir) {
3234
+ const team = listNamedTeams(agentDir).find((entry) => entry.name === name);
3235
+ if (!team) throw new Error(`Unknown team "${name}"`);
3236
+ const members = namedTeamMemberIds(team);
3237
+ if (!members.includes(selfId) || !members.includes(peerId)) {
3238
+ throw new Error(`Both sessions must belong to team "${name}" before messaging`);
3239
+ }
3240
+ return team;
3241
+ }
3242
+ function resolveNamedMessageTeam(selfId, peerId, requested, agentDir) {
3243
+ if (requested !== void 0) return requireNamedTeamMembers(requested, selfId, peerId, agentDir).name;
3244
+ const mine = sessionNamedTeams(selfId, agentDir);
3245
+ const shared = mine.filter((team) => namedTeamMemberIds(team).includes(peerId));
3246
+ if (shared.length === 1) return shared[0].name;
3247
+ if (shared.length > 1) throw new Error("Multiple shared teams; specify `team` for this task");
3248
+ return void 0;
3249
+ }
3250
+ function namedTeamRoster(team, selfId, sessions) {
3251
+ return {
3252
+ name: team.name,
3253
+ ...team.work ? { work: team.work } : {},
3254
+ self: { id: selfId, isManager: selfId === team.managerSessionId },
3255
+ manager: { target: team.managerSessionId, connected: sessions.some((entry) => entry.id === team.managerSessionId) },
3256
+ members: namedTeamMemberIds(team).map((id) => ({
3257
+ id,
3258
+ target: id,
3259
+ name: sessions.find((entry) => entry.id === id)?.name,
3260
+ role: id === team.managerSessionId ? "manager" : "member",
3261
+ connected: sessions.some((entry) => entry.id === id)
3262
+ }))
3263
+ };
3264
+ }
3265
+ function formatNamedTeamRoster(team) {
3266
+ return [
3267
+ `Team: ${team.name}`,
3268
+ ...team.work ? [`Work: ${team.work}`] : [],
3269
+ `You: ${team.self.id}${team.self.isManager ? " [manager]" : ""}`,
3270
+ `Manager: ${team.manager.target}${team.manager.connected ? "" : " [offline]"}`,
3271
+ "Members:",
3272
+ ...team.members.map((member) => `- ${member.name || member.id} (${member.id}) [${member.role}]${member.connected ? "" : " [offline]"}`)
3273
+ ].join("\n");
3274
+ }
3275
+
3067
3276
  // codex/runtime.ts
3068
3277
  function formatAttachments(attachments) {
3069
3278
  if (!attachments?.length) return "";
@@ -3119,10 +3328,20 @@ function formatSessionList(sessions, currentSessionId, currentCwd) {
3119
3328
  }).join("\n");
3120
3329
  }
3121
3330
 
3331
+ // codex/team-guidance.ts
3332
+ var TASK_TEAM_GUIDANCE = `Intercom task-team rules:
3333
+ - When the user delegates to named peers and this task has no approved team, ask once whether to form a team with you and those peers. Wait for approval before creating a team or adding peers. An explicit create/join request is approval; never ask again for an approved task or inbound team message.
3334
+ - After approval, discover the connected peers, then intercom_join({ name: "launch", create: true, members: ["front", "writer"], work: "Current task" }) adds everyone in one call. Do not ask the user to join each terminal manually.
3335
+ - Membership is additive: joining another team preserves previous teams. Reuse the approved team for the same task; a different task may need a different team.
3336
+ - Initial contact without a shared team is allowed: omit team for an ungrouped direct message. Unrelated team memberships must not prevent contact or silently create teams.
3337
+ - Include team on task sends/asks, especially when peers share multiple teams. Replies inherit the original message's team via askId or contextId from intercom_pending; never override it using a current team or mix contexts from different tasks.
3338
+ - Use intercom_send for assignments, progress/status requests, notifications and follow-ups. Use intercom_ask only when your next step genuinely depends on the answer; keep only one unresolved ask per recipient.`;
3339
+
3122
3340
  // codex/bridge-daemon.ts
3123
3341
  var APPROVED_INTERCOM_TOOLS = /* @__PURE__ */ new Set([
3124
3342
  "intercom_whoami",
3125
3343
  "intercom_team",
3344
+ "intercom_join",
3126
3345
  "intercom_status",
3127
3346
  "intercom_list",
3128
3347
  "intercom_set_summary",
@@ -3156,6 +3375,7 @@ ${agent.instructions}` : "";
3156
3375
  return [
3157
3376
  `Intercom message for ${agent.name}.`,
3158
3377
  `From: ${formatSessionDisplay(from)} (${from.id})`,
3378
+ ...message.content.team ? [`[Team: ${message.content.team}]`] : [],
3159
3379
  `Message id: ${message.id}`,
3160
3380
  "",
3161
3381
  message.content.text,
@@ -3251,7 +3471,7 @@ function intercomSendFromArgs(rawArgs) {
3251
3471
  } catch {
3252
3472
  return null;
3253
3473
  }
3254
- return typeof args.to === "string" && typeof args.message === "string" ? { to: args.to, message: args.message } : null;
3474
+ return typeof args.to === "string" && typeof args.message === "string" ? { to: args.to, message: args.message, ...typeof args.team === "string" ? { team: args.team } : {} } : null;
3255
3475
  }
3256
3476
  function getCompletedIntercomSend(params) {
3257
3477
  if (!params || typeof params !== "object") return null;
@@ -3279,7 +3499,7 @@ function asOptionalPositiveInteger(value, name) {
3279
3499
  return validateAskTimeoutMs(value, name);
3280
3500
  }
3281
3501
  function normalizeToolName(name) {
3282
- const mcpMatch = name.match(/(?:^|__|\.)intercom_(whoami|status|list|set_summary|send|ask|pending|reply)$/);
3502
+ const mcpMatch = name.match(/(?:^|__|\.)intercom_(whoami|team|join|status|list|set_summary|send|ask|pending|reply)$/);
3283
3503
  if (mcpMatch) return `intercom_${mcpMatch[1]}`;
3284
3504
  return name;
3285
3505
  }
@@ -3402,7 +3622,7 @@ var VirtualCodexAgent = class {
3402
3622
  }
3403
3623
  scheduleReconnect() {
3404
3624
  if (!this.reconnectEnabled || this.reconnectTimer) return;
3405
- const delay2 = this.reconnectDelays[Math.min(this.reconnectAttempt, this.reconnectDelays.length - 1)];
3625
+ const delay3 = this.reconnectDelays[Math.min(this.reconnectAttempt, this.reconnectDelays.length - 1)];
3406
3626
  this.reconnectTimer = setTimeout(() => {
3407
3627
  this.reconnectTimer = null;
3408
3628
  void this.connectIntercom().then(() => {
@@ -3416,7 +3636,7 @@ var VirtualCodexAgent = class {
3416
3636
  `);
3417
3637
  this.scheduleReconnect();
3418
3638
  });
3419
- }, delay2);
3639
+ }, delay3);
3420
3640
  this.reconnectTimer.unref?.();
3421
3641
  }
3422
3642
  clearReconnectTimer() {
@@ -3455,6 +3675,14 @@ var VirtualCodexAgent = class {
3455
3675
  onNotification(message) {
3456
3676
  const threadId = getNotificationThreadId(message.params);
3457
3677
  if (!threadId || threadId !== this.threadId) return;
3678
+ if (message.method === "thread/name/updated" && isRecord2(message.params)) {
3679
+ const name = message.params.threadName;
3680
+ if (typeof name === "string" && name.trim() && name !== this.agent.name) {
3681
+ this.agent.name = name;
3682
+ this.client.updatePresence({ name });
3683
+ }
3684
+ return;
3685
+ }
3458
3686
  if (message.method === "error") {
3459
3687
  const params = isRecord2(message.params) ? message.params : {};
3460
3688
  const detail = isRecord2(params.error) && typeof params.error.message === "string" ? params.error.message : "Codex turn error";
@@ -3501,13 +3729,19 @@ var VirtualCodexAgent = class {
3501
3729
  if (this.threadId) {
3502
3730
  try {
3503
3731
  const sandbox2 = bridgeAgentSandboxMode(this.agent);
3504
- await this.app.request("thread/resume", {
3732
+ const result2 = await this.app.request("thread/resume", {
3505
3733
  threadId: this.threadId,
3506
3734
  cwd: this.agent.cwd,
3507
3735
  model: this.agent.model ?? null,
3508
3736
  approvalPolicy: bridgeAgentApprovalPolicy(this.agent),
3509
3737
  sandbox: sandbox2
3510
3738
  });
3739
+ if (isRecord2(result2) && isRecord2(result2.thread)) {
3740
+ this.onNotification({ method: "thread/name/updated", params: {
3741
+ threadId: this.threadId,
3742
+ threadName: result2.thread.name
3743
+ } });
3744
+ }
3511
3745
  return this.threadId;
3512
3746
  } catch {
3513
3747
  this.threadId = null;
@@ -3520,7 +3754,7 @@ var VirtualCodexAgent = class {
3520
3754
  approvalPolicy: bridgeAgentApprovalPolicy(this.agent),
3521
3755
  sandbox,
3522
3756
  serviceName: "codex-intercom",
3523
- developerInstructions: this.agent.instructions ?? null,
3757
+ developerInstructions: [this.agent.instructions, TASK_TEAM_GUIDANCE].filter(Boolean).join("\n\n"),
3524
3758
  threadSource: "cli"
3525
3759
  });
3526
3760
  this.threadId = getThreadId(result);
@@ -3533,7 +3767,7 @@ var VirtualCodexAgent = class {
3533
3767
  routeMessage(from, message) {
3534
3768
  const toolWaiter = this.toolReplyWaiters.get(message.replyTo ?? "");
3535
3769
  if (toolWaiter) {
3536
- if (from.id === toolWaiter.from) {
3770
+ if (from.id === toolWaiter.from && message.content.team === toolWaiter.team) {
3537
3771
  this.toolReplyWaiters.delete(message.replyTo ?? "");
3538
3772
  clearTimeout(toolWaiter.timeout);
3539
3773
  toolWaiter.cleanup?.();
@@ -3579,7 +3813,7 @@ var VirtualCodexAgent = class {
3579
3813
  this.waiters.delete(turnId);
3580
3814
  const reply = this.finalMessages.get(turnId)?.trim() || "Codex turn completed without a final message.";
3581
3815
  for (const waiter of waiters) {
3582
- await this.client.send(waiter.from.id, { text: reply, replyTo: waiter.message.id }).catch((error) => {
3816
+ await this.client.send(waiter.from.id, { text: reply, replyTo: waiter.message.id, team: waiter.message.content.team }).catch((error) => {
3583
3817
  process.stderr.write(`reply failed for ${this.agent.id}: ${error instanceof Error ? error.message : String(error)}
3584
3818
  `);
3585
3819
  });
@@ -3591,12 +3825,16 @@ var VirtualCodexAgent = class {
3591
3825
  const lowerTo = send.to.toLowerCase();
3592
3826
  const remaining = [];
3593
3827
  for (const waiter of waiters) {
3828
+ if (send.team !== void 0 && send.team !== waiter.message.content.team) {
3829
+ remaining.push(waiter);
3830
+ continue;
3831
+ }
3594
3832
  const matchesSender = send.to === waiter.from.id || waiter.from.id.startsWith(send.to) || waiter.from.name?.toLowerCase() === lowerTo;
3595
3833
  if (!matchesSender) {
3596
3834
  remaining.push(waiter);
3597
3835
  continue;
3598
3836
  }
3599
- await this.client.send(waiter.from.id, { text: send.message, replyTo: waiter.message.id }).catch((error) => {
3837
+ await this.client.send(waiter.from.id, { text: send.message, replyTo: waiter.message.id, team: waiter.message.content.team }).catch((error) => {
3600
3838
  remaining.push(waiter);
3601
3839
  process.stderr.write(`reply failed for ${this.agent.id}: ${error instanceof Error ? error.message : String(error)}
3602
3840
  `);
@@ -3661,8 +3899,32 @@ name: ${this.agent.name}
3661
3899
  cwd: ${this.agent.cwd}`,
3662
3900
  { session_id: this.agent.id, name: this.agent.name, cwd: this.agent.cwd, model: this.agent.model ?? "codex-app-server" }
3663
3901
  );
3902
+ case "intercom_join": {
3903
+ if (args.name === void 0) {
3904
+ if (args.create || args.members !== void 0 || args.work !== void 0) throw new Error("Creating or extending a team requires a name");
3905
+ return textToolResult(formatJoinableNamedTeamList(listNamedTeams()));
3906
+ }
3907
+ if (args.members !== void 0 && !Array.isArray(args.members)) throw new Error("members must be an array");
3908
+ const sessions = await this.client.listSessions();
3909
+ const members = Array.from(args.members ?? [], (member) => {
3910
+ const id = resolveSessionTarget(sessions, asString(member, "member"));
3911
+ if (!id) throw new Error("Team member is not connected");
3912
+ return id;
3913
+ });
3914
+ const team = await appendNamedTeamMembership({ name: asString(args.name, "name"), selfId: this.agent.id, members, create: args.create === true, ...args.work === void 0 ? {} : { work: asString(args.work, "work") } });
3915
+ const roster = namedTeamRoster(team, this.agent.id, sessions);
3916
+ return textToolResult(formatNamedTeamRoster(roster), { ok: true, team: team.name, roster });
3917
+ }
3664
3918
  case "intercom_team": {
3665
3919
  const sessions = await this.client.listSessions();
3920
+ const mine = sessionNamedTeams(this.agent.id);
3921
+ const requested = args.team === void 0 ? void 0 : asString(args.team, "team");
3922
+ const selected = requested ? mine.filter((entry) => entry.name === requested) : mine;
3923
+ if (requested && !selected.length) throw new Error("You do not belong to that team");
3924
+ if (selected.length) {
3925
+ const teams = selected.map((entry) => namedTeamRoster(entry, this.agent.id, sessions));
3926
+ return textToolResult(teams.map(formatNamedTeamRoster).join("\n\n"), { teams });
3927
+ }
3666
3928
  const team = await resolveIntercomTeam({ selfId: this.agent.id, sessions });
3667
3929
  return textToolResult(formatIntercomTeam(team), team);
3668
3930
  }
@@ -3691,7 +3953,8 @@ Active sessions: ${sessions.length}`,
3691
3953
  const to = asString(args.to, "to");
3692
3954
  const message = asString(args.message, "message");
3693
3955
  const sendTo = await this.resolveTarget(to);
3694
- const result = await this.client.send(sendTo, { text: message });
3956
+ const team = resolveNamedMessageTeam(this.agent.id, sendTo, args.team === void 0 ? void 0 : asString(args.team, "team"));
3957
+ const result = await this.client.send(sendTo, { text: message, team });
3695
3958
  if (!result.delivered) {
3696
3959
  return textToolResult(`Message to "${to}" was not delivered: ${result.reason ?? "Session may not exist or has disconnected."}`, { ok: false, message_id: result.id, reason: result.reason }, true);
3697
3960
  }
@@ -3704,10 +3967,11 @@ Active sessions: ${sessions.length}`,
3704
3967
  const message = asString(args.message, "message");
3705
3968
  const timeoutMs = asOptionalPositiveInteger(args.timeout_ms, "timeout_ms") ?? DEFAULT_ASK_TIMEOUT_MS;
3706
3969
  const sendTo = await this.resolveTarget(to);
3970
+ const team = resolveNamedMessageTeam(this.agent.id, sendTo, args.team === void 0 ? void 0 : asString(args.team, "team"));
3707
3971
  const questionId = randomUUID4();
3708
- const replyPromise = this.waitForToolReply(sendTo, questionId, timeoutMs, signal);
3972
+ const replyPromise = this.waitForToolReply(sendTo, questionId, timeoutMs, signal, team);
3709
3973
  void replyPromise.catch(() => void 0);
3710
- const result = await this.client.send(sendTo, { messageId: questionId, text: message, expectsReply: true });
3974
+ const result = await this.client.send(sendTo, { messageId: questionId, text: message, expectsReply: true, team });
3711
3975
  if (!result.delivered) {
3712
3976
  this.rejectToolReply(questionId, new Error(result.reason ?? "Session may not exist or has disconnected."));
3713
3977
  return textToolResult(`Message to "${to}" was not delivered: ${result.reason ?? "Session may not exist or has disconnected."}`, { ok: false, message_id: result.id, reason: result.reason }, true);
@@ -3743,7 +4007,7 @@ ${reply.content.text}${formatAttachments(reply.content.attachments)}`, { ok: tru
3743
4007
  const sessions = await this.client.listSessions();
3744
4008
  return resolveSessionTarget(sessions, to) ?? to;
3745
4009
  }
3746
- waitForToolReply(from, replyTo, timeoutMs = DEFAULT_ASK_TIMEOUT_MS, signal) {
4010
+ waitForToolReply(from, replyTo, timeoutMs = DEFAULT_ASK_TIMEOUT_MS, signal, team) {
3747
4011
  return new Promise((resolve4, reject) => {
3748
4012
  if (signal?.aborted) {
3749
4013
  reject(new Error("intercom_ask cancelled"));
@@ -3767,7 +4031,7 @@ ${reply.content.text}${formatAttachments(reply.content.attachments)}`, { ok: tru
3767
4031
  reject(new Error(`No reply from "${from}" within ${Math.round(timeoutMs / 1e3)} seconds`));
3768
4032
  }, timeoutMs);
3769
4033
  signal?.addEventListener("abort", onAbort, { once: true });
3770
- this.toolReplyWaiters.set(replyTo, { from, resolve: resolve4, reject, timeout, cleanup });
4034
+ this.toolReplyWaiters.set(replyTo, { from, team, resolve: resolve4, reject, timeout, cleanup });
3771
4035
  });
3772
4036
  }
3773
4037
  rejectToolReply(replyTo, error) {
package/dist/broker.mjs CHANGED
@@ -1,4 +1,4 @@
1
- process.stderr.write("[agent-intercom-build] package=@ctliz/agent-intercom-codex version=0.12.1 target=broker sourceSha256=9e94589286ec909b8fd50e96f38fa52f7c21424081d18e8155e01cd3bffde8fb\n");
1
+ process.stderr.write("[agent-intercom-build] package=@ctliz/agent-intercom-codex version=0.13.0 target=broker sourceSha256=04013522326f3042c72f6454a9c79e26d2d1c8de7af57769ed890e50fca514e3\n");
2
2
 
3
3
  // broker/broker.ts
4
4
  import net from "net";
@@ -1291,6 +1291,7 @@ function isMessage(value) {
1291
1291
  if (typeof content.text !== "string" || Buffer.byteLength(content.text, "utf-8") > MAX_MESSAGE_TEXT_BYTES) {
1292
1292
  return false;
1293
1293
  }
1294
+ if (content.team !== void 0 && (typeof content.team !== "string" || !/^[A-Za-z][A-Za-z0-9_-]{0,31}$/.test(content.team))) return false;
1294
1295
  return content.attachments === void 0 || Array.isArray(content.attachments) && content.attachments.length <= MAX_ATTACHMENTS && content.attachments.every(isAttachment);
1295
1296
  }
1296
1297
  function isSessionId(value) {
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "package": "@ctliz/agent-intercom-codex",
4
- "version": "0.12.1",
5
- "sourceSha256": "9e94589286ec909b8fd50e96f38fa52f7c21424081d18e8155e01cd3bffde8fb",
4
+ "version": "0.13.0",
5
+ "sourceSha256": "04013522326f3042c72f6454a9c79e26d2d1c8de7af57769ed890e50fca514e3",
6
6
  "targets": [
7
7
  "codex-server",
8
8
  "broker",