@ycodium-ai/plugin-team 0.2.3116 → 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/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(run2) {
3514
+ constructor(run3) {
3515
3515
  super();
3516
- this.run = run2;
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, run2, annotations, checks, encoding, context2, encodingChecks) {
3673
+ constructor(typeParameters, run3, annotations, checks, encoding, context2, encodingChecks) {
3674
3674
  super(annotations, checks, encoding, context2);
3675
3675
  this.typeParameters = typeParameters;
3676
- this.run = run2;
3676
+ this.run = run3;
3677
3677
  this.encodingChecks = encodingChecks;
3678
3678
  }
3679
3679
  /** @internal */
3680
3680
  getParser() {
3681
- let run2;
3681
+ let run3;
3682
3682
  return (input, options) => {
3683
3683
  if (input === missing) return missingExit;
3684
- return (run2 ??= this.run(this.typeParameters))(input, this, options);
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(run2, annotations = void 0, aborted = false) {
4638
+ constructor(run3, annotations = void 0, aborted = false) {
4639
4639
  super();
4640
- this.run = run2;
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, run2, annotations) => {
6203
- return make8(new Declaration(typeParameters.map(getAST), (typeParameters2) => run2(typeParameters2.map((ast) => make8(ast))), annotations));
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,8 +6862,216 @@ 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.";
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.";
7072
+ 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.";
7073
+ 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.";
7074
+ var LEAD_NO_MEMBER_TASKS = "Do not do members' tasks yourself.";
6866
7075
  var tableCell = (value) => value.replace(/\|/g, "\\|").replace(/[\r\n]/g, " ");
6867
7076
  function leadBrief(goal, roster) {
6868
7077
  const localMembers = goal.threads.filter((thread) => thread.memberId !== goal.leadMemberId).map((thread) => {
@@ -6876,6 +7085,7 @@ function leadBrief(goal, roster) {
6876
7085
  return [
6877
7086
  "You are the lead of this project's team. Your goal is:",
6878
7087
  goal.goal,
7088
+ handbookMessage("leadBrief"),
6879
7089
  "Team members and their assignment identities (your own conversation is omitted):",
6880
7090
  [
6881
7091
  "| Member | threadId / remote principalId |",
@@ -6883,11 +7093,11 @@ function leadBrief(goal, roster) {
6883
7093
  ...localMembers,
6884
7094
  ...remoteMembers
6885
7095
  ].join("\n"),
6886
- "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.",
7096
+ LEAD_SPLIT_RULE,
6887
7097
  'Then use work_add_relation with type "blocked_by" to record dependencies: the dependent workItemId is blocked by the prerequisite relatedWorkItemId.',
6888
- "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.",
6889
- "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.",
6890
- `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>" }]. A local member is told about an item as soon as it is assigned, so assigning before the dependencies exist starts blocked work early. Do not do members' tasks yourself.`,
7098
+ LEAD_OWN_ITEM_RULE,
7099
+ LEAD_SEND_BACK_RULE,
7100
+ `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>" }]. A local member is told about an item as soon as it is assigned, so assigning before the dependencies exist starts blocked work early. ${LEAD_NO_MEMBER_TASKS}`,
6891
7101
  ...remoteMembers.length === 0 ? [] : [
6892
7102
  '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.',
6893
7103
  "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."
@@ -6991,9 +7201,10 @@ function createGoalService(ctx, rosterService, options = {}) {
6991
7201
  const goal = readFact(await goals.get(), projectId);
6992
7202
  if (goal === null) return null;
6993
7203
  const roster = await rosterService.read(projectId);
7204
+ const execution = goalExecution(goal);
6994
7205
  return {
6995
7206
  ...goal,
6996
- members: [...goal.threads, ...goal.remoteMembers ?? []].map((member) => ({
7207
+ members: execution.members.map((member) => ({
6997
7208
  ...member,
6998
7209
  name: roster?.members.find((row) => row.memberId === member.memberId)?.name ?? null
6999
7210
  }))
@@ -7312,7 +7523,9 @@ function registerBoardCommands(ctx, goals, rosters, options = {}) {
7312
7523
  const timer = NodeTimers.setTimeout(notify, delay2);
7313
7524
  return () => NodeTimers.clearTimeout(timer);
7314
7525
  });
7315
- const isMember2 = async (projectId, threadId) => (await goals.read(projectId))?.members.some((member) => member.threadId === threadId) === true;
7526
+ const isMember2 = async (projectId, threadId) => goalExecution(await goals.read(projectId)).members.some(
7527
+ (member) => "threadId" in member && member.threadId === threadId
7528
+ );
7316
7529
  ctx.command({
7317
7530
  name: "board",
7318
7531
  scope: "read",
@@ -7767,12 +7980,42 @@ function createRosterService(ctx, options = {}) {
7767
7980
  };
7768
7981
  }
7769
7982
 
7770
- // src/runtime/wake.ts
7983
+ // src/runtime/deliver.ts
7771
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
+ }
7772
8015
 
7773
8016
  // src/runtime/wakeState.ts
7774
8017
  var WAKE_STATE_KEY = "wakes";
7775
- var WAKE_STATE_VERSION = 4;
8018
+ var WAKE_STATE_VERSION = 5;
7776
8019
  var WAKE_EVENTS = [
7777
8020
  "work.item-created",
7778
8021
  "work.item-updated",
@@ -7791,6 +8034,7 @@ var MemberWokenFact = Struct({
7791
8034
  });
7792
8035
  var emptyProject = () => ({
7793
8036
  threads: [],
8037
+ leadThreads: [],
7794
8038
  remoteMembers: [],
7795
8039
  items: {},
7796
8040
  blockers: {},
@@ -7913,11 +8157,17 @@ function foldWakes(state, event) {
7913
8157
  if (fact.name === "team/goal-started") {
7914
8158
  const { goal } = fact.data;
7915
8159
  const project3 = readFact(state, goal.projectId) ?? emptyProject();
7916
- const threads = [...new Set(goal.threads.map((thread) => thread.threadId))];
8160
+ const { members } = goalExecution(goal);
8161
+ const threads = [
8162
+ ...new Set(members.flatMap((member) => "threadId" in member ? [member.threadId] : []))
8163
+ ];
7917
8164
  const remoteMembers = [
7918
- ...new Set((goal.remoteMembers ?? []).map((member) => member.principalId))
8165
+ ...new Set(
8166
+ members.flatMap((member) => "principalId" in member ? [member.principalId] : [])
8167
+ )
7919
8168
  ];
7920
- const next2 = { ...project3, threads, remoteMembers };
8169
+ const leadThreads = goal.threads.filter((thread) => thread.memberId === goal.leadMemberId).map((thread) => thread.threadId);
8170
+ const next2 = { ...project3, threads, leadThreads, remoteMembers };
7921
8171
  return {
7922
8172
  ...state,
7923
8173
  [goal.projectId]: {
@@ -8041,6 +8291,7 @@ function dueWakes(state) {
8041
8291
  for (const [projectId, project] of Object.entries(state)) {
8042
8292
  const woken = new Set(project.woken);
8043
8293
  const threads = new Set(project.threads);
8294
+ const leads = new Set(project.leadThreads ?? []);
8044
8295
  for (const [workItemId, item] of Object.entries(project.items)) {
8045
8296
  if (item.closed || hasOpenBlocker(project, workItemId)) continue;
8046
8297
  const unblocked = readFact(project.blockedAfterClaim, workItemId) ?? 0;
@@ -8063,7 +8314,8 @@ function dueWakes(state) {
8063
8314
  threadId,
8064
8315
  kind: finishing ? "unblocked" : "item",
8065
8316
  workItemId,
8066
- sequenceId: item.sequenceId
8317
+ sequenceId: item.sequenceId,
8318
+ ...leads.has(threadId) ? { lead: true } : {}
8067
8319
  });
8068
8320
  }
8069
8321
  }
@@ -8082,10 +8334,11 @@ function dueWakes(state) {
8082
8334
  // src/runtime/wake.ts
8083
8335
  var workLabel = (workItemId, sequenceId) => sequenceId === void 0 ? `the work item with workItemId ${workItemId}` : `work item W${sequenceId} (workItemId ${workItemId})`;
8084
8336
  var titled = (workItemId, sequenceId, title) => `${workLabel(workItemId, sequenceId)}${title ? `: ${title}` : ""}`;
8085
- 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}`;
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}`;
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}`;
8086
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.`;
8087
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.`;
8088
- var stableHash = (key) => NodeCrypto.createHash("sha256").update(key).digest("hex").slice(0, 32);
8089
8342
  function startWakeRuntime(ctx, options = {}) {
8090
8343
  const wakes = ctx.derivedState({
8091
8344
  key: WAKE_STATE_KEY,
@@ -8105,35 +8358,18 @@ function startWakeRuntime(ctx, options = {}) {
8105
8358
  for (const wake of dueWakes(state)) {
8106
8359
  try {
8107
8360
  const id = hash2(wake.key);
8108
- if ("principalId" in wake) {
8109
- await ctx.communication.records.send({
8110
- projectId: wake.projectId,
8111
- requestId: `team-wake:${id}`,
8112
- to: { principalId: wake.principalId },
8113
- text: commentText2(wake.workItemId, wake.sequenceId),
8114
- delivery: "wake"
8115
- });
8116
- } else {
8117
- const execution = await ctx.projections.getThreadExecution(wake.threadId);
8118
- if (execution === null || execution.archivedAt !== null) continue;
8119
- await ctx.threads.turns.start({
8120
- requestId: `team-wake:${id}`,
8121
- threadId: wake.threadId,
8122
- message: {
8123
- messageId: `team-wake-msg:${id}`,
8124
- role: "user",
8125
- text: wake.kind === "comment" ? commentText2(wake.workItemId, wake.sequenceId) : (wake.kind === "item" ? itemText : unblockedText)(
8126
- wake.workItemId,
8127
- wake.sequenceId,
8128
- await readTitle(wake.projectId, wake.workItemId)
8129
- ),
8130
- attachments: []
8131
- },
8132
- modelSelection: execution.modelSelection,
8133
- runtimeMode: execution.runtimeMode,
8134
- interactionMode: execution.interactionMode
8135
- });
8136
- }
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;
8137
8373
  const receipt = await ctx.events.append({
8138
8374
  commandId: `team-woken:${id}`,
8139
8375
  name: "member-woken",
@@ -8165,10 +8401,562 @@ function startWakeRuntime(ctx, options = {}) {
8165
8401
  return { flush, ready };
8166
8402
  }
8167
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
+
8168
8956
  // src/index.ts
8169
8957
  var index_default = definePlugin({
8170
8958
  id: "team",
8171
- apiVersion: 12,
8959
+ apiVersion: 13,
8172
8960
  activate(ctx) {
8173
8961
  const rosters = createRosterService(ctx);
8174
8962
  registerRosterCommands(ctx, rosters);
@@ -8177,6 +8965,7 @@ var index_default = definePlugin({
8177
8965
  registerBoardCommands(ctx, goals, rosters);
8178
8966
  registerDetailCommands(ctx);
8179
8967
  startWakeRuntime(ctx);
8968
+ registerHandbook(ctx, startHandbookRuntime(ctx));
8180
8969
  }
8181
8970
  });
8182
8971
  export {