@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.
- package/assets/ui.js +10 -10
- package/dist/index.js +208 -41
- 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
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8236
|
-
};
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
|
|
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
|
|
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:
|
|
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.
|
|
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.
|
|
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",
|