@ycodium-ai/plugin-team 0.2.3244 → 0.2.3252
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 +742 -60
- package/locales/en.json +1 -0
- package/locales/zh-CN.json +1 -0
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1182,8 +1182,8 @@ var succeed = (success) => {
|
|
|
1182
1182
|
};
|
|
1183
1183
|
|
|
1184
1184
|
// ../../node_modules/.pnpm/effect@4.0.0-beta.103_patch_hash=78523ded7c3dfc3efd3cec0247f4960d00eaa3d3c96a2077457694a4d8b453c6/node_modules/effect/dist/Order.js
|
|
1185
|
-
function make(
|
|
1186
|
-
return (self, that) => self === that ? 0 :
|
|
1185
|
+
function make(compare3) {
|
|
1186
|
+
return (self, that) => self === that ? 0 : compare3(self, that);
|
|
1187
1187
|
}
|
|
1188
1188
|
var Number2 = /* @__PURE__ */ make((self, that) => {
|
|
1189
1189
|
if (globalThis.Number.isNaN(self) && globalThis.Number.isNaN(that)) return 0;
|
|
@@ -7065,6 +7065,7 @@ var en_default = {
|
|
|
7065
7065
|
toolkitsOff: "The lead needs the board and project memory. Turn on both switches above.",
|
|
7066
7066
|
toolkitsOn: "The board and project memory are on for this project.",
|
|
7067
7067
|
staleRevision: "The roster was changed elsewhere and has been reloaded.",
|
|
7068
|
+
updatedElsewhere: "The roster was changed elsewhere. Saving now discards your unsaved edits \u2014 copy them somewhere safe first.",
|
|
7068
7069
|
loadFailed: "The roster could not be loaded. Try reloading it.",
|
|
7069
7070
|
saveFailed: "The roster could not be saved. Try again.",
|
|
7070
7071
|
invalidPayload: "Check the roster fields and try again.",
|
|
@@ -7165,7 +7166,14 @@ function handbookMessage(key, release) {
|
|
|
7165
7166
|
}
|
|
7166
7167
|
|
|
7167
7168
|
// src/domain/leadBrief.ts
|
|
7168
|
-
var PERSON_MESSAGE_RULE = "Send a message to the person only
|
|
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
|
+
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 memberStuckRule = (leadThreadId) => {
|
|
7172
|
+
const mention = leadThreadId === void 0 ? "" : ` that mentions your lead (mentions: [{ "kind": "agent", "threadId": "${leadThreadId}" }])`;
|
|
7173
|
+
const reply = leadThreadId === void 0 ? "" : " Your lead will reply or change the plan.";
|
|
7174
|
+
return `If the same error still fails after you have tried to fix it twice, stop and post a work item comment with work_post_comment${mention} saying what fails and what you tried.${reply}`;
|
|
7175
|
+
};
|
|
7176
|
+
var LEAD_STUCK_RULE = "When a member mentions you in a comment because they are stuck, answer in that item's comments: give a direction, split the item, or reassign it.";
|
|
7169
7177
|
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.";
|
|
7170
7178
|
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.";
|
|
7171
7179
|
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.";
|
|
@@ -7196,7 +7204,8 @@ function leadBrief(goal, roster) {
|
|
|
7196
7204
|
'Then use work_add_relation with type "blocked_by" to record dependencies: the dependent workItemId is blocked by the prerequisite relatedWorkItemId.',
|
|
7197
7205
|
LEAD_OWN_ITEM_RULE,
|
|
7198
7206
|
LEAD_SEND_BACK_RULE,
|
|
7199
|
-
|
|
7207
|
+
LEAD_STUCK_RULE,
|
|
7208
|
+
`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}`,
|
|
7200
7209
|
...remoteMembers.length === 0 ? [] : [
|
|
7201
7210
|
'Remote members are on another machine. To give them work, assign the work item to them with work_update_item and assignees: [{ "kind": "remote-agent", "principalId": "<principalId from the table>" }]; they pick it up from their own machine. Do not create conversations, start turns, or send wake-up signals for them on this machine.',
|
|
7202
7211
|
"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."
|
|
@@ -7209,6 +7218,7 @@ function leadBrief(goal, roster) {
|
|
|
7209
7218
|
|
|
7210
7219
|
// src/domain/goals.ts
|
|
7211
7220
|
var REQUEST_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
|
|
7221
|
+
var isTeamRequestId = (requestId) => REQUEST_ID_PATTERN.test(requestId);
|
|
7212
7222
|
var GOAL_MAX_LENGTH = 4e3;
|
|
7213
7223
|
var goalText = (raw) => {
|
|
7214
7224
|
const text = raw.trim();
|
|
@@ -7324,7 +7334,7 @@ function createGoalService(ctx, rosterService, options = {}) {
|
|
|
7324
7334
|
const input = decoded.value;
|
|
7325
7335
|
const text = goalText(input.goal);
|
|
7326
7336
|
if (text === null) return rejectGoal("invalid-goal");
|
|
7327
|
-
if (!
|
|
7337
|
+
if (!isTeamRequestId(input.requestId)) return rejectGoal("invalid-request-id");
|
|
7328
7338
|
const roster = await rosterService.read(input.projectId);
|
|
7329
7339
|
if (roster === null) return rejectGoal("no-roster");
|
|
7330
7340
|
const project2 = await ctx.projects.get({ projectId: input.projectId });
|
|
@@ -8213,9 +8223,342 @@ async function deliverWake(ctx, input) {
|
|
|
8213
8223
|
return true;
|
|
8214
8224
|
}
|
|
8215
8225
|
|
|
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++;
|
|
8247
|
+
}
|
|
8248
|
+
return count;
|
|
8249
|
+
}
|
|
8250
|
+
|
|
8251
|
+
// src/runtime/recoveryState.ts
|
|
8252
|
+
var RecoveryPlan = Struct({
|
|
8253
|
+
action: Literals(["resume", "lead", "remind", "stall-lead"]),
|
|
8254
|
+
recipientThreadId: String4,
|
|
8255
|
+
text: String4,
|
|
8256
|
+
workItemIds: ArraySchema(String4),
|
|
8257
|
+
resumeQueue: optionalKey2(Literal2(true))
|
|
8258
|
+
});
|
|
8259
|
+
var REQUESTED = "team/member-recovery-requested";
|
|
8260
|
+
var DELIVERED = "team/member-recovery-delivered";
|
|
8261
|
+
var decodeRecoveryFact = decodeUnknownOption2(
|
|
8262
|
+
Struct({
|
|
8263
|
+
pluginId: Literal2("team"),
|
|
8264
|
+
name: Literals([REQUESTED, DELIVERED])
|
|
8265
|
+
})
|
|
8266
|
+
);
|
|
8267
|
+
var isRecoveryFact = (event) => event.type === "plugin.event-appended" && isSome2(decodeRecoveryFact(event.payload));
|
|
8268
|
+
var decodeGoal = decodeUnknownOption2(
|
|
8269
|
+
Struct({
|
|
8270
|
+
pluginId: Literal2("team"),
|
|
8271
|
+
name: Literal2("team/goal-started"),
|
|
8272
|
+
data: GoalStartedFact
|
|
8273
|
+
})
|
|
8274
|
+
);
|
|
8275
|
+
var decodeDelivered = decodeUnknownOption2(
|
|
8276
|
+
Struct({
|
|
8277
|
+
pluginId: Literal2("team"),
|
|
8278
|
+
name: Literal2(DELIVERED),
|
|
8279
|
+
data: Struct({
|
|
8280
|
+
version: Literal2(1),
|
|
8281
|
+
projectId: String4,
|
|
8282
|
+
threadId: String4,
|
|
8283
|
+
turnId: String4,
|
|
8284
|
+
failureSequence: Number5,
|
|
8285
|
+
action: RecoveryPlan.fields.action,
|
|
8286
|
+
workItemIds: ArraySchema(String4)
|
|
8287
|
+
})
|
|
8288
|
+
})
|
|
8289
|
+
);
|
|
8290
|
+
var decodeRequested = decodeUnknownOption2(
|
|
8291
|
+
Struct({
|
|
8292
|
+
pluginId: Literal2("team"),
|
|
8293
|
+
name: Literal2(REQUESTED),
|
|
8294
|
+
data: Struct({
|
|
8295
|
+
version: Literal2(1),
|
|
8296
|
+
projectId: String4,
|
|
8297
|
+
threadId: String4,
|
|
8298
|
+
turnId: String4,
|
|
8299
|
+
failureSequence: Number5,
|
|
8300
|
+
plan: RecoveryPlan
|
|
8301
|
+
})
|
|
8302
|
+
})
|
|
8303
|
+
);
|
|
8304
|
+
var decodeSession = decodeUnknownOption2(
|
|
8305
|
+
Struct({
|
|
8306
|
+
threadId: String4,
|
|
8307
|
+
session: Struct({
|
|
8308
|
+
status: String4,
|
|
8309
|
+
activeTurnId: NullOr(String4),
|
|
8310
|
+
failureCategory: optionalKey2(String4)
|
|
8311
|
+
})
|
|
8312
|
+
})
|
|
8313
|
+
);
|
|
8314
|
+
var decodeWork = decodeUnknownOption2(
|
|
8315
|
+
Struct({
|
|
8316
|
+
projectId: String4,
|
|
8317
|
+
workItemId: String4,
|
|
8318
|
+
actor: optionalKey2(
|
|
8319
|
+
Struct({ kind: String4, threadId: optionalKey2(String4) })
|
|
8320
|
+
)
|
|
8321
|
+
})
|
|
8322
|
+
);
|
|
8323
|
+
function recoveryEventScopeOf(event) {
|
|
8324
|
+
if (event.aggregateKind === "project") return { kind: "project", projectId: event.aggregateId };
|
|
8325
|
+
if (event.aggregateKind === "thread") return { kind: "thread", threadId: event.aggregateId };
|
|
8326
|
+
}
|
|
8327
|
+
function foldRecovery(state, event, previous) {
|
|
8328
|
+
if (event.type === "plugin.event-appended") {
|
|
8329
|
+
const goalFact = decodeGoal(event.payload);
|
|
8330
|
+
if (isSome2(goalFact)) {
|
|
8331
|
+
const { goal } = goalFact.value.data;
|
|
8332
|
+
const project2 = readFact(state, goal.projectId);
|
|
8333
|
+
if (project2 === null) return state;
|
|
8334
|
+
const recovery = {};
|
|
8335
|
+
for (const threadId of project2.threads) {
|
|
8336
|
+
if ((project2.leadThreads ?? []).includes(threadId)) continue;
|
|
8337
|
+
recovery[threadId] = {
|
|
8338
|
+
turnId: null,
|
|
8339
|
+
status: null,
|
|
8340
|
+
retriedItemIds: [],
|
|
8341
|
+
handledTurnIds: [],
|
|
8342
|
+
pending: null
|
|
8343
|
+
};
|
|
8344
|
+
}
|
|
8345
|
+
return { ...state, [goal.projectId]: { ...project2, recovery } };
|
|
8346
|
+
}
|
|
8347
|
+
const requested = decodeRequested(event.payload);
|
|
8348
|
+
if (isSome2(requested)) {
|
|
8349
|
+
const fact = requested.value.data;
|
|
8350
|
+
const project2 = readFact(state, fact.projectId);
|
|
8351
|
+
const member = project2?.recovery?.[fact.threadId];
|
|
8352
|
+
if (project2 === null || member === void 0 || member.pending?.turnId !== fact.turnId)
|
|
8353
|
+
return state;
|
|
8354
|
+
const retriedItemIds = fact.plan.action === "resume" ? [.../* @__PURE__ */ new Set([...member.retriedItemIds, ...fact.plan.workItemIds])].filter(
|
|
8355
|
+
(id) => isAssignedOpen(project2, id, fact.threadId)
|
|
8356
|
+
) : member.retriedItemIds;
|
|
8357
|
+
return {
|
|
8358
|
+
...state,
|
|
8359
|
+
[fact.projectId]: {
|
|
8360
|
+
...project2,
|
|
8361
|
+
recovery: {
|
|
8362
|
+
...project2.recovery,
|
|
8363
|
+
[fact.threadId]: {
|
|
8364
|
+
...member,
|
|
8365
|
+
retriedItemIds,
|
|
8366
|
+
...fact.plan.action === "resume" ? {
|
|
8367
|
+
unprogressedRetryItemIds: [
|
|
8368
|
+
.../* @__PURE__ */ new Set([
|
|
8369
|
+
...member.unprogressedRetryItemIds ?? [],
|
|
8370
|
+
...fact.plan.workItemIds
|
|
8371
|
+
])
|
|
8372
|
+
]
|
|
8373
|
+
} : {},
|
|
8374
|
+
...fact.plan.action === "remind" ? {
|
|
8375
|
+
remindedItemIds: [
|
|
8376
|
+
.../* @__PURE__ */ new Set([...member.remindedItemIds ?? [], ...fact.plan.workItemIds])
|
|
8377
|
+
]
|
|
8378
|
+
} : {},
|
|
8379
|
+
...fact.plan.action === "stall-lead" ? {
|
|
8380
|
+
escalatedItemIds: [
|
|
8381
|
+
.../* @__PURE__ */ new Set([...member.escalatedItemIds ?? [], ...fact.plan.workItemIds])
|
|
8382
|
+
]
|
|
8383
|
+
} : {},
|
|
8384
|
+
pending: { ...member.pending, plan: fact.plan }
|
|
8385
|
+
}
|
|
8386
|
+
}
|
|
8387
|
+
}
|
|
8388
|
+
};
|
|
8389
|
+
}
|
|
8390
|
+
const delivered = decodeDelivered(event.payload);
|
|
8391
|
+
if (isSome2(delivered)) {
|
|
8392
|
+
const fact = delivered.value.data;
|
|
8393
|
+
const project2 = readFact(state, fact.projectId);
|
|
8394
|
+
const member = project2?.recovery?.[fact.threadId];
|
|
8395
|
+
if (project2 === null || member === void 0 || member.handledTurnIds.includes(fact.turnId))
|
|
8396
|
+
return state;
|
|
8397
|
+
return {
|
|
8398
|
+
...state,
|
|
8399
|
+
[fact.projectId]: {
|
|
8400
|
+
...project2,
|
|
8401
|
+
recovery: {
|
|
8402
|
+
...project2.recovery,
|
|
8403
|
+
[fact.threadId]: {
|
|
8404
|
+
...member,
|
|
8405
|
+
handledTurnIds: [...member.handledTurnIds, fact.turnId],
|
|
8406
|
+
pending: member.pending?.turnId === fact.turnId ? null : member.pending
|
|
8407
|
+
}
|
|
8408
|
+
}
|
|
8409
|
+
}
|
|
8410
|
+
};
|
|
8411
|
+
}
|
|
8412
|
+
}
|
|
8413
|
+
if (event.type === "thread.session-set") {
|
|
8414
|
+
const decoded = decodeSession(event.payload);
|
|
8415
|
+
if (isNone2(decoded)) return state;
|
|
8416
|
+
const { threadId, session } = decoded.value;
|
|
8417
|
+
for (const [projectId, project2] of Object.entries(state)) {
|
|
8418
|
+
const member = project2.recovery?.[threadId];
|
|
8419
|
+
if (member === void 0 || !isMember(project2, { threadId })) continue;
|
|
8420
|
+
const turnId = session.activeTurnId ?? member.turnId;
|
|
8421
|
+
if (session.status !== "running" && turnId !== null && member.handledTurnIds.includes(turnId))
|
|
8422
|
+
return state;
|
|
8423
|
+
const succeeded = (session.status === "ready" || session.status === "idle") && member.status === "running";
|
|
8424
|
+
const failed = session.status === "error" && turnId !== null;
|
|
8425
|
+
if (member.status === session.status && member.turnId === turnId && !failed) return state;
|
|
8426
|
+
const hasStarted = Object.values(project2.items).some(
|
|
8427
|
+
(item) => item.stateGroup === "started" && item.assigneeThreads.includes(threadId)
|
|
8428
|
+
);
|
|
8429
|
+
const hasOpenWork = Object.keys(project2.items).some(
|
|
8430
|
+
(id) => isOpenWorkFor(project2, id, threadId)
|
|
8431
|
+
);
|
|
8432
|
+
const queuedContinuation = member.pending?.plan?.resumeQueue === true && !failed;
|
|
8433
|
+
const pending = queuedContinuation ? member.pending : failed && hasOpenWork ? member.pending?.turnId === turnId && member.pending.kind !== "stall" ? { ...member.pending, category: session.failureCategory ?? member.pending.category } : { turnId, category: session.failureCategory ?? "unknown", sequence: event.sequence } : succeeded && turnId !== null && hasStarted ? {
|
|
8434
|
+
turnId,
|
|
8435
|
+
category: "normal-end",
|
|
8436
|
+
kind: "stall",
|
|
8437
|
+
sequence: event.sequence,
|
|
8438
|
+
continuedItemIds: member.unprogressedRetryItemIds ?? []
|
|
8439
|
+
} : session.status === "running" || succeeded ? null : member.pending;
|
|
8440
|
+
return {
|
|
8441
|
+
...state,
|
|
8442
|
+
[projectId]: {
|
|
8443
|
+
...project2,
|
|
8444
|
+
recovery: {
|
|
8445
|
+
...project2.recovery,
|
|
8446
|
+
[threadId]: {
|
|
8447
|
+
...member,
|
|
8448
|
+
turnId,
|
|
8449
|
+
status: session.status,
|
|
8450
|
+
retriedItemIds: succeeded ? [] : member.retriedItemIds,
|
|
8451
|
+
...succeeded ? { unprogressedRetryItemIds: [] } : {},
|
|
8452
|
+
handledTurnIds: failed && !hasOpenWork ? [...member.handledTurnIds, turnId] : session.status === "running" && member.pending?.plan !== void 0 && !queuedContinuation ? [.../* @__PURE__ */ new Set([...member.handledTurnIds, member.pending.turnId])] : member.handledTurnIds,
|
|
8453
|
+
pending
|
|
8454
|
+
}
|
|
8455
|
+
}
|
|
8456
|
+
}
|
|
8457
|
+
};
|
|
8458
|
+
}
|
|
8459
|
+
}
|
|
8460
|
+
if (event.type === "work.item-updated" || event.type === "work.item-claimed" || event.type === "work.comment-posted") {
|
|
8461
|
+
const decoded = decodeWork(event.payload);
|
|
8462
|
+
if (isNone2(decoded)) return state;
|
|
8463
|
+
const work = decoded.value;
|
|
8464
|
+
const project2 = readFact(state, work.projectId);
|
|
8465
|
+
if (project2 === null) return state;
|
|
8466
|
+
const members = Object.entries(project2.recovery ?? {}).filter(
|
|
8467
|
+
([, member]) => member.pending !== null || member.retriedItemIds.length > 0 || (member.remindedItemIds?.length ?? 0) > 0 || (member.escalatedItemIds?.length ?? 0) > 0 || (member.unprogressedRetryItemIds?.length ?? 0) > 0
|
|
8468
|
+
);
|
|
8469
|
+
if (members.length === 0) return state;
|
|
8470
|
+
const pendingThreads = new Set(
|
|
8471
|
+
members.filter(([, member]) => member.pending !== null).map(([threadId]) => threadId)
|
|
8472
|
+
);
|
|
8473
|
+
const startedThreads = /* @__PURE__ */ new Set();
|
|
8474
|
+
const openWorkThreads = /* @__PURE__ */ new Set();
|
|
8475
|
+
if (pendingThreads.size > 0) {
|
|
8476
|
+
for (const [workItemId, item] of Object.entries(project2.items)) {
|
|
8477
|
+
const pendingAssignees = item.assigneeThreads.filter((id) => pendingThreads.has(id));
|
|
8478
|
+
if (pendingAssignees.length === 0) continue;
|
|
8479
|
+
const workable = isWorkable(project2, workItemId);
|
|
8480
|
+
for (const threadId of pendingAssignees) {
|
|
8481
|
+
if (item.stateGroup === "started") startedThreads.add(threadId);
|
|
8482
|
+
if (workable) openWorkThreads.add(threadId);
|
|
8483
|
+
}
|
|
8484
|
+
}
|
|
8485
|
+
}
|
|
8486
|
+
const before = previous[work.projectId]?.items[work.workItemId];
|
|
8487
|
+
const after = project2.items[work.workItemId];
|
|
8488
|
+
const recovery = { ...project2.recovery };
|
|
8489
|
+
let changed = false;
|
|
8490
|
+
for (const [threadId, member] of members) {
|
|
8491
|
+
const progress = event.type === "work.comment-posted" ? work.actor !== void 0 && !(work.actor.kind === "agent" && work.actor.threadId === threadId) : before?.stateGroup !== after?.stateGroup || before?.stateId !== after?.stateId;
|
|
8492
|
+
const assignedOpen = (id) => isAssignedOpen(project2, id, threadId);
|
|
8493
|
+
const retain = (id) => assignedOpen(id) && !(progress && id === work.workItemId);
|
|
8494
|
+
const remindedItemIds = (member.remindedItemIds ?? []).filter(retain);
|
|
8495
|
+
const escalatedItemIds = (member.escalatedItemIds ?? []).filter(retain);
|
|
8496
|
+
const unprogressedRetryItemIds = (member.unprogressedRetryItemIds ?? []).filter(retain);
|
|
8497
|
+
const retriedItemIds = member.retriedItemIds.filter(assignedOpen);
|
|
8498
|
+
const keepsPending = (member.pending?.kind === "stall" ? startedThreads : openWorkThreads).has(threadId);
|
|
8499
|
+
let pending = keepsPending ? member.pending : null;
|
|
8500
|
+
if (pending?.continuedItemIds !== void 0) {
|
|
8501
|
+
const continuedItemIds = pending.continuedItemIds.filter(retain);
|
|
8502
|
+
if (continuedItemIds.length !== pending.continuedItemIds.length)
|
|
8503
|
+
pending = { ...pending, continuedItemIds };
|
|
8504
|
+
}
|
|
8505
|
+
if (retriedItemIds.length === member.retriedItemIds.length && pending === member.pending && remindedItemIds.length === (member.remindedItemIds?.length ?? 0) && escalatedItemIds.length === (member.escalatedItemIds?.length ?? 0) && unprogressedRetryItemIds.length === (member.unprogressedRetryItemIds?.length ?? 0))
|
|
8506
|
+
continue;
|
|
8507
|
+
changed = true;
|
|
8508
|
+
recovery[threadId] = {
|
|
8509
|
+
...member,
|
|
8510
|
+
retriedItemIds,
|
|
8511
|
+
remindedItemIds,
|
|
8512
|
+
escalatedItemIds,
|
|
8513
|
+
unprogressedRetryItemIds,
|
|
8514
|
+
pending,
|
|
8515
|
+
handledTurnIds: pending === null && member.pending !== null ? [.../* @__PURE__ */ new Set([...member.handledTurnIds, member.pending.turnId])] : member.handledTurnIds
|
|
8516
|
+
};
|
|
8517
|
+
}
|
|
8518
|
+
return changed ? { ...state, [work.projectId]: { ...project2, recovery } } : state;
|
|
8519
|
+
}
|
|
8520
|
+
return state;
|
|
8521
|
+
}
|
|
8522
|
+
function dueRecoveries(state, changedThreadId) {
|
|
8523
|
+
return Object.entries(state).flatMap(
|
|
8524
|
+
([projectId, project2]) => Object.entries(project2.recovery ?? {}).flatMap(([threadId, member]) => {
|
|
8525
|
+
const memberId = project2.memberIds?.[threadId];
|
|
8526
|
+
const leadThreadId = project2.leadThreads?.[0];
|
|
8527
|
+
if (member.pending === null || changedThreadId !== void 0 && threadId !== changedThreadId && member.pending.plan?.recipientThreadId !== changedThreadId || memberId === void 0 || leadThreadId === void 0 || threadId === leadThreadId || !isMember(project2, { threadId }))
|
|
8528
|
+
return [];
|
|
8529
|
+
const stalled = member.pending.kind === "stall";
|
|
8530
|
+
if (member.handledTurnIds.includes(member.pending.turnId)) return [];
|
|
8531
|
+
const items = Object.entries(project2.items).filter(
|
|
8532
|
+
([workItemId, item]) => (stalled ? item.stateGroup === "started" && item.assigneeThreads.includes(threadId) : isOpenWorkFor(project2, workItemId, threadId) && !isQueuedUnclaimed(item, threadId)) && (!stalled || (member.pending?.plan?.workItemIds.includes(workItemId) || !(member.escalatedItemIds ?? []).includes(workItemId)) && !hasOpenBlocker(project2, workItemId))
|
|
8533
|
+
).map(([workItemId, item]) => ({
|
|
8534
|
+
workItemId,
|
|
8535
|
+
sequenceId: item.sequenceId,
|
|
8536
|
+
lastCommentId: item.lastCommentId,
|
|
8537
|
+
waitingForReply: item.lastActionCommentThreadId === threadId
|
|
8538
|
+
})).sort((a, b) => a.sequenceId - b.sequenceId);
|
|
8539
|
+
const deferredWakeCount = countDeferredWakes(project2, threadId);
|
|
8540
|
+
return items.length === 0 ? [] : [
|
|
8541
|
+
{
|
|
8542
|
+
...member.pending,
|
|
8543
|
+
projectId,
|
|
8544
|
+
threadId,
|
|
8545
|
+
memberId,
|
|
8546
|
+
leadThreadId,
|
|
8547
|
+
key: JSON.stringify([threadId, member.pending.turnId]),
|
|
8548
|
+
alreadyRetried: items.some(
|
|
8549
|
+
(item) => member.retriedItemIds.includes(item.workItemId) || (member.remindedItemIds ?? []).includes(item.workItemId) || (member.escalatedItemIds ?? []).includes(item.workItemId) || (member.pending?.continuedItemIds ?? []).includes(item.workItemId)
|
|
8550
|
+
),
|
|
8551
|
+
items,
|
|
8552
|
+
...deferredWakeCount > 0 ? { deferredWakeCount } : {}
|
|
8553
|
+
}
|
|
8554
|
+
];
|
|
8555
|
+
})
|
|
8556
|
+
);
|
|
8557
|
+
}
|
|
8558
|
+
|
|
8216
8559
|
// src/runtime/wakeState.ts
|
|
8217
8560
|
var WAKE_STATE_KEY = "wakes";
|
|
8218
|
-
var WAKE_STATE_VERSION =
|
|
8561
|
+
var WAKE_STATE_VERSION = 7;
|
|
8219
8562
|
var WAKE_EVENTS = [
|
|
8220
8563
|
"work.item-created",
|
|
8221
8564
|
"work.item-updated",
|
|
@@ -8223,7 +8566,8 @@ var WAKE_EVENTS = [
|
|
|
8223
8566
|
"work.relation-added",
|
|
8224
8567
|
"work.relation-removed",
|
|
8225
8568
|
"work.comment-posted",
|
|
8226
|
-
"plugin.event-appended"
|
|
8569
|
+
"plugin.event-appended",
|
|
8570
|
+
"thread.session-set"
|
|
8227
8571
|
];
|
|
8228
8572
|
var MemberWokenFact = Struct({
|
|
8229
8573
|
version: Literal2(1),
|
|
@@ -8245,9 +8589,6 @@ var emptyProject = () => ({
|
|
|
8245
8589
|
});
|
|
8246
8590
|
var itemWakeKey = (workItemId, threadId, generation) => generation === 0 ? `item:${workItemId}:${threadId}` : `item:${workItemId}:${threadId}#${generation}`;
|
|
8247
8591
|
var unblockedWakeKey = (workItemId, threadId, generation) => `unblocked:${workItemId}:${threadId}#${generation}`;
|
|
8248
|
-
var hasOpenBlocker = (project2, workItemId) => (readFact(project2.blockers, workItemId) ?? []).some(
|
|
8249
|
-
(blocker) => readFact(project2.items, blocker)?.closed !== true
|
|
8250
|
-
);
|
|
8251
8592
|
var markBlockedAfterClaim = (project2, workItemId) => ({
|
|
8252
8593
|
...project2,
|
|
8253
8594
|
blockedAfterClaim: {
|
|
@@ -8279,8 +8620,10 @@ var Actor = Union2([
|
|
|
8279
8620
|
Struct({ kind: Literal2("plugin"), pluginId: String4 }),
|
|
8280
8621
|
Struct({ kind: Literals(["local-cli", "seat", "system"]) })
|
|
8281
8622
|
]);
|
|
8282
|
-
var StateRef = Struct({
|
|
8283
|
-
|
|
8623
|
+
var StateRef = Struct({
|
|
8624
|
+
group: String4,
|
|
8625
|
+
stateId: optionalKey2(String4)
|
|
8626
|
+
});
|
|
8284
8627
|
var itemFields = { projectId: String4, workItemId: String4 };
|
|
8285
8628
|
var relationFields = {
|
|
8286
8629
|
...itemFields,
|
|
@@ -8337,7 +8680,6 @@ var decodeWorkEvent = decodeUnknownOption2(
|
|
|
8337
8680
|
var agentThreads = (principals) => [
|
|
8338
8681
|
...new Set(principals.flatMap((p) => p.kind === "agent" ? [p.threadId] : []))
|
|
8339
8682
|
];
|
|
8340
|
-
var isMember = (project2, recipient) => "threadId" in recipient ? project2.threads.includes(recipient.threadId) : project2.remoteMembers.includes(recipient.principalId);
|
|
8341
8683
|
var commentRecipients = (project2, actor, people) => {
|
|
8342
8684
|
const recipients = /* @__PURE__ */ new Map();
|
|
8343
8685
|
for (const person of people) {
|
|
@@ -8349,7 +8691,7 @@ var commentRecipients = (project2, actor, people) => {
|
|
|
8349
8691
|
return [...recipients.values()].filter((recipient) => isMember(project2, recipient));
|
|
8350
8692
|
};
|
|
8351
8693
|
var commentWakeKey = (commentId, recipient) => "threadId" in recipient ? `comment:${commentId}:${recipient.threadId}` : `comment:${commentId}:principal:${recipient.principalId}`;
|
|
8352
|
-
function
|
|
8694
|
+
function foldWorkWakes(state, event) {
|
|
8353
8695
|
if (event.type === "plugin.event-appended") {
|
|
8354
8696
|
const decoded2 = decodeEnvelope2(event.payload);
|
|
8355
8697
|
if (isNone2(decoded2)) return state;
|
|
@@ -8367,7 +8709,16 @@ function foldWakes(state, event) {
|
|
|
8367
8709
|
)
|
|
8368
8710
|
];
|
|
8369
8711
|
const leadThreads = goal.threads.filter((thread) => thread.memberId === goal.leadMemberId).map((thread) => thread.threadId);
|
|
8370
|
-
const
|
|
8712
|
+
const memberIds = Object.fromEntries(
|
|
8713
|
+
goal.threads.map((member) => [member.threadId, member.memberId])
|
|
8714
|
+
);
|
|
8715
|
+
const next2 = {
|
|
8716
|
+
...project4,
|
|
8717
|
+
memberIds,
|
|
8718
|
+
threads,
|
|
8719
|
+
leadThreads,
|
|
8720
|
+
remoteMembers
|
|
8721
|
+
};
|
|
8371
8722
|
return {
|
|
8372
8723
|
...state,
|
|
8373
8724
|
[goal.projectId]: {
|
|
@@ -8388,7 +8739,18 @@ function foldWakes(state, event) {
|
|
|
8388
8739
|
};
|
|
8389
8740
|
}
|
|
8390
8741
|
if (project3.woken.includes(key)) return state;
|
|
8391
|
-
|
|
8742
|
+
const items = { ...project3.items };
|
|
8743
|
+
for (const [workItemId2, item] of Object.entries(items)) {
|
|
8744
|
+
for (const threadId of item.assigneeThreads) {
|
|
8745
|
+
if (item.wakeStatus?.[threadId] !== "released" && isWorkable(project3, workItemId2) && key === itemWakeKey(
|
|
8746
|
+
workItemId2,
|
|
8747
|
+
threadId,
|
|
8748
|
+
readFact(project3.rearms, `${workItemId2}:${threadId}`) ?? 0
|
|
8749
|
+
))
|
|
8750
|
+
items[workItemId2] = { ...item, wakeStatus: { ...item.wakeStatus, [threadId]: "active" } };
|
|
8751
|
+
}
|
|
8752
|
+
}
|
|
8753
|
+
return { ...state, [projectId2]: { ...project3, items, woken: [...project3.woken, key] } };
|
|
8392
8754
|
}
|
|
8393
8755
|
const decoded = decodeWorkEvent(event);
|
|
8394
8756
|
if (isNone2(decoded)) return state;
|
|
@@ -8406,8 +8768,15 @@ function foldWakes(state, event) {
|
|
|
8406
8768
|
[workItemId]: {
|
|
8407
8769
|
sequenceId: work.payload.sequenceId,
|
|
8408
8770
|
assigneeThreads: agentThreads(work.payload.assignees),
|
|
8771
|
+
wakeStatus: Object.fromEntries(
|
|
8772
|
+
agentThreads(work.payload.assignees).map((threadId) => [
|
|
8773
|
+
threadId,
|
|
8774
|
+
"pending"
|
|
8775
|
+
])
|
|
8776
|
+
),
|
|
8409
8777
|
claimedBy: [],
|
|
8410
|
-
|
|
8778
|
+
stateGroup: work.payload.state.group,
|
|
8779
|
+
...work.payload.state.stateId === void 0 ? {} : { stateId: work.payload.state.stateId }
|
|
8411
8780
|
}
|
|
8412
8781
|
}
|
|
8413
8782
|
};
|
|
@@ -8421,6 +8790,22 @@ function foldWakes(state, event) {
|
|
|
8421
8790
|
...agentThreads([work.payload.assignee])
|
|
8422
8791
|
])
|
|
8423
8792
|
];
|
|
8793
|
+
const wakeStatus = { ...currentItem.wakeStatus };
|
|
8794
|
+
for (const threadId of currentItem.assigneeThreads)
|
|
8795
|
+
if (!assigneeThreads.includes(threadId)) wakeStatus[threadId] = "released";
|
|
8796
|
+
for (const threadId of assigneeThreads) {
|
|
8797
|
+
if (!currentItem.assigneeThreads.includes(threadId)) {
|
|
8798
|
+
const generation = readFact(project2.rearms, `${workItemId}:${threadId}`) ?? 0;
|
|
8799
|
+
wakeStatus[threadId] = project2.woken.includes(
|
|
8800
|
+
itemWakeKey(workItemId, threadId, generation)
|
|
8801
|
+
) ? "released" : "pending";
|
|
8802
|
+
}
|
|
8803
|
+
if (wakeStatus[threadId] === "active" && (isClosed({ stateGroup: work.payload.state?.group ?? currentItem.stateGroup }) || work.payload.state?.group === "unstarted" || work.payload.state?.group === "backlog"))
|
|
8804
|
+
wakeStatus[threadId] = "released";
|
|
8805
|
+
}
|
|
8806
|
+
if (work.type === "work.item-claimed" && hasOpenBlocker(project2, workItemId))
|
|
8807
|
+
for (const threadId of agentThreads([work.payload.assignee]))
|
|
8808
|
+
wakeStatus[threadId] = "released";
|
|
8424
8809
|
next = {
|
|
8425
8810
|
...project2,
|
|
8426
8811
|
items: {
|
|
@@ -8428,8 +8813,11 @@ function foldWakes(state, event) {
|
|
|
8428
8813
|
[workItemId]: {
|
|
8429
8814
|
...currentItem,
|
|
8430
8815
|
assigneeThreads,
|
|
8816
|
+
wakeStatus,
|
|
8431
8817
|
claimedBy: work.type === "work.item-claimed" ? [.../* @__PURE__ */ new Set([...currentItem.claimedBy, ...agentThreads([work.payload.assignee])])] : currentItem.claimedBy,
|
|
8432
|
-
|
|
8818
|
+
...work.payload.state === void 0 ? {} : { stateGroup: work.payload.state.group },
|
|
8819
|
+
...work.payload.state?.stateId === void 0 ? {} : { stateId: work.payload.state.stateId },
|
|
8820
|
+
lastActionCommentThreadId: null
|
|
8433
8821
|
}
|
|
8434
8822
|
}
|
|
8435
8823
|
};
|
|
@@ -8449,16 +8837,26 @@ function foldWakes(state, event) {
|
|
|
8449
8837
|
[workItemId]: work.type === "work.relation-added" ? [.../* @__PURE__ */ new Set([...blockers, related])] : blockers.filter((blocker) => blocker !== related)
|
|
8450
8838
|
}
|
|
8451
8839
|
};
|
|
8452
|
-
if (work.type === "work.relation-added" && currentItem !== null && readFact(project2.items, related)
|
|
8840
|
+
if (work.type === "work.relation-added" && currentItem !== null && !isClosed(readFact(project2.items, related))) {
|
|
8453
8841
|
const woken = new Set(project2.woken);
|
|
8454
8842
|
const rearms = { ...project2.rearms };
|
|
8843
|
+
const wakeStatus = { ...currentItem.wakeStatus };
|
|
8455
8844
|
for (const threadId of currentItem.assigneeThreads) {
|
|
8456
|
-
if (currentItem.claimedBy.includes(threadId))
|
|
8845
|
+
if (currentItem.claimedBy.includes(threadId)) {
|
|
8846
|
+
wakeStatus[threadId] = "released";
|
|
8847
|
+
continue;
|
|
8848
|
+
}
|
|
8457
8849
|
const generation = rearms[`${workItemId}:${threadId}`] ?? 0;
|
|
8458
|
-
if (woken.has(itemWakeKey(workItemId, threadId, generation)))
|
|
8850
|
+
if (woken.has(itemWakeKey(workItemId, threadId, generation))) {
|
|
8459
8851
|
rearms[`${workItemId}:${threadId}`] = generation + 1;
|
|
8852
|
+
if (wakeStatus[threadId] !== "released") wakeStatus[threadId] = "pending";
|
|
8853
|
+
}
|
|
8460
8854
|
}
|
|
8461
|
-
next = {
|
|
8855
|
+
next = {
|
|
8856
|
+
...next,
|
|
8857
|
+
rearms,
|
|
8858
|
+
items: { ...next.items, [workItemId]: { ...currentItem, wakeStatus } }
|
|
8859
|
+
};
|
|
8462
8860
|
if (currentItem.claimedBy.length > 0 && !hasOpenBlocker(project2, workItemId))
|
|
8463
8861
|
next = markBlockedAfterClaim(next, workItemId);
|
|
8464
8862
|
}
|
|
@@ -8478,45 +8876,104 @@ function foldWakes(state, event) {
|
|
|
8478
8876
|
})).filter(
|
|
8479
8877
|
(comment) => !project2.pendingComments.some((pending) => pending.key === comment.key)
|
|
8480
8878
|
);
|
|
8481
|
-
|
|
8482
|
-
|
|
8879
|
+
next = {
|
|
8880
|
+
...project2,
|
|
8881
|
+
pendingComments: [...project2.pendingComments, ...comments],
|
|
8882
|
+
items: currentItem === null ? project2.items : {
|
|
8883
|
+
...project2.items,
|
|
8884
|
+
[workItemId]: {
|
|
8885
|
+
...currentItem,
|
|
8886
|
+
lastCommentId: commentId,
|
|
8887
|
+
lastActionCommentThreadId: actor.kind === "agent" ? actor.threadId : null
|
|
8888
|
+
}
|
|
8889
|
+
}
|
|
8890
|
+
};
|
|
8483
8891
|
break;
|
|
8484
8892
|
}
|
|
8485
8893
|
}
|
|
8486
8894
|
return { ...state, [projectId]: next };
|
|
8487
8895
|
}
|
|
8488
|
-
|
|
8896
|
+
function foldWakes(state, event) {
|
|
8897
|
+
return foldRecovery(foldWorkWakes(state, event), event, state);
|
|
8898
|
+
}
|
|
8899
|
+
var compare2 = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
8900
|
+
var itemWakeCandidate = (projectId, project2, workItemId, item, threadId, woken) => {
|
|
8901
|
+
if (!project2.threads.includes(threadId) || !isWorkable(project2, workItemId)) return null;
|
|
8902
|
+
const unblocked = readFact(project2.blockedAfterClaim, workItemId) ?? 0;
|
|
8903
|
+
const finishing = unblocked > 0 && item.claimedBy.includes(threadId);
|
|
8904
|
+
if (!finishing && !item.assigneeThreads.includes(threadId)) return null;
|
|
8905
|
+
const lead = project2.leadThreads?.includes(threadId) === true;
|
|
8906
|
+
const key = finishing ? unblockedWakeKey(workItemId, threadId, unblocked) : itemWakeKey(workItemId, threadId, readFact(project2.rearms, `${workItemId}:${threadId}`) ?? 0);
|
|
8907
|
+
if (woken.has(key) || !finishing && !lead && item.wakeStatus?.[threadId] === "released")
|
|
8908
|
+
return null;
|
|
8909
|
+
return {
|
|
8910
|
+
key,
|
|
8911
|
+
projectId,
|
|
8912
|
+
threadId,
|
|
8913
|
+
kind: finishing ? "unblocked" : "item",
|
|
8914
|
+
workItemId,
|
|
8915
|
+
sequenceId: item.sequenceId,
|
|
8916
|
+
// 成员的叫醒话里点名负责人(卡住时找谁);带队自己和解挡续做不带。
|
|
8917
|
+
...lead ? { lead: true } : finishing || project2.leadThreads?.[0] === void 0 ? {} : { leadThreadId: project2.leadThreads[0] }
|
|
8918
|
+
};
|
|
8919
|
+
};
|
|
8920
|
+
var needsMemberSlot = (wake) => wake.kind === "item" && wake.lead !== true;
|
|
8921
|
+
function memberItemSlots(projectId, project2, woken, onlyThreadId) {
|
|
8922
|
+
const busy = /* @__PURE__ */ new Set();
|
|
8923
|
+
const slots = /* @__PURE__ */ new Map();
|
|
8924
|
+
for (const [workItemId, item] of Object.entries(project2.items)) {
|
|
8925
|
+
if (onlyThreadId !== void 0 && !item.assigneeThreads.includes(onlyThreadId)) continue;
|
|
8926
|
+
if (!isWorkable(project2, workItemId)) continue;
|
|
8927
|
+
for (const threadId of item.assigneeThreads) {
|
|
8928
|
+
if (onlyThreadId !== void 0 && threadId !== onlyThreadId || project2.leadThreads?.includes(threadId) || !project2.threads.includes(threadId))
|
|
8929
|
+
continue;
|
|
8930
|
+
if (item.wakeStatus?.[threadId] === "active") busy.add(threadId);
|
|
8931
|
+
const candidate = itemWakeCandidate(projectId, project2, workItemId, item, threadId, woken);
|
|
8932
|
+
if (candidate === null || !needsMemberSlot(candidate)) continue;
|
|
8933
|
+
const first = slots.get(threadId);
|
|
8934
|
+
if (first === void 0 || compareWakeItems(candidate, first) < 0)
|
|
8935
|
+
slots.set(threadId, candidate);
|
|
8936
|
+
}
|
|
8937
|
+
}
|
|
8938
|
+
for (const threadId of busy) slots.delete(threadId);
|
|
8939
|
+
return slots;
|
|
8940
|
+
}
|
|
8941
|
+
function isWakeDue(state, wake) {
|
|
8942
|
+
const project2 = readFact(state, wake.projectId);
|
|
8943
|
+
if (project2 === null) return false;
|
|
8944
|
+
if (wake.kind === "comment")
|
|
8945
|
+
return project2.pendingComments.some((comment) => comment.key === wake.key);
|
|
8946
|
+
if (!("threadId" in wake)) return false;
|
|
8947
|
+
const item = readFact(project2.items, wake.workItemId);
|
|
8948
|
+
if (item === null) return false;
|
|
8949
|
+
const woken = new Set(project2.woken);
|
|
8950
|
+
const candidate = itemWakeCandidate(
|
|
8951
|
+
wake.projectId,
|
|
8952
|
+
project2,
|
|
8953
|
+
wake.workItemId,
|
|
8954
|
+
item,
|
|
8955
|
+
wake.threadId,
|
|
8956
|
+
woken
|
|
8957
|
+
);
|
|
8958
|
+
if (candidate?.key !== wake.key) return false;
|
|
8959
|
+
return !needsMemberSlot(candidate) || memberItemSlots(wake.projectId, project2, woken, wake.threadId).get(wake.threadId)?.key === wake.key;
|
|
8960
|
+
}
|
|
8489
8961
|
function dueWakes(state) {
|
|
8490
8962
|
const due = [];
|
|
8491
8963
|
for (const [projectId, project2] of Object.entries(state)) {
|
|
8492
8964
|
const woken = new Set(project2.woken);
|
|
8493
|
-
const
|
|
8494
|
-
const leads = new Set(project2.leadThreads ?? []);
|
|
8965
|
+
const slots = memberItemSlots(projectId, project2, woken);
|
|
8495
8966
|
for (const [workItemId, item] of Object.entries(project2.items)) {
|
|
8496
|
-
if (item.closed || hasOpenBlocker(project2, workItemId)) continue;
|
|
8497
8967
|
const unblocked = readFact(project2.blockedAfterClaim, workItemId) ?? 0;
|
|
8498
8968
|
const recipients = /* @__PURE__ */ new Set([
|
|
8499
8969
|
...item.assigneeThreads,
|
|
8500
8970
|
...unblocked > 0 ? item.claimedBy : []
|
|
8501
8971
|
]);
|
|
8502
8972
|
for (const threadId of recipients) {
|
|
8503
|
-
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
threadId,
|
|
8508
|
-
readFact(project2.rearms, `${workItemId}:${threadId}`) ?? 0
|
|
8509
|
-
);
|
|
8510
|
-
if (woken.has(key)) continue;
|
|
8511
|
-
due.push({
|
|
8512
|
-
key,
|
|
8513
|
-
projectId,
|
|
8514
|
-
threadId,
|
|
8515
|
-
kind: finishing ? "unblocked" : "item",
|
|
8516
|
-
workItemId,
|
|
8517
|
-
sequenceId: item.sequenceId,
|
|
8518
|
-
...leads.has(threadId) ? { lead: true } : {}
|
|
8519
|
-
});
|
|
8973
|
+
const candidate = itemWakeCandidate(projectId, project2, workItemId, item, threadId, woken);
|
|
8974
|
+
if (candidate === null) continue;
|
|
8975
|
+
if (needsMemberSlot(candidate) && slots.get(threadId)?.key !== candidate.key) continue;
|
|
8976
|
+
due.push(candidate);
|
|
8520
8977
|
}
|
|
8521
8978
|
}
|
|
8522
8979
|
for (const comment of project2.pendingComments) {
|
|
@@ -8528,13 +8985,26 @@ function dueWakes(state) {
|
|
|
8528
8985
|
});
|
|
8529
8986
|
}
|
|
8530
8987
|
}
|
|
8531
|
-
return due.sort((a, b) =>
|
|
8988
|
+
return due.sort((a, b) => compare2(a.projectId, b.projectId) || compare2(a.key, b.key));
|
|
8532
8989
|
}
|
|
8533
8990
|
|
|
8534
8991
|
// src/runtime/wake.ts
|
|
8535
8992
|
var workLabel = (workItemId, sequenceId) => sequenceId === void 0 ? `the work item with workItemId ${workItemId}` : `work item W${sequenceId} (workItemId ${workItemId})`;
|
|
8536
8993
|
var titled = (workItemId, sequenceId, title) => `${workLabel(workItemId, sequenceId)}${title ? `: ${title}` : ""}`;
|
|
8537
|
-
var
|
|
8994
|
+
var ITEM_SUMMARY_MAX_CHARS = 300;
|
|
8995
|
+
var descriptionStart = (description) => {
|
|
8996
|
+
const first = description?.trim().split(/\n\s*\n/u)[0]?.trim() ?? "";
|
|
8997
|
+
if (first === "") return "";
|
|
8998
|
+
const points = Array.from(first);
|
|
8999
|
+
const summary = points.length > ITEM_SUMMARY_MAX_CHARS ? `${points.slice(0, ITEM_SUMMARY_MAX_CHARS).join("")}\u2026` : first;
|
|
9000
|
+
return `Its description starts: ${summary}`;
|
|
9001
|
+
};
|
|
9002
|
+
var itemHeadline = (sequenceId, item) => {
|
|
9003
|
+
const title = item?.title ? `${sequenceId === void 0 ? "Work item" : `W${sequenceId}`}: ${item.title}.` : "";
|
|
9004
|
+
const parts = [title, descriptionStart(item?.description)].filter((part) => part !== "");
|
|
9005
|
+
return parts.length === 0 ? "" : `${parts.join(" ")} `;
|
|
9006
|
+
};
|
|
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}`;
|
|
8538
9008
|
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}`;
|
|
8539
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}`;
|
|
8540
9010
|
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.`;
|
|
@@ -8549,13 +9019,14 @@ function startWakeRuntime(ctx, options = {}) {
|
|
|
8549
9019
|
});
|
|
8550
9020
|
const hash2 = options.hash ?? stableHash;
|
|
8551
9021
|
let pending = Promise.resolve();
|
|
8552
|
-
const
|
|
9022
|
+
const readItem = async (projectId, workItemId) => {
|
|
8553
9023
|
const result2 = await ctx.work.items.get({ projectId, workItemIds: [workItemId] });
|
|
8554
|
-
return result2.items.find((item) => item.workItemId === workItemId)
|
|
9024
|
+
return result2.items.find((item) => item.workItemId === workItemId);
|
|
8555
9025
|
};
|
|
8556
9026
|
const execute = async () => {
|
|
8557
|
-
|
|
9027
|
+
let state = await wakes.get();
|
|
8558
9028
|
for (const wake of dueWakes(state)) {
|
|
9029
|
+
if (!isWakeDue(state, wake)) continue;
|
|
8559
9030
|
try {
|
|
8560
9031
|
const id = hash2(wake.key);
|
|
8561
9032
|
const delivered = await deliverWake(ctx, {
|
|
@@ -8563,11 +9034,19 @@ function startWakeRuntime(ctx, options = {}) {
|
|
|
8563
9034
|
recipient: wake,
|
|
8564
9035
|
requestId: `team-wake:${id}`,
|
|
8565
9036
|
messageId: `team-wake-msg:${id}`,
|
|
8566
|
-
text: async () =>
|
|
8567
|
-
wake.
|
|
8568
|
-
|
|
8569
|
-
await
|
|
8570
|
-
|
|
9037
|
+
text: async () => {
|
|
9038
|
+
if ("principalId" in wake || wake.kind === "comment")
|
|
9039
|
+
return commentText2(wake.workItemId, wake.sequenceId);
|
|
9040
|
+
const item = await readItem(wake.projectId, wake.workItemId);
|
|
9041
|
+
const title = item?.title;
|
|
9042
|
+
if (wake.kind === "item")
|
|
9043
|
+
return wake.lead === true ? leadItemText(wake.workItemId, wake.sequenceId, title) : itemText(wake.workItemId, wake.sequenceId, item, wake.leadThreadId);
|
|
9044
|
+
return (wake.lead === true ? leadUnblockedText : unblockedText)(
|
|
9045
|
+
wake.workItemId,
|
|
9046
|
+
wake.sequenceId,
|
|
9047
|
+
title
|
|
9048
|
+
);
|
|
9049
|
+
}
|
|
8571
9050
|
});
|
|
8572
9051
|
if (!delivered) continue;
|
|
8573
9052
|
const receipt = await ctx.events.append({
|
|
@@ -8581,7 +9060,7 @@ function startWakeRuntime(ctx, options = {}) {
|
|
|
8581
9060
|
..."principalId" in wake ? { principalId: wake.principalId } : { threadId: wake.threadId }
|
|
8582
9061
|
}
|
|
8583
9062
|
});
|
|
8584
|
-
await wakes.get({ throughSequence: receipt.sequence });
|
|
9063
|
+
state = await wakes.get({ throughSequence: receipt.sequence });
|
|
8585
9064
|
} catch (error) {
|
|
8586
9065
|
ctx.log?.warn("team member wake failed", { key: wake.key, error });
|
|
8587
9066
|
}
|
|
@@ -8595,10 +9074,212 @@ function startWakeRuntime(ctx, options = {}) {
|
|
|
8595
9074
|
);
|
|
8596
9075
|
return result2;
|
|
8597
9076
|
};
|
|
8598
|
-
ctx.reactor({
|
|
9077
|
+
ctx.reactor({
|
|
9078
|
+
name: "team-wake",
|
|
9079
|
+
events: WAKE_EVENTS,
|
|
9080
|
+
handle: (event) => event.type === "thread.session-set" || isRecoveryFact(event) ? Promise.resolve() : flush()
|
|
9081
|
+
});
|
|
8599
9082
|
const ready = flush();
|
|
8600
9083
|
void ready.catch((error) => ctx.log?.warn("team wake catch-up failed", { error }));
|
|
8601
|
-
return { flush, ready };
|
|
9084
|
+
return { flush, ready, state: wakes };
|
|
9085
|
+
}
|
|
9086
|
+
|
|
9087
|
+
// src/runtime/recovery.ts
|
|
9088
|
+
var readStallExecution = async (ctx, threadId) => {
|
|
9089
|
+
const shell = await ctx.threads.get({ threadId });
|
|
9090
|
+
const summary = await ctx.threads.execution.summary({ threadId, limit: 1 });
|
|
9091
|
+
return { ...shell, queuePaused: summary.queuePaused, summary };
|
|
9092
|
+
};
|
|
9093
|
+
var itemLabel = (item) => `W${item.sequenceId} (workItemId ${item.workItemId})`;
|
|
9094
|
+
var deferredText = (recovery) => recovery.deferredWakeCount === void 0 ? "" : `They also have ${recovery.deferredWakeCount} assigned ${recovery.deferredWakeCount === 1 ? "item that has" : "items that have"} not been woken yet. `;
|
|
9095
|
+
var leadText = (recovery, name, paused) => `Member ${name} (threadId ${recovery.threadId}) stopped because of an upstream error (${recovery.category}). ${recovery.items.map(itemLabel).join(", ")} ${recovery.items.length === 1 ? "is" : "are"} still open. ${deferredText(recovery)}${recovery.alreadyRetried ? "An automatic continuation has already been attempted for this work during these consecutive failures. " : ""}${leadInstructions(paused)}`;
|
|
9096
|
+
var commentInstructions = (recovery) => recovery.items.map(
|
|
9097
|
+
(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.`
|
|
9098
|
+
).join("\n");
|
|
9099
|
+
var leadInstructions = (paused = false) => `${paused ? "Their sending is paused; a person needs to click Resume sending. " : ""}You can post a comment mentioning the member with work_post_comment to ask them to continue (the comment 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." : ""}`;
|
|
9100
|
+
var resumeText = (recovery) => `Your previous turn was interrupted by an upstream error (${recovery.category}). Continue ${recovery.items.map(itemLabel).join(", ")} from where you stopped.
|
|
9101
|
+
${commentInstructions(recovery)}`;
|
|
9102
|
+
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.
|
|
9103
|
+
${commentInstructions(recovery)}`;
|
|
9104
|
+
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
|
+
(item) => `Their last action on ${itemLabel(item)} was their own comment (commentId ${item.lastCommentId}); reply to it or arrange another member to help. `
|
|
9106
|
+
).join("")}${leadInstructions()}`;
|
|
9107
|
+
function startRecoveryRuntime(ctx, goals, state) {
|
|
9108
|
+
let pending = Promise.resolve();
|
|
9109
|
+
let disposed = false;
|
|
9110
|
+
const execute = async (scope2) => {
|
|
9111
|
+
if (disposed) return;
|
|
9112
|
+
const current = await state.get();
|
|
9113
|
+
let projects = current;
|
|
9114
|
+
if (scope2?.kind === "project") {
|
|
9115
|
+
const project2 = readFact(current, scope2.projectId);
|
|
9116
|
+
projects = project2 === null ? {} : { [scope2.projectId]: project2 };
|
|
9117
|
+
} else if (scope2?.kind === "thread") {
|
|
9118
|
+
projects = Object.fromEntries(
|
|
9119
|
+
Object.entries(current).filter(
|
|
9120
|
+
([, project2]) => Object.entries(project2.recovery ?? {}).some(
|
|
9121
|
+
([threadId, member]) => threadId === scope2.threadId || member.pending?.plan?.recipientThreadId === scope2.threadId
|
|
9122
|
+
)
|
|
9123
|
+
)
|
|
9124
|
+
);
|
|
9125
|
+
}
|
|
9126
|
+
const recoveries = dueRecoveries(
|
|
9127
|
+
projects,
|
|
9128
|
+
scope2?.kind === "thread" ? scope2.threadId : void 0
|
|
9129
|
+
);
|
|
9130
|
+
const wakingThreads = new Set(
|
|
9131
|
+
recoveries.some((recovery) => recovery.kind === "stall") ? dueWakes(projects).flatMap((wake) => "threadId" in wake ? [wake.threadId] : []) : []
|
|
9132
|
+
);
|
|
9133
|
+
for (const recovery of recoveries) {
|
|
9134
|
+
try {
|
|
9135
|
+
const stalled = recovery.kind === "stall";
|
|
9136
|
+
const execution = stalled ? await readStallExecution(ctx, recovery.threadId) : await ctx.projections.getThreadExecution(recovery.threadId);
|
|
9137
|
+
const queuedContinuation = recovery.plan?.resumeQueue === true;
|
|
9138
|
+
if (execution === null || execution.archivedAt !== null || execution.session == null || !queuedContinuation && (execution.session.activeTurnId !== null || execution.latestTurn?.turnId !== recovery.turnId || (stalled ? !["ready", "idle", "stopped"].includes(execution.session.status) || execution.latestTurn.state !== "completed" || execution.latestTurn.completedAt === null : execution.session.status !== "error" || execution.latestTurn.state !== "error")))
|
|
9139
|
+
continue;
|
|
9140
|
+
if ("summary" in execution) {
|
|
9141
|
+
if (execution.summary.queuePaused || execution.summary.pendingApprovalCount > 0 || execution.summary.pendingUserInputCount > 0 || execution.summary.queue.length > 0 || (execution.pendingAsyncQuestionCount ?? 0) > 0 || // 成员给人发了消息、人还没回:他在等回答,不提醒也不叫负责人。
|
|
9142
|
+
(execution.awaitingPersonReplyCount ?? 0) > 0)
|
|
9143
|
+
continue;
|
|
9144
|
+
if (wakingThreads.has(recovery.threadId) || Object.values(projects).some(
|
|
9145
|
+
(project2) => Object.values(project2.recovery ?? {}).some(
|
|
9146
|
+
(member) => member.pending?.plan?.recipientThreadId === recovery.threadId && member.pending !== current[recovery.projectId]?.recovery?.[recovery.threadId]?.pending
|
|
9147
|
+
)
|
|
9148
|
+
))
|
|
9149
|
+
continue;
|
|
9150
|
+
}
|
|
9151
|
+
const id = stableHash(recovery.key);
|
|
9152
|
+
let plan = recovery.plan;
|
|
9153
|
+
let tryQueueResume = false;
|
|
9154
|
+
const requestPlan = async (next) => {
|
|
9155
|
+
const receipt2 = await ctx.events.append({
|
|
9156
|
+
commandId: `team-recovery-requested:${id}:${next.action}${next.resumeQueue === true ? "-queue" : ""}`,
|
|
9157
|
+
name: "member-recovery-requested",
|
|
9158
|
+
aggregate: { kind: "project", id: recovery.projectId },
|
|
9159
|
+
data: {
|
|
9160
|
+
version: 1,
|
|
9161
|
+
projectId: recovery.projectId,
|
|
9162
|
+
threadId: recovery.threadId,
|
|
9163
|
+
turnId: recovery.turnId,
|
|
9164
|
+
failureSequence: recovery.sequence,
|
|
9165
|
+
plan: next
|
|
9166
|
+
}
|
|
9167
|
+
});
|
|
9168
|
+
await state.get({ throughSequence: receipt2.sequence });
|
|
9169
|
+
};
|
|
9170
|
+
if (plan === void 0 || plan.action === "resume" && execution.queuePaused && plan.resumeQueue !== true) {
|
|
9171
|
+
const resume = !stalled && (!recovery.alreadyRetried || plan?.action === "resume") && (recovery.category === "transport" || recovery.category === "rate-limit" && !execution.queuePaused);
|
|
9172
|
+
const remind = stalled && !recovery.alreadyRetried && !recovery.items.some((item) => item.waitingForReply);
|
|
9173
|
+
const goal = resume || remind ? null : await goals.read(recovery.projectId);
|
|
9174
|
+
const name = goal?.members.find((member) => member.threadId === recovery.threadId)?.name ?? recovery.memberId;
|
|
9175
|
+
plan = {
|
|
9176
|
+
action: stalled ? remind ? "remind" : "stall-lead" : resume ? "resume" : "lead",
|
|
9177
|
+
recipientThreadId: resume || remind ? recovery.threadId : recovery.leadThreadId,
|
|
9178
|
+
text: stalled ? remind ? remindText(recovery) : stallLeadText(recovery, name) : resume ? resumeText(recovery) : leadText(recovery, name, execution.queuePaused),
|
|
9179
|
+
workItemIds: recovery.items.map((item) => item.workItemId),
|
|
9180
|
+
...resume && execution.queuePaused ? { resumeQueue: true } : {}
|
|
9181
|
+
};
|
|
9182
|
+
await requestPlan(plan);
|
|
9183
|
+
tryQueueResume = plan.resumeQueue === true;
|
|
9184
|
+
}
|
|
9185
|
+
if (plan.resumeQueue === true && execution.queuePaused) {
|
|
9186
|
+
const project2 = (await state.get())[recovery.projectId];
|
|
9187
|
+
if (project2 === void 0 || !isMember(project2, { threadId: recovery.threadId }) || project2.leadThreads?.includes(recovery.threadId) || project2.recovery?.[recovery.threadId]?.pending?.turnId !== recovery.turnId)
|
|
9188
|
+
continue;
|
|
9189
|
+
let resumed = false;
|
|
9190
|
+
let outcome = "already-attempted";
|
|
9191
|
+
if (tryQueueResume) {
|
|
9192
|
+
try {
|
|
9193
|
+
const receipt2 = await ctx.threads.turnQueue.resume({
|
|
9194
|
+
requestId: `team-recovery-queue:${id}`,
|
|
9195
|
+
threadId: recovery.threadId
|
|
9196
|
+
});
|
|
9197
|
+
resumed = receipt2.status === "accepted";
|
|
9198
|
+
outcome = "noop";
|
|
9199
|
+
} catch {
|
|
9200
|
+
outcome = "rejected";
|
|
9201
|
+
}
|
|
9202
|
+
}
|
|
9203
|
+
if (!resumed) {
|
|
9204
|
+
const latest = await ctx.projections.getThreadExecution(recovery.threadId).catch(() => null);
|
|
9205
|
+
resumed = latest?.queuePaused === false;
|
|
9206
|
+
ctx.log.warn("team member queue recovery refused", {
|
|
9207
|
+
threadId: recovery.threadId,
|
|
9208
|
+
turnId: recovery.turnId,
|
|
9209
|
+
outcome,
|
|
9210
|
+
queuePaused: !resumed
|
|
9211
|
+
});
|
|
9212
|
+
}
|
|
9213
|
+
if (!resumed) {
|
|
9214
|
+
const goal = await goals.read(recovery.projectId);
|
|
9215
|
+
const name = goal?.members.find((member) => member.threadId === recovery.threadId)?.name ?? recovery.memberId;
|
|
9216
|
+
plan = {
|
|
9217
|
+
action: "lead",
|
|
9218
|
+
recipientThreadId: recovery.leadThreadId,
|
|
9219
|
+
text: leadText(recovery, name, true),
|
|
9220
|
+
workItemIds: plan.workItemIds
|
|
9221
|
+
};
|
|
9222
|
+
await requestPlan(plan);
|
|
9223
|
+
}
|
|
9224
|
+
}
|
|
9225
|
+
const delivered = await deliverWake(ctx, {
|
|
9226
|
+
projectId: recovery.projectId,
|
|
9227
|
+
recipient: { threadId: plan.recipientThreadId },
|
|
9228
|
+
requestId: `team-recovery:${id}:${plan.action}`,
|
|
9229
|
+
messageId: `team-recovery-msg:${id}:${plan.action}`,
|
|
9230
|
+
text: plan.text
|
|
9231
|
+
});
|
|
9232
|
+
if (!delivered) continue;
|
|
9233
|
+
const receipt = await ctx.events.append({
|
|
9234
|
+
commandId: `team-recovered:${id}:${plan.action}`,
|
|
9235
|
+
name: "member-recovery-delivered",
|
|
9236
|
+
aggregate: { kind: "project", id: recovery.projectId },
|
|
9237
|
+
data: {
|
|
9238
|
+
version: 1,
|
|
9239
|
+
projectId: recovery.projectId,
|
|
9240
|
+
threadId: recovery.threadId,
|
|
9241
|
+
turnId: recovery.turnId,
|
|
9242
|
+
failureSequence: recovery.sequence,
|
|
9243
|
+
action: plan.action,
|
|
9244
|
+
workItemIds: plan.workItemIds
|
|
9245
|
+
}
|
|
9246
|
+
});
|
|
9247
|
+
await state.get({ throughSequence: receipt.sequence });
|
|
9248
|
+
} catch (error) {
|
|
9249
|
+
ctx.log.warn("team member recovery failed", { key: recovery.key, error });
|
|
9250
|
+
}
|
|
9251
|
+
}
|
|
9252
|
+
};
|
|
9253
|
+
const flush = (scope2) => {
|
|
9254
|
+
const result2 = pending.then(() => execute(scope2));
|
|
9255
|
+
pending = result2.catch(() => void 0);
|
|
9256
|
+
return result2;
|
|
9257
|
+
};
|
|
9258
|
+
ctx.reactor({
|
|
9259
|
+
name: "team-member-recovery",
|
|
9260
|
+
events: [
|
|
9261
|
+
"thread.session-set",
|
|
9262
|
+
"thread.turn-queue-paused",
|
|
9263
|
+
"plugin.event-appended",
|
|
9264
|
+
"work.item-updated",
|
|
9265
|
+
"work.item-claimed",
|
|
9266
|
+
"work.comment-posted",
|
|
9267
|
+
"work.relation-added",
|
|
9268
|
+
"work.relation-removed",
|
|
9269
|
+
"thread.turn-intent-queued",
|
|
9270
|
+
"thread.turn-intent-transitioned",
|
|
9271
|
+
"thread.turn-queue-updated",
|
|
9272
|
+
"thread.request-resolved",
|
|
9273
|
+
"thread.user-input-pending"
|
|
9274
|
+
],
|
|
9275
|
+
handle: (event) => flush(recoveryEventScopeOf(event))
|
|
9276
|
+
});
|
|
9277
|
+
ctx.onDispose(() => {
|
|
9278
|
+
disposed = true;
|
|
9279
|
+
});
|
|
9280
|
+
const ready = flush();
|
|
9281
|
+
void ready.catch((error) => ctx.log.warn("team member recovery catch-up failed", { error }));
|
|
9282
|
+
return { ready, flush };
|
|
8602
9283
|
}
|
|
8603
9284
|
|
|
8604
9285
|
// src/handbook/content.ts
|
|
@@ -9304,7 +9985,7 @@ function registerHandbookPanel(ctx, runtime) {
|
|
|
9304
9985
|
// src/index.ts
|
|
9305
9986
|
var index_default = definePlugin({
|
|
9306
9987
|
id: "team",
|
|
9307
|
-
apiVersion:
|
|
9988
|
+
apiVersion: 29,
|
|
9308
9989
|
activate(ctx) {
|
|
9309
9990
|
const rosters = createRosterService(ctx);
|
|
9310
9991
|
registerRosterCommands(ctx, rosters);
|
|
@@ -9312,7 +9993,8 @@ var index_default = definePlugin({
|
|
|
9312
9993
|
registerGoalCommands(ctx, goals);
|
|
9313
9994
|
registerBoardCommands(ctx, goals, rosters);
|
|
9314
9995
|
registerDetailCommands(ctx, goals);
|
|
9315
|
-
startWakeRuntime(ctx);
|
|
9996
|
+
const wakes = startWakeRuntime(ctx);
|
|
9997
|
+
startRecoveryRuntime(ctx, goals, wakes.state);
|
|
9316
9998
|
const handbook = startHandbookRuntime(ctx);
|
|
9317
9999
|
registerHandbook(ctx, handbook);
|
|
9318
10000
|
registerHandbookPanel(ctx, handbook);
|