@ycodium-ai/plugin-team 0.2.3395 → 0.2.3405
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/assets/ui.js +8 -8
- package/dist/index.js +163 -28
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -7472,7 +7472,7 @@ var PERSON_MESSAGE_RULE = "Send a message to the person only for a product decis
|
|
|
7472
7472
|
var MEMBER_CHECKS_RULE = "Before you mark an item done, run the checks that this project's rules name for the code you changed, and fix every failure. Do not mark an item done while a check still fails; say in a work item comment what still fails instead.";
|
|
7473
7473
|
var NO_STASH = "Do not use git stash: all worktrees of this repository share one stash stack.";
|
|
7474
7474
|
var MEMBER_NO_STASH_RULE = `${NO_STASH} To try something you can undo, commit to a temporary branch or copy files into an ignored folder inside your own worktree, and delete it before you mark the item done.`;
|
|
7475
|
-
var MEMBER_ASK_BEFORE_OUTSIDE_RULE = "Before you change dependencies or patches, write outside the project root, or fetch code from the network (git clone, downloads), mention your lead in a work item comment and wait for an answer.";
|
|
7475
|
+
var MEMBER_ASK_BEFORE_OUTSIDE_RULE = "Before you change dependencies or patches, write outside the project root, or fetch code from the network (git clone, downloads), mention your lead in a work item comment (put them in the mentions parameter of the comment) and wait for an answer.";
|
|
7476
7476
|
var MEMBER_READ_RANGES_RULE = "Read files in ranges: find the place with Grep first, then read only the lines you need; read only the relevant section of a skill file.";
|
|
7477
7477
|
var LEAD_ACCEPT_RULE = `When you check a finished item, run the checks that this project's rules name yourself. Send the item back if its changes touch patches/ or a lockfile, unless the item has a comment from you approving that change. ${NO_STASH} To compare against the base, run the same check in your own working directory on the base branch and compare the error counts; do not stash in the member's worktree.`;
|
|
7478
7478
|
var PROGRESS_CHECKIN_MINUTES = 20;
|
|
@@ -7490,8 +7490,8 @@ var LEAD_SCHEDULE_CHECKIN_RULE = "Before you end a turn while any item of this g
|
|
|
7490
7490
|
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.";
|
|
7491
7491
|
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. Each time a member marks an item done you will be told, so check it then; when they are all done, mark that item done with work_update_item.";
|
|
7492
7492
|
var LEAD_REBALANCE_RULE = "When a member has nothing in progress while another member has an item waiting in line that is not blocked, move one waiting item to the idle member with work_update_item.";
|
|
7493
|
-
var LEAD_CHECKPOINT_RULE = `Members commit and post a one-line progress comment each time they reach a step (a group of tests turns green, a file passes its typecheck, before they change approach, before they stop to wait for someone), and the team plugin mentions a member on an item in progress after ${PROGRESS_CHECKIN_MINUTES} minutes with no progress from them, up to ${PROGRESS_CHECKIN_LIMIT} times in a row. Each time you look in on members, check each item in progress: if the plugin has asked the member and there has been no answer for ${PROGRESS_CHECKIN_LIMIT * PROGRESS_CHECKIN_MINUTES} minutes since its first ask (a member on another machine that has gone offline is asked only once), or the member keeps posting progress but their branch has had no new commit for more than 30 minutes, post a comment with work_post_comment
|
|
7494
|
-
var LEAD_SEND_BACK_RULE =
|
|
7493
|
+
var LEAD_CHECKPOINT_RULE = `Members commit and post a one-line progress comment each time they reach a step (a group of tests turns green, a file passes its typecheck, before they change approach, before they stop to wait for someone), and the team plugin mentions a member on an item in progress after ${PROGRESS_CHECKIN_MINUTES} minutes with no progress from them, up to ${PROGRESS_CHECKIN_LIMIT} times in a row. Each time you look in on members, check each item in progress: if the plugin has asked the member and there has been no answer for ${PROGRESS_CHECKIN_LIMIT * PROGRESS_CHECKIN_MINUTES} minutes since its first ask (a member on another machine that has gone offline is asked only once), or the member keeps posting progress but their branch has had no new commit for more than 30 minutes, post a comment with work_post_comment, mentioning the member in its mentions parameter, that asks them to run the tests and typecheck for what they have changed so far, commit the work in progress to their branch, and post a one-line progress and blocker comment on the item. Take the times from work_history and, when you can read the member's worktree on this machine, from git log -1 --format=%cI there, and compare them with the current time; do not estimate. When you cannot read their worktree, as with a remote member, judge by work_history and comments alone; not being able to see commits does not mean there are none.`;
|
|
7494
|
+
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 in its mentions parameter (for a local member: { "kind": "agent", "threadId": <their threadId> }); that is what wakes them.';
|
|
7495
7495
|
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.";
|
|
7496
7496
|
var tableCell = (value) => value.replace(/\|/g, "\\|").replace(/[\r\n]/g, " ");
|
|
7497
7497
|
function leadBrief(goal, roster) {
|
|
@@ -8904,6 +8904,89 @@ ${await worktreeSetupSentence(ctx, input.projectId, branch)}`
|
|
|
8904
8904
|
return true;
|
|
8905
8905
|
}
|
|
8906
8906
|
|
|
8907
|
+
// src/runtime/commentWakes.ts
|
|
8908
|
+
var COMMENT_MENTIONS_FACT = "comment-mentions";
|
|
8909
|
+
var COMMENT_MENTIONS_EVENT = `team/${COMMENT_MENTIONS_FACT}`;
|
|
8910
|
+
var CommentMentionsFact = Struct({
|
|
8911
|
+
version: Literal2(1),
|
|
8912
|
+
projectId: String4,
|
|
8913
|
+
workItemId: String4,
|
|
8914
|
+
commentId: String4,
|
|
8915
|
+
recipients: ArraySchema(
|
|
8916
|
+
Union2([
|
|
8917
|
+
Struct({ threadId: String4 }),
|
|
8918
|
+
Struct({ principalId: String4 })
|
|
8919
|
+
])
|
|
8920
|
+
)
|
|
8921
|
+
});
|
|
8922
|
+
var commentPeople = (comment) => [
|
|
8923
|
+
...comment.parentAuthor === void 0 ? [] : [comment.parentAuthor],
|
|
8924
|
+
...comment.mentions
|
|
8925
|
+
];
|
|
8926
|
+
var recipientsOf = (people) => {
|
|
8927
|
+
const recipients = /* @__PURE__ */ new Map();
|
|
8928
|
+
for (const person of people) {
|
|
8929
|
+
const recipient = recipientOf(person);
|
|
8930
|
+
if (recipient !== void 0) recipients.set(recipientKey(recipient), recipient);
|
|
8931
|
+
}
|
|
8932
|
+
return recipients;
|
|
8933
|
+
};
|
|
8934
|
+
var reachedKeys = (actor, people) => new Set(recipientsOf([actor, ...people]).keys());
|
|
8935
|
+
var commentRecipients = (project2, actor, people) => {
|
|
8936
|
+
const author = recipientOf(actor);
|
|
8937
|
+
const authorKey = author === void 0 ? void 0 : recipientKey(author);
|
|
8938
|
+
return [...recipientsOf(people)].filter(([key]) => key !== authorKey).map(([, recipient]) => recipient).filter((recipient) => isMember(project2, recipient));
|
|
8939
|
+
};
|
|
8940
|
+
var commentWakeKey = (commentId, recipient) => "threadId" in recipient ? `comment:${commentId}:${recipient.threadId}` : `comment:${commentId}:principal:${recipient.principalId}`;
|
|
8941
|
+
var queueCommentWakes = (project2, comment, recipients) => [
|
|
8942
|
+
...project2.pendingComments,
|
|
8943
|
+
...recipients.map((recipient) => ({
|
|
8944
|
+
...recipient,
|
|
8945
|
+
key: commentWakeKey(comment.commentId, recipient),
|
|
8946
|
+
projectId: comment.projectId,
|
|
8947
|
+
workItemId: comment.workItemId,
|
|
8948
|
+
commentId: comment.commentId
|
|
8949
|
+
})).filter((queued) => !project2.pendingComments.some((pending) => pending.key === queued.key))
|
|
8950
|
+
];
|
|
8951
|
+
|
|
8952
|
+
// src/domain/mentionText.ts
|
|
8953
|
+
var isAtSign = (character) => character === "@" || character === "\uFF20";
|
|
8954
|
+
var MENTION_BOUNDARY = /[\s\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\u3000-\u303f\uff01-\uff0f\uff1a-\uff20\uff3b-\uff40\uff5b-\uff65]/u;
|
|
8955
|
+
var isMentionStart = (text, index) => {
|
|
8956
|
+
if (!isAtSign(text[index])) return false;
|
|
8957
|
+
const before = Array.from(text.slice(Math.max(0, index - 2), index)).at(-1);
|
|
8958
|
+
return before === void 0 || MENTION_BOUNDARY.test(before);
|
|
8959
|
+
};
|
|
8960
|
+
|
|
8961
|
+
// src/runtime/mentionedMembers.ts
|
|
8962
|
+
var ASCII_WORD = /[A-Za-z0-9_-]/;
|
|
8963
|
+
function mentionedMembers(body, members) {
|
|
8964
|
+
const aliases = members.flatMap((member) => {
|
|
8965
|
+
const principal = mentionPrincipal(member);
|
|
8966
|
+
const recipient = principal === null ? void 0 : recipientOf(principal);
|
|
8967
|
+
if (recipient === void 0) return [];
|
|
8968
|
+
const id = "threadId" in recipient ? recipient.threadId : recipient.principalId;
|
|
8969
|
+
return [id, member.memberId, member.name].flatMap(
|
|
8970
|
+
(text) => text === void 0 || text === null || text === "" ? [] : [{ text: text.toLowerCase(), key: recipientKey(recipient), recipient }]
|
|
8971
|
+
);
|
|
8972
|
+
}).sort((a, b) => b.text.length - a.text.length);
|
|
8973
|
+
const found = /* @__PURE__ */ new Map();
|
|
8974
|
+
for (let at = 0; at < body.length; at++) {
|
|
8975
|
+
if (!isMentionStart(body, at)) continue;
|
|
8976
|
+
const rest = body.slice(at + 1);
|
|
8977
|
+
const hits = aliases.filter((alias) => {
|
|
8978
|
+
if (rest.slice(0, alias.text.length).toLowerCase() !== alias.text) return false;
|
|
8979
|
+
const next = rest[alias.text.length];
|
|
8980
|
+
return next === void 0 || !(ASCII_WORD.test(alias.text.slice(-1)) && ASCII_WORD.test(next));
|
|
8981
|
+
});
|
|
8982
|
+
const longest = hits.filter((hit) => hit.text.length === hits[0]?.text.length);
|
|
8983
|
+
const keys = new Set(longest.map((hit) => hit.key));
|
|
8984
|
+
const [only] = longest;
|
|
8985
|
+
if (only !== void 0 && keys.size === 1) found.set(only.key, only.recipient);
|
|
8986
|
+
}
|
|
8987
|
+
return [...found.values()];
|
|
8988
|
+
}
|
|
8989
|
+
|
|
8907
8990
|
// src/runtime/recoveryState.ts
|
|
8908
8991
|
var RecoveryPlan = Struct({
|
|
8909
8992
|
action: Literals(["resume", "lead", "remind", "stall-lead"]),
|
|
@@ -9354,6 +9437,11 @@ var decodeEnvelope2 = decodeUnknownOption2(
|
|
|
9354
9437
|
name: Literal2("team/member-woken"),
|
|
9355
9438
|
data: MemberWokenFact
|
|
9356
9439
|
}),
|
|
9440
|
+
Struct({
|
|
9441
|
+
pluginId: Literal2("team"),
|
|
9442
|
+
name: Literal2(COMMENT_MENTIONS_EVENT),
|
|
9443
|
+
data: CommentMentionsFact
|
|
9444
|
+
}),
|
|
9357
9445
|
Struct({
|
|
9358
9446
|
pluginId: Literal2("team"),
|
|
9359
9447
|
name: Literal2("team/member-worktree-failed"),
|
|
@@ -9440,18 +9528,6 @@ var decodeWorkEvent = decodeUnknownOption2(
|
|
|
9440
9528
|
var agentThreads = (principals) => [
|
|
9441
9529
|
...new Set(principals.flatMap((p) => p.kind === "agent" ? [p.threadId] : []))
|
|
9442
9530
|
];
|
|
9443
|
-
var commentRecipients = (project2, actor, people) => {
|
|
9444
|
-
const author = recipientOf(actor);
|
|
9445
|
-
const recipients = /* @__PURE__ */ new Map();
|
|
9446
|
-
for (const person of people) {
|
|
9447
|
-
const recipient = recipientOf(person);
|
|
9448
|
-
if (recipient === void 0) continue;
|
|
9449
|
-
const key = recipientKey(recipient);
|
|
9450
|
-
if (author === void 0 || recipientKey(author) !== key) recipients.set(key, recipient);
|
|
9451
|
-
}
|
|
9452
|
-
return [...recipients.values()].filter((recipient) => isMember(project2, recipient));
|
|
9453
|
-
};
|
|
9454
|
-
var commentWakeKey = (commentId, recipient) => "threadId" in recipient ? `comment:${commentId}:${recipient.threadId}` : `comment:${commentId}:principal:${recipient.principalId}`;
|
|
9455
9531
|
function foldWorkWakes(state, event) {
|
|
9456
9532
|
if (event.type === "plugin.event-appended") {
|
|
9457
9533
|
const decoded2 = decodeEnvelope2(event.payload);
|
|
@@ -9488,6 +9564,21 @@ function foldWorkWakes(state, event) {
|
|
|
9488
9564
|
}
|
|
9489
9565
|
};
|
|
9490
9566
|
}
|
|
9567
|
+
if (fact.name === COMMENT_MENTIONS_EVENT) {
|
|
9568
|
+
const { projectId: projectId3, workItemId: workItemId2, commentId, recipients } = fact.data;
|
|
9569
|
+
const project4 = readFact(state, projectId3) ?? emptyProject();
|
|
9570
|
+
return {
|
|
9571
|
+
...state,
|
|
9572
|
+
[projectId3]: {
|
|
9573
|
+
...project4,
|
|
9574
|
+
pendingComments: queueCommentWakes(
|
|
9575
|
+
project4,
|
|
9576
|
+
{ projectId: projectId3, workItemId: workItemId2, commentId },
|
|
9577
|
+
recipients.filter((recipient) => isMember(project4, recipient))
|
|
9578
|
+
)
|
|
9579
|
+
}
|
|
9580
|
+
};
|
|
9581
|
+
}
|
|
9491
9582
|
if (fact.name === "team/member-worktree-failed" || fact.name === "team/member-worktree-reported") {
|
|
9492
9583
|
const { projectId: projectId3, threadId } = fact.data;
|
|
9493
9584
|
const project4 = readFact(state, projectId3) ?? emptyProject();
|
|
@@ -9651,21 +9742,18 @@ function foldWorkWakes(state, event) {
|
|
|
9651
9742
|
}
|
|
9652
9743
|
case "work.comment-posted": {
|
|
9653
9744
|
const { actor, parentAuthor, mentions, commentId } = work.payload;
|
|
9654
|
-
const
|
|
9655
|
-
|
|
9656
|
-
|
|
9657
|
-
|
|
9658
|
-
...recipient,
|
|
9659
|
-
key: commentWakeKey(commentId, recipient),
|
|
9660
|
-
projectId,
|
|
9661
|
-
workItemId,
|
|
9662
|
-
commentId
|
|
9663
|
-
})).filter(
|
|
9664
|
-
(comment) => !project2.pendingComments.some((pending) => pending.key === comment.key)
|
|
9745
|
+
const recipients = commentRecipients(
|
|
9746
|
+
project2,
|
|
9747
|
+
actor,
|
|
9748
|
+
commentPeople({ parentAuthor, mentions })
|
|
9665
9749
|
);
|
|
9666
9750
|
next = {
|
|
9667
9751
|
...project2,
|
|
9668
|
-
pendingComments:
|
|
9752
|
+
pendingComments: queueCommentWakes(
|
|
9753
|
+
project2,
|
|
9754
|
+
{ projectId, workItemId, commentId },
|
|
9755
|
+
recipients
|
|
9756
|
+
),
|
|
9669
9757
|
items: currentItem === null ? project2.items : {
|
|
9670
9758
|
...project2.items,
|
|
9671
9759
|
[workItemId]: {
|
|
@@ -9789,6 +9877,52 @@ function dueWakes(state) {
|
|
|
9789
9877
|
return due.sort((a, b) => compare2(a.projectId, b.projectId) || compare2(a.key, b.key));
|
|
9790
9878
|
}
|
|
9791
9879
|
|
|
9880
|
+
// src/runtime/commentMentions.ts
|
|
9881
|
+
var COMMENT_MENTION_RETRIES = 2;
|
|
9882
|
+
function startCommentMentionRuntime(ctx, goals) {
|
|
9883
|
+
const handle = async (event) => {
|
|
9884
|
+
const decoded = decodeWorkEvent(event);
|
|
9885
|
+
if (isNone2(decoded) || decoded.value.type !== "work.comment-posted") return;
|
|
9886
|
+
const comment = decoded.value.payload;
|
|
9887
|
+
const { projectId, workItemId, commentId } = comment;
|
|
9888
|
+
const resolve2 = async () => {
|
|
9889
|
+
const goal = await goals.read(projectId);
|
|
9890
|
+
if (goal === null) return;
|
|
9891
|
+
const { comment: posted } = await ctx.work.comments.get({ projectId, commentId });
|
|
9892
|
+
if (posted.body === null) return;
|
|
9893
|
+
const reached = reachedKeys(comment.actor, commentPeople(comment));
|
|
9894
|
+
const recipients = mentionedMembers(posted.body, goal.members).filter(
|
|
9895
|
+
(recipient) => !reached.has(recipientKey(recipient))
|
|
9896
|
+
);
|
|
9897
|
+
if (recipients.length === 0) return;
|
|
9898
|
+
await ctx.events.append({
|
|
9899
|
+
commandId: `team-comment-mentions:${commentId}`,
|
|
9900
|
+
name: COMMENT_MENTIONS_FACT,
|
|
9901
|
+
aggregate: { kind: "project", id: projectId },
|
|
9902
|
+
data: { version: 1, projectId, workItemId, commentId, recipients }
|
|
9903
|
+
});
|
|
9904
|
+
};
|
|
9905
|
+
for (let attempt = 1; ; attempt++) {
|
|
9906
|
+
try {
|
|
9907
|
+
return await resolve2();
|
|
9908
|
+
} catch (error) {
|
|
9909
|
+
if (attempt > COMMENT_MENTION_RETRIES) {
|
|
9910
|
+
ctx.log.warn("team comment mentions failed", {
|
|
9911
|
+
projectId,
|
|
9912
|
+
workItemId,
|
|
9913
|
+
commentId,
|
|
9914
|
+
eventId: event.eventId,
|
|
9915
|
+
attempts: attempt,
|
|
9916
|
+
error
|
|
9917
|
+
});
|
|
9918
|
+
return;
|
|
9919
|
+
}
|
|
9920
|
+
}
|
|
9921
|
+
}
|
|
9922
|
+
};
|
|
9923
|
+
ctx.reactor({ name: "team-comment-mentions", events: ["work.comment-posted"], handle });
|
|
9924
|
+
}
|
|
9925
|
+
|
|
9792
9926
|
// src/runtime/wake.ts
|
|
9793
9927
|
var workLabel = (workItemId, sequenceId) => sequenceId === void 0 ? `the work item with workItemId ${workItemId}` : `work item W${sequenceId} (workItemId ${workItemId})`;
|
|
9794
9928
|
var titled = (workItemId, sequenceId, title) => `${workLabel(workItemId, sequenceId)}${title ? `: ${title}` : ""}`;
|
|
@@ -9936,7 +10070,7 @@ var leadText = (recovery, name, paused) => `Member ${name} (threadId ${recovery.
|
|
|
9936
10070
|
var commentInstructions = (recovery) => recovery.items.map(
|
|
9937
10071
|
(item) => item.lastCommentId === void 0 ? `${itemLabel(item)} has no comments yet. Continue from your existing workspace changes and turn history.` : `For ${itemLabel(item)}, the latest comment is commentId ${item.lastCommentId}. Read work_list_comments with workItemId: "${item.workItemId}" and continue from that comment and your existing workspace changes; do not start over.`
|
|
9938
10072
|
).join("\n");
|
|
9939
|
-
var leadInstructions = (paused = false) => `${paused ? "Their sending is paused; a person needs to click Resume sending. " : ""}You can post a comment
|
|
10073
|
+
var leadInstructions = (paused = false) => `${paused ? "Their sending is paused; a person needs to click Resume sending. " : ""}You can post a comment with work_post_comment, with the member in its mentions parameter, to ask them to continue (the mention wakes them), reassign the work with work_update_item, or report the problem to the person.${paused ? " A comment will wait until the person resumes sending." : ""}`;
|
|
9940
10074
|
var resumeText = (recovery) => `Your previous turn was interrupted by an upstream error (${recovery.category}). Continue ${recovery.items.map(itemLabel).join(", ")} from where you stopped.
|
|
9941
10075
|
${commentInstructions(recovery)}`;
|
|
9942
10076
|
var remindText = (recovery) => `Your turn ended normally, but ${recovery.items.map(itemLabel).join(", ")} ${recovery.items.length === 1 ? "is" : "are"} still in progress. If finished, mark the work done with work_update_item. If blocked, explain where you are stuck on the item with work_post_comment. Otherwise continue from where you stopped; do not start over.
|
|
@@ -11100,6 +11234,7 @@ var index_default = definePlugin({
|
|
|
11100
11234
|
const goals = createGoalService(ctx, rosters);
|
|
11101
11235
|
registerGoalCommands(ctx, goals);
|
|
11102
11236
|
const wakes = startWakeRuntime(ctx, goals);
|
|
11237
|
+
startCommentMentionRuntime(ctx, goals);
|
|
11103
11238
|
registerBoardCommands(ctx, goals, rosters, wakes.state);
|
|
11104
11239
|
registerDetailCommands(ctx, goals);
|
|
11105
11240
|
startRecoveryRuntime(ctx, goals, wakes.state, wakes.worktrees);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ycodium-ai/plugin-team",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3405",
|
|
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.
|
|
37
|
+
"@ycodium-ai/plugin-api": "0.2.3405"
|
|
38
38
|
},
|
|
39
39
|
"ycodium": {
|
|
40
40
|
"plugin": "./dist/index.js",
|