@ycodium-ai/plugin-team 0.2.3120 → 0.2.3122
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 +3 -3
- package/dist/index.js +826 -47
- package/locales/en.json +16 -0
- package/locales/zh-CN.json +16 -0
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -3511,9 +3511,9 @@ var fromOptionExit = (option2) => option2._tag === "None" ? missingExit : succee
|
|
|
3511
3511
|
// ../../node_modules/.pnpm/effect@4.0.0-beta.103_patch_hash=78523ded7c3dfc3efd3cec0247f4960d00eaa3d3c96a2077457694a4d8b453c6/node_modules/effect/dist/SchemaGetter.js
|
|
3512
3512
|
var Getter = class _Getter extends Class {
|
|
3513
3513
|
run;
|
|
3514
|
-
constructor(
|
|
3514
|
+
constructor(run3) {
|
|
3515
3515
|
super();
|
|
3516
|
-
this.run =
|
|
3516
|
+
this.run = run3;
|
|
3517
3517
|
}
|
|
3518
3518
|
map(f) {
|
|
3519
3519
|
return new _Getter((oe, options) => this.run(oe, options).pipe(mapEager2(map(f))));
|
|
@@ -3670,18 +3670,18 @@ var Declaration = class _Declaration extends Base2 {
|
|
|
3670
3670
|
typeParameters;
|
|
3671
3671
|
run;
|
|
3672
3672
|
encodingChecks;
|
|
3673
|
-
constructor(typeParameters,
|
|
3673
|
+
constructor(typeParameters, run3, annotations, checks, encoding, context2, encodingChecks) {
|
|
3674
3674
|
super(annotations, checks, encoding, context2);
|
|
3675
3675
|
this.typeParameters = typeParameters;
|
|
3676
|
-
this.run =
|
|
3676
|
+
this.run = run3;
|
|
3677
3677
|
this.encodingChecks = encodingChecks;
|
|
3678
3678
|
}
|
|
3679
3679
|
/** @internal */
|
|
3680
3680
|
getParser() {
|
|
3681
|
-
let
|
|
3681
|
+
let run3;
|
|
3682
3682
|
return (input, options) => {
|
|
3683
3683
|
if (input === missing) return missingExit;
|
|
3684
|
-
return (
|
|
3684
|
+
return (run3 ??= this.run(this.typeParameters))(input, this, options);
|
|
3685
3685
|
};
|
|
3686
3686
|
}
|
|
3687
3687
|
_rebuild(recur, checks, encodingChecks) {
|
|
@@ -4635,9 +4635,9 @@ var Filter2 = class _Filter extends Class {
|
|
|
4635
4635
|
* Whether the parsing process should be aborted after this check has failed.
|
|
4636
4636
|
*/
|
|
4637
4637
|
aborted;
|
|
4638
|
-
constructor(
|
|
4638
|
+
constructor(run3, annotations = void 0, aborted = false) {
|
|
4639
4639
|
super();
|
|
4640
|
-
this.run =
|
|
4640
|
+
this.run = run3;
|
|
4641
4641
|
this.annotations = annotations;
|
|
4642
4642
|
this.aborted = aborted;
|
|
4643
4643
|
}
|
|
@@ -6199,8 +6199,8 @@ function fromASTs(asts) {
|
|
|
6199
6199
|
|
|
6200
6200
|
// ../../node_modules/.pnpm/effect@4.0.0-beta.103_patch_hash=78523ded7c3dfc3efd3cec0247f4960d00eaa3d3c96a2077457694a4d8b453c6/node_modules/effect/dist/Schema.js
|
|
6201
6201
|
function declareConstructor() {
|
|
6202
|
-
return (typeParameters,
|
|
6203
|
-
return make8(new Declaration(typeParameters.map(getAST), (typeParameters2) =>
|
|
6202
|
+
return (typeParameters, run3, annotations) => {
|
|
6203
|
+
return make8(new Declaration(typeParameters.map(getAST), (typeParameters2) => run3(typeParameters2.map((ast) => make8(ast))), annotations));
|
|
6204
6204
|
};
|
|
6205
6205
|
}
|
|
6206
6206
|
function declare(is2, annotations) {
|
|
@@ -6379,6 +6379,7 @@ function isBase64(annotations) {
|
|
|
6379
6379
|
...annotations
|
|
6380
6380
|
});
|
|
6381
6381
|
}
|
|
6382
|
+
var Finite = /* @__PURE__ */ make8(finite);
|
|
6382
6383
|
function makeIsGreaterThanOrEqualTo(options) {
|
|
6383
6384
|
const gte = isGreaterThanOrEqualTo(options.order);
|
|
6384
6385
|
const formatter = options.formatter ?? format;
|
|
@@ -6861,6 +6862,210 @@ function readFact(state, key) {
|
|
|
6861
6862
|
var nowIso = map5(now2, formatIso2);
|
|
6862
6863
|
var factTime = () => runPromise2(nowIso);
|
|
6863
6864
|
|
|
6865
|
+
// src/domain/execution.ts
|
|
6866
|
+
function goalExecution(goal) {
|
|
6867
|
+
return {
|
|
6868
|
+
leadMemberId: goal?.leadMemberId ?? null,
|
|
6869
|
+
members: goal === null ? [] : [...goal.threads, ...goal.remoteMembers ?? []]
|
|
6870
|
+
};
|
|
6871
|
+
}
|
|
6872
|
+
|
|
6873
|
+
// locales/en.json
|
|
6874
|
+
var en_default = {
|
|
6875
|
+
handbook: {
|
|
6876
|
+
requestConflict: "This requestId was already used for a different handbook request. Use a new requestId.",
|
|
6877
|
+
requestVoided: "This requestId was abandoned before the handbook received it. Use a new requestId.",
|
|
6878
|
+
ackTool: "Acknowledge the currently required handbook release for your member seat in the current goal. Reading alone does not acknowledge it. Without a goal there are no team members, acknowledgement gates or reminders.",
|
|
6879
|
+
publishTool: "Publish handbook pages as the local team lead. Reuse requestId only for the same request. Rules changes require acknowledgement; for decisions changes choose requireAck (default false).",
|
|
6880
|
+
revertTool: "Restore a handbook release as the local team lead. Reuse requestId only for the same request. Rules changes require acknowledgement; for decisions changes choose requireAck (default false).",
|
|
6881
|
+
unknownMember: "Your verified caller identity is not mapped to a member of this project's current goal.",
|
|
6882
|
+
leadOnly: "Only the local team lead member of the current goal can publish or revert the handbook.",
|
|
6883
|
+
noGoal: "Start a goal before publishing or reverting the handbook. Without a goal there are no team members, acknowledgement gates or reminders.",
|
|
6884
|
+
noRequirement: "No handbook release requires acknowledgement yet.",
|
|
6885
|
+
wrongRelease: "Acknowledge handbook release {{release}}, the currently required version.",
|
|
6886
|
+
guard: "First acknowledge handbook release {{release}} (team_acknowledge_handbook).",
|
|
6887
|
+
reminder: "Handbook release {{release}} requires acknowledgement. Read wiki_read_handbook, then call team_acknowledge_handbook with this release number.",
|
|
6888
|
+
leadBrief: "Read the handbook with wiki_read_handbook and explicitly acknowledge its currently required release with team_acknowledge_handbook before taking new work. Publish or revert it through team_publish_handbook or team_revert_handbook; publishing a release that requires acknowledgement also acknowledges it for you.",
|
|
6889
|
+
memberBrief: "Read the handbook with wiki_read_handbook and call team_acknowledge_handbook for its currently required release before taking new work; reading alone does not acknowledge it."
|
|
6890
|
+
},
|
|
6891
|
+
tab: { title: "Teams" },
|
|
6892
|
+
board: {
|
|
6893
|
+
title: "Project board",
|
|
6894
|
+
newItem: "New work item",
|
|
6895
|
+
itemTitle: "Title",
|
|
6896
|
+
description: "Description (optional)",
|
|
6897
|
+
assignee: "Assign to (optional)",
|
|
6898
|
+
create: "Create work item",
|
|
6899
|
+
retryCreate: "Retry creation",
|
|
6900
|
+
saving: "Saving\u2026",
|
|
6901
|
+
moveTo: "Move to\u2026",
|
|
6902
|
+
moveItem: "Move work item #{{number}}",
|
|
6903
|
+
moveItemTo: "Move work item #{{number}} to {{state}}",
|
|
6904
|
+
assignTo: "Assign to\u2026",
|
|
6905
|
+
assignItem: "Assign work item #{{number}}",
|
|
6906
|
+
assignItemTo: "Assign work item #{{number}} to {{name}}",
|
|
6907
|
+
unassign: "Unassign",
|
|
6908
|
+
unassignItem: "Unassign work item #{{number}}",
|
|
6909
|
+
dragItem: "Drag work item #{{number}} to another state",
|
|
6910
|
+
untitled: "(Untitled)",
|
|
6911
|
+
other: "Other",
|
|
6912
|
+
unassigned: "Unassigned",
|
|
6913
|
+
blockedBy: "Blocked by {{count}} work items",
|
|
6914
|
+
goalRequired: "Start a goal above before assigning work to members.",
|
|
6915
|
+
loading: "Loading board\u2026",
|
|
6916
|
+
loadFailed: "The board could not be loaded. Try reloading it.",
|
|
6917
|
+
watchFailed: "Live board updates are unavailable. Reload to reconnect.",
|
|
6918
|
+
createFailed: "The work item could not be created. Retry with the same request.",
|
|
6919
|
+
updateFailed: "The work item could not be changed. Reloading the board.",
|
|
6920
|
+
staleVersion: "The work item was changed elsewhere. Your change was rolled back and the board is being reloaded.",
|
|
6921
|
+
notAMember: "This thread is not a member of the current goal. Choose a current member.",
|
|
6922
|
+
invalidTitle: "Enter a title for the work item.",
|
|
6923
|
+
reload: "Reload board",
|
|
6924
|
+
truncated: "Showing the first 500 work items in this project."
|
|
6925
|
+
},
|
|
6926
|
+
detail: {
|
|
6927
|
+
title: "Work item details",
|
|
6928
|
+
open: "Details",
|
|
6929
|
+
openItem: "Open details for work item #{{number}}",
|
|
6930
|
+
close: "Close",
|
|
6931
|
+
via: "via {{source}}",
|
|
6932
|
+
description: "Description",
|
|
6933
|
+
noDescription: "(No description)",
|
|
6934
|
+
blockedBy: "Blocked by these work items",
|
|
6935
|
+
blocking: "Blocking these work items",
|
|
6936
|
+
dependency: "#{{number}} {{title}} \xB7 {{state}}",
|
|
6937
|
+
comments: "Comments",
|
|
6938
|
+
noComments: "No comments yet.",
|
|
6939
|
+
deletedComment: "(This comment has been deleted)",
|
|
6940
|
+
commentsTruncated: "Showing the latest 200 comments, including replies.",
|
|
6941
|
+
reply: "Reply",
|
|
6942
|
+
replyTo: "Reply to comment {{number}}",
|
|
6943
|
+
newComment: "Post a comment",
|
|
6944
|
+
send: "Send",
|
|
6945
|
+
sendComment: "Send comment",
|
|
6946
|
+
sendReply: "Send reply to comment {{number}}",
|
|
6947
|
+
sending: "Sending\u2026",
|
|
6948
|
+
retry: "Retry",
|
|
6949
|
+
retryComment: "Retry comment",
|
|
6950
|
+
retryReply: "Retry reply to comment {{number}}",
|
|
6951
|
+
invalidBody: "The comment must be 1\u20138000 characters after trimming spaces.",
|
|
6952
|
+
commentFailed: "The comment couldn't be posted. Try again.",
|
|
6953
|
+
loading: "Loading work item details\u2026",
|
|
6954
|
+
loadFailed: "The work item details could not be loaded. Try reloading them.",
|
|
6955
|
+
reload: "Reload details",
|
|
6956
|
+
notFound: "This work item was not found in this project.",
|
|
6957
|
+
actor: {
|
|
6958
|
+
you: "You",
|
|
6959
|
+
human: "A person",
|
|
6960
|
+
otherAgent: "Other agent",
|
|
6961
|
+
team: "Teams plugin",
|
|
6962
|
+
plugin: "Plugin {{pluginId}}",
|
|
6963
|
+
localCli: "Local command line",
|
|
6964
|
+
system: "System",
|
|
6965
|
+
remoteAgentFallback: "Remote agent"
|
|
6966
|
+
},
|
|
6967
|
+
history: {
|
|
6968
|
+
title: "Activity history",
|
|
6969
|
+
empty: "No activity yet.",
|
|
6970
|
+
truncated: "Showing the latest 200 activities.",
|
|
6971
|
+
deletedState: "(Deleted state)",
|
|
6972
|
+
created: "{{author}} created this work item",
|
|
6973
|
+
createdAssigned: "{{author}} created this work item and assigned it to {{names}}",
|
|
6974
|
+
stateChanged: "{{author}} changed the state from {{old}} to {{new}}",
|
|
6975
|
+
assigned: "{{author}} assigned this work item to {{names}}",
|
|
6976
|
+
unassigned: "{{author}} removed the assignment",
|
|
6977
|
+
titleChanged: "{{author}} changed the title",
|
|
6978
|
+
descriptionChanged: "{{author}} changed the description",
|
|
6979
|
+
claimed: "{{author}} claimed this work item",
|
|
6980
|
+
commented: "{{author}} posted a comment",
|
|
6981
|
+
relationAdded: "{{author}} added a dependency",
|
|
6982
|
+
relationRemoved: "{{author}} removed a dependency"
|
|
6983
|
+
}
|
|
6984
|
+
},
|
|
6985
|
+
goal: {
|
|
6986
|
+
current: "Current goal: {{goal}}",
|
|
6987
|
+
lead: "Lead: {{name}}",
|
|
6988
|
+
unknownLead: "Member no longer in the roster",
|
|
6989
|
+
openLead: "Open lead thread",
|
|
6990
|
+
label: "Goal for the lead",
|
|
6991
|
+
rosterRequired: "Save the roster and choose a lead first.",
|
|
6992
|
+
start: "Start",
|
|
6993
|
+
startNew: "Start a new goal",
|
|
6994
|
+
starting: "Starting\u2026",
|
|
6995
|
+
retry: "Retry",
|
|
6996
|
+
reload: "Reload goal",
|
|
6997
|
+
loading: "Loading goal\u2026",
|
|
6998
|
+
confirmTitle: "Start a new goal?",
|
|
6999
|
+
confirmDescription: "Threads for the previous goal will keep running and remain in the thread list.",
|
|
7000
|
+
confirmStart: "Start new goal",
|
|
7001
|
+
cancel: "Cancel",
|
|
7002
|
+
noRoster: "No saved roster was found. Save the roster and choose a lead first.",
|
|
7003
|
+
invalidPayload: "Check the goal fields and try again.",
|
|
7004
|
+
invalidGoal: "The goal must be 1\u20134000 characters after trimming spaces.",
|
|
7005
|
+
invalidRequestId: "The request ID must be 1\u201364 letters, numbers, underscores, or hyphens.",
|
|
7006
|
+
loadFailed: "The current goal could not be loaded. Try reloading it.",
|
|
7007
|
+
startFailed: "The goal could not be fully started. Retry to finish creating the threads and sending the lead's brief.",
|
|
7008
|
+
remoteMembers: "Remote members:"
|
|
7009
|
+
},
|
|
7010
|
+
roster: {
|
|
7011
|
+
title: "Roster",
|
|
7012
|
+
members: "Members",
|
|
7013
|
+
memberName: "Member {{number}} name",
|
|
7014
|
+
lead: "Lead",
|
|
7015
|
+
leadFor: "Make member {{number}} the lead",
|
|
7016
|
+
remove: "Remove member {{number}}",
|
|
7017
|
+
add: "Add member",
|
|
7018
|
+
addRemoteAgent: "Add remote agent",
|
|
7019
|
+
noRemoteAgents: "There are no available remote agents on this machine yet.",
|
|
7020
|
+
remote: "Remote",
|
|
7021
|
+
save: "Save",
|
|
7022
|
+
saving: "Saving\u2026",
|
|
7023
|
+
saved: "Roster saved",
|
|
7024
|
+
loading: "Loading roster\u2026",
|
|
7025
|
+
retry: "Reload roster",
|
|
7026
|
+
emptyTitle: "No team roster yet",
|
|
7027
|
+
emptyDescription: "Add members and choose a lead for this project.",
|
|
7028
|
+
toolsHint: "Keep the board and project memory on for this project so members can pick up work and record what they learn.",
|
|
7029
|
+
toolkitsOff: "The lead needs the board and project memory. Turn on both switches above.",
|
|
7030
|
+
toolkitsOn: "The board and project memory are on for this project.",
|
|
7031
|
+
staleRevision: "The roster was changed elsewhere and has been reloaded.",
|
|
7032
|
+
loadFailed: "The roster could not be loaded. Try reloading it.",
|
|
7033
|
+
saveFailed: "The roster could not be saved. Try again.",
|
|
7034
|
+
invalidPayload: "Check the roster fields and try again.",
|
|
7035
|
+
memberCount: "A team needs between 1 and 8 members.",
|
|
7036
|
+
invalidMemberId: "Member IDs must be 1\u201364 letters, numbers, underscores, or hyphens.",
|
|
7037
|
+
invalidName: "Member names must be 1\u201340 characters after trimming spaces.",
|
|
7038
|
+
duplicateMemberId: "Each member needs a different ID.",
|
|
7039
|
+
duplicateName: "Member names must be different, ignoring capitalization and surrounding spaces.",
|
|
7040
|
+
leadNotMember: "Choose a lead from the members.",
|
|
7041
|
+
leadRemote: "The lead must be a local member.",
|
|
7042
|
+
duplicatePrincipal: "Each remote agent can appear only once in the roster.",
|
|
7043
|
+
invalidPrincipal: "A remote agent must have a non-empty principal ID.",
|
|
7044
|
+
allRemoteAgentsAdded: "All available remote agents are already on the roster.",
|
|
7045
|
+
remoteAgentsUnavailable: "Could not read the remote agents on this machine. Try again later.",
|
|
7046
|
+
remoteSource: "Remote agent: {{label}}"
|
|
7047
|
+
},
|
|
7048
|
+
remoteStatus: {
|
|
7049
|
+
title: "Remote members",
|
|
7050
|
+
none: "No remote members on the roster. Add one with \u201CAdd remote agent\u201D in the roster.",
|
|
7051
|
+
lastSeen: "Last seen {{time}}",
|
|
7052
|
+
neverSeen: "Not seen yet",
|
|
7053
|
+
workUnavailable: "Could not read this member's work. Try again later.",
|
|
7054
|
+
noWork: "No work assigned.",
|
|
7055
|
+
item: "#{{number}} {{title}}",
|
|
7056
|
+
disabled: "Disabled",
|
|
7057
|
+
revoked: "No active credentials",
|
|
7058
|
+
lastSeenUnavailable: "Could not read when it was last seen",
|
|
7059
|
+
blocked: "Blocked",
|
|
7060
|
+
more: "{{count}} more not listed here"
|
|
7061
|
+
}
|
|
7062
|
+
};
|
|
7063
|
+
|
|
7064
|
+
// src/handbook/messages.ts
|
|
7065
|
+
function handbookMessage(key, release) {
|
|
7066
|
+
return en_default.handbook[key].replace(/{{release}}/g, String(release ?? ""));
|
|
7067
|
+
}
|
|
7068
|
+
|
|
6864
7069
|
// src/domain/leadBrief.ts
|
|
6865
7070
|
var PERSON_MESSAGE_RULE = "Send a message to the person only when you need their answer; put progress and updates in work item comments with work_post_comment instead.";
|
|
6866
7071
|
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.";
|
|
@@ -6880,6 +7085,7 @@ function leadBrief(goal, roster) {
|
|
|
6880
7085
|
return [
|
|
6881
7086
|
"You are the lead of this project's team. Your goal is:",
|
|
6882
7087
|
goal.goal,
|
|
7088
|
+
handbookMessage("leadBrief"),
|
|
6883
7089
|
"Team members and their assignment identities (your own conversation is omitted):",
|
|
6884
7090
|
[
|
|
6885
7091
|
"| Member | threadId / remote principalId |",
|
|
@@ -6995,9 +7201,10 @@ function createGoalService(ctx, rosterService, options = {}) {
|
|
|
6995
7201
|
const goal = readFact(await goals.get(), projectId);
|
|
6996
7202
|
if (goal === null) return null;
|
|
6997
7203
|
const roster = await rosterService.read(projectId);
|
|
7204
|
+
const execution = goalExecution(goal);
|
|
6998
7205
|
return {
|
|
6999
7206
|
...goal,
|
|
7000
|
-
members:
|
|
7207
|
+
members: execution.members.map((member) => ({
|
|
7001
7208
|
...member,
|
|
7002
7209
|
name: roster?.members.find((row) => row.memberId === member.memberId)?.name ?? null
|
|
7003
7210
|
}))
|
|
@@ -7316,7 +7523,9 @@ function registerBoardCommands(ctx, goals, rosters, options = {}) {
|
|
|
7316
7523
|
const timer = NodeTimers.setTimeout(notify, delay2);
|
|
7317
7524
|
return () => NodeTimers.clearTimeout(timer);
|
|
7318
7525
|
});
|
|
7319
|
-
const isMember2 = async (projectId, threadId) => (await goals.read(projectId))
|
|
7526
|
+
const isMember2 = async (projectId, threadId) => goalExecution(await goals.read(projectId)).members.some(
|
|
7527
|
+
(member) => "threadId" in member && member.threadId === threadId
|
|
7528
|
+
);
|
|
7320
7529
|
ctx.command({
|
|
7321
7530
|
name: "board",
|
|
7322
7531
|
scope: "read",
|
|
@@ -7771,8 +7980,38 @@ function createRosterService(ctx, options = {}) {
|
|
|
7771
7980
|
};
|
|
7772
7981
|
}
|
|
7773
7982
|
|
|
7774
|
-
// src/runtime/
|
|
7983
|
+
// src/runtime/deliver.ts
|
|
7775
7984
|
import * as NodeCrypto from "node:crypto";
|
|
7985
|
+
var stableHash = (key) => NodeCrypto.createHash("sha256").update(key).digest("hex").slice(0, 32);
|
|
7986
|
+
async function deliverWake(ctx, input) {
|
|
7987
|
+
const { recipient } = input;
|
|
7988
|
+
if ("principalId" in recipient) {
|
|
7989
|
+
await ctx.communication.records.send({
|
|
7990
|
+
projectId: input.projectId,
|
|
7991
|
+
requestId: input.requestId,
|
|
7992
|
+
to: { principalId: recipient.principalId },
|
|
7993
|
+
text: typeof input.text === "string" ? input.text : await input.text(),
|
|
7994
|
+
delivery: "wake"
|
|
7995
|
+
});
|
|
7996
|
+
} else {
|
|
7997
|
+
const execution = await ctx.projections.getThreadExecution(recipient.threadId);
|
|
7998
|
+
if (execution === null || execution.archivedAt !== null) return false;
|
|
7999
|
+
await ctx.threads.turns.start({
|
|
8000
|
+
requestId: input.requestId,
|
|
8001
|
+
threadId: recipient.threadId,
|
|
8002
|
+
message: {
|
|
8003
|
+
messageId: input.messageId,
|
|
8004
|
+
role: "user",
|
|
8005
|
+
text: typeof input.text === "string" ? input.text : await input.text(),
|
|
8006
|
+
attachments: []
|
|
8007
|
+
},
|
|
8008
|
+
modelSelection: execution.modelSelection,
|
|
8009
|
+
runtimeMode: execution.runtimeMode,
|
|
8010
|
+
interactionMode: execution.interactionMode
|
|
8011
|
+
});
|
|
8012
|
+
}
|
|
8013
|
+
return true;
|
|
8014
|
+
}
|
|
7776
8015
|
|
|
7777
8016
|
// src/runtime/wakeState.ts
|
|
7778
8017
|
var WAKE_STATE_KEY = "wakes";
|
|
@@ -7918,9 +8157,14 @@ function foldWakes(state, event) {
|
|
|
7918
8157
|
if (fact.name === "team/goal-started") {
|
|
7919
8158
|
const { goal } = fact.data;
|
|
7920
8159
|
const project3 = readFact(state, goal.projectId) ?? emptyProject();
|
|
7921
|
-
const
|
|
8160
|
+
const { members } = goalExecution(goal);
|
|
8161
|
+
const threads = [
|
|
8162
|
+
...new Set(members.flatMap((member) => "threadId" in member ? [member.threadId] : []))
|
|
8163
|
+
];
|
|
7922
8164
|
const remoteMembers = [
|
|
7923
|
-
...new Set(
|
|
8165
|
+
...new Set(
|
|
8166
|
+
members.flatMap((member) => "principalId" in member ? [member.principalId] : [])
|
|
8167
|
+
)
|
|
7924
8168
|
];
|
|
7925
8169
|
const leadThreads = goal.threads.filter((thread) => thread.memberId === goal.leadMemberId).map((thread) => thread.threadId);
|
|
7926
8170
|
const next2 = { ...project3, threads, leadThreads, remoteMembers };
|
|
@@ -8090,12 +8334,11 @@ function dueWakes(state) {
|
|
|
8090
8334
|
// src/runtime/wake.ts
|
|
8091
8335
|
var workLabel = (workItemId, sequenceId) => sequenceId === void 0 ? `the work item with workItemId ${workItemId}` : `work item W${sequenceId} (workItemId ${workItemId})`;
|
|
8092
8336
|
var titled = (workItemId, sequenceId, title) => `${workLabel(workItemId, sequenceId)}${title ? `: ${title}` : ""}`;
|
|
8093
|
-
var itemText = (workItemId, sequenceId, title) => `You have ${titled(workItemId, sequenceId, title)}. 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. ${PERSON_MESSAGE_RULE}`;
|
|
8337
|
+
var itemText = (workItemId, sequenceId, title) => `You have ${titled(workItemId, sequenceId, title)}. 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. ${handbookMessage("memberBrief")} ${PERSON_MESSAGE_RULE}`;
|
|
8094
8338
|
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}`;
|
|
8095
8339
|
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}`;
|
|
8096
8340
|
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.`;
|
|
8097
8341
|
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.`;
|
|
8098
|
-
var stableHash = (key) => NodeCrypto.createHash("sha256").update(key).digest("hex").slice(0, 32);
|
|
8099
8342
|
function startWakeRuntime(ctx, options = {}) {
|
|
8100
8343
|
const wakes = ctx.derivedState({
|
|
8101
8344
|
key: WAKE_STATE_KEY,
|
|
@@ -8115,35 +8358,18 @@ function startWakeRuntime(ctx, options = {}) {
|
|
|
8115
8358
|
for (const wake of dueWakes(state)) {
|
|
8116
8359
|
try {
|
|
8117
8360
|
const id = hash2(wake.key);
|
|
8118
|
-
|
|
8119
|
-
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
|
|
8124
|
-
|
|
8125
|
-
|
|
8126
|
-
|
|
8127
|
-
|
|
8128
|
-
|
|
8129
|
-
|
|
8130
|
-
requestId: `team-wake:${id}`,
|
|
8131
|
-
threadId: wake.threadId,
|
|
8132
|
-
message: {
|
|
8133
|
-
messageId: `team-wake-msg:${id}`,
|
|
8134
|
-
role: "user",
|
|
8135
|
-
text: wake.kind === "comment" ? commentText2(wake.workItemId, wake.sequenceId) : (wake.kind === "item" ? wake.lead === true ? leadItemText : itemText : wake.lead === true ? leadUnblockedText : unblockedText)(
|
|
8136
|
-
wake.workItemId,
|
|
8137
|
-
wake.sequenceId,
|
|
8138
|
-
await readTitle(wake.projectId, wake.workItemId)
|
|
8139
|
-
),
|
|
8140
|
-
attachments: []
|
|
8141
|
-
},
|
|
8142
|
-
modelSelection: execution.modelSelection,
|
|
8143
|
-
runtimeMode: execution.runtimeMode,
|
|
8144
|
-
interactionMode: execution.interactionMode
|
|
8145
|
-
});
|
|
8146
|
-
}
|
|
8361
|
+
const delivered = await deliverWake(ctx, {
|
|
8362
|
+
projectId: wake.projectId,
|
|
8363
|
+
recipient: wake,
|
|
8364
|
+
requestId: `team-wake:${id}`,
|
|
8365
|
+
messageId: `team-wake-msg:${id}`,
|
|
8366
|
+
text: async () => "principalId" in wake || wake.kind === "comment" ? commentText2(wake.workItemId, wake.sequenceId) : (wake.kind === "item" ? wake.lead === true ? leadItemText : itemText : wake.lead === true ? leadUnblockedText : unblockedText)(
|
|
8367
|
+
wake.workItemId,
|
|
8368
|
+
wake.sequenceId,
|
|
8369
|
+
await readTitle(wake.projectId, wake.workItemId)
|
|
8370
|
+
)
|
|
8371
|
+
});
|
|
8372
|
+
if (!delivered) continue;
|
|
8147
8373
|
const receipt = await ctx.events.append({
|
|
8148
8374
|
commandId: `team-woken:${id}`,
|
|
8149
8375
|
name: "member-woken",
|
|
@@ -8175,10 +8401,562 @@ function startWakeRuntime(ctx, options = {}) {
|
|
|
8175
8401
|
return { flush, ready };
|
|
8176
8402
|
}
|
|
8177
8403
|
|
|
8404
|
+
// src/handbook/content.ts
|
|
8405
|
+
async function releaseRefs(memory, projectId, release) {
|
|
8406
|
+
return release === 0 ? [] : (await memory.handbook.releases.get({ projectId, release })).pages;
|
|
8407
|
+
}
|
|
8408
|
+
async function readBody(memory, projectId, ref) {
|
|
8409
|
+
let offset = 0;
|
|
8410
|
+
let body = "";
|
|
8411
|
+
while (true) {
|
|
8412
|
+
const page = await memory.pageRevisions.get({
|
|
8413
|
+
projectId,
|
|
8414
|
+
pageId: ref.pageId,
|
|
8415
|
+
revision: ref.revision,
|
|
8416
|
+
textOffset: offset
|
|
8417
|
+
});
|
|
8418
|
+
if (page.bodyState !== "present" || page.body === null)
|
|
8419
|
+
throw new Error("handbook-content-unavailable");
|
|
8420
|
+
body += page.body;
|
|
8421
|
+
const next = page.bodyWindow?.nextOffset;
|
|
8422
|
+
if (next === void 0 || next === null) break;
|
|
8423
|
+
offset = next;
|
|
8424
|
+
}
|
|
8425
|
+
return body;
|
|
8426
|
+
}
|
|
8427
|
+
async function publicationRequiresAck(memory, projectId, input) {
|
|
8428
|
+
const kinds = input.requireAck ? ["handbook_rules", "handbook_decisions"] : ["handbook_rules"];
|
|
8429
|
+
if (input.operation === "publish" && !input.pages.some((page) => kinds.includes(page.kind)))
|
|
8430
|
+
return false;
|
|
8431
|
+
const current = await releaseRefs(memory, projectId, input.expectedRelease);
|
|
8432
|
+
const target = input.operation === "revert" ? await releaseRefs(memory, projectId, input.toRelease) : [];
|
|
8433
|
+
for (const kind of kinds) {
|
|
8434
|
+
const previous = current.find((page) => page.kind === kind);
|
|
8435
|
+
if (input.operation === "publish") {
|
|
8436
|
+
const page = input.pages.find((row) => row.kind === kind);
|
|
8437
|
+
if (page === void 0) continue;
|
|
8438
|
+
if (previous === void 0) return true;
|
|
8439
|
+
if (await readBody(memory, projectId, previous) !== page.body) return true;
|
|
8440
|
+
} else {
|
|
8441
|
+
const next = target.find((page) => page.kind === kind);
|
|
8442
|
+
if (previous === void 0 || next === void 0) {
|
|
8443
|
+
if (previous !== next) return true;
|
|
8444
|
+
continue;
|
|
8445
|
+
}
|
|
8446
|
+
if (previous.pageId === next.pageId && previous.revision === next.revision) continue;
|
|
8447
|
+
if (await readBody(memory, projectId, previous) !== await readBody(memory, projectId, next))
|
|
8448
|
+
return true;
|
|
8449
|
+
}
|
|
8450
|
+
}
|
|
8451
|
+
return false;
|
|
8452
|
+
}
|
|
8453
|
+
|
|
8454
|
+
// src/handbook/contracts.ts
|
|
8455
|
+
var Release = Int.check(isGreaterThanOrEqualTo3(1));
|
|
8456
|
+
var Page = Struct({
|
|
8457
|
+
kind: Literals(["handbook_decisions", "handbook_rules", "handbook_progress"]),
|
|
8458
|
+
baseRevision: NullOr(Finite),
|
|
8459
|
+
title: optionalKey2(String4),
|
|
8460
|
+
body: String4,
|
|
8461
|
+
changeSummary: optionalKey2(String4)
|
|
8462
|
+
});
|
|
8463
|
+
var WriteFields = {
|
|
8464
|
+
requestId: String4,
|
|
8465
|
+
expectedRelease: Finite,
|
|
8466
|
+
requireAck: optionalKey2(Boolean2)
|
|
8467
|
+
};
|
|
8468
|
+
var PublishInput = Struct({
|
|
8469
|
+
...WriteFields,
|
|
8470
|
+
pages: ArraySchema(Page),
|
|
8471
|
+
sourceExperience: optionalKey2(
|
|
8472
|
+
Struct({ experienceId: String4, revision: Finite })
|
|
8473
|
+
)
|
|
8474
|
+
});
|
|
8475
|
+
var RevertInput = Struct({ ...WriteFields, toRelease: Finite });
|
|
8476
|
+
var AcknowledgeInput = Struct({ release: Release });
|
|
8477
|
+
var ReadHandbookInput = Struct({ projectId: String4 });
|
|
8478
|
+
var PublicationIntent = Struct({
|
|
8479
|
+
version: Literal2(1),
|
|
8480
|
+
projectId: String4,
|
|
8481
|
+
memberId: String4,
|
|
8482
|
+
requestId: String4,
|
|
8483
|
+
op: Literals(["publish", "revert"]),
|
|
8484
|
+
requireAckChoice: optionalKey2(Boolean2),
|
|
8485
|
+
requiresAck: optionalKey2(Boolean2)
|
|
8486
|
+
});
|
|
8487
|
+
var PublicationCompleted = Struct({
|
|
8488
|
+
version: Literal2(1),
|
|
8489
|
+
projectId: String4,
|
|
8490
|
+
requestId: String4,
|
|
8491
|
+
outcome: Union2([
|
|
8492
|
+
Struct({ status: Literal2("succeeded"), release: Release }),
|
|
8493
|
+
Struct({ status: Literal2("rejected") }),
|
|
8494
|
+
Struct({ status: Literal2("voided") })
|
|
8495
|
+
])
|
|
8496
|
+
});
|
|
8497
|
+
var Acknowledged = Struct({
|
|
8498
|
+
version: Literal2(1),
|
|
8499
|
+
projectId: String4,
|
|
8500
|
+
release: Release,
|
|
8501
|
+
memberId: String4
|
|
8502
|
+
});
|
|
8503
|
+
var ReminderSent = Struct({
|
|
8504
|
+
...Acknowledged.fields
|
|
8505
|
+
});
|
|
8506
|
+
|
|
8507
|
+
// src/handbook/state.ts
|
|
8508
|
+
var HANDBOOK_EVENTS = ["plugin.event-appended", "wiki.handbook-released"];
|
|
8509
|
+
var emptyHandbookState = () => ({
|
|
8510
|
+
projects: {},
|
|
8511
|
+
intents: {},
|
|
8512
|
+
completed: {},
|
|
8513
|
+
pendingPublications: {},
|
|
8514
|
+
pendingAcknowledgements: {},
|
|
8515
|
+
pendingReminders: {}
|
|
8516
|
+
});
|
|
8517
|
+
var emptyHandbookProject = () => ({
|
|
8518
|
+
goal: null,
|
|
8519
|
+
currentRelease: null,
|
|
8520
|
+
requiredRelease: null,
|
|
8521
|
+
acknowledgements: {},
|
|
8522
|
+
reminded: {}
|
|
8523
|
+
});
|
|
8524
|
+
var envelope = (name, data) => Struct({ pluginId: Literal2("team"), name: Literal2(`team/${name}`), data });
|
|
8525
|
+
var decodeFact3 = decodeUnknownOption2(
|
|
8526
|
+
Union2([
|
|
8527
|
+
envelope("goal-started", GoalStartedFact),
|
|
8528
|
+
envelope("handbook-publication-intended", PublicationIntent),
|
|
8529
|
+
envelope("handbook-publication-completed", PublicationCompleted),
|
|
8530
|
+
envelope("handbook-acknowledged", Acknowledged),
|
|
8531
|
+
envelope("handbook-reminder-sent", ReminderSent)
|
|
8532
|
+
])
|
|
8533
|
+
);
|
|
8534
|
+
var decodeRelease = decodeUnknownSync(
|
|
8535
|
+
Struct({
|
|
8536
|
+
projectId: String4,
|
|
8537
|
+
release: Number5,
|
|
8538
|
+
requiresAck: Boolean2
|
|
8539
|
+
})
|
|
8540
|
+
);
|
|
8541
|
+
function callerMember(project, caller) {
|
|
8542
|
+
if (caller === void 0) return void 0;
|
|
8543
|
+
return goalExecution(project.goal).members.find(
|
|
8544
|
+
(member) => caller.kind === "thread" ? "threadId" in member && member.threadId === caller.threadId : "principalId" in member && member.principalId === caller.principalId
|
|
8545
|
+
);
|
|
8546
|
+
}
|
|
8547
|
+
var hasAcknowledged = (project, memberId, release) => readFact(readFact(project.acknowledgements, memberId)?.releases ?? {}, String(release)) === true;
|
|
8548
|
+
var reminderKey = (projectId, release, memberId) => JSON.stringify([projectId, release, memberId]);
|
|
8549
|
+
var without = (rows, key) => {
|
|
8550
|
+
if (!Object.hasOwn(rows, key)) return rows;
|
|
8551
|
+
const next = { ...rows };
|
|
8552
|
+
delete next[key];
|
|
8553
|
+
return next;
|
|
8554
|
+
};
|
|
8555
|
+
function foldHandbook(state, event) {
|
|
8556
|
+
if (event.type === "wiki.handbook-released") {
|
|
8557
|
+
const fact2 = decodeRelease(event.payload);
|
|
8558
|
+
const project2 = readFact(state.projects, fact2.projectId) ?? emptyHandbookProject();
|
|
8559
|
+
const pendingReminders = { ...state.pendingReminders };
|
|
8560
|
+
if (fact2.requiresAck)
|
|
8561
|
+
for (const { memberId, ...recipient } of goalExecution(project2.goal).members) {
|
|
8562
|
+
const key = reminderKey(fact2.projectId, fact2.release, memberId);
|
|
8563
|
+
if (readFact(project2.reminded, key) === null)
|
|
8564
|
+
pendingReminders[key] = {
|
|
8565
|
+
projectId: fact2.projectId,
|
|
8566
|
+
release: fact2.release,
|
|
8567
|
+
member: { memberId, recipient }
|
|
8568
|
+
};
|
|
8569
|
+
}
|
|
8570
|
+
return {
|
|
8571
|
+
...state,
|
|
8572
|
+
pendingReminders,
|
|
8573
|
+
projects: {
|
|
8574
|
+
...state.projects,
|
|
8575
|
+
[fact2.projectId]: {
|
|
8576
|
+
...project2,
|
|
8577
|
+
currentRelease: fact2.release,
|
|
8578
|
+
requiredRelease: fact2.requiresAck ? fact2.release : project2.requiredRelease
|
|
8579
|
+
}
|
|
8580
|
+
}
|
|
8581
|
+
};
|
|
8582
|
+
}
|
|
8583
|
+
if (event.type !== "plugin.event-appended") return state;
|
|
8584
|
+
const decoded = decodeFact3(event.payload);
|
|
8585
|
+
if (isNone2(decoded)) return state;
|
|
8586
|
+
const fact = decoded.value;
|
|
8587
|
+
const projectId = fact.name === "team/goal-started" ? fact.data.goal.projectId : fact.data.projectId;
|
|
8588
|
+
const project = readFact(state.projects, projectId) ?? emptyHandbookProject();
|
|
8589
|
+
let next = project;
|
|
8590
|
+
switch (fact.name) {
|
|
8591
|
+
case "team/goal-started":
|
|
8592
|
+
next = { ...project, goal: fact.data.goal };
|
|
8593
|
+
break;
|
|
8594
|
+
case "team/handbook-publication-intended": {
|
|
8595
|
+
const intent = fact.data;
|
|
8596
|
+
if (readFact(state.intents, intent.requestId) !== null) return state;
|
|
8597
|
+
return {
|
|
8598
|
+
...state,
|
|
8599
|
+
intents: { ...state.intents, [intent.requestId]: intent },
|
|
8600
|
+
pendingPublications: { ...state.pendingPublications, [intent.requestId]: intent }
|
|
8601
|
+
};
|
|
8602
|
+
}
|
|
8603
|
+
case "team/handbook-publication-completed": {
|
|
8604
|
+
const completed = fact.data;
|
|
8605
|
+
if (readFact(state.completed, completed.requestId) !== null) return state;
|
|
8606
|
+
const intent = readFact(state.pendingPublications, completed.requestId);
|
|
8607
|
+
let pendingAcknowledgements = state.pendingAcknowledgements;
|
|
8608
|
+
let pendingReminders = state.pendingReminders;
|
|
8609
|
+
if (intent !== null && intent.requiresAck && completed.outcome.status === "succeeded") {
|
|
8610
|
+
const { release } = completed.outcome;
|
|
8611
|
+
const key = reminderKey(projectId, release, intent.memberId);
|
|
8612
|
+
pendingReminders = without(pendingReminders, key);
|
|
8613
|
+
if (!hasAcknowledged(project, intent.memberId, release))
|
|
8614
|
+
pendingAcknowledgements = {
|
|
8615
|
+
...pendingAcknowledgements,
|
|
8616
|
+
[key]: { version: 1, projectId, release, memberId: intent.memberId }
|
|
8617
|
+
};
|
|
8618
|
+
}
|
|
8619
|
+
return {
|
|
8620
|
+
...state,
|
|
8621
|
+
completed: { ...state.completed, [completed.requestId]: completed },
|
|
8622
|
+
pendingPublications: without(state.pendingPublications, completed.requestId),
|
|
8623
|
+
pendingAcknowledgements,
|
|
8624
|
+
pendingReminders
|
|
8625
|
+
};
|
|
8626
|
+
}
|
|
8627
|
+
case "team/handbook-acknowledged": {
|
|
8628
|
+
const { memberId, release } = fact.data;
|
|
8629
|
+
if (hasAcknowledged(project, memberId, release)) return state;
|
|
8630
|
+
const previous = readFact(project.acknowledgements, memberId);
|
|
8631
|
+
next = {
|
|
8632
|
+
...project,
|
|
8633
|
+
acknowledgements: {
|
|
8634
|
+
...project.acknowledgements,
|
|
8635
|
+
[memberId]: {
|
|
8636
|
+
releases: { ...previous?.releases, [release]: true },
|
|
8637
|
+
latestRelease: Math.max(previous?.latestRelease ?? 0, release)
|
|
8638
|
+
}
|
|
8639
|
+
}
|
|
8640
|
+
};
|
|
8641
|
+
return {
|
|
8642
|
+
...state,
|
|
8643
|
+
pendingAcknowledgements: without(
|
|
8644
|
+
state.pendingAcknowledgements,
|
|
8645
|
+
reminderKey(projectId, release, memberId)
|
|
8646
|
+
),
|
|
8647
|
+
projects: { ...state.projects, [projectId]: next }
|
|
8648
|
+
};
|
|
8649
|
+
}
|
|
8650
|
+
case "team/handbook-reminder-sent": {
|
|
8651
|
+
const key = reminderKey(projectId, fact.data.release, fact.data.memberId);
|
|
8652
|
+
if (readFact(project.reminded, key) !== null) return state;
|
|
8653
|
+
next = { ...project, reminded: { ...project.reminded, [key]: true } };
|
|
8654
|
+
return {
|
|
8655
|
+
...state,
|
|
8656
|
+
pendingReminders: without(state.pendingReminders, key),
|
|
8657
|
+
projects: { ...state.projects, [projectId]: next }
|
|
8658
|
+
};
|
|
8659
|
+
}
|
|
8660
|
+
}
|
|
8661
|
+
return { ...state, projects: { ...state.projects, [projectId]: next } };
|
|
8662
|
+
}
|
|
8663
|
+
|
|
8664
|
+
// src/handbook/runtime.ts
|
|
8665
|
+
var terminalFailure = (error) => {
|
|
8666
|
+
if (typeof error !== "object" || error === null || !("_tag" in error)) return null;
|
|
8667
|
+
const reason = "reason" in error ? error.reason : void 0;
|
|
8668
|
+
const rejected = error._tag === "DataCommandError" && ["rejected", "request-conflict", "command-id-conflict", "not-qualified"].includes(
|
|
8669
|
+
String(reason)
|
|
8670
|
+
);
|
|
8671
|
+
const invalid2 = error._tag === "PluginCapabilityError" && reason === void 0 && "capability" in error && ["memory.handbook.publish", "memory.handbook.revert"].includes(String(error.capability));
|
|
8672
|
+
if (!rejected && !invalid2) return null;
|
|
8673
|
+
return error instanceof Error ? error.message : "detail" in error ? String(error.detail) : String(reason);
|
|
8674
|
+
};
|
|
8675
|
+
function startHandbookRuntime(ctx) {
|
|
8676
|
+
const state = ctx.derivedState({
|
|
8677
|
+
key: "handbook",
|
|
8678
|
+
version: 4,
|
|
8679
|
+
events: HANDBOOK_EVENTS,
|
|
8680
|
+
init: emptyHandbookState,
|
|
8681
|
+
apply: foldHandbook
|
|
8682
|
+
});
|
|
8683
|
+
let pending = Promise.resolve();
|
|
8684
|
+
let disposed = false;
|
|
8685
|
+
const serial = (execute2) => {
|
|
8686
|
+
const result2 = pending.then(() => {
|
|
8687
|
+
if (disposed) throw new Error("team-handbook-disposed");
|
|
8688
|
+
return execute2();
|
|
8689
|
+
});
|
|
8690
|
+
pending = result2.then(
|
|
8691
|
+
() => void 0,
|
|
8692
|
+
() => void 0
|
|
8693
|
+
);
|
|
8694
|
+
return result2;
|
|
8695
|
+
};
|
|
8696
|
+
const append2 = async (projectId, name, key, data) => {
|
|
8697
|
+
const receipt = await ctx.events.append({
|
|
8698
|
+
commandId: `handbook:${name}:${stableHash(key)}`,
|
|
8699
|
+
name,
|
|
8700
|
+
aggregate: { kind: "project", id: projectId },
|
|
8701
|
+
data
|
|
8702
|
+
});
|
|
8703
|
+
return state.get({ throughSequence: receipt.sequence });
|
|
8704
|
+
};
|
|
8705
|
+
const acknowledge = async (projectId, release, memberId) => {
|
|
8706
|
+
const project = readFact((await state.get()).projects, projectId) ?? emptyHandbookProject();
|
|
8707
|
+
if (hasAcknowledged(project, memberId, release)) return;
|
|
8708
|
+
await append2(
|
|
8709
|
+
projectId,
|
|
8710
|
+
"handbook-acknowledged",
|
|
8711
|
+
JSON.stringify([projectId, release, memberId]),
|
|
8712
|
+
{
|
|
8713
|
+
version: 1,
|
|
8714
|
+
projectId,
|
|
8715
|
+
release,
|
|
8716
|
+
memberId
|
|
8717
|
+
}
|
|
8718
|
+
);
|
|
8719
|
+
};
|
|
8720
|
+
const complete = async (intent, outcome) => {
|
|
8721
|
+
await append2(intent.projectId, "handbook-publication-completed", intent.requestId, {
|
|
8722
|
+
version: 1,
|
|
8723
|
+
requestId: intent.requestId,
|
|
8724
|
+
projectId: intent.projectId,
|
|
8725
|
+
outcome
|
|
8726
|
+
});
|
|
8727
|
+
if (outcome.status === "succeeded" && intent.requiresAck) {
|
|
8728
|
+
await acknowledge(intent.projectId, outcome.release, intent.memberId);
|
|
8729
|
+
}
|
|
8730
|
+
return outcome;
|
|
8731
|
+
};
|
|
8732
|
+
const reconcile = async (intent) => {
|
|
8733
|
+
const completed = readFact((await state.get()).completed, intent.requestId);
|
|
8734
|
+
if (completed !== null) return completed.outcome;
|
|
8735
|
+
const receipt = await ctx.commands.getReceipt({
|
|
8736
|
+
commandId: intent.requestId,
|
|
8737
|
+
commandType: `wiki.handbook.${intent.op}`
|
|
8738
|
+
});
|
|
8739
|
+
if (receipt.status === "absent") return complete(intent, { status: "voided" });
|
|
8740
|
+
if (receipt.status === "rejected") return complete(intent, { status: "rejected" });
|
|
8741
|
+
const result2 = receipt.result;
|
|
8742
|
+
if (typeof result2 !== "object" || result2 === null || !("release" in result2) || typeof result2.release !== "number" || !Number.isSafeInteger(result2.release) || result2.release < 1)
|
|
8743
|
+
throw new Error("handbook-receipt-result-unavailable");
|
|
8744
|
+
await state.get({ throughSequence: receipt.sequence });
|
|
8745
|
+
return complete(intent, { status: "succeeded", release: result2.release });
|
|
8746
|
+
};
|
|
8747
|
+
const remind = async () => {
|
|
8748
|
+
const snapshot = await state.get();
|
|
8749
|
+
for (const [key, reminder] of Object.entries(snapshot.pendingReminders)) {
|
|
8750
|
+
const { projectId, release, member } = reminder;
|
|
8751
|
+
try {
|
|
8752
|
+
const id = stableHash(key);
|
|
8753
|
+
const delivered = await deliverWake(ctx, {
|
|
8754
|
+
projectId,
|
|
8755
|
+
recipient: member.recipient,
|
|
8756
|
+
requestId: `team-handbook:${id}`,
|
|
8757
|
+
messageId: `team-handbook-msg:${id}`,
|
|
8758
|
+
text: handbookMessage("reminder", release)
|
|
8759
|
+
});
|
|
8760
|
+
if (!delivered) continue;
|
|
8761
|
+
const previousKey = JSON.stringify([release, member.memberId]);
|
|
8762
|
+
await append2(
|
|
8763
|
+
projectId,
|
|
8764
|
+
"handbook-reminder-sent",
|
|
8765
|
+
JSON.stringify([projectId, previousKey]),
|
|
8766
|
+
{
|
|
8767
|
+
version: 1,
|
|
8768
|
+
projectId,
|
|
8769
|
+
release,
|
|
8770
|
+
memberId: member.memberId
|
|
8771
|
+
}
|
|
8772
|
+
);
|
|
8773
|
+
} catch (error) {
|
|
8774
|
+
ctx.log.warn("team handbook reminder failed", { projectId, key, error });
|
|
8775
|
+
}
|
|
8776
|
+
}
|
|
8777
|
+
};
|
|
8778
|
+
const execute = async () => {
|
|
8779
|
+
for (const intent of Object.values((await state.get()).pendingPublications)) {
|
|
8780
|
+
try {
|
|
8781
|
+
await reconcile(intent);
|
|
8782
|
+
} catch (error) {
|
|
8783
|
+
ctx.log.warn("team handbook publication catch-up failed", {
|
|
8784
|
+
requestId: intent.requestId,
|
|
8785
|
+
error
|
|
8786
|
+
});
|
|
8787
|
+
}
|
|
8788
|
+
}
|
|
8789
|
+
for (const ack of Object.values((await state.get()).pendingAcknowledgements)) {
|
|
8790
|
+
try {
|
|
8791
|
+
await acknowledge(ack.projectId, ack.release, ack.memberId);
|
|
8792
|
+
} catch (error) {
|
|
8793
|
+
ctx.log.warn("team handbook publisher acknowledgement failed", { ...ack, error });
|
|
8794
|
+
}
|
|
8795
|
+
}
|
|
8796
|
+
await remind();
|
|
8797
|
+
};
|
|
8798
|
+
const flush = () => serial(execute);
|
|
8799
|
+
ctx.reactor({ name: "team-handbook", events: HANDBOOK_EVENTS, handle: () => flush() });
|
|
8800
|
+
ctx.onDispose(async () => {
|
|
8801
|
+
disposed = true;
|
|
8802
|
+
await pending;
|
|
8803
|
+
});
|
|
8804
|
+
const ready = flush();
|
|
8805
|
+
void ready.catch((error) => ctx.log.warn("team handbook catch-up failed", { error }));
|
|
8806
|
+
return {
|
|
8807
|
+
ready,
|
|
8808
|
+
flush,
|
|
8809
|
+
async read(projectId) {
|
|
8810
|
+
const project = readFact((await state.get()).projects, projectId) ?? emptyHandbookProject();
|
|
8811
|
+
return {
|
|
8812
|
+
currentRelease: project.currentRelease,
|
|
8813
|
+
requiredRelease: project.requiredRelease,
|
|
8814
|
+
members: goalExecution(project.goal).members.map(({ memberId }) => ({
|
|
8815
|
+
memberId,
|
|
8816
|
+
acknowledgedRelease: readFact(project.acknowledgements, memberId)?.latestRelease ?? null,
|
|
8817
|
+
acknowledgedRequiredRelease: project.requiredRelease === null || hasAcknowledged(project, memberId, project.requiredRelease)
|
|
8818
|
+
}))
|
|
8819
|
+
};
|
|
8820
|
+
},
|
|
8821
|
+
ack(release, invocation) {
|
|
8822
|
+
return serial(async () => {
|
|
8823
|
+
const projectId = invocation.projectId;
|
|
8824
|
+
const project = projectId === null ? emptyHandbookProject() : readFact((await state.get()).projects, projectId) ?? emptyHandbookProject();
|
|
8825
|
+
const member = callerMember(project, invocation.caller);
|
|
8826
|
+
if (member === void 0 || projectId === null)
|
|
8827
|
+
throw new Error(handbookMessage("unknownMember"));
|
|
8828
|
+
if (project.requiredRelease === null) throw new Error(handbookMessage("noRequirement"));
|
|
8829
|
+
if (release !== project.requiredRelease)
|
|
8830
|
+
throw new Error(handbookMessage("wrongRelease", project.requiredRelease));
|
|
8831
|
+
await acknowledge(projectId, release, member.memberId);
|
|
8832
|
+
return { release, memberId: member.memberId };
|
|
8833
|
+
});
|
|
8834
|
+
},
|
|
8835
|
+
publish(input, invocation) {
|
|
8836
|
+
return serial(async () => {
|
|
8837
|
+
const projectId = invocation.projectId;
|
|
8838
|
+
const project = projectId === null ? emptyHandbookProject() : readFact((await state.get()).projects, projectId) ?? emptyHandbookProject();
|
|
8839
|
+
const member = callerMember(project, invocation.caller);
|
|
8840
|
+
const execution = goalExecution(project.goal);
|
|
8841
|
+
if (execution.leadMemberId === null) throw new Error(handbookMessage("noGoal"));
|
|
8842
|
+
if (projectId === null || invocation.caller?.kind !== "thread" || member === void 0 || member.memberId !== execution.leadMemberId)
|
|
8843
|
+
throw new Error(handbookMessage("leadOnly"));
|
|
8844
|
+
const requestId = `team-handbook:${stableHash(JSON.stringify([projectId, input.requestId]))}`;
|
|
8845
|
+
const current = await state.get();
|
|
8846
|
+
const existing = readFact(current.intents, requestId);
|
|
8847
|
+
const done2 = readFact(current.completed, requestId);
|
|
8848
|
+
if (existing !== null) {
|
|
8849
|
+
if (existing.op !== input.operation || existing.memberId !== member.memberId || (existing.requireAckChoice ?? false) !== (input.requireAck ?? false))
|
|
8850
|
+
throw new Error(handbookMessage("requestConflict"));
|
|
8851
|
+
if (done2?.outcome.status === "voided") throw new Error(handbookMessage("requestVoided"));
|
|
8852
|
+
}
|
|
8853
|
+
const requireAck = existing?.requiresAck ?? await publicationRequiresAck(ctx.memory, projectId, input);
|
|
8854
|
+
const intent = existing ?? {
|
|
8855
|
+
version: 1,
|
|
8856
|
+
projectId,
|
|
8857
|
+
memberId: member.memberId,
|
|
8858
|
+
requestId,
|
|
8859
|
+
op: input.operation,
|
|
8860
|
+
requireAckChoice: input.requireAck ?? false,
|
|
8861
|
+
requiresAck: requireAck
|
|
8862
|
+
};
|
|
8863
|
+
if (existing === null)
|
|
8864
|
+
await append2(projectId, "handbook-publication-intended", requestId, intent);
|
|
8865
|
+
let outcome;
|
|
8866
|
+
let detail = null;
|
|
8867
|
+
try {
|
|
8868
|
+
const command = { ...input, projectId, requestId, requireAck };
|
|
8869
|
+
const result2 = command.operation === "publish" ? await ctx.memory.handbook.publish(command) : await ctx.memory.handbook.revert(command);
|
|
8870
|
+
await state.get({ throughSequence: result2.sequence });
|
|
8871
|
+
outcome = { status: "succeeded", release: result2.result.release };
|
|
8872
|
+
} catch (error) {
|
|
8873
|
+
detail = terminalFailure(error);
|
|
8874
|
+
if (detail === null) throw error;
|
|
8875
|
+
outcome = { status: "rejected" };
|
|
8876
|
+
}
|
|
8877
|
+
if (done2 === null) await complete(intent, outcome);
|
|
8878
|
+
await remind();
|
|
8879
|
+
return detail === null ? outcome : { ...outcome, detail };
|
|
8880
|
+
});
|
|
8881
|
+
},
|
|
8882
|
+
async check(input) {
|
|
8883
|
+
const project = readFact(
|
|
8884
|
+
(await state.get({ throughSequence: input.throughSequence })).projects,
|
|
8885
|
+
input.projectId
|
|
8886
|
+
);
|
|
8887
|
+
if (project === null || project.requiredRelease === null) return { allowed: true };
|
|
8888
|
+
for (const actor of input.added) {
|
|
8889
|
+
const caller = actor.kind === "human" ? void 0 : actor.kind === "agent" ? { kind: "thread", threadId: actor.threadId } : actor;
|
|
8890
|
+
const member = callerMember(project, caller);
|
|
8891
|
+
if (member !== void 0 && !hasAcknowledged(project, member.memberId, project.requiredRelease)) {
|
|
8892
|
+
return {
|
|
8893
|
+
allowed: false,
|
|
8894
|
+
detail: handbookMessage("guard", project.requiredRelease)
|
|
8895
|
+
};
|
|
8896
|
+
}
|
|
8897
|
+
}
|
|
8898
|
+
return { allowed: true };
|
|
8899
|
+
}
|
|
8900
|
+
};
|
|
8901
|
+
}
|
|
8902
|
+
|
|
8903
|
+
// src/handbook/entry.ts
|
|
8904
|
+
var decodeAcknowledgement = decodeUnknownSync(AcknowledgeInput);
|
|
8905
|
+
var decodePublish = decodeUnknownSync(PublishInput);
|
|
8906
|
+
var decodeRevert = decodeUnknownSync(RevertInput);
|
|
8907
|
+
var decodeRead3 = decodeUnknownSync(ReadHandbookInput);
|
|
8908
|
+
var toolSchema = (schema) => ({
|
|
8909
|
+
...payloadSchemaOf(schema),
|
|
8910
|
+
type: "object",
|
|
8911
|
+
additionalProperties: false
|
|
8912
|
+
});
|
|
8913
|
+
var run2 = async (operation) => {
|
|
8914
|
+
try {
|
|
8915
|
+
const result2 = await operation();
|
|
8916
|
+
const rejected = typeof result2 === "object" && result2 !== null && "status" in result2 && (result2.status === "rejected" || result2.status === "voided");
|
|
8917
|
+
return { text: JSON.stringify(result2), ...rejected ? { isError: true } : {} };
|
|
8918
|
+
} catch (error) {
|
|
8919
|
+
return { text: error instanceof Error ? error.message : String(error), isError: true };
|
|
8920
|
+
}
|
|
8921
|
+
};
|
|
8922
|
+
function registerHandbook(ctx, runtime) {
|
|
8923
|
+
ctx.toolkit({
|
|
8924
|
+
name: "handbook",
|
|
8925
|
+
tools: [
|
|
8926
|
+
{
|
|
8927
|
+
name: "acknowledge_handbook",
|
|
8928
|
+
remoteAvailable: true,
|
|
8929
|
+
description: handbookMessage("ackTool"),
|
|
8930
|
+
inputSchema: toolSchema(AcknowledgeInput),
|
|
8931
|
+
handle: (input, invocation) => run2(() => runtime.ack(decodeAcknowledgement(input).release, invocation))
|
|
8932
|
+
},
|
|
8933
|
+
{
|
|
8934
|
+
name: "publish_handbook",
|
|
8935
|
+
description: handbookMessage("publishTool"),
|
|
8936
|
+
inputSchema: toolSchema(PublishInput),
|
|
8937
|
+
handle: (input, invocation) => run2(() => runtime.publish({ operation: "publish", ...decodePublish(input) }, invocation))
|
|
8938
|
+
},
|
|
8939
|
+
{
|
|
8940
|
+
name: "revert_handbook",
|
|
8941
|
+
description: handbookMessage("revertTool"),
|
|
8942
|
+
inputSchema: toolSchema(RevertInput),
|
|
8943
|
+
handle: (input, invocation) => run2(() => runtime.publish({ operation: "revert", ...decodeRevert(input) }, invocation))
|
|
8944
|
+
}
|
|
8945
|
+
]
|
|
8946
|
+
});
|
|
8947
|
+
ctx.command({
|
|
8948
|
+
name: "handbook",
|
|
8949
|
+
scope: "read",
|
|
8950
|
+
payloadSchema: payloadSchemaOf(ReadHandbookInput),
|
|
8951
|
+
handle: (input) => runtime.read(decodeRead3(input).projectId)
|
|
8952
|
+
});
|
|
8953
|
+
ctx.operations.guard({ operations: ["work.assign"], check: runtime.check });
|
|
8954
|
+
}
|
|
8955
|
+
|
|
8178
8956
|
// src/index.ts
|
|
8179
8957
|
var index_default = definePlugin({
|
|
8180
8958
|
id: "team",
|
|
8181
|
-
apiVersion:
|
|
8959
|
+
apiVersion: 13,
|
|
8182
8960
|
activate(ctx) {
|
|
8183
8961
|
const rosters = createRosterService(ctx);
|
|
8184
8962
|
registerRosterCommands(ctx, rosters);
|
|
@@ -8187,6 +8965,7 @@ var index_default = definePlugin({
|
|
|
8187
8965
|
registerBoardCommands(ctx, goals, rosters);
|
|
8188
8966
|
registerDetailCommands(ctx);
|
|
8189
8967
|
startWakeRuntime(ctx);
|
|
8968
|
+
registerHandbook(ctx, startHandbookRuntime(ctx));
|
|
8190
8969
|
}
|
|
8191
8970
|
});
|
|
8192
8971
|
export {
|