@ycodium-ai/plugin-team 0.2.3252 → 0.2.3265

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.
Files changed (3) hide show
  1. package/assets/ui.js +10 -10
  2. package/dist/index.js +208 -41
  3. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -7168,6 +7168,10 @@ function handbookMessage(key, release) {
7168
7168
  // src/domain/leadBrief.ts
7169
7169
  var PERSON_MESSAGE_RULE = "Send a message to the person only for a product decision you cannot make yourself; put progress, updates and technical problems in work item comments with work_post_comment instead.";
7170
7170
  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.";
7171
+ var MEMBER_NO_STASH_RULE = "Do not use git stash: all worktrees of this repository share one stash stack. 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.";
7172
+ 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.";
7173
+ 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.";
7174
+ 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.";
7171
7175
  var memberStuckRule = (leadThreadId) => {
7172
7176
  const mention = leadThreadId === void 0 ? "" : ` that mentions your lead (mentions: [{ "kind": "agent", "threadId": "${leadThreadId}" }])`;
7173
7177
  const reply = leadThreadId === void 0 ? "" : " Your lead will reply or change the plan.";
@@ -7204,6 +7208,7 @@ function leadBrief(goal, roster) {
7204
7208
  'Then use work_add_relation with type "blocked_by" to record dependencies: the dependent workItemId is blocked by the prerequisite relatedWorkItemId.',
7205
7209
  LEAD_OWN_ITEM_RULE,
7206
7210
  LEAD_SEND_BACK_RULE,
7211
+ LEAD_ACCEPT_RULE,
7207
7212
  LEAD_STUCK_RULE,
7208
7213
  `Only after all dependencies are recorded, assign each item to exactly one member with work_update_item. For local members use assignees: [{ "kind": "agent", "threadId": "<threadId from the table>" }]. Assign work to members with no item in progress first; do not give one member two items while another is idle. An item assigned to a local member who already has work waits in line and wakes them after their current item is finished. Record dependencies before assigning so blocked work waits until it can start. ${LEAD_NO_MEMBER_TASKS}`,
7209
7214
  ...remoteMembers.length === 0 ? [] : [
@@ -7211,6 +7216,7 @@ function leadBrief(goal, roster) {
7211
7216
  "A comment that mentions a remote member, or replies to one of their comments, wakes them on their machine, so you can ask or answer them in work item comments. Once they mark an item done they stop working on it, so to send work back to a remote member you still reopen the item as described above."
7212
7217
  ],
7213
7218
  "Use work_list_items to check progress. When all work is complete, summarize the results here in one or two sentences.",
7219
+ "Members' worktrees and branches remain after the goal ends. After their work is merged into the main line, they can be deleted from the source control panel.",
7214
7220
  PERSON_MESSAGE_RULE,
7215
7221
  "If the work item tools are unavailable, tell the user directly that this project needs the board and project memory switches turned on first."
7216
7222
  ].join("\n\n");
@@ -8190,9 +8196,74 @@ function createRosterService(ctx, options = {}) {
8190
8196
  };
8191
8197
  }
8192
8198
 
8199
+ // src/runtime/wakeQueries.ts
8200
+ function memberWorktreeFor(goals, project2, projectId, recipient) {
8201
+ if (!("threadId" in recipient) || project2 === void 0 || !project2.threads.includes(recipient.threadId) || project2.leadThreads?.includes(recipient.threadId))
8202
+ return;
8203
+ const { threadId } = recipient;
8204
+ const leadThreadId = project2.leadThreads?.[0];
8205
+ if (leadThreadId === void 0) return;
8206
+ return {
8207
+ leadThreadId,
8208
+ failure: project2.worktreeFailures?.[threadId],
8209
+ // 只在要建树或报失败时读目标取名字,叫醒的热路径不多读。
8210
+ name: async () => (await goals.read(projectId))?.members.find((member) => member.threadId === threadId)?.name ?? project2.memberIds?.[threadId] ?? threadId
8211
+ };
8212
+ }
8213
+ var compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
8214
+ var compareWakeItems = (a, b) => a.sequenceId - b.sequenceId || compare(a.workItemId, b.workItemId);
8215
+ var isClosed = (item) => item?.stateGroup === "completed" || item?.stateGroup === "cancelled";
8216
+ var hasOpenBlocker = (project2, workItemId) => (readFact(project2.blockers, workItemId) ?? []).some(
8217
+ (blocker) => !isClosed(readFact(project2.items, blocker))
8218
+ );
8219
+ var isAssignedOpen = (project2, workItemId, threadId) => {
8220
+ const item = readFact(project2.items, workItemId);
8221
+ return item !== null && item !== void 0 && !isClosed(item) && item.assigneeThreads.includes(threadId);
8222
+ };
8223
+ var isWorkable = (project2, workItemId) => !isClosed(readFact(project2.items, workItemId)) && !hasOpenBlocker(project2, workItemId);
8224
+ var isOpenWorkFor = (project2, workItemId, threadId) => isAssignedOpen(project2, workItemId, threadId) && !hasOpenBlocker(project2, workItemId);
8225
+ var isMember = (project2, recipient) => "threadId" in recipient ? project2.threads.includes(recipient.threadId) : project2.remoteMembers.includes(recipient.principalId);
8226
+ var isQueuedUnclaimed = (item, threadId) => item.wakeStatus?.[threadId] === "pending" && !item.claimedBy.includes(threadId);
8227
+ var isDeferredWake = (project2, item, threadId) => project2.threads.includes(threadId) && !project2.leadThreads?.includes(threadId) && item.assigneeThreads.includes(threadId) && isQueuedUnclaimed(item, threadId) && !isClosed(item);
8228
+ function countDeferredWakes(project2, threadId) {
8229
+ let count = 0;
8230
+ for (const workItemId in project2.items) {
8231
+ const item = readFact(project2.items, workItemId);
8232
+ if (item !== null && isDeferredWake(project2, item, threadId)) count++;
8233
+ }
8234
+ return count;
8235
+ }
8236
+
8193
8237
  // src/runtime/deliver.ts
8194
8238
  import * as NodeCrypto from "node:crypto";
8195
8239
  var stableHash = (key) => NodeCrypto.createHash("sha256").update(key).digest("hex").slice(0, 32);
8240
+ var memberWorktreeBranch = (name, threadId) => {
8241
+ const slug = Array.from(
8242
+ name.normalize("NFKC").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "-")
8243
+ ).slice(0, 32).join("").replace(/^-+|-+$/g, "") || "member";
8244
+ return `team/${slug}-${stableHash(threadId).slice(0, 16)}`;
8245
+ };
8246
+ var createMemberWorktreeLane = (state, events) => {
8247
+ const tails = /* @__PURE__ */ new Map();
8248
+ return {
8249
+ state,
8250
+ events,
8251
+ serialize: (threadId, run3) => {
8252
+ const next = (tails.get(threadId) ?? Promise.resolve()).then(run3);
8253
+ const tail = next.then(
8254
+ () => void 0,
8255
+ () => void 0
8256
+ );
8257
+ tails.set(threadId, tail);
8258
+ void tail.then(() => {
8259
+ if (tails.get(threadId) === tail) tails.delete(threadId);
8260
+ });
8261
+ return next;
8262
+ }
8263
+ };
8264
+ };
8265
+ var shortReason = (error) => (error instanceof Error ? error.message : String(error)).replace(/\s+/g, " ").trim().slice(0, 240) || "Worktree preparation failed";
8266
+ var FALLBACK_SUFFIX = ":worktree-fallback";
8196
8267
  async function deliverWake(ctx, input) {
8197
8268
  const { recipient } = input;
8198
8269
  if ("principalId" in recipient) {
@@ -8206,46 +8277,93 @@ async function deliverWake(ctx, input) {
8206
8277
  } else {
8207
8278
  const execution = await ctx.projections.getThreadExecution(recipient.threadId);
8208
8279
  if (execution === null || execution.archivedAt !== null) return false;
8209
- await ctx.threads.turns.start({
8210
- requestId: input.requestId,
8211
- threadId: recipient.threadId,
8280
+ const { threadId } = recipient;
8281
+ const text = typeof input.text === "string" ? input.text : await input.text();
8282
+ const worktree = input.memberWorktree;
8283
+ const turn = {
8284
+ threadId,
8212
8285
  message: {
8213
8286
  messageId: input.messageId,
8214
8287
  role: "user",
8215
- text: typeof input.text === "string" ? input.text : await input.text(),
8288
+ text,
8216
8289
  attachments: []
8217
8290
  },
8218
8291
  modelSelection: execution.modelSelection,
8219
8292
  runtimeMode: execution.runtimeMode,
8220
8293
  interactionMode: execution.interactionMode
8294
+ };
8295
+ const startTurn = (suffix) => ctx.threads.turns.start({
8296
+ ...turn,
8297
+ requestId: `${input.requestId}${suffix}`,
8298
+ message: { ...turn.message, messageId: `${input.messageId}${suffix}` }
8221
8299
  });
8222
- }
8223
- return true;
8224
- }
8300
+ let failure = worktree?.failure;
8301
+ let delivered = false;
8302
+ if (worktree !== void 0 && failure === void 0 && !execution.worktreePath) {
8303
+ const { lane } = worktree;
8304
+ const first = await lane.serialize(threadId, async () => {
8305
+ const current = await ctx.projections.getThreadExecution(threadId);
8306
+ if (current?.worktreePath) return { kind: "registered" };
8307
+ const recorded = (await lane.state.get())[input.projectId]?.worktreeFailures?.[threadId];
8308
+ if (recorded !== void 0) return { kind: "failed", failure: recorded };
8309
+ const branch = memberWorktreeBranch(await worktree.name(), threadId);
8310
+ try {
8311
+ await ctx.threads.turns.start({
8312
+ ...turn,
8313
+ requestId: input.requestId,
8314
+ message: {
8315
+ ...turn.message,
8316
+ text: `${text}
8225
8317
 
8226
- // src/runtime/wakeQueries.ts
8227
- var compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
8228
- var compareWakeItems = (a, b) => a.sequenceId - b.sequenceId || compare(a.workItemId, b.workItemId);
8229
- var isClosed = (item) => item?.stateGroup === "completed" || item?.stateGroup === "cancelled";
8230
- var hasOpenBlocker = (project2, workItemId) => (readFact(project2.blockers, workItemId) ?? []).some(
8231
- (blocker) => !isClosed(readFact(project2.items, blocker))
8232
- );
8233
- var isAssignedOpen = (project2, workItemId, threadId) => {
8234
- const item = readFact(project2.items, workItemId);
8235
- return item !== null && item !== void 0 && !isClosed(item) && item.assigneeThreads.includes(threadId);
8236
- };
8237
- var isWorkable = (project2, workItemId) => !isClosed(readFact(project2.items, workItemId)) && !hasOpenBlocker(project2, workItemId);
8238
- var isOpenWorkFor = (project2, workItemId, threadId) => isAssignedOpen(project2, workItemId, threadId) && !hasOpenBlocker(project2, workItemId);
8239
- var isMember = (project2, recipient) => "threadId" in recipient ? project2.threads.includes(recipient.threadId) : project2.remoteMembers.includes(recipient.principalId);
8240
- var isQueuedUnclaimed = (item, threadId) => item.wakeStatus?.[threadId] === "pending" && !item.claimedBy.includes(threadId);
8241
- var isDeferredWake = (project2, item, threadId) => project2.threads.includes(threadId) && !project2.leadThreads?.includes(threadId) && item.assigneeThreads.includes(threadId) && isQueuedUnclaimed(item, threadId) && !isClosed(item);
8242
- function countDeferredWakes(project2, threadId) {
8243
- let count = 0;
8244
- for (const workItemId in project2.items) {
8245
- const item = readFact(project2.items, workItemId);
8246
- if (item !== null && isDeferredWake(project2, item, threadId)) count++;
8318
+ You are in your own newly created worktree on branch ${branch}. Install dependencies according to the project's instructions before starting work.`
8319
+ },
8320
+ bootstrap: {
8321
+ prepareWorktree: {
8322
+ // bootstrapTurnStart 将它传作 refName;gitDriver/worktrees 用 cwd 下的 HEAD 执行 git worktree add -b。
8323
+ baseBranch: "HEAD",
8324
+ branch,
8325
+ startFromOrigin: false
8326
+ }
8327
+ }
8328
+ });
8329
+ } catch (error) {
8330
+ await startTurn(FALLBACK_SUFFIX);
8331
+ const reason = shortReason(error);
8332
+ const receipt = await lane.events.append({
8333
+ commandId: `team-worktree-failed:${threadId}`,
8334
+ name: "member-worktree-failed",
8335
+ aggregate: { kind: "project", id: input.projectId },
8336
+ data: { version: 1, projectId: input.projectId, threadId, reason }
8337
+ });
8338
+ const persisted = (await lane.state.get({ throughSequence: receipt.sequence }))[input.projectId]?.worktreeFailures?.[threadId];
8339
+ return { kind: "fallback", failure: persisted ?? { reason } };
8340
+ }
8341
+ return { kind: "worktree" };
8342
+ });
8343
+ if (first.kind === "worktree") return true;
8344
+ if (first.kind !== "registered") failure = first.failure;
8345
+ delivered = first.kind === "fallback";
8346
+ }
8347
+ if (!delivered) await startTurn(failure === void 0 ? "" : FALLBACK_SUFFIX);
8348
+ if (worktree !== void 0 && failure !== void 0 && failure.reported !== true) {
8349
+ const reported = await deliverWake(ctx, {
8350
+ projectId: input.projectId,
8351
+ recipient: { threadId: worktree.leadThreadId },
8352
+ requestId: `team-worktree-report:${threadId}`,
8353
+ messageId: `team-worktree-report-msg:${threadId}`,
8354
+ text: `Could not create a worktree for member ${await worktree.name()} (threadId ${threadId}): ${failure.reason}. They will work in the project folder for now.`
8355
+ });
8356
+ if (!reported) throw new Error("Member worktree failure could not be reported to the lead");
8357
+ const receipt = await worktree.lane.events.append({
8358
+ commandId: `team-worktree-reported:${threadId}`,
8359
+ name: "member-worktree-reported",
8360
+ aggregate: { kind: "project", id: input.projectId },
8361
+ data: { version: 1, projectId: input.projectId, threadId }
8362
+ });
8363
+ await worktree.lane.state.get({ throughSequence: receipt.sequence });
8364
+ }
8247
8365
  }
8248
- return count;
8366
+ return true;
8249
8367
  }
8250
8368
 
8251
8369
  // src/runtime/recoveryState.ts
@@ -8576,6 +8694,12 @@ var MemberWokenFact = Struct({
8576
8694
  threadId: optionalKey2(String4),
8577
8695
  principalId: optionalKey2(String4)
8578
8696
  });
8697
+ var MemberWorktreeFailedFact = Struct({
8698
+ version: Literal2(1),
8699
+ projectId: String4,
8700
+ threadId: String4,
8701
+ reason: String4
8702
+ });
8579
8703
  var emptyProject = () => ({
8580
8704
  threads: [],
8581
8705
  leadThreads: [],
@@ -8607,6 +8731,20 @@ var decodeEnvelope2 = decodeUnknownOption2(
8607
8731
  pluginId: Literal2("team"),
8608
8732
  name: Literal2("team/member-woken"),
8609
8733
  data: MemberWokenFact
8734
+ }),
8735
+ Struct({
8736
+ pluginId: Literal2("team"),
8737
+ name: Literal2("team/member-worktree-failed"),
8738
+ data: MemberWorktreeFailedFact
8739
+ }),
8740
+ Struct({
8741
+ pluginId: Literal2("team"),
8742
+ name: Literal2("team/member-worktree-reported"),
8743
+ data: Struct({
8744
+ version: Literal2(1),
8745
+ projectId: String4,
8746
+ threadId: String4
8747
+ })
8610
8748
  })
8611
8749
  ])
8612
8750
  );
