@ycodium-ai/plugin-team 0.2.3576 → 0.2.3608

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/dist/index.js CHANGED
@@ -7268,6 +7268,7 @@ async function createGoalThread(ctx, goal, thread) {
7268
7268
  requestId: `thread:${thread.threadId}`,
7269
7269
  threadId: thread.threadId,
7270
7270
  projectId: goal.projectId,
7271
+ memberId: thread.memberId,
7271
7272
  title: goalThreadTitle(goal, thread.name),
7272
7273
  ...thread.selection,
7273
7274
  // 有意写死、不读默认档:带团队的 AI 要无人值守地干活,成员子会话按 YCO-337 只能与负责人同档或更严。
@@ -7290,6 +7291,7 @@ async function createTeamThread(ctx, thread) {
7290
7291
  requestId: `thread:${thread.threadId}`,
7291
7292
  threadId: thread.threadId,
7292
7293
  projectId: thread.projectId,
7294
+ memberId: thread.memberId,
7293
7295
  title: thread.title,
7294
7296
  ...thread.selection,
7295
7297
  runtimeMode: "full-access",
@@ -7411,6 +7413,16 @@ function registerGoalCommands(ctx, service2) {
7411
7413
  // src/watch.ts
7412
7414
  import * as NodeTimers from "node:timers";
7413
7415
 
7416
+ // src/domain/person.ts
7417
+ var personKey = (person) => person.kind === "account" ? `account:${person.userId}` : `local-owner:${person.environmentId}`;
7418
+ var samePerson = (a, b) => personKey(a) === personKey(b);
7419
+ var personOfMember = (member) => member.identity?.kind === "human" && member.identity.principal !== void 0 ? member.identity.principal : null;
7420
+ var currentPerson = async (invocation) => invocation.viewer === void 0 ? null : (await invocation.viewer.communication.people.current({})).person;
7421
+ async function personDisplayName(ctx, projectId, person, fallback) {
7422
+ const { items } = await ctx.communication.people.list({ projectId, limit: 100 });
7423
+ return items.find((item) => samePerson(item.person, person))?.displayName ?? fallback;
7424
+ }
7425
+
7414
7426
  // src/domain/roster.ts
7415
7427
  var MEMBER_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
7416
7428
  var MAX_NAME_LENGTH = 40;
@@ -7535,7 +7547,6 @@ var WorkIdentity = Struct({
7535
7547
  /** 人的稳定键(见 personKey),界面拿它到快照的人名表里找名字。 */
7536
7548
  personKey: optionalKey2(String4)
7537
7549
  });
7538
- var personKey = (principal) => principal.kind === "account" ? `account:${principal.userId}` : `local-owner:${principal.environmentId}`;
7539
7550
  var workIdentity = (value) => ({
7540
7551
  kind: value.kind,
7541
7552
  ...value.principalId === void 0 ? {} : { principalId: value.principalId },
@@ -9045,6 +9056,67 @@ function createRosterService(ctx, options = {}) {
9045
9056
 
9046
9057
  // src/runtime/deliver.ts
9047
9058
  import * as NodeCrypto from "node:crypto";
9059
+
9060
+ // ../../packages/ycodium-system-text/src/systemText.ts
9061
+ var SYSTEM_NOTIFICATION_OPEN_TAG = "<ycodium-system>";
9062
+ var SYSTEM_NOTIFICATION_CLOSE_TAG = "</ycodium-system>";
9063
+ var isSystemNotificationText = (text) => text.startsWith(SYSTEM_NOTIFICATION_OPEN_TAG);
9064
+ var rendersAsSystemMessage = (text) => isSystemNotificationText(text) && !(isPresetPrefixMessage(text) && text.includes(PRESET_PREFIX_CLOSE) && parsePresetPrefixText(text) === null);
9065
+ var FIRST_BLOCK_START = `^(?:${SYSTEM_NOTIFICATION_OPEN_TAG}\\n)?`;
9066
+ var PRESET_PREFIX_MESSAGE_START = `${SYSTEM_NOTIFICATION_OPEN_TAG}
9067
+ <preset-prefix `;
9068
+ var isPresetPrefixMessage = (text) => text.startsWith(PRESET_PREFIX_MESSAGE_START);
9069
+ var SYSTEM_TEXT_ESCAPED_ATTRIBUTE = 'esc="1"';
9070
+ var escapeSystemText = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;");
9071
+ var SYSTEM_TEXT_MAX_ESCAPE_GROWTH = "&amp;".length;
9072
+ var escapeSystemAttribute = (text) => escapeSystemText(text).replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/\r/g, "&#13;").replace(/\n/g, "&#10;");
9073
+ var ESCAPED_BLOCK_NAMES = [
9074
+ "thread-response",
9075
+ "thread-handover",
9076
+ "scheduled-wake",
9077
+ "plugin-wake"
9078
+ ];
9079
+ var escapedBlockOpenTag = (name, attributes) => `<${name} ${[
9080
+ ...attributes.map(([key, value]) => `${key}="${escapeSystemAttribute(value)}"`),
9081
+ SYSTEM_TEXT_ESCAPED_ATTRIBUTE
9082
+ ].join(" ")}>`;
9083
+ var ESCAPED_OPEN_LINE_PATTERN = new RegExp(
9084
+ `^<(?:${ESCAPED_BLOCK_NAMES.join("|")})(?:\\s[^>]*)? ${SYSTEM_TEXT_ESCAPED_ATTRIBUTE}>$`
9085
+ );
9086
+ var PRESET_PREFIX_CLOSE = `
9087
+ </preset-prefix>
9088
+ ${SYSTEM_NOTIFICATION_CLOSE_TAG}`;
9089
+ var PRESET_PREFIX_PATTERN = new RegExp(
9090
+ `^${SYSTEM_NOTIFICATION_OPEN_TAG}\\n<preset-prefix id="([^"]*)">\\n([\\s\\S]*?)\\n</preset-prefix>\\n${SYSTEM_NOTIFICATION_CLOSE_TAG}(?:\\n\\n([\\s\\S]*))?$`
9091
+ );
9092
+ var parsePresetPrefixText = (text) => {
9093
+ if (text.split(PRESET_PREFIX_CLOSE).length !== 2) return null;
9094
+ const match7 = PRESET_PREFIX_PATTERN.exec(text);
9095
+ if (match7 === null) return null;
9096
+ const [, presetId, prefix, body] = match7;
9097
+ if (!presetId) return null;
9098
+ return { presetId, prefix: prefix ?? "", body: body ?? "" };
9099
+ };
9100
+ var userSpokenText = (text) => {
9101
+ if (!rendersAsSystemMessage(text)) return text;
9102
+ return parsePresetPrefixText(text)?.body || null;
9103
+ };
9104
+ var formatPluginWakeText = (input) => [
9105
+ SYSTEM_NOTIFICATION_OPEN_TAG,
9106
+ escapedBlockOpenTag("plugin-wake", [
9107
+ ["plugin", input.pluginId],
9108
+ ["reason", input.reason],
9109
+ ...input.item ? [["item", input.item]] : []
9110
+ ]),
9111
+ escapeSystemText(input.text),
9112
+ "</plugin-wake>",
9113
+ SYSTEM_NOTIFICATION_CLOSE_TAG
9114
+ ].join("\n");
9115
+ var PLUGIN_WAKE_PATTERN = new RegExp(
9116
+ `${FIRST_BLOCK_START}<plugin-wake plugin="([^"]+)" reason="([^"]+)"(?: item="([^"]+)")? ${SYSTEM_TEXT_ESCAPED_ATTRIBUTE}>\\n[\\s\\S]*?\\n</plugin-wake>`
9117
+ );
9118
+
9119
+ // src/runtime/deliver.ts
9048
9120
  var worktreeSetupSentence = async (ctx, projectId, branch) => {
9049
9121
  const intro = `You are in your own newly created worktree on branch ${branch}.`;
9050
9122
  const project2 = await ctx.projects.get({ projectId }).catch(() => null);
@@ -9053,6 +9125,12 @@ var worktreeSetupSentence = async (ctx, projectId, branch) => {
9053
9125
  return `${intro} Install dependencies according to the project's instructions before starting work.`;
9054
9126
  return `${intro} If dependencies are missing, install them before starting work. This project defines a setup command for that: ${setup.command}; run it from the worktree root with the environment variables YCODIUM_PROJECT_ROOT set to "${project2.workspaceRoot}" and YCODIUM_WORKTREE_PATH set to this worktree's path. If it fails with "could not lock config file", wait a moment and run it again.`;
9055
9127
  };
9128
+ var teamWakeText = (display, text) => formatPluginWakeText({
9129
+ pluginId: "team",
9130
+ reason: display.reason,
9131
+ item: display.sequenceId === void 0 ? void 0 : `W${display.sequenceId}`,
9132
+ text
9133
+ });
9056
9134
  var stableHash = (key) => NodeCrypto.createHash("sha256").update(key).digest("hex").slice(0, 32);
9057
9135
  var memberWorktreeBranch = (name, threadId) => {
9058
9136
  const slug = Array.from(
@@ -9104,7 +9182,7 @@ async function deliverWake(ctx, input) {
9104
9182
  message: {
9105
9183
  messageId: input.messageId,
9106
9184
  role: "user",
9107
- text,
9185
+ text: teamWakeText(input.wake, text),
9108
9186
  attachments: []
9109
9187
  },
9110
9188
  modelSelection: execution.modelSelection,
@@ -9141,9 +9219,12 @@ async function deliverWake(ctx, input) {
9141
9219
  requestId: input.requestId,
9142
9220
  message: {
9143
9221
  ...turn.message,
9144
- text: `${text}
9222
+ text: teamWakeText(
9223
+ input.wake,
9224
+ `${text}
9145
9225
 
9146
9226
  ${await worktreeSetupSentence(ctx, input.projectId, branch)}`
9227
+ )
9147
9228
  },
9148
9229
  bootstrap: {
9149
9230
  prepareWorktree: {
@@ -9179,7 +9260,8 @@ ${await worktreeSetupSentence(ctx, input.projectId, branch)}`
9179
9260
  recipient: { threadId: worktree.leadThreadId },
9180
9261
  requestId: `team-worktree-report:${threadId}`,
9181
9262
  messageId: `team-worktree-report-msg:${threadId}`,
9182
- text: `Could not create a worktree for member ${await worktree.name()} (threadId ${threadId}): ${failure.reason}. They will work in the project folder for now.`
9263
+ text: `Could not create a worktree for member ${await worktree.name()} (threadId ${threadId}): ${failure.reason}. They will work in the project folder for now.`,
9264
+ wake: { reason: "notice" }
9183
9265
  });
9184
9266
  if (!reported) throw new Error("Member worktree failure could not be reported to the lead");
9185
9267
  const receipt = await worktree.lane.events.append({
@@ -9487,6 +9569,10 @@ var en_default = {
9487
9569
  kindAgentHint: "Runs with the provider and model you pick when the machine is this one. A member set to another machine fails its trial run: set it back to this machine, or skip the trial run, and it runs on this machine with the project's default model.",
9488
9570
  kindRemoteHint: "An agent that connects from another machine with its own credentials.",
9489
9571
  kindPersonHint: "A teammate. People are listed as members but do not run sessions.",
9572
+ thisIsMe: "This is me",
9573
+ thisIsMeHint: "When someone @-mentions this member in a team group, the message goes to your Sent to me.",
9574
+ boundToOther: "This member is another person. Only they can change this.",
9575
+ you: "You",
9490
9576
  name: "Name",
9491
9577
  memberTitle: "Title",
9492
9578
  responsibility: "Responsibility",
@@ -9533,6 +9619,7 @@ var en_default = {
9533
9619
  noDefaultModel: "This project has no default model. Choose one in the project settings under New threads \u2192 Model, then try again.",
9534
9620
  modelUnavailable: "The project's default model isn't available on this machine. Check the provider, or choose another model in the project settings under New threads \u2192 Model.",
9535
9621
  duplicateMember: "Another member already has this name or remote agent. Rename or remove that member first.",
9622
+ alreadyYou: "You are already {{name}} on this roster. Clear \u201CThis is me\u201D on {{name}} first.",
9536
9623
  staleVersion: "This member was changed elsewhere and has been reloaded. Review it and save again.",
9537
9624
  notFound: "This member no longer exists.",
9538
9625
  invalidState: "The member's state was changed elsewhere and has been reloaded.",
@@ -10035,7 +10122,10 @@ function createGoalService(ctx, rosterService, options = {}) {
10035
10122
  message: {
10036
10123
  messageId: `team-lead-message:${goal.projectId}:${goal.goalId}`,
10037
10124
  role: "user",
10038
- text: leadBrief(goal, roster, { engineMembers: view.stored?.version === 2 }),
10125
+ text: teamWakeText(
10126
+ { reason: "instructions" },
10127
+ leadBrief(goal, roster, { engineMembers: view.stored?.version === 2 })
10128
+ ),
10039
10129
  attachments: []
10040
10130
  },
10041
10131
  modelSelection: selections.get(goal.leadMemberId).modelSelection,
@@ -10090,6 +10180,7 @@ import {
10090
10180
  MEMBER_RESPONSIBILITY_MAX,
10091
10181
  MEMBER_TITLE_MAX
10092
10182
  } from "@ycodium-ai/plugin-api";
10183
+ var MAX_MEMBER_NAME_LENGTH = MEMBER_NAME_MAX;
10093
10184
  var MemberAvatar = Struct({ pattern: String4, color: String4 });
10094
10185
  var AgentChoice = Struct({
10095
10186
  environmentId: String4,
@@ -10104,6 +10195,8 @@ var MemberRow = Struct({
10104
10195
  avatar: NullOr(MemberAvatar),
10105
10196
  /** 远端 agent 成员绑的主体;本机 agent 与人是 null。 */
10106
10197
  remotePrincipalId: NullOr(String4),
10198
+ /** 人成员绑着的人是不是正在看这一页的人;没绑就没有这一项。 */
10199
+ person: optionalKey2(Literals(["me", "other"])),
10107
10200
  agent: NullOr(
10108
10201
  Struct({
10109
10202
  environmentId: String4,
@@ -10158,7 +10251,9 @@ var CreateMemberInput = Struct({
10158
10251
  avatar: optionalKey2(MemberAvatar),
10159
10252
  /** 给了就是远端 agent:绑这个主体。 */
10160
10253
  remotePrincipalId: optionalKey2(String4),
10161
- agent: optionalKey2(AgentChoice)
10254
+ agent: optionalKey2(AgentChoice),
10255
+ /** 人成员「这是我」:绑到发起这次调用的人。 */
10256
+ bindToMe: optionalKey2(Boolean3)
10162
10257
  });
10163
10258
  var UpdateMemberInput = Struct({
10164
10259
  ...projectAndRequest,
@@ -10168,7 +10263,9 @@ var UpdateMemberInput = Struct({
10168
10263
  ...memberTextFields,
10169
10264
  avatar: optionalKey2(MemberAvatar),
10170
10265
  clearAvatar: optionalKey2(Boolean3),
10171
- agent: optionalKey2(AgentChoice)
10266
+ agent: optionalKey2(AgentChoice),
10267
+ /** 人成员「这是我」:`true` 绑到发起这次调用的人,`false` 解绑。 */
10268
+ bindToMe: optionalKey2(Boolean3)
10172
10269
  });
10173
10270
  var MemberRefInput = Struct({ ...projectAndRequest, memberId: String4 });
10174
10271
  var ImportRosterInput = Struct({ projectId: String4 });
@@ -10646,24 +10743,30 @@ function startCoordinatorRuntime(ctx, deps) {
10646
10743
  }
10647
10744
 
10648
10745
  // src/members/snapshot.ts
10649
- var toMemberRow = (member, trial) => ({
10650
- memberId: member.memberId,
10651
- kind: member.kind,
10652
- name: member.name,
10653
- title: member.title,
10654
- responsibility: member.responsibility,
10655
- avatar: member.avatar === null ? null : { pattern: member.avatar.pattern, color: member.avatar.color },
10656
- remotePrincipalId: member.identity?.kind === "remote-agent" ? member.identity.principalId : null,
10657
- agent: member.agent === null ? null : {
10658
- environmentId: member.agent.environmentId,
10659
- instanceId: member.agent.modelSelection.instanceId,
10660
- model: member.agent.modelSelection.model
10661
- },
10662
- hasInstructions: member.workInstructionsRevision !== null,
10663
- version: member.version,
10664
- removed: member.removedAt !== null,
10665
- ...trial === null ? {} : { trial }
10666
- });
10746
+ var toMemberRow = (member, trial, viewer) => {
10747
+ const person = personOfMember(member);
10748
+ return {
10749
+ memberId: member.memberId,
10750
+ kind: member.kind,
10751
+ name: member.name,
10752
+ title: member.title,
10753
+ responsibility: member.responsibility,
10754
+ avatar: member.avatar === null ? null : { pattern: member.avatar.pattern, color: member.avatar.color },
10755
+ remotePrincipalId: member.identity?.kind === "remote-agent" ? member.identity.principalId : null,
10756
+ ...person === null ? {} : {
10757
+ person: viewer !== null && samePerson(person, viewer) ? "me" : "other"
10758
+ },
10759
+ agent: member.agent === null ? null : {
10760
+ environmentId: member.agent.environmentId,
10761
+ instanceId: member.agent.modelSelection.instanceId,
10762
+ model: member.agent.modelSelection.model
10763
+ },
10764
+ hasInstructions: member.workInstructionsRevision !== null,
10765
+ version: member.version,
10766
+ removed: member.removedAt !== null,
10767
+ ...trial === null ? {} : { trial }
10768
+ };
10769
+ };
10667
10770
  var legacyOffer = (stored) => ({
10668
10771
  revision: stored.roster.revision,
10669
10772
  leadMemberId: stored.roster.leadMemberId,
@@ -10673,10 +10776,10 @@ var legacyOffer = (stored) => ({
10673
10776
  principalId: member.principal?.principalId ?? null
10674
10777
  }))
10675
10778
  });
10676
- function membersSnapshot(view, trialOf = () => null) {
10779
+ function membersSnapshot(view, trialOf = () => null, viewer = null) {
10677
10780
  const { stored } = view;
10678
10781
  return {
10679
- members: view.members.map((member) => toMemberRow(member, trialOf(member.memberId))),
10782
+ members: view.members.map((member) => toMemberRow(member, trialOf(member.memberId), viewer)),
10680
10783
  lead: {
10681
10784
  memberId: stored?.version === 2 ? stored.roster.leadMemberId : null,
10682
10785
  revision: stored?.roster.revision ?? 0
@@ -10999,10 +11102,18 @@ function registerMemberCommands(ctx, rosters, writer, trials2) {
10999
11102
  name: "members",
11000
11103
  scope: "read",
11001
11104
  payloadSchema: payloadSchemaOf(ReadMembersInput),
11002
- handle: async (payload) => {
11105
+ handle: async (payload, invocation) => {
11003
11106
  const { projectId } = decodeReadMembers(payload);
11004
- const [view, trial] = await Promise.all([rosters.inspect(projectId), trials2.get()]);
11005
- return membersSnapshot(view, (memberId) => trialViewOf(recordOf(trial, projectId, memberId)));
11107
+ const [view, trial, viewer] = await Promise.all([
11108
+ rosters.inspect(projectId),
11109
+ trials2.get(),
11110
+ currentPerson(invocation)
11111
+ ]);
11112
+ return membersSnapshot(
11113
+ view,
11114
+ (memberId) => trialViewOf(recordOf(trial, projectId, memberId)),
11115
+ viewer
11116
+ );
11006
11117
  }
11007
11118
  });
11008
11119
  ctx.command({
@@ -11091,14 +11202,21 @@ function createMemberWriter(ctx, rosters, queue = createSerial()) {
11091
11202
  const serial = queue.run;
11092
11203
  const asPerson = (commandType, handler) => (payload, invocation) => serial(async () => {
11093
11204
  const person = invocation.asPerson;
11094
- return person === void 0 ? rejectMember(commandType, "needs-person") : handler(payload, person.members);
11205
+ return person === void 0 ? rejectMember(commandType, "needs-person") : handler(payload, person.members, invocation);
11095
11206
  });
11096
- const create = asPerson("createMember", async (payload, members) => {
11207
+ const meOf = async (commandType, invocation) => await currentPerson(invocation) ?? rejectMember(commandType, "needs-person");
11208
+ const create = asPerson("createMember", async (payload, members, invocation) => {
11097
11209
  const decoded = decodeCreate3(payload);
11098
11210
  if (isNone2(decoded)) return rejectMember("createMember", "invalid-payload");
11099
11211
  const input = decoded.value;
11100
- if (input.kind === "human" && (input.agent !== void 0 || input.remotePrincipalId !== void 0))
11212
+ if (input.kind === "human" ? input.agent !== void 0 || input.remotePrincipalId !== void 0 : input.bindToMe !== void 0)
11101
11213
  return rejectMember("createMember", "invalid-payload");
11214
+ let me;
11215
+ if (input.bindToMe === true) {
11216
+ const found = await meOf("createMember", invocation);
11217
+ if ("code" in found) return found;
11218
+ me = found;
11219
+ }
11102
11220
  const memberId = createdMemberId(input.projectId, input.requestId);
11103
11221
  let agent;
11104
11222
  if (input.kind === "agent") {
@@ -11125,17 +11243,29 @@ function createMemberWriter(ctx, rosters, queue = createSerial()) {
11125
11243
  ...input.avatar === void 0 ? {} : { avatar: input.avatar },
11126
11244
  ...input.workInstructions === void 0 ? {} : { workInstructions: input.workInstructions },
11127
11245
  ...input.remotePrincipalId === void 0 ? {} : { identity: { kind: "remote-agent", principalId: input.remotePrincipalId } },
11246
+ ...me === void 0 ? {} : { identity: { kind: "human", principal: me } },
11128
11247
  ...agent === void 0 ? {} : { agent }
11129
11248
  })
11130
11249
  );
11131
11250
  return "code" in result2 ? result2 : written(result2);
11132
11251
  });
11133
- const update = asPerson("updateMember", async (payload, members) => {
11252
+ const update = asPerson("updateMember", async (payload, members, invocation) => {
11134
11253
  const decoded = decodeUpdate3(payload);
11135
11254
  if (isNone2(decoded)) return rejectMember("updateMember", "invalid-payload");
11136
11255
  const input = decoded.value;
11137
11256
  if (input.avatar !== void 0 && input.clearAvatar === true)
11138
11257
  return rejectMember("updateMember", "invalid-payload");
11258
+ let identity2;
11259
+ if (input.bindToMe !== void 0) {
11260
+ const me = await meOf("updateMember", invocation);
11261
+ if ("code" in me) return me;
11262
+ const member = await findMember(ctx, input.projectId, input.memberId);
11263
+ if (member === null) return rejectMember("updateMember", "not-found");
11264
+ const held = personOfMember(member);
11265
+ if (held !== null && !samePerson(held, me))
11266
+ return rejectMember("updateMember", "bound-to-other");
11267
+ identity2 = input.bindToMe ? { kind: "human", principal: me } : null;
11268
+ }
11139
11269
  const result2 = await refused(
11140
11270
  "updateMember",
11141
11271
  () => members.update({
@@ -11148,7 +11278,8 @@ function createMemberWriter(ctx, rosters, queue = createSerial()) {
11148
11278
  ...input.responsibility === void 0 ? {} : { responsibility: input.responsibility },
11149
11279
  ...input.clearAvatar === true ? { avatar: null } : input.avatar === void 0 ? {} : { avatar: input.avatar },
11150
11280
  ...input.workInstructions === void 0 ? {} : { workInstructions: input.workInstructions },
11151
- ...input.agent === void 0 ? {} : { agent: toEngineAgent(input.agent) }
11281
+ ...input.agent === void 0 ? {} : { agent: toEngineAgent(input.agent) },
11282
+ ...identity2 === void 0 ? {} : { identity: identity2 }
11152
11283
  })
11153
11284
  );
11154
11285
  return "code" in result2 ? result2 : written(result2);
@@ -11694,7 +11825,15 @@ function startGroupDeliveryRuntime(ctx, wakes, now3 = () => runPromise2(map5(now
11694
11825
  };
11695
11826
  ctx.reactor({
11696
11827
  name: "team-group-delivery",
11697
- events: ["communication.group-message-sent", "work.line-restored", "plugin.event-appended"],
11828
+ // 成员建出或改了(人点「这是我」绑上)也补扫:停在待绑定的人目标立刻投到人。
11829
+ events: [
11830
+ "communication.group-message-sent",
11831
+ "work.line-restored",
11832
+ "plugin.event-appended",
11833
+ "member.created",
11834
+ "member.updated",
11835
+ "member.restored"
11836
+ ],
11698
11837
  handle: (event) => {
11699
11838
  const projectId = decodeProjectEvent(event.payload);
11700
11839
  if (isSome2(projectId)) return sweep(projectId.value.projectId);
@@ -11792,6 +11931,7 @@ function startMemberBindingRuntime(ctx, goals, rosters, wakes, trials2) {
11792
11931
  if (goal.workLineId !== void 0 && lead !== void 0)
11793
11932
  await createTeamThread(ctx, {
11794
11933
  projectId,
11934
+ memberId: member.memberId,
11795
11935
  threadId,
11796
11936
  parentThreadId: lead.threadId,
11797
11937
  title: goalThreadTitle(goal, member.name),
@@ -11836,6 +11976,7 @@ function startMemberBindingRuntime(ctx, goals, rosters, wakes, trials2) {
11836
11976
  recipient: { threadId: leadThreadId },
11837
11977
  requestId: `team-member-unbound-notice:${key}`,
11838
11978
  messageId: `team-member-unbound-notice-message:${key}`,
11979
+ wake: { reason: "notice" },
11839
11980
  text: rebinding ? `Member ${member.name} (memberId ${member.memberId}) passed its trial run with the new setup but could not be moved onto it: ${failure.detail}. Work not yet handed to them waits until it is. Fix the cause, for example change their model with team_update_member; they are set up again within a couple of minutes.` : `Member ${member.name} (memberId ${member.memberId}) could not join this goal: ${failure.detail}. Work assigned to them waits until they join. Fix the cause, for example change their model with team_update_member; they are set up again within a couple of minutes.`
11840
11981
  });
11841
11982
  if (delivered) told.add(key);
@@ -12014,6 +12155,7 @@ function startMemberRemovalRuntime(ctx, goals, wakes) {
12014
12155
  recipient: { threadId: lead },
12015
12156
  requestId: `team-member-removed-notice:${key}`,
12016
12157
  messageId: `team-member-removed-notice-message:${key}`,
12158
+ wake: { reason: "notice" },
12017
12159
  text: [
12018
12160
  `Member ${member.name} (memberId ${memberId}) was removed from this project's team.`,
12019
12161
  `Work that had not reached them yet was taken off them and now has no assignee: ${list(mine(taken))}. Assign it to another member with work_update_item.`,
@@ -13305,6 +13447,7 @@ function startWakeRuntime(ctx, goals, options = {}) {
13305
13447
  recipient: wake,
13306
13448
  requestId: wakeRequestId(id),
13307
13449
  messageId: `team-wake-msg:${id}`,
13450
+ wake: { reason: "work", sequenceId: wake.sequenceId },
13308
13451
  memberWorktree: worktree === void 0 ? void 0 : { ...worktree, lane: worktrees },
13309
13452
  // 评论在收件人正在跑时插进当前这一轮,不排到这一轮结束(试跑 3:一轮 97 分钟,检查点落空)。
13310
13453
  ...wake.kind === "comment" ? { steer: true } : {},
@@ -13508,6 +13651,10 @@ function startRecoveryRuntime(ctx, goals, state, worktrees) {
13508
13651
  requestId: `team-recovery:${id}:${plan.action}`,
13509
13652
  messageId: `team-recovery-msg:${id}:${plan.action}`,
13510
13653
  text: plan.text,
13654
+ // 续做与提醒是叫成员接着干活;告诉负责人谁停了是通知。
13655
+ wake: {
13656
+ reason: plan.action === "resume" || plan.action === "remind" ? "work" : "notice"
13657
+ },
13511
13658
  memberWorktree: worktree === void 0 ? void 0 : { ...worktree, lane: worktrees }
13512
13659
  });
13513
13660
  if (!delivered) continue;
@@ -13599,7 +13746,8 @@ fired:${fired.wakes.length}`);
13599
13746
  recipient: { threadId },
13600
13747
  requestId: `team-lead-checkin:${threadId}:${key}`,
13601
13748
  messageId: `team-lead-checkin-message:${threadId}:${key}`,
13602
- text: LEAD_CHECKIN_REMINDER
13749
+ text: LEAD_CHECKIN_REMINDER,
13750
+ wake: { reason: "check-in" }
13603
13751
  });
13604
13752
  }
13605
13753
  };
@@ -14306,6 +14454,7 @@ function startHandbookRuntime(ctx, worktrees) {
14306
14454
  messageId: `team-handbook-msg:${id}`,
14307
14455
  // 成员闲着时被换版提醒叫醒,不能借机自己挑活:成员的提醒写死只确认、确认完就结束这一轮。
14308
14456
  text: handbookReminder(release, seat.goal.leadMemberId === member.memberId),
14457
+ wake: { reason: "instructions" },
14309
14458
  memberWorktree: worktree === void 0 ? void 0 : { ...worktree, lane: worktrees.lane }
14310
14459
  });
14311
14460
  if (!delivered) continue;
@@ -15184,14 +15333,78 @@ function startLineNameMirror(ctx, queue) {
15184
15333
  });
15185
15334
  }
15186
15335
 
15336
+ // src/team/freeName.ts
15337
+ var NAME_RETRIES = 3;
15338
+ var nameTag = (seed, attempt) => attempt === 0 ? "" : ` #${stableHash(`${seed}${attempt}`).slice(0, 4)}`;
15339
+ var withNameTag = (name, tag2) => {
15340
+ const base = name.trim().slice(0, MAX_MEMBER_NAME_LENGTH);
15341
+ return tag2 === "" ? base : `${base.slice(0, MAX_MEMBER_NAME_LENGTH - tag2.length)}${tag2}`;
15342
+ };
15343
+
15344
+ // src/team/ownerMember.ts
15345
+ async function ownerOf(ctx, threadId) {
15346
+ const createdBy = (await ctx.threads.get({ threadId })).createdBy ?? null;
15347
+ const person = createdBy === null ? { kind: "local-owner", environmentId: ctx.environment.id } : { kind: "account", userId: createdBy.userId };
15348
+ return { person, createdName: createdBy?.displayName ?? null };
15349
+ }
15350
+ async function boundOwnerMember(ctx, projectId, threadId) {
15351
+ const { person } = await ownerOf(ctx, threadId);
15352
+ const { items } = await readPages(
15353
+ Number.POSITIVE_INFINITY,
15354
+ (page) => ctx.members.list({ projectId, ...page })
15355
+ );
15356
+ return items.find((row) => {
15357
+ const held = row.kind === "human" && row.removedAt === null ? personOfMember(row) : null;
15358
+ return held !== null && samePerson(held, person);
15359
+ })?.memberId ?? null;
15360
+ }
15361
+ async function ensureOwnerMember(ctx, projectId, threadId, chosen) {
15362
+ if (chosen !== null) return chosen;
15363
+ const memberId = `person:${stableHash(JSON.stringify([projectId, threadId])).slice(0, 24)}`;
15364
+ const mine = await findMember(ctx, projectId, memberId);
15365
+ if (mine !== null) return mine.removedAt === null ? memberId : null;
15366
+ const { person, createdName } = await ownerOf(ctx, threadId);
15367
+ const name = await personDisplayName(ctx, projectId, person, createdName);
15368
+ if (name === null) return null;
15369
+ for (let attempt = 0; ; attempt++) {
15370
+ const candidate = withNameTag(name, nameTag(threadId, attempt));
15371
+ try {
15372
+ await ctx.members.create({
15373
+ projectId,
15374
+ requestId: `team-owner-member:${mark(projectId, threadId, "owner")}:${attempt}`,
15375
+ memberId,
15376
+ kind: "human",
15377
+ name: candidate,
15378
+ identity: { kind: "human", principal: person }
15379
+ });
15380
+ return memberId;
15381
+ } catch (error) {
15382
+ const refusal = memberRefusal(error);
15383
+ if (refusal?.code !== "duplicate-member") throw error;
15384
+ const details = refusal.details;
15385
+ if (typeof details === "object" && details !== null && Reflect.get(details, "field") === "identity")
15386
+ return boundOwnerMember(ctx, projectId, threadId);
15387
+ if (attempt >= NAME_RETRIES) throw error;
15388
+ }
15389
+ }
15390
+ }
15391
+
15187
15392
  // src/team/open.ts
15188
15393
  var decodeOpen = decodeUnknownOption2(TeamOpenInput);
15394
+ var decodeOwner = decodeUnknownOption2(
15395
+ Struct({ ownerMemberId: NullOr(String4) })
15396
+ );
15189
15397
  function statusOf2(trials2, projectId, member) {
15190
15398
  if (member.kind === "human") return "human";
15191
15399
  const view = trialViewOf(recordOf(trials2, projectId, member.memberId));
15192
15400
  if (view === null) return "ready";
15193
15401
  return view.status === "passed" || view.status === "overridden" ? "ready" : view.status === "failed" ? "trial-failed" : "trial-pending";
15194
15402
  }
15403
+ function leadMemberName(leadName, title, tag2) {
15404
+ if (leadName !== void 0) return withNameTag(leadName, tag2);
15405
+ const tail = ` lead${tag2}`;
15406
+ return `${title.trim().slice(0, MAX_MEMBER_NAME_LENGTH - tail.length).trimEnd()}${tail}`.trim();
15407
+ }
15195
15408
  var remotePrincipalOf2 = (member) => member.identity?.kind === "remote-agent" ? member.identity.principalId : null;
15196
15409
  function createTeamOpener(ctx, deps, runner = createLineChangeRunner(ctx, deps), now3 = factTime) {
15197
15410
  const lineIdOf = (projectId, threadId, requestId) => `wl-${mark(projectId, threadId, requestId).slice(0, 24)}`;
@@ -15211,9 +15424,8 @@ function createTeamOpener(ctx, deps, runner = createLineChangeRunner(ctx, deps),
15211
15424
  ctx.projections.getThreadExecution(threadId)
15212
15425
  ]);
15213
15426
  if (execution === null) throw new TeamRuleError("not-local", teamMessage("notLocal"));
15214
- const base = (leadName ?? `${shell.title} lead`).trim().slice(0, 40);
15215
15427
  for (let attempt = 0; ; attempt++) {
15216
- const name = attempt === 0 ? base : `${base.slice(0, 33)} #${stableHash(`${threadId}${attempt}`).slice(0, 4)}`;
15428
+ const name = leadMemberName(leadName, shell.title, nameTag(threadId, attempt));
15217
15429
  try {
15218
15430
  await ctx.members.create({
15219
15431
  projectId,
@@ -15228,7 +15440,7 @@ function createTeamOpener(ctx, deps, runner = createLineChangeRunner(ctx, deps),
15228
15440
  });
15229
15441
  return memberId;
15230
15442
  } catch (error) {
15231
- if (hostRefusalCode(error) !== "duplicate-member" || attempt >= 3) throw error;
15443
+ if (hostRefusalCode(error) !== "duplicate-member" || attempt >= NAME_RETRIES) throw error;
15232
15444
  }
15233
15445
  }
15234
15446
  };
@@ -15265,6 +15477,17 @@ function createTeamOpener(ctx, deps, runner = createLineChangeRunner(ctx, deps),
15265
15477
  const local = rows.filter((row) => row.kind === "agent" && remotePrincipalOf2(row) === null);
15266
15478
  const selections = await resolveLocalSelections(projectId, local);
15267
15479
  const leadMemberId = await ensureLeadMember(projectId, callerThreadId, input.leadName);
15480
+ const ownerMemberId = await ensureOwnerMember(
15481
+ ctx,
15482
+ projectId,
15483
+ callerThreadId,
15484
+ match(decodeOwner(intent.input), {
15485
+ onNone: () => null,
15486
+ onSome: (recorded) => recorded.ownerMemberId
15487
+ })
15488
+ );
15489
+ const owner = ownerMemberId === null || rows.some((row) => row.memberId === ownerMemberId) ? null : await findMember(ctx, projectId, ownerMemberId);
15490
+ const team = owner === null ? rows : [owner, ...rows];
15268
15491
  try {
15269
15492
  const created = await ctx.work.lines.create({
15270
15493
  projectId,
@@ -15272,7 +15495,7 @@ function createTeamOpener(ctx, deps, runner = createLineChangeRunner(ctx, deps),
15272
15495
  workLineId,
15273
15496
  name,
15274
15497
  leadMemberId,
15275
- members: [leadMemberId, ...rows.map((row) => row.memberId)],
15498
+ members: [leadMemberId, ...team.map((row) => row.memberId)],
15276
15499
  threadId: callerThreadId
15277
15500
  });
15278
15501
  await ctx.events.append(baselineMirrored(projectId, workLineId, created.result.version));
@@ -15317,6 +15540,7 @@ function createTeamOpener(ctx, deps, runner = createLineChangeRunner(ctx, deps),
15317
15540
  for (const row of local) {
15318
15541
  await createTeamThread(ctx, {
15319
15542
  projectId,
15543
+ memberId: row.memberId,
15320
15544
  threadId: teamThreadId(projectId, goalId, row.memberId),
15321
15545
  parentThreadId: callerThreadId,
15322
15546
  title: goalThreadTitle(goal, row.name),
@@ -15335,7 +15559,7 @@ function createTeamOpener(ctx, deps, runner = createLineChangeRunner(ctx, deps),
15335
15559
  });
15336
15560
  await syncLineTitle(ctx, projectId, workLineId);
15337
15561
  const trials2 = await deps.trials.get();
15338
- const roster = rows.map((row) => {
15562
+ const roster = team.map((row) => {
15339
15563
  const principalId = remotePrincipalOf2(row);
15340
15564
  return {
15341
15565
  name: row.name,
@@ -15416,7 +15640,10 @@ function createTeamOpener(ctx, deps, runner = createLineChangeRunner(ctx, deps),
15416
15640
  projectId,
15417
15641
  rows.filter((row) => row.kind === "agent" && remotePrincipalOf2(row) === null)
15418
15642
  );
15419
- return input;
15643
+ return {
15644
+ ...input,
15645
+ ownerMemberId: await boundOwnerMember(ctx, projectId, callerThreadId)
15646
+ };
15420
15647
  }
15421
15648
  });
15422
15649
  };
@@ -15787,39 +16014,6 @@ async function teamWaitingList(ctx, memberThreads, nameOf2) {
15787
16014
  return waiting.length === 0 ? null : `${count(waiting.length, "member is", "members are")} waiting on the person: ${waiting.join("; ")}. These appear in this conversation for the person to answer; you cannot answer them and should not wait for them. Hand out work to members who are not blocked.`;
15788
16015
  }
15789
16016
 
15790
- // ../../packages/ycodium-system-text/src/systemText.ts
15791
- var SYSTEM_NOTIFICATION_OPEN_TAG = "<ycodium-system>";
15792
- var SYSTEM_NOTIFICATION_CLOSE_TAG = "</ycodium-system>";
15793
- var isSystemNotificationText = (text) => text.startsWith(SYSTEM_NOTIFICATION_OPEN_TAG);
15794
- var rendersAsSystemMessage = (text) => isSystemNotificationText(text) && !(isPresetPrefixMessage(text) && text.includes(PRESET_PREFIX_CLOSE) && parsePresetPrefixText(text) === null);
15795
- var FIRST_BLOCK_START = `^(?:${SYSTEM_NOTIFICATION_OPEN_TAG}\\n)?`;
15796
- var PRESET_PREFIX_MESSAGE_START = `${SYSTEM_NOTIFICATION_OPEN_TAG}
15797
- <preset-prefix `;
15798
- var isPresetPrefixMessage = (text) => text.startsWith(PRESET_PREFIX_MESSAGE_START);
15799
- var SYSTEM_TEXT_ESCAPED_ATTRIBUTE = 'esc="1"';
15800
- var ESCAPED_BLOCK_NAMES = ["thread-response", "thread-handover", "scheduled-wake"];
15801
- var ESCAPED_OPEN_LINE_PATTERN = new RegExp(
15802
- `^<(?:${ESCAPED_BLOCK_NAMES.join("|")})(?:\\s[^>]*)? ${SYSTEM_TEXT_ESCAPED_ATTRIBUTE}>$`
15803
- );
15804
- var PRESET_PREFIX_CLOSE = `
15805
- </preset-prefix>
15806
- ${SYSTEM_NOTIFICATION_CLOSE_TAG}`;
15807
- var PRESET_PREFIX_PATTERN = new RegExp(
15808
- `^${SYSTEM_NOTIFICATION_OPEN_TAG}\\n<preset-prefix id="([^"]*)">\\n([\\s\\S]*?)\\n</preset-prefix>\\n${SYSTEM_NOTIFICATION_CLOSE_TAG}(?:\\n\\n([\\s\\S]*))?$`
15809
- );
15810
- var parsePresetPrefixText = (text) => {
15811
- if (text.split(PRESET_PREFIX_CLOSE).length !== 2) return null;
15812
- const match7 = PRESET_PREFIX_PATTERN.exec(text);
15813
- if (match7 === null) return null;
15814
- const [, presetId, prefix, body] = match7;
15815
- if (!presetId) return null;
15816
- return { presetId, prefix: prefix ?? "", body: body ?? "" };
15817
- };
15818
- var userSpokenText = (text) => {
15819
- if (!rendersAsSystemMessage(text)) return text;
15820
- return parsePresetPrefixText(text)?.body || null;
15821
- };
15822
-
15823
16017
  // src/team/groupDigest.ts
15824
16018
  var DIGEST_LIMIT = 30;
15825
16019
  var SCAN_PAGES = 5;
@@ -16278,6 +16472,10 @@ var zh_CN_default = {
16278
16472
  kindAgentHint: "\u673A\u5668\u9009\u8FD9\u4E00\u53F0\u65F6\uFF0C\u7528\u4F60\u9009\u7684 provider \u548C\u6A21\u578B\u8FD0\u884C\u3002\u9009\u4E86\u522B\u7684\u673A\u5668\u7684\u6210\u5458\u8BD5\u8DD1\u4F1A\u51FA\u9519\uFF1A\u6539\u56DE\u8FD9\u4E00\u53F0\uFF0C\u6216\u8005\u8DF3\u8FC7\u8BD5\u8DD1\uFF0C\u5B83\u5C31\u5728\u8FD9\u53F0\u673A\u5668\u4E0A\u7528\u9879\u76EE\u7684\u9ED8\u8BA4\u6A21\u578B\u8FD0\u884C\u3002",
16279
16473
  kindRemoteHint: "\u4ECE\u53E6\u4E00\u53F0\u673A\u5668\u7528\u81EA\u5DF1\u7684\u51ED\u636E\u8FDE\u8FDB\u6765\u7684 agent\u3002",
16280
16474
  kindPersonHint: "\u56E2\u961F\u91CC\u7684\u4EBA\u3002\u4EBA\u4F1A\u5217\u5728\u6210\u5458\u91CC\uFF0C\u4F46\u4E0D\u4F1A\u8FD0\u884C\u4F1A\u8BDD\u3002",
16475
+ thisIsMe: "\u8FD9\u662F\u6211",
16476
+ thisIsMeHint: "\u6709\u4EBA\u5728\u56E2\u961F\u7FA4\u91CC @ \u8FD9\u4E2A\u6210\u5458\u65F6\uFF0C\u6D88\u606F\u4F1A\u8FDB\u4F60\u7684\u300C\u53D1\u7ED9\u6211\u7684\u300D\u3002",
16477
+ boundToOther: "\u8FD9\u4E2A\u6210\u5458\u662F\u53E6\u4E00\u4E2A\u4EBA\uFF0C\u53EA\u6709\u5BF9\u65B9\u672C\u4EBA\u80FD\u6539\u8FD9\u4E00\u9879\u3002",
16478
+ you: "\u6211",
16281
16479
  name: "\u540D\u5B57",
16282
16480
  memberTitle: "\u5934\u8854",
16283
16481
  responsibility: "\u804C\u8D23",
@@ -16324,6 +16522,7 @@ var zh_CN_default = {
16324
16522
  noDefaultModel: "\u8FD9\u4E2A\u9879\u76EE\u6CA1\u8BBE\u9ED8\u8BA4\u6A21\u578B\u3002\u5230\u9879\u76EE\u8BBE\u7F6E\u7684\u300C\u65B0\u4F1A\u8BDD \u2192 \u6A21\u578B\u300D\u9009\u4E00\u4E2A\uFF0C\u518D\u91CD\u8BD5\u3002",
16325
16523
  modelUnavailable: "\u8FD9\u4E2A\u9879\u76EE\u7684\u9ED8\u8BA4\u6A21\u578B\u5728\u8FD9\u53F0\u673A\u5668\u4E0A\u7528\u4E0D\u4E86\u3002\u68C0\u67E5\u4E00\u4E0B provider\uFF0C\u6216\u5230\u9879\u76EE\u8BBE\u7F6E\u7684\u300C\u65B0\u4F1A\u8BDD \u2192 \u6A21\u578B\u300D\u6362\u4E00\u4E2A\u3002",
16326
16524
  duplicateMember: "\u5DF2\u6709\u6210\u5458\u7528\u4E86\u8FD9\u4E2A\u540D\u5B57\u6216\u8FD9\u4E2A\u8FDC\u7AEF agent\uFF0C\u8BF7\u5148\u6539\u540D\u6216\u79FB\u9664\u5B83\u3002",
16525
+ alreadyYou: "\u4F60\u5728\u540D\u518C\u4E0A\u5DF2\u7ECF\u662F\u300C{{name}}\u300D\u4E86\uFF0C\u8BF7\u5148\u5728\u300C{{name}}\u300D\u90A3\u91CC\u53D6\u6D88\u300C\u8FD9\u662F\u6211\u300D\u3002",
16327
16526
  staleVersion: "\u8FD9\u540D\u6210\u5458\u5DF2\u88AB\u522B\u5904\u6539\u8FC7\uFF0C\u5DF2\u91CD\u65B0\u8F7D\u5165\u3002\u8BF7\u786E\u8BA4\u540E\u518D\u4FDD\u5B58\u3002",
16328
16527
  notFound: "\u8FD9\u540D\u6210\u5458\u5DF2\u4E0D\u5B58\u5728\u3002",
16329
16528
  invalidState: "\u8FD9\u540D\u6210\u5458\u7684\u72B6\u6001\u5DF2\u88AB\u522B\u5904\u6539\u8FC7\uFF0C\u5DF2\u91CD\u65B0\u8F7D\u5165\u3002",
@@ -16742,6 +16941,7 @@ async function ensureSession(env, record2, ensured, prepared) {
16742
16941
  if (parentThreadId !== void 0)
16743
16942
  await createTeamThread(ctx, {
16744
16943
  projectId: record2.projectId,
16944
+ memberId: record2.memberId,
16745
16945
  threadId: trialId,
16746
16946
  parentThreadId,
16747
16947
  title: member.name,
@@ -16752,6 +16952,7 @@ async function ensureSession(env, record2, ensured, prepared) {
16752
16952
  requestId: `thread:${trialId}`,
16753
16953
  threadId: trialId,
16754
16954
  projectId: record2.projectId,
16955
+ memberId: record2.memberId,
16755
16956
  title: member.name,
16756
16957
  ...selection,
16757
16958
  // 与旧目标的成员一致:不然试跑里的工具调用会卡在审批上。
@@ -16766,7 +16967,7 @@ async function ensureSession(env, record2, ensured, prepared) {
16766
16967
  message: {
16767
16968
  messageId: `team-trial-message:${key}`,
16768
16969
  role: "user",
16769
- text: trialMessage("localPrompt", { trialId }),
16970
+ text: teamWakeText({ reason: "instructions" }, trialMessage("localPrompt", { trialId })),
16770
16971
  attachments: []
16771
16972
  },
16772
16973
  modelSelection: selection.modelSelection,
@@ -16836,7 +17037,8 @@ async function tellLeads(env, leadThreads, projectId, key, text) {
16836
17037
  recipient: { threadId: leadThreadId },
16837
17038
  requestId,
16838
17039
  messageId: `${requestId}:message`,
16839
- text
17040
+ text,
17041
+ wake: { reason: "notice" }
16840
17042
  });
16841
17043
  delivered ||= sent;
16842
17044
  }
@@ -17262,7 +17464,7 @@ function startTrialRuntime(ctx, deps, options = {}) {
17262
17464
  // src/index.ts
17263
17465
  var index_default = definePlugin({
17264
17466
  id: "team",
17265
- apiVersion: 47,
17467
+ apiVersion: 49,
17266
17468
  activate(ctx) {
17267
17469
  const trialState = ctx.derivedState(trialStateDefinition);
17268
17470
  const rosters = createRosterService(ctx);