@ycodium-ai/plugin-team 0.2.3158 → 0.2.3180

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
@@ -6867,6 +6867,19 @@ function goalExecution(goal) {
6867
6867
  members: goal === null ? [] : [...goal.threads, ...goal.remoteMembers ?? []]
6868
6868
  };
6869
6869
  }
6870
+ function findGoalMember(members, identity2) {
6871
+ return members.find(
6872
+ (member) => "threadId" in identity2 ? "threadId" in member && member.threadId === identity2.threadId : "principalId" in member && member.principalId === identity2.principalId
6873
+ );
6874
+ }
6875
+
6876
+ // src/domain/goalItem.ts
6877
+ var teamGoalItemId = (projectId, goalId) => `team-goal:${projectId}:${goalId}`;
6878
+ var GOAL_ITEM_TITLE_MAX = 120;
6879
+ var goalItemTitle = (goal) => {
6880
+ const line = (goal.titleSeed ?? goal.goal).split(/\r?\n/u, 1)[0].trim();
6881
+ return line.length <= GOAL_ITEM_TITLE_MAX ? line : `${line.slice(0, GOAL_ITEM_TITLE_MAX - 1)}\u2026`;
6882
+ };
6870
6883
 
6871
6884
  // locales/en.json
6872
6885
  var en_default = {
@@ -6897,6 +6910,7 @@ var en_default = {
6897
6910
  retryCreate: "Retry creation",
6898
6911
  saving: "Saving\u2026",
6899
6912
  moveTo: "Move to\u2026",
6913
+ goalItem: "Goal",
6900
6914
  moveItem: "Move work item #{{number}}",
6901
6915
  moveItemTo: "Move work item #{{number}} to {{state}}",
6902
6916
  assignTo: "Assign to\u2026",
@@ -6939,6 +6953,18 @@ var en_default = {
6939
6953
  reply: "Reply",
6940
6954
  replyTo: "Reply to comment {{number}}",
6941
6955
  newComment: "Post a comment",
6956
+ commentHint: "To wake a member, choose it under Mention\u2026 (or type @), or choose Reply under its comment. Commenting on an open work item assigned to a remote member also wakes it.",
6957
+ mention: "Mention\u2026",
6958
+ mentionLabel: "Mention a member \u2014 {{comment}}",
6959
+ mentionMember: "Mention {{name}} \u2014 {{comment}}",
6960
+ mentionMemberSelected: "{{name}} (mentioned) \u2014 {{comment}}",
6961
+ mentionSelected: "{{name}} (mentioned)",
6962
+ mentionRemove: "Remove mention of {{name}} \u2014 {{comment}}",
6963
+ mentionNone: "Start a goal to mention members.",
6964
+ mentionNoneLabel: "Start a goal to mention members \u2014 {{comment}}",
6965
+ mentioned: "Mentioned: {{names}}",
6966
+ mentionSeparator: ", ",
6967
+ notAMember: "One of the mentioned members is not in the current goal. Remove it and try again.",
6942
6968
  send: "Send",
6943
6969
  sendComment: "Send comment",
6944
6970
  sendReply: "Send reply to comment {{number}}",
@@ -7005,7 +7031,9 @@ var en_default = {
7005
7031
  invalidRequestId: "The request ID must be 1\u201364 letters, numbers, underscores, or hyphens.",
7006
7032
  loadFailed: "The current goal could not be loaded. Try reloading it.",
7007
7033
  startFailed: "The goal could not be fully started. Retry to finish creating the threads and sending the lead's brief.",
7008
- remoteMembers: "Remote members:"
7034
+ remoteMembers: "Remote members:",
7035
+ item: "Goal work item #{{number}}: {{state}}",
7036
+ openItem: "Open details for goal work item #{{number}}"
7009
7037
  },
7010
7038
  roster: {
7011
7039
  title: "Roster",
@@ -7133,7 +7161,7 @@ var PERSON_MESSAGE_RULE = "Send a message to the person only when you need their
7133
7161
  var LEAD_SPLIT_RULE = "Use work_create_item to split the goal into a few work items: one item per task, with a clear action in the title. Members do not see this conversation or your goal, so put everything a member needs into the description: the deliverable, the constraints, and how to hand the work in. Create them without assignees.";
7134
7162
  var LEAD_OWN_ITEM_RULE = "If a work item for this goal is assigned to you, also record it as blocked by every item you create. When they are all done you will be told; then check the results and mark that item done with work_update_item.";
7135
7163
  var LEAD_SEND_BACK_RULE = "If a finished item is not acceptable, send it back: use work_update_item to set its state back to an open one (see work_list_states), and change its assignees too if someone else should redo it. Then say what is wrong in a comment with work_post_comment, mentioning the member.";
7136
- var LEAD_NO_MEMBER_TASKS = "Do not do members' tasks yourself.";
7164
+ var LEAD_NO_MEMBER_TASKS = "Do not do members' tasks yourself, and never assign an item you create to yourself: merging, pushing and integrating the work are member tasks too.";
7137
7165
  var tableCell = (value) => value.replace(/\|/g, "\\|").replace(/[\r\n]/g, " ");
7138
7166
  function leadBrief(goal, roster) {
7139
7167
  const localMembers = goal.threads.filter((thread) => thread.memberId !== goal.leadMemberId).map((thread) => {
@@ -7155,6 +7183,7 @@ function leadBrief(goal, roster) {
7155
7183
  ...localMembers,
7156
7184
  ...remoteMembers
7157
7185
  ].join("\n"),
7186
+ `This goal is on the board as work item "${teamGoalItemId(goal.projectId, goal.goalId)}", assigned to you so the person can see when it is finished. Do not claim it or work on it yourself.`,
7158
7187
  LEAD_SPLIT_RULE,
7159
7188
  'Then use work_add_relation with type "blocked_by" to record dependencies: the dependent workItemId is blocked by the prerequisite relatedWorkItemId.',
7160
7189
  LEAD_OWN_ITEM_RULE,
@@ -7350,6 +7379,14 @@ function createGoalService(ctx, rosterService, options = {}) {
7350
7379
  }
7351
7380
  });
7352
7381
  }
7382
+ await ctx.work.items.create({
7383
+ projectId: goal.projectId,
7384
+ workItemId: teamGoalItemId(goal.projectId, goal.goalId),
7385
+ requestId: `goal-item:${goal.projectId}:${goal.goalId}`,
7386
+ title: goalItemTitle(goal),
7387
+ description: goal.goal,
7388
+ assignees: [{ kind: "agent", threadId: leadThreadId }]
7389
+ });
7353
7390
  await ctx.threads.turns.start({
7354
7391
  requestId: `lead-turn:${goal.projectId}:${goal.goalId}`,
7355
7392
  threadId: leadThreadId,
@@ -7489,11 +7526,17 @@ var BoardItem = Struct({
7489
7526
  blockedByTotal: Number5,
7490
7527
  version: Number5
7491
7528
  });
7529
+ var BoardGoalItem = Struct({
7530
+ workItemId: String4,
7531
+ sequenceId: Number5,
7532
+ state: Struct({ stateId: String4, name: String4, group: String4 })
7533
+ });
7492
7534
  var BoardSnapshot = Struct({
7493
7535
  states: ArraySchema(BoardState),
7494
7536
  items: ArraySchema(BoardItem),
7495
7537
  // 当前目标与 goal 命令同一份形状;目标栏与看板都从这里读,同一个门铃刷新。
7496
7538
  goal: ReadGoalResult,
7539
+ goalItem: optionalKey2(BoardGoalItem),
7497
7540
  people: ArraySchema(BoardPerson),
7498
7541
  // 已保存名单里的成员;名字显示(含远端成员)读这里,名单编辑器的草稿另存。
7499
7542
  rosterMembers: ArraySchema(TeamMember),
@@ -7704,9 +7747,7 @@ var decodeRead2 = decodeUnknownSync(ReadBoardInput);
7704
7747
  var decodeCreate = decodeUnknownOption2(CreateItemInput);
7705
7748
  var decodeUpdate = decodeUnknownOption2(UpdateItemInput);
7706
7749
  function registerBoardCommands(ctx, goals, rosters, options = {}) {
7707
- const isMember2 = async (projectId, threadId) => goalExecution(await goals.read(projectId)).members.some(
7708
- (member) => "threadId" in member && member.threadId === threadId
7709
- );
7750
+ const isMember2 = async (projectId, threadId) => findGoalMember(goalExecution(await goals.read(projectId)).members, { threadId }) !== void 0;
7710
7751
  ctx.command({
7711
7752
  name: "board",
7712
7753
  scope: "read",
@@ -7729,7 +7770,8 @@ function registerBoardCommands(ctx, goals, rosters, options = {}) {
7729
7770
  // 认出当前读者只为把作者显示成「你」:读不出来就当没有,不连累整张看板。
7730
7771
  invocation.viewer?.communication.people.current({}).catch(() => null)
7731
7772
  ]);
7732
- const [remoteWork, { items: rows, truncated }] = await Promise.all([
7773
+ const goalItemId = goal === null ? null : teamGoalItemId(projectId, goal.goalId);
7774
+ const [remoteWork, { items: rows, truncated }, goalItem] = await Promise.all([
7733
7775
  Promise.all(
7734
7776
  (roster?.members ?? []).flatMap(
7735
7777
  (member) => member.principal === void 0 ? [] : [
@@ -7740,7 +7782,9 @@ function registerBoardCommands(ctx, goals, rosters, options = {}) {
7740
7782
  ]
7741
7783
  )
7742
7784
  ),
7743
- readPages(ITEM_LIMIT, (page) => ctx.work.items.list({ projectId, ...page }))
7785
+ readPages(ITEM_LIMIT, (page) => ctx.work.items.list({ projectId, ...page })),
7786
+ // 目标那件按号单读:看板只列最新的 500 件,目标那件可能不在里面。
7787
+ goalItemId === null ? void 0 : ctx.work.items.get({ projectId, workItemIds: [goalItemId] }).then(({ items: items2 }) => items2.find((item) => item.workItemId === goalItemId))
7744
7788
  ]);
7745
7789
  const enabled = (toolkit) => toolkitStates.toolkits.some((row) => row.toolkit === toolkit && row.enabled);
7746
7790
  const items = rows.map((item) => ({
@@ -7761,6 +7805,17 @@ function registerBoardCommands(ctx, goals, rosters, options = {}) {
7761
7805
  })).sort((a, b) => a.sequence - b.sequence),
7762
7806
  items,
7763
7807
  goal,
7808
+ ...goalItem === void 0 ? {} : {
7809
+ goalItem: {
7810
+ workItemId: goalItem.workItemId,
7811
+ sequenceId: goalItem.sequenceId,
7812
+ state: {
7813
+ stateId: goalItem.state.stateId,
7814
+ name: goalItem.state.name,
7815
+ group: goalItem.state.group
7816
+ }
7817
+ }
7818
+ },
7764
7819
  people: people.items.map((row) => ({
7765
7820
  key: personKey(row.person),
7766
7821
  displayName: row.displayName
@@ -7854,8 +7909,17 @@ var PostCommentInput = Struct({
7854
7909
  workItemId: String4,
7855
7910
  body: String4,
7856
7911
  parentId: optionalKey2(String4),
7912
+ mentions: optionalKey2(
7913
+ ArraySchema(
7914
+ Struct({
7915
+ threadId: optionalKey2(String4),
7916
+ principalId: optionalKey2(String4)
7917
+ })
7918
+ )
7919
+ ),
7857
7920
  requestId: String4
7858
7921
  });
7922
+ var mentionPrincipal = (mention) => mention.threadId !== void 0 ? mention.principalId === void 0 ? { kind: "agent", threadId: mention.threadId } : null : mention.principalId !== void 0 ? { kind: "remote-agent", principalId: mention.principalId } : null;
7859
7923
  var PostCommentResult = Union2([
7860
7924
  Struct({ commentId: String4 }),
7861
7925
  BoardRejection
@@ -7863,6 +7927,7 @@ var PostCommentResult = Union2([
7863
7927
  var DetailComment = Struct({
7864
7928
  commentId: String4,
7865
7929
  author: WorkIdentity,
7930
+ mentions: optionalKey2(ArraySchema(WorkIdentity)),
7866
7931
  body: NullOr(String4),
7867
7932
  parentId: NullOr(String4),
7868
7933
  createdAt: String4
@@ -7912,7 +7977,7 @@ var HISTORY_LIMIT = 200;
7912
7977
  var GET_BATCH_SIZE = 50;
7913
7978
  var decodeRead3 = decodeUnknownSync(ReadItemDetailInput);
7914
7979
  var decodePost = decodeUnknownOption2(PostCommentInput);
7915
- function registerDetailCommands(ctx) {
7980
+ function registerDetailCommands(ctx, goals) {
7916
7981
  ctx.command({
7917
7982
  name: "itemDetail",
7918
7983
  scope: "read",
@@ -7972,6 +8037,7 @@ function registerDetailCommands(ctx) {
7972
8037
  comments: comments.items.map((comment) => ({
7973
8038
  commentId: comment.commentId,
7974
8039
  author: workIdentity(comment.author),
8040
+ mentions: comment.mentions.map(workIdentity),
7975
8041
  body: comment.bodyState === "present" ? comment.body : null,
7976
8042
  parentId: comment.parentId,
7977
8043
  createdAt: comment.createdAt
@@ -8015,13 +8081,25 @@ function registerDetailCommands(ctx) {
8015
8081
  handle: async (payload, invocation) => {
8016
8082
  const decoded = decodePost(payload);
8017
8083
  if (isNone2(decoded)) return rejectWork("postComment", "invalid-payload");
8018
- const input = decoded.value;
8084
+ const { mentions, ...input } = decoded.value;
8019
8085
  const body = commentText(input.body);
8020
8086
  if (body === null) return rejectWork("postComment", "invalid-body");
8087
+ const principals = mentions?.flatMap((mention) => mentionPrincipal(mention) ?? []);
8088
+ if (principals?.length !== mentions?.length)
8089
+ return rejectWork("postComment", "invalid-payload");
8090
+ if (principals !== void 0 && principals.length > 0) {
8091
+ const members = goalExecution(await goals.read(input.projectId)).members;
8092
+ if (principals.some((principal) => findGoalMember(members, principal) === void 0))
8093
+ return rejectWork("postComment", "not-a-member");
8094
+ }
8021
8095
  const result2 = await writeWork(
8022
8096
  invocation,
8023
8097
  "postComment",
8024
- (work) => work.comments.post({ ...input, body })
8098
+ (work) => work.comments.post({
8099
+ ...input,
8100
+ body,
8101
+ ...principals === void 0 ? {} : { mentions: principals }
8102
+ })
8025
8103
  );
8026
8104
  return "code" in result2 ? result2 : { commentId: result2.result.commentId };
8027
8105
  }
@@ -8661,8 +8739,9 @@ var decodeRelease = decodeUnknownSync(
8661
8739
  );
8662
8740
  function callerMember(project2, caller) {
8663
8741
  if (caller === void 0) return void 0;
8664
- return goalExecution(project2.goal).members.find(
8665
- (member) => caller.kind === "thread" ? "threadId" in member && member.threadId === caller.threadId : "principalId" in member && member.principalId === caller.principalId
8742
+ return findGoalMember(
8743
+ goalExecution(project2.goal).members,
8744
+ caller.kind === "thread" ? { threadId: caller.threadId } : { principalId: caller.principalId }
8666
8745
  );
8667
8746
  }
8668
8747
  var hasAcknowledged = (project2, memberId, release) => readFact(readFact(project2.acknowledgements, memberId)?.releases ?? {}, String(release)) === true;
@@ -9032,6 +9111,8 @@ function startHandbookRuntime(ctx) {
9032
9111
  input.projectId
9033
9112
  );
9034
9113
  if (project2 === null || project2.requiredRelease === null) return { allowed: true };
9114
+ if (project2.goal !== null && input.workItemId === teamGoalItemId(input.projectId, project2.goal.goalId))
9115
+ return { allowed: true };
9035
9116
  for (const actor of input.added) {
9036
9117
  const caller = actor.kind === "human" ? void 0 : actor.kind === "agent" ? { kind: "thread", threadId: actor.threadId } : actor;
9037
9118
  const member = callerMember(project2, caller);
@@ -9215,14 +9296,14 @@ function registerHandbookPanel(ctx, runtime) {
9215
9296
  // src/index.ts
9216
9297
  var index_default = definePlugin({
9217
9298
  id: "team",
9218
- apiVersion: 15,
9299
+ apiVersion: 16,
9219
9300
  activate(ctx) {
9220
9301
  const rosters = createRosterService(ctx);
9221
9302
  registerRosterCommands(ctx, rosters);
9222
9303
  const goals = createGoalService(ctx, rosters);
9223
9304
  registerGoalCommands(ctx, goals);
9224
9305
  registerBoardCommands(ctx, goals, rosters);
9225
- registerDetailCommands(ctx);
9306
+ registerDetailCommands(ctx, goals);
9226
9307
  startWakeRuntime(ctx);
9227
9308
  const handbook = startHandbookRuntime(ctx);
9228
9309
  registerHandbook(ctx, handbook);
package/locales/en.json CHANGED
@@ -26,6 +26,7 @@
26
26
  "retryCreate": "Retry creation",
27
27
  "saving": "Saving…",
28
28
  "moveTo": "Move to…",
29
+ "goalItem": "Goal",
29
30
  "moveItem": "Move work item #{{number}}",
30
31
  "moveItemTo": "Move work item #{{number}} to {{state}}",
31
32
  "assignTo": "Assign to…",
@@ -68,6 +69,18 @@
68
69
  "reply": "Reply",
69
70
  "replyTo": "Reply to comment {{number}}",
70
71
  "newComment": "Post a comment",
72
+ "commentHint": "To wake a member, choose it under Mention… (or type @), or choose Reply under its comment. Commenting on an open work item assigned to a remote member also wakes it.",
73
+ "mention": "Mention…",
74
+ "mentionLabel": "Mention a member — {{comment}}",
75
+ "mentionMember": "Mention {{name}} — {{comment}}",
76
+ "mentionMemberSelected": "{{name}} (mentioned) — {{comment}}",
77
+ "mentionSelected": "{{name}} (mentioned)",
78
+ "mentionRemove": "Remove mention of {{name}} — {{comment}}",
79
+ "mentionNone": "Start a goal to mention members.",
80
+ "mentionNoneLabel": "Start a goal to mention members — {{comment}}",
81
+ "mentioned": "Mentioned: {{names}}",
82
+ "mentionSeparator": ", ",
83
+ "notAMember": "One of the mentioned members is not in the current goal. Remove it and try again.",
71
84
  "send": "Send",
72
85
  "sendComment": "Send comment",
73
86
  "sendReply": "Send reply to comment {{number}}",
@@ -134,7 +147,9 @@
134
147
  "invalidRequestId": "The request ID must be 1–64 letters, numbers, underscores, or hyphens.",
135
148
  "loadFailed": "The current goal could not be loaded. Try reloading it.",
136
149
  "startFailed": "The goal could not be fully started. Retry to finish creating the threads and sending the lead's brief.",
137
- "remoteMembers": "Remote members:"
150
+ "remoteMembers": "Remote members:",
151
+ "item": "Goal work item #{{number}}: {{state}}",
152
+ "openItem": "Open details for goal work item #{{number}}"
138
153
  },
139
154
  "roster": {
140
155
  "title": "Roster",
@@ -26,6 +26,7 @@
26
26
  "retryCreate": "重试创建",
27
27
  "saving": "保存中…",
28
28
  "moveTo": "移到…",
29
+ "goalItem": "目标",
29
30
  "moveItem": "移动工作项 #{{number}}",
30
31
  "moveItemTo": "将工作项 #{{number}} 移到 {{state}}",
31
32
  "assignTo": "指派给…",
@@ -68,6 +69,18 @@
68
69
  "reply": "回复",
69
70
  "replyTo": "回复第 {{number}} 条评论",
70
71
  "newComment": "发表评论",
72
+ "commentHint": "要叫醒成员,用「提及…」选择它(或输入 @),或在它的评论下点「回复」;在指派给远端成员、还没结束的工作项上评论也会叫醒它。",
73
+ "mention": "提及…",
74
+ "mentionLabel": "提及成员({{comment}})",
75
+ "mentionMember": "提及 {{name}}({{comment}})",
76
+ "mentionMemberSelected": "{{name}}(已提及)({{comment}})",
77
+ "mentionSelected": "{{name}}(已提及)",
78
+ "mentionRemove": "移除对 {{name}} 的提及({{comment}})",
79
+ "mentionNone": "先开始一个目标,才能提及成员。",
80
+ "mentionNoneLabel": "先开始一个目标,才能提及成员({{comment}})",
81
+ "mentioned": "提及:{{names}}",
82
+ "mentionSeparator": "、",
83
+ "notAMember": "被提及的成员里有人不在当前目标中,请移除后重试。",
71
84
  "send": "发送",
72
85
  "sendComment": "发送评论",
73
86
  "sendReply": "发送第 {{number}} 条评论的回复",
@@ -134,7 +147,9 @@
134
147
  "invalidRequestId": "请求 ID 需为 1 到 64 个字母、数字、下划线或连字符。",
135
148
  "loadFailed": "未能载入当前目标,请重新载入。",
136
149
  "startFailed": "目标未能全部开始,请重试以补齐会话并给负责人发话。",
137
- "remoteMembers": "远端成员:"
150
+ "remoteMembers": "远端成员:",
151
+ "item": "目标工作项 #{{number}}:{{state}}",
152
+ "openItem": "打开目标工作项 #{{number}} 的详情"
138
153
  },
139
154
  "roster": {
140
155
  "title": "名单",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ycodium-ai/plugin-team",
3
- "version": "0.2.3158",
3
+ "version": "0.2.3180",
4
4
  "description": "A project team roster with members, a lead, and goals.",
5
5
  "keywords": [
6
6
  "teams",
@@ -34,7 +34,7 @@
34
34
  "tailwindcss": "^4.0.0",
35
35
  "typescript": "~6.0.3",
36
36
  "vite-plus": "0.2.2",
37
- "@ycodium-ai/plugin-api": "0.2.3158"
37
+ "@ycodium-ai/plugin-api": "0.2.3180"
38
38
  },
39
39
  "ycodium": {
40
40
  "plugin": "./dist/index.js",