@@ -8727,6 +8865,20 @@ function foldWorkWakes(state, event) {
8727
8865
  }
8728
8866
  };
8729
8867
  }
8868
+ if (fact.name === "team/member-worktree-failed" || fact.name === "team/member-worktree-reported") {
8869
+ const { projectId: projectId3, threadId } = fact.data;
8870
+ const project4 = readFact(state, projectId3) ?? emptyProject();
8871
+ const previous = project4.worktreeFailures?.[threadId];
8872
+ const failure = fact.name === "team/member-worktree-failed" ? previous ?? { reason: fact.data.reason } : previous === void 0 ? void 0 : { ...previous, reported: true };
8873
+ if (failure === void 0 || failure === previous) return state;
8874
+ return {
8875
+ ...state,
8876
+ [projectId3]: {
8877
+ ...project4,
8878
+ worktreeFailures: { ...project4.worktreeFailures, [threadId]: failure }
8879
+ }
8880
+ };
8881
+ }
8730
8882
  const { projectId: projectId2, key } = fact.data;
8731
8883
  const project3 = readFact(state, projectId2) ?? emptyProject();
8732
8884
  if (project3.pendingComments.some((comment) => comment.key === key)) {
@@ -9004,12 +9156,12 @@ var itemHeadline = (sequenceId, item) => {
9004
9156
  const parts = [title, descriptionStart(item?.description)].filter((part) => part !== "");
9005
9157
  return parts.length === 0 ? "" : `${parts.join(" ")} `;
9006
9158
  };
9007
- var itemText = (workItemId, sequenceId, item, leadThreadId) => `${itemHeadline(sequenceId, item)}You have ${workLabel(workItemId, sequenceId)}. Read it with work_read_items, passing workItemIds: ["${workItemId}"]. If it is blocked by items that are not done yet, do not start it; you will be told again when they are done. Otherwise claim it with work_claim_item. Before you change anything, search this repository's rules and checks with memory_search and follow what applies. Then work on it. ${MEMBER_CHECKS_RULE} ${memberStuckRule(leadThreadId)} ${handbookMessage("memberBrief")} ${PERSON_MESSAGE_RULE}`;
9159
+ var itemText = (workItemId, sequenceId, item, leadThreadId) => `${itemHeadline(sequenceId, item)}You have ${workLabel(workItemId, sequenceId)}. Read it with work_read_items, passing workItemIds: ["${workItemId}"]. If it is blocked by items that are not done yet, do not start it; you will be told again when they are done. Otherwise claim it with work_claim_item. Before you change anything, search this repository's rules and checks with memory_search and follow what applies. Then work on it. ${MEMBER_READ_RANGES_RULE} ${MEMBER_NO_STASH_RULE} ${MEMBER_ASK_BEFORE_OUTSIDE_RULE} ${MEMBER_CHECKS_RULE} ${memberStuckRule(leadThreadId)} ${handbookMessage("memberBrief")} ${PERSON_MESSAGE_RULE}`;
9008
9160
  var leadItemText = (workItemId, sequenceId, title) => `You have ${titled(workItemId, sequenceId, title)}. You lead this team, so this item is the goal itself: do not claim it or do its work yourself. Read it with work_read_items, passing workItemIds: ["${workItemId}"]. If it has no work items yet, split it as your team brief says. ${LEAD_SPLIT_RULE} ${LEAD_OWN_ITEM_RULE} Record dependencies and assign each item to one member as the brief describes. ${LEAD_NO_MEMBER_TASKS} ${PERSON_MESSAGE_RULE}`;
9009
- var leadUnblockedText = (workItemId, sequenceId, title) => `Everything blocking ${titled(workItemId, sequenceId, title)} is done now. Read those items with work_read_items and check the results. ${LEAD_SEND_BACK_RULE} When all of them are acceptable, mark this item done with work_update_item. ${LEAD_NO_MEMBER_TASKS}`;
9161
+ var leadUnblockedText = (workItemId, sequenceId, title) => `Everything blocking ${titled(workItemId, sequenceId, title)} is done now. Read those items with work_read_items and check the results. ${LEAD_ACCEPT_RULE} ${LEAD_SEND_BACK_RULE} When all of them are acceptable, mark this item done with work_update_item. ${LEAD_NO_MEMBER_TASKS}`;
9010
9162
  var unblockedText = (workItemId, sequenceId, title) => `Everything blocking ${titled(workItemId, sequenceId, title)} is done now. Read those items with work_read_items, finish this one, and mark it done with work_update_item.`;
9011
9163
  var commentText2 = (workItemId, sequenceId) => `There is a new comment for you on ${workLabel(workItemId, sequenceId)}. Read it with work_list_comments, passing workItemId: "${workItemId}", and reply there if needed.`;
9012
- function startWakeRuntime(ctx, options = {}) {
9164
+ function startWakeRuntime(ctx, goals, options = {}) {
9013
9165
  const wakes = ctx.derivedState({
9014
9166
  key: WAKE_STATE_KEY,
9015
9167
  version: WAKE_STATE_VERSION,
@@ -9017,6 +9169,7 @@ function startWakeRuntime(ctx, options = {}) {
9017
9169
  init: () => ({}),
9018
9170
  apply: foldWakes
9019
9171
  });
9172
+ const worktrees = createMemberWorktreeLane(wakes, ctx.events);
9020
9173
  const hash2 = options.hash ?? stableHash;
9021
9174
  let pending = Promise.resolve();
9022
9175
  const readItem = async (projectId, workItemId) => {
@@ -9029,11 +9182,13 @@ function startWakeRuntime(ctx, options = {}) {
9029
9182
  if (!isWakeDue(state, wake)) continue;
9030
9183
  try {
9031
9184
  const id = hash2(wake.key);
9185
+ const worktree = memberWorktreeFor(goals, state[wake.projectId], wake.projectId, wake);
9032
9186
  const delivered = await deliverWake(ctx, {
9033
9187
  projectId: wake.projectId,
9034
9188
  recipient: wake,
9035
9189
  requestId: `team-wake:${id}`,
9036
9190
  messageId: `team-wake-msg:${id}`,
9191
+ memberWorktree: worktree === void 0 ? void 0 : { ...worktree, lane: worktrees },
9037
9192
  text: async () => {
9038
9193
  if ("principalId" in wake || wake.kind === "comment")
9039
9194
  return commentText2(wake.workItemId, wake.sequenceId);
@@ -9081,7 +9236,7 @@ function startWakeRuntime(ctx, options = {}) {
9081
9236
  });
9082
9237
  const ready = flush();
9083
9238
  void ready.catch((error) => ctx.log?.warn("team wake catch-up failed", { error }));
9084
- return { flush, ready, state: wakes };
9239
+ return { flush, ready, state: wakes, worktrees };
9085
9240
  }
9086
9241
 
9087
9242
  // src/runtime/recovery.ts
@@ -9104,7 +9259,7 @@ ${commentInstructions(recovery)}`;
9104
9259
  var stallLeadText = (recovery, name) => `Member ${name} (threadId ${recovery.threadId}) ended this turn normally, but ${recovery.items.map(itemLabel).join(", ")} ${recovery.items.length === 1 ? "is" : "are"} still open. ${deferredText(recovery)}${recovery.alreadyRetried ? "They have already been reminded once to continue this work, with no progress since. " : ""}${recovery.items.filter((item) => item.waitingForReply).map(
9105
9260
  (item) => `Their last action on ${itemLabel(item)} was their own comment (commentId ${item.lastCommentId}); reply to it or arrange another member to help. `
9106
9261
  ).join("")}${leadInstructions()}`;
9107
- function startRecoveryRuntime(ctx, goals, state) {
9262
+ function startRecoveryRuntime(ctx, goals, state, worktrees) {
9108
9263
  let pending = Promise.resolve();
9109
9264
  let disposed = false;
9110
9265
  const execute = async (scope2) => {
@@ -9222,12 +9377,16 @@ function startRecoveryRuntime(ctx, goals, state) {
9222
9377
  await requestPlan(plan);
9223
9378
  }
9224
9379
  }
9380
+ const worktree = memberWorktreeFor(goals, current[recovery.projectId], recovery.projectId, {
9381
+ threadId: plan.recipientThreadId
9382
+ });
9225
9383
  const delivered = await deliverWake(ctx, {
9226
9384
  projectId: recovery.projectId,
9227
9385
  recipient: { threadId: plan.recipientThreadId },
9228
9386
  requestId: `team-recovery:${id}:${plan.action}`,
9229
9387
  messageId: `team-recovery-msg:${id}:${plan.action}`,
9230
- text: plan.text
9388
+ text: plan.text,
9389
+ memberWorktree: worktree === void 0 ? void 0 : { ...worktree, lane: worktrees }
9231
9390
  });
9232
9391
  if (!delivered) continue;
9233
9392
  const receipt = await ctx.events.append({
@@ -9572,7 +9731,7 @@ var terminalFailure = (error) => {
9572
9731
  if (!rejected && !invalid2) return null;
9573
9732
  return error instanceof Error ? error.message : "detail" in error ? String(error.detail) : String(reason);
9574
9733
  };
9575
- function startHandbookRuntime(ctx) {
9734
+ function startHandbookRuntime(ctx, worktrees) {
9576
9735
  const state = ctx.derivedState({
9577
9736
  key: "handbook",
9578
9737
  version: 5,
@@ -9651,16 +9810,24 @@ function startHandbookRuntime(ctx) {
9651
9810
  };
9652
9811
  const remind = async () => {
9653
9812
  const snapshot = await state.get();
9813
+ const wakeState = Object.keys(snapshot.pendingReminders).length === 0 ? void 0 : await worktrees.lane.state.get();
9654
9814
  for (const [key, reminder] of Object.entries(snapshot.pendingReminders)) {
9655
9815
  const { projectId, release, member } = reminder;
9656
9816
  try {
9657
9817
  const id = stableHash(key);
9818
+ const worktree = memberWorktreeFor(
9819
+ worktrees.goals,
9820
+ wakeState?.[projectId],
9821
+ projectId,
9822
+ member.recipient
9823
+ );
9658
9824
  const delivered = await deliverWake(ctx, {
9659
9825
  projectId,
9660
9826
  recipient: member.recipient,
9661
9827
  requestId: `team-handbook:${id}`,
9662
9828
  messageId: `team-handbook-msg:${id}`,
9663
- text: handbookMessage("reminder", release)
9829
+ text: handbookMessage("reminder", release),
9830
+ memberWorktree: worktree === void 0 ? void 0 : { ...worktree, lane: worktrees.lane }
9664
9831
  });
9665
9832
  if (!delivered) continue;
9666
9833
  const previousKey = JSON.stringify([release, member.memberId]);
@@ -9985,7 +10152,7 @@ function registerHandbookPanel(ctx, runtime) {
9985
10152
  // src/index.ts
9986
10153
  var index_default = definePlugin({
9987
10154
  id: "team",
9988
- apiVersion: 29,
10155
+ apiVersion: 31,
9989
10156
  activate(ctx) {
9990
10157
  const rosters = createRosterService(ctx);
9991
10158
  registerRosterCommands(ctx, rosters);
@@ -9993,9 +10160,9 @@ var index_default = definePlugin({
9993
10160
  registerGoalCommands(ctx, goals);
9994
10161
  registerBoardCommands(ctx, goals, rosters);
9995
10162
  registerDetailCommands(ctx, goals);
9996
- const wakes = startWakeRuntime(ctx);
9997
- startRecoveryRuntime(ctx, goals, wakes.state);
9998
- const handbook = startHandbookRuntime(ctx);
10163
+ const wakes = startWakeRuntime(ctx, goals);
10164
+ startRecoveryRuntime(ctx, goals, wakes.state, wakes.worktrees);
10165
+ const handbook = startHandbookRuntime(ctx, { lane: wakes.worktrees, goals });
9999
10166
  registerHandbook(ctx, handbook);
10000
10167
  registerHandbookPanel(ctx, handbook);
10001
10168
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ycodium-ai/plugin-team",
3
- "version": "0.2.3252",
3
+ "version": "0.2.3265",
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.3252"
37
+ "@ycodium-ai/plugin-api": "0.2.3265"
38
38
  },
39
39
  "ycodium": {
40
40
  "plugin": "./dist/index.js",
@@ -46,6 +46,7 @@
46
46
  "commands": [
47
47
  "thread.create",
48
48
  "thread.turn.start",
49
+ "thread.worktree.prepare",
49
50
  "thread.turn-queue.resume",
50
51
  "work.item.create",
51
52
  "work.item.update",