@ctliz/agent-intercom-pi 0.12.2 → 0.14.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.
- package/README.md +80 -10
- package/bin/intercom-send.mjs +4 -0
- package/broker/client.ts +7 -1
- package/broker/framing.ts +4 -1
- package/cli-send.ts +39 -0
- package/index.ts +180 -72
- package/named-team-membership.ts +124 -0
- package/named-teams.ts +12 -0
- package/package.json +12 -2
- package/reply-tracker.ts +36 -13
- package/skills/pi-intercom/SKILL.md +74 -8
- package/tool-result.ts +26 -0
- package/types.ts +2 -0
- package/ui/inline-message.ts +2 -1
package/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { intercomOutputSchema, structuredIntercomResult } from "./tool-result.ts";
|
|
2
3
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
3
4
|
import { randomUUID } from "crypto";
|
|
4
5
|
import { spawn, spawnSync } from "child_process";
|
|
@@ -15,7 +16,8 @@ import { formatMessageTiming } from "./ui/timestamps.ts";
|
|
|
15
16
|
import { formatSessionDisplayName, sanitizeDisplayText, sessionOriginLabel, shortestUniqueIdPrefixes } from "./ui/session-identity.ts";
|
|
16
17
|
import { getAskTimeoutMs, getAskWaitMs, loadConfig, type IntercomConfig } from "./config.ts";
|
|
17
18
|
import type { SessionInfo, SessionRegistration, Message, Attachment } from "./types.ts";
|
|
18
|
-
import { pendingAskId, ReplyTracker, type IntercomContext } from "./reply-tracker.ts";
|
|
19
|
+
import { pendingAskId, replyContextId, ReplyTracker, type IntercomContext } from "./reply-tracker.ts";
|
|
20
|
+
import { appendNamedTeamMembership, formatNamedTeamRoster, namedTeamRoster, requireNamedTeamMembers, resolveNamedMessageTeam, sessionNamedTeams } from "./named-team-membership.ts";
|
|
19
21
|
import { InboundMessageConflictError, PersistentInboundInbox, readPendingAsksSnapshot, type StoredInboundMessage } from "./inbound-inbox.ts";
|
|
20
22
|
import { PersistentOutboundOutbox } from "./outbound-outbox.ts";
|
|
21
23
|
import { formatIntercomTeam, resolveBossIntercomTeam, resolveIntercomTeam, resolveManagedInboxSession } from "./team.ts";
|
|
@@ -33,7 +35,6 @@ import {
|
|
|
33
35
|
} from "./workspace-join.ts";
|
|
34
36
|
import {
|
|
35
37
|
buildJoinableCircles,
|
|
36
|
-
createNamedTeam,
|
|
37
38
|
findNamedTeamByScope,
|
|
38
39
|
formatCreateSuccess,
|
|
39
40
|
formatJoinableCircleList,
|
|
@@ -147,9 +148,8 @@ function toError(error: unknown): Error {
|
|
|
147
148
|
}
|
|
148
149
|
|
|
149
150
|
function toolErrorDetails(error: unknown): { error: true; code?: string } {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
: { error: true };
|
|
151
|
+
const code = (error as { code?: unknown } | null)?.code;
|
|
152
|
+
return { error: true, ...(typeof code === "string" ? { code } : {}) };
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
class AskWaitElapsedError extends Error {
|
|
@@ -680,6 +680,15 @@ function getNamePollMs(): number {
|
|
|
680
680
|
return 1000;
|
|
681
681
|
}
|
|
682
682
|
export default function piIntercomExtension(pi: ExtensionAPI) {
|
|
683
|
+
function registerIntercomTool(tool: ToolDefinition<any, any>): void {
|
|
684
|
+
pi.registerTool({
|
|
685
|
+
...tool,
|
|
686
|
+
outputSchema: intercomOutputSchema,
|
|
687
|
+
async execute(...args) {
|
|
688
|
+
return structuredIntercomResult(await tool.execute(...args));
|
|
689
|
+
},
|
|
690
|
+
});
|
|
691
|
+
}
|
|
683
692
|
let runtimeScopeId = intercomScopeIdFromEnvForRegistration();
|
|
684
693
|
const initialHarnessSessionId = process.env[INTERCOM_SESSION_ID_ENV]?.trim();
|
|
685
694
|
const initialGenericSessionId = process.env.AGENT_INTERCOM_SESSION_ID?.trim();
|
|
@@ -706,6 +715,7 @@ export default function piIntercomExtension(pi: ExtensionAPI) {
|
|
|
706
715
|
let reconnectPromiseGeneration: number | null = null;
|
|
707
716
|
let startupConnectTimer: NodeJS.Timeout | null = null;
|
|
708
717
|
let reconnectAttempt = 0;
|
|
718
|
+
let registrationConflict: Error | null = null;
|
|
709
719
|
let shuttingDown = false;
|
|
710
720
|
let disposed = true;
|
|
711
721
|
let runtimeStarted = false;
|
|
@@ -722,13 +732,14 @@ export default function piIntercomExtension(pi: ExtensionAPI) {
|
|
|
722
732
|
const replyWaiters = new Map<string, {
|
|
723
733
|
from: string;
|
|
724
734
|
replyTo: string;
|
|
735
|
+
team?: string;
|
|
725
736
|
resolve: (message: Message) => void;
|
|
726
737
|
reject: (error: Error) => void;
|
|
727
738
|
}>();
|
|
728
739
|
function hasReplyWaiterForTarget(from: string): boolean {
|
|
729
740
|
return Array.from(replyWaiters.values()).some((waiter) => waiter.from.toLowerCase() === from.toLowerCase());
|
|
730
741
|
}
|
|
731
|
-
function waitForReply(from: string, replyTo: string, signal?: AbortSignal, onCancel?: () => void): Promise<Message> {
|
|
742
|
+
function waitForReply(from: string, replyTo: string, signal?: AbortSignal, onCancel?: () => void, team?: string): Promise<Message> {
|
|
732
743
|
if (hasReplyWaiterForTarget(from)) {
|
|
733
744
|
return Promise.reject(new Error(`Already waiting for a reply from "${from}"`));
|
|
734
745
|
}
|
|
@@ -753,6 +764,7 @@ export default function piIntercomExtension(pi: ExtensionAPI) {
|
|
|
753
764
|
replyWaiters.set(replyTo, {
|
|
754
765
|
from,
|
|
755
766
|
replyTo,
|
|
767
|
+
team,
|
|
756
768
|
resolve: (message) => {
|
|
757
769
|
cleanup();
|
|
758
770
|
resolve(message);
|
|
@@ -835,7 +847,8 @@ export default function piIntercomExtension(pi: ExtensionAPI) {
|
|
|
835
847
|
}
|
|
836
848
|
function currentStatus(): string {
|
|
837
849
|
const activeToolName = activeTools.values().next().value;
|
|
838
|
-
const
|
|
850
|
+
const busy = agentRunning || getLiveContext()?.isIdle() === false;
|
|
851
|
+
const lifecycleStatus = activeToolName ? `tool:${activeToolName}` : busy ? "thinking" : "idle";
|
|
839
852
|
const queueStatus = inboundInbox?.size ? ` · inbox:${inboundInbox.size}` : "";
|
|
840
853
|
const outboxStatus = client?.outboxSize ? ` · outbox:${client.outboxSize}` : "";
|
|
841
854
|
return config.status ? `${lifecycleStatus}${queueStatus}${outboxStatus} · ${config.status}` : `${lifecycleStatus}${queueStatus}${outboxStatus}`;
|
|
@@ -910,6 +923,9 @@ export default function piIntercomExtension(pi: ExtensionAPI) {
|
|
|
910
923
|
}
|
|
911
924
|
return rejectManagedJoin(classifyMembership(), isZhLocale());
|
|
912
925
|
}
|
|
926
|
+
function taskTeamsAvailable(): boolean {
|
|
927
|
+
return !rejectCircleChange();
|
|
928
|
+
}
|
|
913
929
|
async function applyCircleScope(ctx: ExtensionContext, scopeId: string, managerSessionId?: string): Promise<void> {
|
|
914
930
|
if (managerSessionId) {
|
|
915
931
|
process.env.AGENT_INTERCOM_MANAGER_TARGET = managerSessionId;
|
|
@@ -922,16 +938,22 @@ export default function piIntercomExtension(pi: ExtensionAPI) {
|
|
|
922
938
|
await switchRuntimeScope(scopeId);
|
|
923
939
|
syncPresenceIdentity(currentSessionId ?? ctx.sessionManager.getSessionId());
|
|
924
940
|
}
|
|
925
|
-
async function
|
|
941
|
+
async function appendTaskTeam(ctx: ExtensionContext, name: string, create = false, members: string[] = [], work?: string) {
|
|
926
942
|
const blocked = rejectCircleChange();
|
|
927
943
|
if (blocked) throw new Error(blocked);
|
|
928
944
|
const teamName = parseTeamName(name);
|
|
929
|
-
if (!getLiveContext(ctx))
|
|
930
|
-
|
|
945
|
+
if (!getLiveContext(ctx)) startSessionRuntime(ctx);
|
|
946
|
+
const activeClient = await ensureConnected("tool");
|
|
947
|
+
const memberIds: string[] = [];
|
|
948
|
+
for (const target of members) {
|
|
949
|
+
const id = await resolveSessionTarget(activeClient, target);
|
|
950
|
+
if (!id) throw new Error(`Team member "${target}" is not connected`);
|
|
951
|
+
memberIds.push(id);
|
|
931
952
|
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
953
|
+
return appendNamedTeamMembership({ name: teamName, selfId: activeClient.sessionId!, create, members: memberIds, work });
|
|
954
|
+
}
|
|
955
|
+
async function createAndJoinNamedTeam(ctx: ExtensionContext, name: string): Promise<string> {
|
|
956
|
+
const team = await appendTaskTeam(ctx, name, true);
|
|
935
957
|
return formatCreateSuccess({ team: team.name, name: currentDisplayName(), zh: isZhLocale() });
|
|
936
958
|
}
|
|
937
959
|
async function listOrJoinCircle(ctx: ExtensionContext, args: string): Promise<string> {
|
|
@@ -962,10 +984,12 @@ export default function piIntercomExtension(pi: ExtensionAPI) {
|
|
|
962
984
|
? (zh ? "没有这个编号的团队。" : "No team uses that number.")
|
|
963
985
|
: (zh ? "无法加入该团队。" : "Could not join that team."));
|
|
964
986
|
}
|
|
987
|
+
if (selected.kind === "named") {
|
|
988
|
+
await appendTaskTeam(ctx, selected.name);
|
|
989
|
+
return formatNamedJoinSuccess({ team: selected.name, name: currentDisplayName(), zh });
|
|
990
|
+
}
|
|
965
991
|
await applyCircleScope(ctx, selected.scopeId, selected.managerSessionId);
|
|
966
|
-
return selected.
|
|
967
|
-
? formatNamedJoinSuccess({ team: selected.name, name: currentDisplayName(), zh })
|
|
968
|
-
: formatJoinSuccess({ workspace: selected.name, name: currentDisplayName(), zh });
|
|
992
|
+
return formatJoinSuccess({ workspace: selected.name, name: currentDisplayName(), zh });
|
|
969
993
|
}
|
|
970
994
|
|
|
971
995
|
function restoreIntercomSessionId(): void {
|
|
@@ -1010,11 +1034,13 @@ export default function piIntercomExtension(pi: ExtensionAPI) {
|
|
|
1010
1034
|
const attachmentText = stored.message.content.attachments?.length
|
|
1011
1035
|
? formatAttachments(stored.message.content.attachments)
|
|
1012
1036
|
: "";
|
|
1013
|
-
const replyCommand = config.replyHint && stored.message.
|
|
1014
|
-
?
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1037
|
+
const replyCommand = config.replyHint && stored.message.content.team
|
|
1038
|
+
? `intercom_reply({ contextId: "${replyContextId(stored.from.id, stored.message.id)}", message: "..." })`
|
|
1039
|
+
: config.replyHint && stored.message.expectsReply
|
|
1040
|
+
? batchSize === 1
|
|
1041
|
+
? `intercom_reply({ message: "..." })`
|
|
1042
|
+
: `intercom_reply({ to: "${stored.from.id}", message: "..." })`
|
|
1043
|
+
: undefined;
|
|
1018
1044
|
return {
|
|
1019
1045
|
key: stored.key,
|
|
1020
1046
|
from: stored.from,
|
|
@@ -1028,7 +1054,8 @@ export default function piIntercomExtension(pi: ExtensionAPI) {
|
|
|
1028
1054
|
const senderDisplay = formatSessionDisplayName(entry.from);
|
|
1029
1055
|
const prefix = position ? `[${position.index}/${position.total}] ` : "";
|
|
1030
1056
|
const replyInstruction = entry.replyCommand ? `\n\nTo reply, use: ${entry.replyCommand}` : "";
|
|
1031
|
-
|
|
1057
|
+
const teamLabel = entry.message.content.team ? `[Team: ${sanitizeDisplayText(entry.message.content.team)}] ` : "";
|
|
1058
|
+
return `**${prefix}${teamLabel}📨 From ${senderDisplay}** (${entry.from.cwd})${replyInstruction}\n\n${entry.bodyText}`;
|
|
1032
1059
|
}
|
|
1033
1060
|
function sendIncomingBatch(entries: InboundMessageEntry[], generation = runtimeGeneration, forceTrigger = false): void {
|
|
1034
1061
|
if (entries.length === 0) return;
|
|
@@ -1079,6 +1106,7 @@ export default function piIntercomExtension(pi: ExtensionAPI) {
|
|
|
1079
1106
|
inboundFlushTimer.unref?.();
|
|
1080
1107
|
}
|
|
1081
1108
|
function flushIdleMessages(generation = runtimeGeneration): void {
|
|
1109
|
+
if (registrationConflict) return;
|
|
1082
1110
|
if (!inboundInbox || inboundInbox.size === 0) {
|
|
1083
1111
|
return;
|
|
1084
1112
|
}
|
|
@@ -1086,6 +1114,10 @@ export default function piIntercomExtension(pi: ExtensionAPI) {
|
|
|
1086
1114
|
if (!ctx) {
|
|
1087
1115
|
return;
|
|
1088
1116
|
}
|
|
1117
|
+
if (!client?.isConnected()) {
|
|
1118
|
+
scheduleInboundFlush(INBOUND_IDLE_RETRY_MS);
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1089
1121
|
|
|
1090
1122
|
let isIdle: boolean;
|
|
1091
1123
|
try {
|
|
@@ -1194,6 +1226,18 @@ export default function piIntercomExtension(pi: ExtensionAPI) {
|
|
|
1194
1226
|
});
|
|
1195
1227
|
return;
|
|
1196
1228
|
}
|
|
1229
|
+
try {
|
|
1230
|
+
if (message.content.team !== undefined) {
|
|
1231
|
+
if (!taskTeamsAvailable()) throw new Error("Named task-team messages are unavailable in managed-team mode");
|
|
1232
|
+
requireNamedTeamMembers(message.content.team, receivingClient.sessionId!, from.id);
|
|
1233
|
+
}
|
|
1234
|
+
const waiter = message.replyTo ? replyWaiters.get(message.replyTo) : undefined;
|
|
1235
|
+
if (waiter && waiter.team !== message.content.team) throw new Error("Reply team does not match the original ask");
|
|
1236
|
+
} catch (error) {
|
|
1237
|
+
receivingClient.rejectMessage(deliveryId, getErrorMessage(error));
|
|
1238
|
+
pi.appendEntry("intercom_inbox_team_denied", { from: from.id, messageId: message.id, error: getErrorMessage(error), timestamp: Date.now() });
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1197
1241
|
let enqueued;
|
|
1198
1242
|
try {
|
|
1199
1243
|
enqueued = inboxAtReceive.enqueue(from, message);
|
|
@@ -1305,7 +1349,7 @@ export default function piIntercomExtension(pi: ExtensionAPI) {
|
|
|
1305
1349
|
});
|
|
1306
1350
|
}
|
|
1307
1351
|
function scheduleReconnect(): void {
|
|
1308
|
-
if (disposed || shuttingDown || reconnectTimer || reconnectPromise || !getLiveContext()) {
|
|
1352
|
+
if (disposed || shuttingDown || registrationConflict || reconnectTimer || reconnectPromise || !getLiveContext()) {
|
|
1309
1353
|
return;
|
|
1310
1354
|
}
|
|
1311
1355
|
const scheduledGeneration = runtimeGeneration;
|
|
@@ -1327,6 +1371,7 @@ export default function piIntercomExtension(pi: ExtensionAPI) {
|
|
|
1327
1371
|
if (disposed || shuttingDown) {
|
|
1328
1372
|
throw new Error("Intercom shutting down");
|
|
1329
1373
|
}
|
|
1374
|
+
if (registrationConflict) throw registrationConflict;
|
|
1330
1375
|
if (bossTeamScope.present) {
|
|
1331
1376
|
const selfError = currentSessionId ? bossSelfSessionError(bossTeamScope, currentSessionId) : "Boss session identity is unavailable";
|
|
1332
1377
|
if (selfError) throw new BossTeamScopeError("BOSS_TEAM_METADATA_INVALID", selfError);
|
|
@@ -1359,19 +1404,24 @@ export default function piIntercomExtension(pi: ExtensionAPI) {
|
|
|
1359
1404
|
}
|
|
1360
1405
|
client = nextClient;
|
|
1361
1406
|
reconnectAttempt = 0;
|
|
1407
|
+
scheduleInboundFlush(0);
|
|
1362
1408
|
return nextClient;
|
|
1363
1409
|
} catch (error) {
|
|
1364
1410
|
if (client === nextClient) {
|
|
1365
1411
|
client = null;
|
|
1366
1412
|
}
|
|
1367
|
-
if (
|
|
1368
|
-
|
|
1413
|
+
if ((error as { code?: string })?.code === "SESSION_ID_IN_USE" && getLiveContext(contextAtStart, generationAtStart)) {
|
|
1414
|
+
registrationConflict = toError(error);
|
|
1415
|
+
clearReconnectTimer();
|
|
1416
|
+
pi.appendEntry("intercom_registration_conflict", { sessionId: currentSessionId, code: "SESSION_ID_IN_USE", timestamp: Date.now() });
|
|
1417
|
+
notifyIfLive(contextAtStart, "Intercom session is owned by another runtime. Switch to a different session, or release the duplicate owner and /reload. The existing owner was not changed.", "warning", generationAtStart);
|
|
1369
1418
|
}
|
|
1370
1419
|
throw toError(error);
|
|
1371
1420
|
} finally {
|
|
1372
1421
|
if (reconnectPromise === nextReconnectPromise) {
|
|
1373
1422
|
reconnectPromise = null;
|
|
1374
1423
|
reconnectPromiseGeneration = null;
|
|
1424
|
+
if (reason === "background" && !client) scheduleReconnect();
|
|
1375
1425
|
}
|
|
1376
1426
|
}
|
|
1377
1427
|
})();
|
|
@@ -1490,6 +1540,7 @@ export default function piIntercomExtension(pi: ExtensionAPI) {
|
|
|
1490
1540
|
runtimeStarted = true;
|
|
1491
1541
|
runtimeGeneration += 1;
|
|
1492
1542
|
reconnectAttempt = 0;
|
|
1543
|
+
registrationConflict = null;
|
|
1493
1544
|
clearReconnectTimer();
|
|
1494
1545
|
clearStartupConnectTimer();
|
|
1495
1546
|
clearNamePollTimer();
|
|
@@ -1810,11 +1861,16 @@ export default function piIntercomExtension(pi: ExtensionAPI) {
|
|
|
1810
1861
|
activeTools.delete(event.toolCallId);
|
|
1811
1862
|
syncPresenceStatus();
|
|
1812
1863
|
});
|
|
1813
|
-
pi.on("agent_end", () => {
|
|
1814
|
-
if (!getLiveContext())
|
|
1815
|
-
return;
|
|
1816
|
-
}
|
|
1864
|
+
pi.on("agent_end", (_event, ctx) => {
|
|
1865
|
+
if (!getLiveContext(ctx)) return;
|
|
1817
1866
|
replyTracker.endTurn();
|
|
1867
|
+
agentRunning = !ctx.isIdle();
|
|
1868
|
+
activeTools.clear();
|
|
1869
|
+
syncPresenceStatus();
|
|
1870
|
+
scheduleInboundFlush(0);
|
|
1871
|
+
});
|
|
1872
|
+
pi.on("agent_settled", (_event, ctx) => {
|
|
1873
|
+
if (!getLiveContext(ctx)) return;
|
|
1818
1874
|
agentRunning = false;
|
|
1819
1875
|
activeTools.clear();
|
|
1820
1876
|
syncPresenceStatus();
|
|
@@ -1895,7 +1951,7 @@ export default function piIntercomExtension(pi: ExtensionAPI) {
|
|
|
1895
1951
|
|
|
1896
1952
|
const childOrchestratorMetadata = readChildOrchestratorMetadata();
|
|
1897
1953
|
if (childOrchestratorMetadata) {
|
|
1898
|
-
|
|
1954
|
+
registerIntercomTool({
|
|
1899
1955
|
name: "contact_supervisor",
|
|
1900
1956
|
label: "Contact Supervisor",
|
|
1901
1957
|
description: "Subagent-only tool for contacting the supervisor agent that delegated this task. Use need_decision when blocked, uncertain, needing approval, or facing a product/API/scope decision before continuing; this waits up to 30 seconds, then continues asynchronously if unanswered. Use interview_request when multiple structured questions need supervisor answers; it has the same soft wait. Use progress_update only for meaningful progress or unexpected discoveries that change the plan; this does not wait for a reply. Do not use for routine completion handoffs.",
|
|
@@ -2198,6 +2254,8 @@ Usage:
|
|
|
2198
2254
|
message: Type.Optional(Type.String({
|
|
2199
2255
|
description: "Message to send (for 'send', 'ask', or 'reply' action)",
|
|
2200
2256
|
})),
|
|
2257
|
+
team: Type.Optional(Type.String({ description: "Task team for sending or selecting a reply; replies cannot change the original team" })),
|
|
2258
|
+
contextId: Type.Optional(Type.String({ description: "Exact inbound message selector from its reply hint" })),
|
|
2201
2259
|
attachments: Type.Optional(Type.Array(Type.Object({
|
|
2202
2260
|
type: StringEnum(["file", "snippet", "context"] as const),
|
|
2203
2261
|
name: Type.String(),
|
|
@@ -2219,6 +2277,12 @@ Usage:
|
|
|
2219
2277
|
}),
|
|
2220
2278
|
|
|
2221
2279
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
2280
|
+
if (params.action === "status" && registrationConflict) {
|
|
2281
|
+
return {
|
|
2282
|
+
content: [{ type: "text", text: `**Intercom Status:**\nConnected: No\nSession ID: ${currentSessionId}\nState: SESSION_ID_IN_USE\nAnother runtime owns this session; automatic reconnect is paused. Release the duplicate owner and /reload, or switch to a different session.` }],
|
|
2283
|
+
details: { connected: false, sessionId: currentSessionId, code: "SESSION_ID_IN_USE", error: true },
|
|
2284
|
+
};
|
|
2285
|
+
}
|
|
2222
2286
|
let connectedClient: IntercomClient;
|
|
2223
2287
|
try {
|
|
2224
2288
|
connectedClient = await ensureConnected("tool");
|
|
@@ -2241,7 +2305,7 @@ Usage:
|
|
|
2241
2305
|
}
|
|
2242
2306
|
}
|
|
2243
2307
|
|
|
2244
|
-
const { action, to, message, attachments, replyTo, askId, session, which } = params;
|
|
2308
|
+
const { action, to, message, attachments, replyTo, askId, session, which, team, contextId } = params;
|
|
2245
2309
|
|
|
2246
2310
|
switch (action) {
|
|
2247
2311
|
case "list": {
|
|
@@ -2272,7 +2336,7 @@ Usage:
|
|
|
2272
2336
|
|
|
2273
2337
|
return {
|
|
2274
2338
|
content: [{ type: "text", text: `${currentSection}\n\n${otherSection}` }],
|
|
2275
|
-
details: {},
|
|
2339
|
+
details: { sessionId: mySessionId, sessions },
|
|
2276
2340
|
};
|
|
2277
2341
|
} catch (error) {
|
|
2278
2342
|
return {
|
|
@@ -2303,6 +2367,10 @@ Usage:
|
|
|
2303
2367
|
details: { error: true },
|
|
2304
2368
|
};
|
|
2305
2369
|
}
|
|
2370
|
+
const messageTeam = taskTeamsAvailable()
|
|
2371
|
+
? resolveNamedMessageTeam(connectedClient.sessionId!, sendTo, team)
|
|
2372
|
+
: undefined;
|
|
2373
|
+
if (!taskTeamsAvailable() && team !== undefined) throw new Error("Named task teams are unavailable in managed-team mode");
|
|
2306
2374
|
if (!replyTo && config.confirmSend && ctx.hasUI) {
|
|
2307
2375
|
const attachmentText = attachments?.length ? formatAttachments(attachments) : "";
|
|
2308
2376
|
const confirmed = await ctx.ui.confirm(
|
|
@@ -2318,6 +2386,7 @@ Usage:
|
|
|
2318
2386
|
}
|
|
2319
2387
|
const result = await connectedClient.send(sendTo, {
|
|
2320
2388
|
text: message,
|
|
2389
|
+
team: messageTeam,
|
|
2321
2390
|
attachments,
|
|
2322
2391
|
replyTo,
|
|
2323
2392
|
});
|
|
@@ -2330,7 +2399,7 @@ Usage:
|
|
|
2330
2399
|
}
|
|
2331
2400
|
pi.appendEntry("intercom_sent", {
|
|
2332
2401
|
to,
|
|
2333
|
-
message: { text: message, attachments, replyTo },
|
|
2402
|
+
message: { text: message, attachments, replyTo, team: messageTeam },
|
|
2334
2403
|
messageId: result.id,
|
|
2335
2404
|
timestamp: Date.now(),
|
|
2336
2405
|
});
|
|
@@ -2339,8 +2408,8 @@ Usage:
|
|
|
2339
2408
|
inboundInbox?.dismissPendingAsk(replyTo, sendTo);
|
|
2340
2409
|
}
|
|
2341
2410
|
return {
|
|
2342
|
-
content: [{ type: "text", text: `Message sent to ${to}` }],
|
|
2343
|
-
details: deliveryResultDetails(result),
|
|
2411
|
+
content: [{ type: "text", text: `Message sent to ${to}${messageTeam ? ` [Team: ${messageTeam}]` : ""}` }],
|
|
2412
|
+
details: deliveryResultDetails(result, messageTeam ? { team: messageTeam } : {}),
|
|
2344
2413
|
};
|
|
2345
2414
|
} catch (error) {
|
|
2346
2415
|
return {
|
|
@@ -2372,6 +2441,7 @@ Usage:
|
|
|
2372
2441
|
}
|
|
2373
2442
|
let replyPromise: Promise<Message> | null = null;
|
|
2374
2443
|
let questionId: string | null = null;
|
|
2444
|
+
let messageTeam: string | undefined;
|
|
2375
2445
|
|
|
2376
2446
|
try {
|
|
2377
2447
|
const sendTo = await resolveAuthorizedTarget(connectedClient, to);
|
|
@@ -2393,12 +2463,17 @@ Usage:
|
|
|
2393
2463
|
details: { error: true },
|
|
2394
2464
|
};
|
|
2395
2465
|
}
|
|
2466
|
+
messageTeam = taskTeamsAvailable()
|
|
2467
|
+
? resolveNamedMessageTeam(connectedClient.sessionId!, sendTo, team)
|
|
2468
|
+
: undefined;
|
|
2469
|
+
if (!taskTeamsAvailable() && team !== undefined) throw new Error("Named task teams are unavailable in managed-team mode");
|
|
2396
2470
|
questionId = randomUUID();
|
|
2397
|
-
replyPromise = waitForReply(sendTo, questionId, _signal, () => { void connectedClient.cancelAsk(questionId!); });
|
|
2471
|
+
replyPromise = waitForReply(sendTo, questionId, _signal, () => { void connectedClient.cancelAsk(questionId!); }, messageTeam);
|
|
2398
2472
|
replyPromise.catch(() => undefined);
|
|
2399
2473
|
const sendResult = await connectedClient.send(sendTo, {
|
|
2400
2474
|
messageId: questionId,
|
|
2401
2475
|
text: message,
|
|
2476
|
+
team: messageTeam,
|
|
2402
2477
|
attachments,
|
|
2403
2478
|
replyTo,
|
|
2404
2479
|
expectsReply: true,
|
|
@@ -2421,7 +2496,7 @@ Usage:
|
|
|
2421
2496
|
}
|
|
2422
2497
|
pi.appendEntry("intercom_sent", {
|
|
2423
2498
|
to,
|
|
2424
|
-
message: { text: message, attachments, replyTo },
|
|
2499
|
+
message: { text: message, attachments, replyTo, team: messageTeam },
|
|
2425
2500
|
messageId: sendResult.id,
|
|
2426
2501
|
timestamp: Date.now(),
|
|
2427
2502
|
});
|
|
@@ -2432,17 +2507,18 @@ Usage:
|
|
|
2432
2507
|
: "";
|
|
2433
2508
|
pi.appendEntry("intercom_received", {
|
|
2434
2509
|
from: to,
|
|
2435
|
-
message: { text: replyText, attachments: replyMessage.content.attachments },
|
|
2510
|
+
message: { text: replyText, team: replyMessage.content.team, attachments: replyMessage.content.attachments },
|
|
2436
2511
|
messageId: replyMessage.id,
|
|
2437
2512
|
timestamp: replyMessage.timestamp,
|
|
2438
2513
|
});
|
|
2439
2514
|
return {
|
|
2440
|
-
content: [{ type: "text", text:
|
|
2441
|
-
details: {},
|
|
2515
|
+
content: [{ type: "text", text: `**${messageTeam ? `[Team: ${messageTeam}] ` : ""}Reply from ${to}:**\n${replyText}${replyAttachments}` }],
|
|
2516
|
+
details: messageTeam ? { team: messageTeam } : {},
|
|
2442
2517
|
};
|
|
2443
2518
|
} catch (error) {
|
|
2444
2519
|
if (error instanceof AskWaitElapsedError) {
|
|
2445
|
-
|
|
2520
|
+
const deferred = deferredAskResult(to, error, await connectedClient.deferAsk(error.replyTo));
|
|
2521
|
+
return { ...deferred, details: { ...deferred.details, ...(messageTeam ? { team: messageTeam } : {}) } };
|
|
2446
2522
|
}
|
|
2447
2523
|
if (questionId) rejectReplyWaiter(questionId, toError(error));
|
|
2448
2524
|
if (replyPromise) {
|
|
@@ -2474,7 +2550,9 @@ Usage:
|
|
|
2474
2550
|
if ("code" in requested) throw new BossTeamScopeError(requested.code, requested.error);
|
|
2475
2551
|
exactReplyTarget = requested.targetId;
|
|
2476
2552
|
}
|
|
2477
|
-
const target = replyTracker.resolveReplyTarget({ to: exactReplyTarget, replyTo, askId, which });
|
|
2553
|
+
const target = replyTracker.resolveReplyTarget({ to: exactReplyTarget, replyTo, askId, which, team, contextId });
|
|
2554
|
+
const replyTeam = target.message.content.team;
|
|
2555
|
+
if (replyTeam !== undefined) requireNamedTeamMembers(replyTeam, connectedClient.sessionId!, target.from.id);
|
|
2478
2556
|
if (bossTeamScope.present) {
|
|
2479
2557
|
const resolution = resolveBossLiveTarget(bossTeamScope, target.from.id, await connectedClient.listSessions(), connectedClient.sessionId);
|
|
2480
2558
|
if ("code" in resolution) throw new BossTeamScopeError(resolution.code, resolution.error);
|
|
@@ -2488,6 +2566,7 @@ Usage:
|
|
|
2488
2566
|
const threadedReplyTo = target.message.expectsReply ? target.message.id : undefined;
|
|
2489
2567
|
const result = await connectedClient.send(target.from.id, {
|
|
2490
2568
|
text: message,
|
|
2569
|
+
team: replyTeam,
|
|
2491
2570
|
...(threadedReplyTo ? { replyTo: threadedReplyTo } : {}),
|
|
2492
2571
|
});
|
|
2493
2572
|
if (!result.delivered) {
|
|
@@ -2505,17 +2584,17 @@ Usage:
|
|
|
2505
2584
|
replyTracker.markReplied(threadedReplyTo, target.from.id);
|
|
2506
2585
|
inboundInbox?.dismissPendingAsk(threadedReplyTo, target.from.id);
|
|
2507
2586
|
} else {
|
|
2508
|
-
replyTracker.dismissOrdinarySender(target.from.id);
|
|
2587
|
+
replyTracker.dismissOrdinarySender(target.from.id, target.message.id);
|
|
2509
2588
|
}
|
|
2510
2589
|
pi.appendEntry("intercom_sent", {
|
|
2511
2590
|
to: bossTeamScope.present ? target.from.id : target.from.name || target.from.id,
|
|
2512
|
-
message: { text: message, ...(threadedReplyTo ? { replyTo: threadedReplyTo } : {}) },
|
|
2591
|
+
message: { text: message, team: replyTeam, ...(threadedReplyTo ? { replyTo: threadedReplyTo } : {}) },
|
|
2513
2592
|
messageId: result.id,
|
|
2514
2593
|
timestamp: Date.now(),
|
|
2515
2594
|
});
|
|
2516
2595
|
return {
|
|
2517
|
-
content: [{ type: "text", text: `Reply sent to ${bossTeamScope.present ? target.from.id : target.from.name || target.from.id}` }],
|
|
2518
|
-
details: deliveryResultDetails(result, threadedReplyTo ? { replyTo: threadedReplyTo } : {}),
|
|
2596
|
+
content: [{ type: "text", text: `Reply sent to ${bossTeamScope.present ? target.from.id : target.from.name || target.from.id}${replyTeam ? ` [Team: ${replyTeam}]` : ""}` }],
|
|
2597
|
+
details: deliveryResultDetails(result, { ...(threadedReplyTo ? { replyTo: threadedReplyTo } : {}), ...(replyTeam ? { team: replyTeam } : {}) }),
|
|
2519
2598
|
};
|
|
2520
2599
|
} catch (error) {
|
|
2521
2600
|
return {
|
|
@@ -2587,7 +2666,7 @@ Usage:
|
|
|
2587
2666
|
: "";
|
|
2588
2667
|
const sender = bossTeamScope.present ? ask.from.id : ask.from.name || ask.from.id;
|
|
2589
2668
|
return {
|
|
2590
|
-
content: [{ type: "text", text: `**Pending ask ${ask.id}**\nInbox: ${inboxSessionId}\nFrom: ${sender}\n\n${ask.message.content.text}${attachmentText}` }],
|
|
2669
|
+
content: [{ type: "text", text: `**Pending ask ${ask.id}**\nInbox: ${inboxSessionId}\nFrom: ${sender}${ask.message.content.team ? `\nTeam: ${ask.message.content.team}` : ""}\n\n${ask.message.content.text}${attachmentText}` }],
|
|
2591
2670
|
details: {
|
|
2592
2671
|
inboxSessionId,
|
|
2593
2672
|
ask: {
|
|
@@ -2597,6 +2676,7 @@ Usage:
|
|
|
2597
2676
|
...(ask.deferredAt ? { deferredAt: ask.deferredAt } : {}),
|
|
2598
2677
|
preview: ask.preview,
|
|
2599
2678
|
body: ask.message.content.text,
|
|
2679
|
+
...(ask.message.content.team ? { team: ask.message.content.team } : {}),
|
|
2600
2680
|
attachments: ask.message.content.attachments ?? [],
|
|
2601
2681
|
},
|
|
2602
2682
|
},
|
|
@@ -2606,7 +2686,7 @@ Usage:
|
|
|
2606
2686
|
const lines = asks.map((ask) => {
|
|
2607
2687
|
const state = ask.deferredAt ? " · async" : "";
|
|
2608
2688
|
const sender = bossTeamScope.present ? ask.from.id : ask.from.name || ask.from.id;
|
|
2609
|
-
return `- ${ask.id} · ${sender}${ask.selector} · ${ask.elapsedSeconds}s ago${state} · ${ask.preview}`;
|
|
2689
|
+
return `- ${ask.id} · ${sender}${ask.message.content.team ? ` · Team: ${ask.message.content.team}` : ""}${ask.selector} · ${ask.elapsedSeconds}s ago${state} · ${ask.preview}`;
|
|
2610
2690
|
});
|
|
2611
2691
|
return {
|
|
2612
2692
|
content: [{ type: "text", text: `**Pending asks:**\n${lines.join("\n")}` }],
|
|
@@ -2618,6 +2698,7 @@ Usage:
|
|
|
2618
2698
|
receivedAt: ask.receivedAt,
|
|
2619
2699
|
...(ask.deferredAt ? { deferredAt: ask.deferredAt } : {}),
|
|
2620
2700
|
preview: ask.preview,
|
|
2701
|
+
...(ask.message.content.team ? { team: ask.message.content.team } : {}),
|
|
2621
2702
|
})),
|
|
2622
2703
|
},
|
|
2623
2704
|
};
|
|
@@ -2632,7 +2713,7 @@ Usage:
|
|
|
2632
2713
|
type: "text",
|
|
2633
2714
|
text: `**Intercom Status:**\nConnected: Yes\nSession ID: ${mySessionId}\nActive sessions: ${sessions.length}\nQueued inbound messages: ${inboundInbox?.size ?? 0}\nQueued outbound messages: ${connectedClient.outboxSize}\nPending inbound asks: ${replyTracker.listPending().length}`,
|
|
2634
2715
|
}],
|
|
2635
|
-
details: {},
|
|
2716
|
+
details: { connected: true, sessionId: mySessionId, activeSessions: sessions.length, inboundMessages: inboundInbox?.size ?? 0, outboundMessages: connectedClient.outboxSize, pendingAsks: replyTracker.listPending().length },
|
|
2636
2717
|
};
|
|
2637
2718
|
} catch (error) {
|
|
2638
2719
|
return {
|
|
@@ -2705,7 +2786,7 @@ Usage:
|
|
|
2705
2786
|
legacyIntercomTool.renderCall({ ...args, action }, theme, context);
|
|
2706
2787
|
const renderSplitResult = legacyIntercomTool.renderResult;
|
|
2707
2788
|
|
|
2708
|
-
|
|
2789
|
+
registerIntercomTool({
|
|
2709
2790
|
name: "intercom_send",
|
|
2710
2791
|
label: "Intercom Send",
|
|
2711
2792
|
description: "Send a message to another local Pi session. Both the recipient and message are required.",
|
|
@@ -2713,6 +2794,7 @@ Usage:
|
|
|
2713
2794
|
promptGuidelines: ["Use intercom_send for progress updates, notifications, and other messages that do not require a conversational reply."],
|
|
2714
2795
|
parameters: Type.Object({
|
|
2715
2796
|
to: Type.String({ description: "Required recipient session name or stable session ID" }),
|
|
2797
|
+
team: Type.Optional(Type.String({ description: "Task team name. Required when both sessions share multiple teams." })),
|
|
2716
2798
|
message: Type.String({ description: "Required message to send" }),
|
|
2717
2799
|
attachments: attachmentParameters,
|
|
2718
2800
|
}),
|
|
@@ -2721,7 +2803,7 @@ Usage:
|
|
|
2721
2803
|
renderResult: renderSplitResult,
|
|
2722
2804
|
} as any);
|
|
2723
2805
|
|
|
2724
|
-
|
|
2806
|
+
registerIntercomTool({
|
|
2725
2807
|
name: "intercom_ask",
|
|
2726
2808
|
label: "Intercom Ask",
|
|
2727
2809
|
description: "Ask another local Pi session a blocking question, waiting briefly before continuing asynchronously. Do not use this for progress or status checkpoints.",
|
|
@@ -2729,6 +2811,7 @@ Usage:
|
|
|
2729
2811
|
promptGuidelines: ["Use intercom_ask only when work genuinely depends on another session's answer. Use intercom_send for progress checks, status requests, assignments, notifications, and follow-ups. Different recipients may be asked concurrently, but keep only one unresolved ask per recipient."],
|
|
2730
2812
|
parameters: Type.Object({
|
|
2731
2813
|
to: Type.String({ description: "Required recipient session name or stable session ID" }),
|
|
2814
|
+
team: Type.Optional(Type.String({ description: "Task team name. Required when both sessions share multiple teams." })),
|
|
2732
2815
|
message: Type.String({ description: "Required question to ask" }),
|
|
2733
2816
|
attachments: attachmentParameters,
|
|
2734
2817
|
}),
|
|
@@ -2737,31 +2820,33 @@ Usage:
|
|
|
2737
2820
|
renderResult: renderSplitResult,
|
|
2738
2821
|
} as any);
|
|
2739
2822
|
|
|
2740
|
-
|
|
2823
|
+
registerIntercomTool({
|
|
2741
2824
|
name: "intercom_reply",
|
|
2742
2825
|
label: "Intercom Reply",
|
|
2743
2826
|
description: "Reply to an inbound intercom message or ask. Exact protocol threading is resolved internally.",
|
|
2744
2827
|
promptSnippet: "Reply to the active or pending inbound intercom message.",
|
|
2745
|
-
promptGuidelines: ["Use intercom_reply to answer an inbound
|
|
2828
|
+
promptGuidelines: ["Use intercom_reply to answer an inbound message, inheriting its original team. Prefer its `contextId` reply hint or the `askId` from intercom_pending. Never route replies using a mutable current team or mix work from different teams."],
|
|
2746
2829
|
parameters: Type.Object({
|
|
2747
2830
|
message: Type.String({ description: "Required reply text" }),
|
|
2748
2831
|
to: Type.Optional(Type.String({ description: "Optional sender/session selector when multiple senders or asks are pending; never a message or thread ID" })),
|
|
2749
2832
|
which: Type.Optional(StringEnum(["oldest", "latest"] as const, { description: "Select the oldest or latest ask when the chosen sender has multiple unresolved asks" })),
|
|
2750
2833
|
askId: Type.Optional(Type.String({ description: "Stable ask selector returned by intercom_pending" })),
|
|
2834
|
+
contextId: Type.Optional(Type.String({ description: "Exact inbound message selector from its reply hint, including ordinary messages" })),
|
|
2835
|
+
team: Type.Optional(Type.String({ description: "Select an original-message team; cannot override it" })),
|
|
2751
2836
|
}),
|
|
2752
2837
|
execute: executeSplitAction("reply"),
|
|
2753
2838
|
renderCall: renderSplitCall("reply"),
|
|
2754
2839
|
renderResult: renderSplitResult,
|
|
2755
2840
|
} as any);
|
|
2756
2841
|
|
|
2757
|
-
|
|
2842
|
+
registerIntercomTool({
|
|
2758
2843
|
name: "intercom_team",
|
|
2759
2844
|
label: "Intercom Team",
|
|
2760
|
-
description: "Show
|
|
2761
|
-
promptSnippet: "Find your
|
|
2762
|
-
promptGuidelines: ["Use intercom_team
|
|
2763
|
-
parameters: Type.Object({}),
|
|
2764
|
-
async execute(_toolCallId,
|
|
2845
|
+
description: "Show all named task teams you belong to, or inspect one by name. Falls back to managed-team discovery when no named task team exists.",
|
|
2846
|
+
promptSnippet: "Find your task teams, managers, and teammates.",
|
|
2847
|
+
promptGuidelines: ["Use intercom_team to inspect your teams. A session can belong to multiple teams, with a different manager in each; specify `team` when the task is ambiguous."],
|
|
2848
|
+
parameters: Type.Object({ team: Type.Optional(Type.String({ description: "Named task team to inspect; omit to show all your teams" })) }),
|
|
2849
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
2765
2850
|
try {
|
|
2766
2851
|
const connectedClient = await ensureConnected("tool");
|
|
2767
2852
|
syncPresenceIdentity(ctx.sessionManager.getSessionId());
|
|
@@ -2770,6 +2855,15 @@ Usage:
|
|
|
2770
2855
|
if (selfError) throw new BossTeamScopeError("BOSS_TEAM_METADATA_INVALID", selfError);
|
|
2771
2856
|
}
|
|
2772
2857
|
const sessions = await connectedClient.listSessions();
|
|
2858
|
+
if (taskTeamsAvailable()) {
|
|
2859
|
+
const mine = sessionNamedTeams(connectedClient.sessionId!);
|
|
2860
|
+
const selected = params.team === undefined ? mine : mine.filter((entry) => entry.name === params.team);
|
|
2861
|
+
if (params.team !== undefined && selected.length === 0) throw new Error("You are not a member of that task team");
|
|
2862
|
+
if (selected.length > 0) {
|
|
2863
|
+
const teams = selected.map((entry) => namedTeamRoster(entry, connectedClient.sessionId!, sessions));
|
|
2864
|
+
return { content: [{ type: "text", text: teams.map(formatNamedTeamRoster).join("\n\n") }], details: { self: { id: connectedClient.sessionId }, teams } };
|
|
2865
|
+
}
|
|
2866
|
+
} else if (params.team !== undefined) throw new Error("Named task teams are unavailable in Boss mode");
|
|
2773
2867
|
const team = bossTeamScope.restricted
|
|
2774
2868
|
? resolveBossIntercomTeam({ selfId: connectedClient.sessionId, sessions, scope: bossTeamScope })
|
|
2775
2869
|
: await resolveIntercomTeam({ selfId: connectedClient.sessionId, sessions });
|
|
@@ -2789,29 +2883,38 @@ Usage:
|
|
|
2789
2883
|
},
|
|
2790
2884
|
} as any);
|
|
2791
2885
|
|
|
2792
|
-
|
|
2886
|
+
registerIntercomTool({
|
|
2793
2887
|
name: "intercom_join",
|
|
2888
|
+
executionMode: "sequential",
|
|
2794
2889
|
label: "Intercom Join",
|
|
2795
|
-
description: "List, join, or create a
|
|
2796
|
-
promptSnippet: "
|
|
2890
|
+
description: "List, join, or create a task team. Joining appends membership without leaving other named teams. The manager can add connected sessions using members; the creator need not have a session name.",
|
|
2891
|
+
promptSnippet: "After user approval, form a task team with the named sessions they want to collaborate with.",
|
|
2797
2892
|
promptGuidelines: [
|
|
2798
|
-
"
|
|
2799
|
-
"
|
|
2800
|
-
"
|
|
2893
|
+
"For independent Pi sessions: when the user asks you to collaborate with named Pi sessions or delegate work, and this task has no approved team, ask once whether to form a team with you and those sessions (for example: 要把我、front、writer 组成一个 team 来负责这个任务吗?). Wait for approval before creating a team or adding peers.",
|
|
2894
|
+
"An explicit user request to create/join a team is approval. Do not ask again for an approved team or in an inbound team-message turn. If the user declines, do not keep prompting or silently create a team. Without a shared team, send an ungrouped direct message by omitting `team`; belonging to an unrelated team must not prevent initial contact. Explicit team messages still require both sessions to be members.",
|
|
2895
|
+
"After approval, discover the intended sessions, then use intercom_join({ name: \"launch\", create: true, members: [\"front\", \"writer\"], work: \"Current task\" }). The tool adds everyone together; do not ask the user to join each terminal manually.",
|
|
2896
|
+
"Reuse the approved team for the same task and append new approved members. Joining another team preserves previous memberships. The same participants doing a different task may need a different team.",
|
|
2897
|
+
"Pass `team` when sending task messages, especially if peers share multiple teams. Replies inherit their original message's team automatically.",
|
|
2801
2898
|
],
|
|
2802
2899
|
parameters: Type.Object({
|
|
2803
2900
|
name: Type.Optional(Type.String({ description: "Team name to join. Omit to list joinable teams." })),
|
|
2804
2901
|
create: Type.Optional(Type.Boolean({ description: "Create this named team and join as manager. Requires name." })),
|
|
2902
|
+
members: Type.Optional(Type.Array(Type.String({ description: "Connected session name or stable ID to add; manager-only, and requires name" }))),
|
|
2903
|
+
work: Type.Optional(Type.String({ description: "Task description when creating a team; requires create=true" })),
|
|
2805
2904
|
}),
|
|
2806
2905
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
2807
2906
|
try {
|
|
2808
2907
|
const zh = isZhLocale();
|
|
2809
|
-
if (params.create) {
|
|
2908
|
+
if (params.create || params.members !== undefined || params.work !== undefined) {
|
|
2810
2909
|
if (typeof params.name !== "string" || !params.name.trim()) {
|
|
2811
|
-
throw new Error(zh ? "
|
|
2910
|
+
throw new Error(zh ? "组队需要名称。" : "Forming a team requires a name.");
|
|
2812
2911
|
}
|
|
2813
|
-
const
|
|
2814
|
-
|
|
2912
|
+
const team = await appendTaskTeam(ctx, params.name, params.create === true, params.members ?? [], params.work);
|
|
2913
|
+
const roster = namedTeamRoster(team, currentSessionId!, await client!.listSessions());
|
|
2914
|
+
const text = params.create
|
|
2915
|
+
? formatCreateSuccess({ team: team.name, name: currentDisplayName(), zh })
|
|
2916
|
+
: formatNamedJoinSuccess({ team: team.name, name: currentDisplayName(), zh });
|
|
2917
|
+
return { content: [{ type: "text", text: `${text}\n\n${formatNamedTeamRoster(roster)}` }], details: { team: roster } };
|
|
2815
2918
|
}
|
|
2816
2919
|
const text = await listOrJoinCircle(ctx, typeof params.name === "string" ? params.name : "");
|
|
2817
2920
|
return { content: [{ type: "text", text }] };
|
|
@@ -2838,7 +2941,7 @@ Usage:
|
|
|
2838
2941
|
{ name: "intercom_status", label: "Intercom Status", action: "status", description: "Show this session's intercom connection status.", promptSnippet: "Show intercom connection status." },
|
|
2839
2942
|
] as const) {
|
|
2840
2943
|
if (definition.name === "intercom_list" && bossTeamScope.restricted) continue;
|
|
2841
|
-
|
|
2944
|
+
registerIntercomTool({
|
|
2842
2945
|
name: definition.name,
|
|
2843
2946
|
label: definition.label,
|
|
2844
2947
|
description: definition.description,
|
|
@@ -2856,7 +2959,7 @@ Usage:
|
|
|
2856
2959
|
}
|
|
2857
2960
|
|
|
2858
2961
|
if (config.legacyTool) {
|
|
2859
|
-
|
|
2962
|
+
registerIntercomTool(legacyIntercomTool);
|
|
2860
2963
|
}
|
|
2861
2964
|
|
|
2862
2965
|
async function resolveCurrentContact(ctx: ExtensionContext, generation = runtimeGeneration): Promise<{ target: string; name?: string; id: string; duplicateName: boolean } | undefined> {
|
|
@@ -3043,6 +3146,11 @@ Usage:
|
|
|
3043
3146
|
const zh = isZhLocale();
|
|
3044
3147
|
try {
|
|
3045
3148
|
const membership = classifyMembership();
|
|
3149
|
+
const taskTeams = currentSessionId ? sessionNamedTeams(currentSessionId) : [];
|
|
3150
|
+
if (taskTeams.length > 0 && taskTeamsAvailable()) {
|
|
3151
|
+
notifyIfLive(ctx, `${zh ? "任务团队" : "Task teams"}: ${taskTeams.map((entry) => entry.name).join(", ")}\n${zh ? "你的显示名" : "Display name"}: ${currentDisplayName()}`, "info");
|
|
3152
|
+
return;
|
|
3153
|
+
}
|
|
3046
3154
|
const namedTeam = runtimeScopeId ? findNamedTeamByScope(runtimeScopeId) : undefined;
|
|
3047
3155
|
const workspace = namedTeam?.name
|
|
3048
3156
|
? undefined
|