@ycodium-ai/plugin-team 0.2.3120 → 0.2.3130

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
@@ -1,9 +1,6 @@
1
1
  // src/index.ts
2
2
  import { definePlugin } from "@ycodium-ai/plugin-api";
3
3
 
4
- // src/board.ts
5
- import * as NodeTimers from "node:timers";
6
-
7
4
  // ../../node_modules/.pnpm/effect@4.0.0-beta.103_patch_hash=78523ded7c3dfc3efd3cec0247f4960d00eaa3d3c96a2077457694a4d8b453c6/node_modules/effect/dist/Pipeable.js
8
5
  var pipeArguments = (self, args2) => {
9
6
  switch (args2.length) {
@@ -3511,9 +3508,9 @@ var fromOptionExit = (option2) => option2._tag === "None" ? missingExit : succee
3511
3508
  // ../../node_modules/.pnpm/effect@4.0.0-beta.103_patch_hash=78523ded7c3dfc3efd3cec0247f4960d00eaa3d3c96a2077457694a4d8b453c6/node_modules/effect/dist/SchemaGetter.js
3512
3509
  var Getter = class _Getter extends Class {
3513
3510
  run;
3514
- constructor(run2) {
3511
+ constructor(run3) {
3515
3512
  super();
3516
- this.run = run2;
3513
+ this.run = run3;
3517
3514
  }
3518
3515
  map(f) {
3519
3516
  return new _Getter((oe, options) => this.run(oe, options).pipe(mapEager2(map(f))));
@@ -3670,18 +3667,18 @@ var Declaration = class _Declaration extends Base2 {
3670
3667
  typeParameters;
3671
3668
  run;
3672
3669
  encodingChecks;
3673
- constructor(typeParameters, run2, annotations, checks, encoding, context2, encodingChecks) {
3670
+ constructor(typeParameters, run3, annotations, checks, encoding, context2, encodingChecks) {
3674
3671
  super(annotations, checks, encoding, context2);
3675
3672
  this.typeParameters = typeParameters;
3676
- this.run = run2;
3673
+ this.run = run3;
3677
3674
  this.encodingChecks = encodingChecks;
3678
3675
  }
3679
3676
  /** @internal */
3680
3677
  getParser() {
3681
- let run2;
3678
+ let run3;
3682
3679
  return (input, options) => {
3683
3680
  if (input === missing) return missingExit;
3684
- return (run2 ??= this.run(this.typeParameters))(input, this, options);
3681
+ return (run3 ??= this.run(this.typeParameters))(input, this, options);
3685
3682
  };
3686
3683
  }
3687
3684
  _rebuild(recur, checks, encodingChecks) {
@@ -3843,7 +3840,7 @@ function numberToJson(checks) {
3843
3840
  return new Link(new Union([encodedFinite, nonFiniteLiterals], "anyOf"), new Transformation(Number3(), transform((n) => globalThis.Number.isFinite(n) ? n : globalThis.String(n))));
3844
3841
  }
3845
3842
  var number2 = /* @__PURE__ */ new Number4();
3846
- var Boolean = class extends Base2 {
3843
+ var Boolean2 = class extends Base2 {
3847
3844
  _tag = "Boolean";
3848
3845
  /** @internal */
3849
3846
  getParser() {
@@ -3854,7 +3851,7 @@ var Boolean = class extends Base2 {
3854
3851
  return "boolean";
3855
3852
  }
3856
3853
  };
3857
- var boolean = /* @__PURE__ */ new Boolean();
3854
+ var boolean = /* @__PURE__ */ new Boolean2();
3858
3855
  var Arrays = class _Arrays extends Base2 {
3859
3856
  _tag = "Arrays";
3860
3857
  isMutable;
@@ -4635,9 +4632,9 @@ var Filter2 = class _Filter extends Class {
4635
4632
  * Whether the parsing process should be aborted after this check has failed.
4636
4633
  */
4637
4634
  aborted;
4638
- constructor(run2, annotations = void 0, aborted = false) {
4635
+ constructor(run3, annotations = void 0, aborted = false) {
4639
4636
  super();
4640
- this.run = run2;
4637
+ this.run = run3;
4641
4638
  this.annotations = annotations;
4642
4639
  this.aborted = aborted;
4643
4640
  }
@@ -6199,8 +6196,8 @@ function fromASTs(asts) {
6199
6196
 
6200
6197
  // ../../node_modules/.pnpm/effect@4.0.0-beta.103_patch_hash=78523ded7c3dfc3efd3cec0247f4960d00eaa3d3c96a2077457694a4d8b453c6/node_modules/effect/dist/Schema.js
6201
6198
  function declareConstructor() {
6202
- return (typeParameters, run2, annotations) => {
6203
- return make8(new Declaration(typeParameters.map(getAST), (typeParameters2) => run2(typeParameters2.map((ast) => make8(ast))), annotations));
6199
+ return (typeParameters, run3, annotations) => {
6200
+ return make8(new Declaration(typeParameters.map(getAST), (typeParameters2) => run3(typeParameters2.map((ast) => make8(ast))), annotations));
6204
6201
  };
6205
6202
  }
6206
6203
  function declare(is2, annotations) {
@@ -6265,7 +6262,7 @@ var Null2 = /* @__PURE__ */ make8(null_);
6265
6262
  var Undefined2 = /* @__PURE__ */ make8(undefined_2);
6266
6263
  var String4 = /* @__PURE__ */ make8(string2);
6267
6264
  var Number5 = /* @__PURE__ */ make8(number2);
6268
- var Boolean2 = /* @__PURE__ */ make8(boolean);
6265
+ var Boolean3 = /* @__PURE__ */ make8(boolean);
6269
6266
  function makeStruct(ast, fields) {
6270
6267
  return make8(ast, {
6271
6268
  fields,
@@ -6379,6 +6376,7 @@ function isBase64(annotations) {
6379
6376
  ...annotations
6380
6377
  });
6381
6378
  }
6379
+ var Finite = /* @__PURE__ */ make8(finite);
6382
6380
  function makeIsGreaterThanOrEqualTo(options) {
6383
6381
  const gte = isGreaterThanOrEqualTo(options.order);
6384
6382
  const formatter = options.formatter ?? format;
@@ -6861,6 +6859,272 @@ function readFact(state, key) {
6861
6859
  var nowIso = map5(now2, formatIso2);
6862
6860
  var factTime = () => runPromise2(nowIso);
6863
6861
 
6862
+ // src/domain/execution.ts
6863
+ function goalExecution(goal) {
6864
+ return {
6865
+ leadMemberId: goal?.leadMemberId ?? null,
6866
+ members: goal === null ? [] : [...goal.threads, ...goal.remoteMembers ?? []]
6867
+ };
6868
+ }
6869
+
6870
+ // locales/en.json
6871
+ var en_default = {
6872
+ handbook: {
6873
+ requestConflict: "This requestId was already used for a different handbook request. Use a new requestId.",
6874
+ requestVoided: "This requestId was abandoned before the handbook received it. Use a new requestId.",
6875
+ 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.",
6876
+ 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).",
6877
+ 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).",
6878
+ unknownMember: "Your verified caller identity is not mapped to a member of this project's current goal.",
6879
+ leadOnly: "Only the local team lead member of the current goal can publish or revert the handbook.",
6880
+ noGoal: "Start a goal before publishing or reverting the handbook. Without a goal there are no team members, acknowledgement gates or reminders.",
6881
+ noRequirement: "No handbook release requires acknowledgement yet.",
6882
+ wrongRelease: "Acknowledge handbook release {{release}}, the currently required version.",
6883
+ guard: "First acknowledge handbook release {{release}} (team_acknowledge_handbook).",
6884
+ reminder: "Handbook release {{release}} requires acknowledgement. Read wiki_read_handbook, then call team_acknowledge_handbook with this release number.",
6885
+ 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.",
6886
+ 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."
6887
+ },
6888
+ tab: { title: "Teams" },
6889
+ board: {
6890
+ title: "Project board",
6891
+ newItem: "New work item",
6892
+ itemTitle: "Title",
6893
+ description: "Description (optional)",
6894
+ assignee: "Assign to (optional)",
6895
+ create: "Create work item",
6896
+ retryCreate: "Retry creation",
6897
+ saving: "Saving\u2026",
6898
+ moveTo: "Move to\u2026",
6899
+ moveItem: "Move work item #{{number}}",
6900
+ moveItemTo: "Move work item #{{number}} to {{state}}",
6901
+ assignTo: "Assign to\u2026",
6902
+ assignItem: "Assign work item #{{number}}",
6903
+ assignItemTo: "Assign work item #{{number}} to {{name}}",
6904
+ unassign: "Unassign",
6905
+ unassignItem: "Unassign work item #{{number}}",
6906
+ dragItem: "Drag work item #{{number}} to another state",
6907
+ untitled: "(Untitled)",
6908
+ other: "Other",
6909
+ unassigned: "Unassigned",
6910
+ blockedBy: "Blocked by {{count}} work items",
6911
+ goalRequired: "Start a goal above before assigning work to members.",
6912
+ loading: "Loading board\u2026",
6913
+ loadFailed: "The board could not be loaded. Try reloading it.",
6914
+ watchFailed: "Live board updates are unavailable. Reload to reconnect.",
6915
+ createFailed: "The work item could not be created. Retry with the same request.",
6916
+ updateFailed: "The work item could not be changed. Reloading the board.",
6917
+ staleVersion: "The work item was changed elsewhere. Your change was rolled back and the board is being reloaded.",
6918
+ notAMember: "This thread is not a member of the current goal. Choose a current member.",
6919
+ invalidTitle: "Enter a title for the work item.",
6920
+ reload: "Reload board",
6921
+ truncated: "Showing the first 500 work items in this project."
6922
+ },
6923
+ detail: {
6924
+ title: "Work item details",
6925
+ open: "Details",
6926
+ openItem: "Open details for work item #{{number}}",
6927
+ close: "Close",
6928
+ via: "via {{source}}",
6929
+ description: "Description",
6930
+ noDescription: "(No description)",
6931
+ blockedBy: "Blocked by these work items",
6932
+ blocking: "Blocking these work items",
6933
+ dependency: "#{{number}} {{title}} \xB7 {{state}}",
6934
+ comments: "Comments",
6935
+ noComments: "No comments yet.",
6936
+ deletedComment: "(This comment has been deleted)",
6937
+ commentsTruncated: "Showing the latest 200 comments, including replies.",
6938
+ reply: "Reply",
6939
+ replyTo: "Reply to comment {{number}}",
6940
+ newComment: "Post a comment",
6941
+ send: "Send",
6942
+ sendComment: "Send comment",
6943
+ sendReply: "Send reply to comment {{number}}",
6944
+ sending: "Sending\u2026",
6945
+ retry: "Retry",
6946
+ retryComment: "Retry comment",
6947
+ retryReply: "Retry reply to comment {{number}}",
6948
+ invalidBody: "The comment must be 1\u20138000 characters after trimming spaces.",
6949
+ commentFailed: "The comment couldn't be posted. Try again.",
6950
+ loading: "Loading work item details\u2026",
6951
+ loadFailed: "The work item details could not be loaded. Try reloading them.",
6952
+ reload: "Reload details",
6953
+ notFound: "This work item was not found in this project.",
6954
+ actor: {
6955
+ you: "You",
6956
+ human: "A person",
6957
+ otherAgent: "Other agent",
6958
+ team: "Teams plugin",
6959
+ plugin: "Plugin {{pluginId}}",
6960
+ localCli: "Local command line",
6961
+ system: "System",
6962
+ remoteAgentFallback: "Remote agent"
6963
+ },
6964
+ history: {
6965
+ title: "Activity history",
6966
+ empty: "No activity yet.",
6967
+ truncated: "Showing the latest 200 activities.",
6968
+ deletedState: "(Deleted state)",
6969
+ created: "{{author}} created this work item",
6970
+ createdAssigned: "{{author}} created this work item and assigned it to {{names}}",
6971
+ stateChanged: "{{author}} changed the state from {{old}} to {{new}}",
6972
+ assigned: "{{author}} assigned this work item to {{names}}",
6973
+ unassigned: "{{author}} removed the assignment",
6974
+ titleChanged: "{{author}} changed the title",
6975
+ descriptionChanged: "{{author}} changed the description",
6976
+ claimed: "{{author}} claimed this work item",
6977
+ commented: "{{author}} posted a comment",
6978
+ relationAdded: "{{author}} added a dependency",
6979
+ relationRemoved: "{{author}} removed a dependency"
6980
+ }
6981
+ },
6982
+ goal: {
6983
+ current: "Current goal: {{goal}}",
6984
+ lead: "Lead: {{name}}",
6985
+ unknownLead: "Member no longer in the roster",
6986
+ openLead: "Open lead thread",
6987
+ label: "Goal for the lead",
6988
+ rosterRequired: "Save the roster and choose a lead first.",
6989
+ start: "Start",
6990
+ startNew: "Start a new goal",
6991
+ starting: "Starting\u2026",
6992
+ retry: "Retry",
6993
+ reload: "Reload goal",
6994
+ loading: "Loading goal\u2026",
6995
+ confirmTitle: "Start a new goal?",
6996
+ confirmDescription: "Threads for the previous goal will keep running and remain in the thread list.",
6997
+ confirmStart: "Start new goal",
6998
+ cancel: "Cancel",
6999
+ noRoster: "No saved roster was found. Save the roster and choose a lead first.",
7000
+ invalidPayload: "Check the goal fields and try again.",
7001
+ invalidGoal: "The goal must be 1\u20134000 characters after trimming spaces.",
7002
+ invalidRequestId: "The request ID must be 1\u201364 letters, numbers, underscores, or hyphens.",
7003
+ loadFailed: "The current goal could not be loaded. Try reloading it.",
7004
+ startFailed: "The goal could not be fully started. Retry to finish creating the threads and sending the lead's brief.",
7005
+ remoteMembers: "Remote members:"
7006
+ },
7007
+ roster: {
7008
+ title: "Roster",
7009
+ members: "Members",
7010
+ memberName: "Member {{number}} name",
7011
+ lead: "Lead",
7012
+ leadFor: "Make member {{number}} the lead",
7013
+ remove: "Remove member {{number}}",
7014
+ add: "Add member",
7015
+ addRemoteAgent: "Add remote agent",
7016
+ noRemoteAgents: "There are no available remote agents on this machine yet.",
7017
+ remote: "Remote",
7018
+ save: "Save",
7019
+ saving: "Saving\u2026",
7020
+ saved: "Roster saved",
7021
+ loading: "Loading roster\u2026",
7022
+ retry: "Reload roster",
7023
+ emptyTitle: "No team roster yet",
7024
+ emptyDescription: "Add members and choose a lead for this project.",
7025
+ toolsHint: "Keep the board and project memory on for this project so members can pick up work and record what they learn.",
7026
+ toolkitsOff: "The lead needs the board and project memory. Turn on both switches above.",
7027
+ toolkitsOn: "The board and project memory are on for this project.",
7028
+ staleRevision: "The roster was changed elsewhere and has been reloaded.",
7029
+ loadFailed: "The roster could not be loaded. Try reloading it.",
7030
+ saveFailed: "The roster could not be saved. Try again.",
7031
+ invalidPayload: "Check the roster fields and try again.",
7032
+ memberCount: "A team needs between 1 and 8 members.",
7033
+ invalidMemberId: "Member IDs must be 1\u201364 letters, numbers, underscores, or hyphens.",
7034
+ invalidName: "Member names must be 1\u201340 characters after trimming spaces.",
7035
+ duplicateMemberId: "Each member needs a different ID.",
7036
+ duplicateName: "Member names must be different, ignoring capitalization and surrounding spaces.",
7037
+ leadNotMember: "Choose a lead from the members.",
7038
+ leadRemote: "The lead must be a local member.",
7039
+ duplicatePrincipal: "Each remote agent can appear only once in the roster.",
7040
+ invalidPrincipal: "A remote agent must have a non-empty principal ID.",
7041
+ allRemoteAgentsAdded: "All available remote agents are already on the roster.",
7042
+ remoteAgentsUnavailable: "Could not read the remote agents on this machine. Try again later.",
7043
+ remoteSource: "Remote agent: {{label}}"
7044
+ },
7045
+ handbookPanel: {
7046
+ title: "Handbook",
7047
+ open: "View handbook",
7048
+ close: "Close handbook",
7049
+ current: "Current version {{version}}",
7050
+ required: "Version {{version}} currently requires acknowledgement",
7051
+ noCurrent: "No handbook has been published",
7052
+ noRequired: "No version has required acknowledgement",
7053
+ handbook_decisions: "Decisions",
7054
+ handbook_rules: "Rules",
7055
+ handbook_progress: "Progress",
7056
+ missingPage: "This page is absent from the current version",
7057
+ erased: "Erased",
7058
+ unavailable: "Content is unavailable",
7059
+ uneditable: "This page cannot be edited because its content is erased or unavailable",
7060
+ loading: "Loading handbook\u2026",
7061
+ loadFailed: "Could not read the handbook. Reload to try again.",
7062
+ reload: "Reload handbook",
7063
+ moreBody: "Read more",
7064
+ moreBodyFailed: "Couldn't load the rest of this page. Click Read more to try again.",
7065
+ releases: "Version history",
7066
+ noReleases: "No versions yet",
7067
+ version: "Version {{version}}",
7068
+ publishedBy: "Published by {{author}}",
7069
+ requiresAck: "Acknowledgement required",
7070
+ noAck: "Acknowledgement not required",
7071
+ restoredFrom: "Restored from version {{version}}",
7072
+ moreReleases: "Load older versions",
7073
+ members: "Member acknowledgements",
7074
+ noMembers: "No executing members yet. Start a goal to assign team seats.",
7075
+ member: "Member {{id}}",
7076
+ neverAck: "Has not acknowledged a version",
7077
+ acked: "Acknowledged version {{version}}",
7078
+ pendingAck: "Needs to acknowledge version {{version}}",
7079
+ edit: "Edit handbook",
7080
+ pageTitle: "{{page}} \u2014 title (optional)",
7081
+ pageBody: "{{page}} \u2014 body",
7082
+ pageSummary: "{{page}} \u2014 change summary (optional)",
7083
+ ackChoice: "Require acknowledgement for changes to Decisions",
7084
+ ackPolicy: "Rules changes always require acknowledgement; Progress-only changes don't; Decisions changes follow this choice.",
7085
+ cancel: "Cancel",
7086
+ publish: "Save and publish",
7087
+ saved: "Handbook published",
7088
+ conflict: "Someone else changed the handbook. Your edits are kept. Review the latest version shown below before saving again.",
7089
+ failed: "Publishing did not finish. Click Save and publish again; as long as you don't change the edits, it counts as the same publication and won't publish twice.",
7090
+ revert: "Revert to this version",
7091
+ revertVersion: "Revert to version {{version}}",
7092
+ revertTitle: "Revert handbook to version {{version}}?",
7093
+ revertDescription: "Publish the content of version {{version}} as a new version, under your name. The current version remains in history.",
7094
+ confirmRevert: "Revert and publish",
7095
+ historyFailed: "Couldn't load the version history. Try again.",
7096
+ emptyPages: "Once the handbook is published, its three pages show here.",
7097
+ fieldTitle: "Title (optional)",
7098
+ fieldBody: "Body",
7099
+ fieldSummary: "Change summary (optional)",
7100
+ currentBadge: "Current",
7101
+ currentReadOnly: "Current handbook (read-only)",
7102
+ discardTitle: "Discard your unsaved edits?",
7103
+ discardDescription: "Your edits to the handbook pages will be lost.",
7104
+ keepEditing: "Keep editing",
7105
+ discard: "Discard edits"
7106
+ },
7107
+ remoteStatus: {
7108
+ title: "Remote members",
7109
+ none: "No remote members on the roster. Add one with \u201CAdd remote agent\u201D in the roster.",
7110
+ lastSeen: "Last seen {{time}}",
7111
+ neverSeen: "Not seen yet",
7112
+ workUnavailable: "Could not read this member's work. Try again later.",
7113
+ noWork: "No work assigned.",
7114
+ item: "#{{number}} {{title}}",
7115
+ disabled: "Disabled",
7116
+ revoked: "No active credentials",
7117
+ lastSeenUnavailable: "Could not read when it was last seen",
7118
+ blocked: "Blocked",
7119
+ more: "{{count}} more not listed here"
7120
+ }
7121
+ };
7122
+
7123
+ // src/handbook/messages.ts
7124
+ function handbookMessage(key, release) {
7125
+ return en_default.handbook[key].replace(/{{release}}/g, String(release ?? ""));
7126
+ }
7127
+
6864
7128
  // src/domain/leadBrief.ts
6865
7129
  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
7130
  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 +7144,7 @@ function leadBrief(goal, roster) {
6880
7144
  return [
6881
7145
  "You are the lead of this project's team. Your goal is:",
6882
7146
  goal.goal,
7147
+ handbookMessage("leadBrief"),
6883
7148
  "Team members and their assignment identities (your own conversation is omitted):",
6884
7149
  [
6885
7150
  "| Member | threadId / remote principalId |",
@@ -6995,9 +7260,10 @@ function createGoalService(ctx, rosterService, options = {}) {
6995
7260
  const goal = readFact(await goals.get(), projectId);
6996
7261
  if (goal === null) return null;
6997
7262
  const roster = await rosterService.read(projectId);
7263
+ const execution = goalExecution(goal);
6998
7264
  return {
6999
7265
  ...goal,
7000
- members: [...goal.threads, ...goal.remoteMembers ?? []].map((member) => ({
7266
+ members: execution.members.map((member) => ({
7001
7267
  ...member,
7002
7268
  name: roster?.members.find((row) => row.memberId === member.memberId)?.name ?? null
7003
7269
  }))
@@ -7038,8 +7304,8 @@ function createGoalService(ctx, rosterService, options = {}) {
7038
7304
  });
7039
7305
  await goals.get({ throughSequence: receipt.sequence });
7040
7306
  goal = readFact(await requests.get({ throughSequence: receipt.sequence }), key) ?? goal;
7041
- const project = await ctx.projects.get({ projectId: goal.projectId });
7042
- const selection = await ctx.providers.resolveSelection({ base: project.defaultModelSelection });
7307
+ const project2 = await ctx.projects.get({ projectId: goal.projectId });
7308
+ const selection = await ctx.providers.resolveSelection({ base: project2.defaultModelSelection });
7043
7309
  const orderedThreads = [
7044
7310
  ...goal.threads.filter((thread) => thread.memberId === goal.leadMemberId),
7045
7311
  ...goal.threads.filter((thread) => thread.memberId !== goal.leadMemberId)
@@ -7101,16 +7367,32 @@ function createGoalService(ctx, rosterService, options = {}) {
7101
7367
  // src/commands.ts
7102
7368
  var decodeReadRoster = decodeUnknownSync(ReadRosterInput);
7103
7369
  var decodeReadGoal = decodeUnknownSync(ReadGoalInput);
7104
- var mergeAllOf = (node) => {
7105
- if (Array.isArray(node)) return node.map(mergeAllOf);
7370
+ var commandSchema = (node) => {
7371
+ if (Array.isArray(node)) return node.map(commandSchema);
7106
7372
  if (typeof node !== "object" || node === null) return node;
7107
7373
  const { allOf, ...rest } = node;
7108
7374
  const merged = Object.fromEntries(
7109
- Object.entries(rest).map(([key, value]) => [key, mergeAllOf(value)])
7375
+ Object.entries(rest).map(([key, value]) => [key, commandSchema(value)])
7110
7376
  );
7111
- return Array.isArray(allOf) ? Object.assign(merged, ...allOf.map(mergeAllOf)) : merged;
7377
+ if (Array.isArray(allOf)) Object.assign(merged, ...allOf.map(commandSchema));
7378
+ if (!Array.isArray(merged.anyOf) || merged.anyOf.length !== 2) return merged;
7379
+ const branches = merged.anyOf.filter(
7380
+ (branch) => typeof branch === "object" && branch !== null
7381
+ );
7382
+ if (branches.length !== 2) return merged;
7383
+ const base = branches.find((branch) => branch.type !== "null");
7384
+ if (!branches.some((branch) => branch.type === "null") || base === void 0 || typeof base.type !== "string" || !["string", "number", "integer", "boolean"].includes(base.type))
7385
+ return merged;
7386
+ const { anyOf: _, ...common } = merged;
7387
+ const { const: literal, ...constraints } = base;
7388
+ return {
7389
+ ...common,
7390
+ ...constraints,
7391
+ type: [base.type, "null"],
7392
+ ...Object.hasOwn(base, "const") ? { enum: [literal, null] } : Array.isArray(base.enum) ? { enum: [...base.enum, null] } : {}
7393
+ };
7112
7394
  };
7113
- var payloadSchemaOf = (schema) => mergeAllOf(toJsonSchemaDocument2(schema).schema);
7395
+ var payloadSchemaOf = (schema) => commandSchema(toJsonSchemaDocument2(schema).schema);
7114
7396
  function registerRosterCommands(ctx, service2) {
7115
7397
  ctx.command({
7116
7398
  name: "roster",
@@ -7140,57 +7422,8 @@ function registerGoalCommands(ctx, service2) {
7140
7422
  });
7141
7423
  }
7142
7424
 
7143
- // src/workReads.ts
7144
- var PAGE_SIZE = 200;
7145
- async function readPages(maximum, read, keep = "oldest") {
7146
- let items = [];
7147
- let truncated = false;
7148
- let cursor = null;
7149
- do {
7150
- const page = await read({ limit: PAGE_SIZE, ...cursor === null ? {} : { cursor } });
7151
- items.push(...page.items);
7152
- cursor = page.nextCursor;
7153
- if (keep === "oldest" && items.length >= maximum) {
7154
- return {
7155
- items: items.slice(0, maximum),
7156
- truncated: items.length > maximum || cursor !== null
7157
- };
7158
- }
7159
- if (items.length > maximum) {
7160
- truncated = true;
7161
- items = items.slice(-maximum);
7162
- }
7163
- } while (cursor !== null);
7164
- return { items, truncated };
7165
- }
7166
-
7167
- // src/workWrites.ts
7168
- var decodeWorkRefusal = decodeUnknownOption2(
7169
- Struct({
7170
- rejection: Struct({
7171
- code: String4,
7172
- details: optionalKey2(Unknown2)
7173
- })
7174
- })
7175
- );
7176
- var rejectWork = (commandType, code) => ({ _tag: "TeamBoardRejection", commandType, code });
7177
- async function writeWork(invocation, commandType, write) {
7178
- if (invocation.asPerson === void 0) return rejectWork(commandType, "needs-person");
7179
- try {
7180
- return await write(invocation.asPerson.work);
7181
- } catch (error) {
7182
- const refusal = decodeWorkRefusal(error);
7183
- if (isNone2(refusal)) throw error;
7184
- const { code, details } = refusal.value.rejection;
7185
- return {
7186
- ...rejectWork(
7187
- commandType,
7188
- commandType !== "postComment" && code === "version-conflict" ? "stale-version" : code
7189
- ),
7190
- ...details === void 0 ? {} : { details }
7191
- };
7192
- }
7193
- }
7425
+ // src/watch.ts
7426
+ import * as NodeTimers from "node:timers";
7194
7427
 
7195
7428
  // src/boardContracts.ts
7196
7429
  var BoardState = Struct({
@@ -7228,7 +7461,7 @@ var BoardRemoteWork = Struct({
7228
7461
  pending: NullOr(
7229
7462
  Struct({
7230
7463
  count: Number5,
7231
- items: ArraySchema(Struct({ workItemId: String4, blocked: Boolean2 }))
7464
+ items: ArraySchema(Struct({ workItemId: String4, blocked: Boolean3 }))
7232
7465
  })
7233
7466
  )
7234
7467
  });
@@ -7254,8 +7487,8 @@ var BoardSnapshot = Struct({
7254
7487
  remoteWork: ArraySchema(BoardRemoteWork),
7255
7488
  viewerKey: optionalKey2(String4),
7256
7489
  // 负责人要用看板与项目记忆两组工具;开关由宿主控件拨,这里只读来提示。
7257
- toolkits: Struct({ board: Boolean2, memory: Boolean2 }),
7258
- truncated: Boolean2
7490
+ toolkits: Struct({ board: Boolean3, memory: Boolean3 }),
7491
+ truncated: Boolean3
7259
7492
  });
7260
7493
  var ReadBoardInput = Struct({ projectId: String4 });
7261
7494
  var CreateItemInput = Struct({
@@ -7271,7 +7504,7 @@ var UpdateItemInput = Struct({
7271
7504
  expectedVersion: Int.check(isGreaterThanOrEqualTo3(1)),
7272
7505
  stateId: optionalKey2(String4),
7273
7506
  assigneeThreadId: optionalKey2(String4),
7274
- unassign: optionalKey2(Boolean2)
7507
+ unassign: optionalKey2(Boolean3)
7275
7508
  });
7276
7509
  var BoardRejection = Struct({
7277
7510
  _tag: Literal2("TeamBoardRejection"),
@@ -7288,18 +7521,14 @@ var UpdateItemResult = Union2([
7288
7521
  BoardRejection
7289
7522
  ]);
7290
7523
 
7291
- // src/board.ts
7292
- var ITEM_LIMIT = 500;
7293
- var PEOPLE_LIMIT = 200;
7294
- var REMOTE_AGENT_LIMIT = 1e3;
7524
+ // src/watch.ts
7525
+ var emptyChanges = () => ({ cursor: 0, handbookCursor: 0 });
7295
7526
  var decodeRead = decodeUnknownSync(ReadBoardInput);
7296
- var decodeCreate = decodeUnknownOption2(CreateItemInput);
7297
- var decodeUpdate = decodeUnknownOption2(UpdateItemInput);
7298
7527
  var decodeProject = decodeUnknownOption2(Struct({ projectId: String4 }));
7299
7528
  var decodeFact = decodeUnknownOption2(
7300
7529
  Struct({ pluginId: Literal2("team"), name: String4 })
7301
7530
  );
7302
- var WORK_EVENTS = [
7531
+ var WORK_EVENTS = /* @__PURE__ */ new Set([
7303
7532
  "work.item-created",
7304
7533
  "work.item-updated",
7305
7534
  "work.item-claimed",
@@ -7307,22 +7536,168 @@ var WORK_EVENTS = [
7307
7536
  "work.relation-removed",
7308
7537
  "work.comment-posted",
7309
7538
  "work.states-seeded",
7310
- // 快照带看板与记忆两只开关的状态,拨一下就敲门。
7311
7539
  "board.toolkit-set",
7312
7540
  "memory.toolkit-set"
7313
- ];
7314
- function registerBoardCommands(ctx, goals, rosters, options = {}) {
7541
+ ]);
7542
+ var HANDBOOK_EVENTS = /* @__PURE__ */ new Set([
7543
+ "wiki.handbook-released",
7544
+ "wiki.page-erased",
7545
+ "memory.toolkit-set"
7546
+ ]);
7547
+ function registerTeamWatch(ctx, options) {
7315
7548
  const after = options.after ?? ((delay2, notify) => {
7316
7549
  const timer = NodeTimers.setTimeout(notify, delay2);
7317
7550
  return () => NodeTimers.clearTimeout(timer);
7318
7551
  });
7319
- const isMember2 = async (projectId, threadId) => (await goals.read(projectId))?.members.some((member) => member.threadId === threadId) === true;
7552
+ const listeners = /* @__PURE__ */ new Map();
7553
+ const cursors = /* @__PURE__ */ new Map();
7554
+ const timers = /* @__PURE__ */ new Map();
7555
+ let disposed = false;
7556
+ const changed = (event, board, handbook) => {
7557
+ const payload = decodeProject(event.payload);
7558
+ const projectId = isSome2(payload) ? payload.value.projectId : event.aggregateKind === "project" ? event.aggregateId : null;
7559
+ if (projectId === null || disposed || !listeners.has(projectId)) return;
7560
+ const previous = cursors.get(projectId) ?? emptyChanges();
7561
+ cursors.set(projectId, {
7562
+ cursor: board ? Math.max(previous.cursor, event.sequence) : previous.cursor,
7563
+ handbookCursor: handbook ? Math.max(previous.handbookCursor, event.sequence) : previous.handbookCursor
7564
+ });
7565
+ if (timers.has(projectId)) return;
7566
+ timers.set(
7567
+ projectId,
7568
+ after(50, () => {
7569
+ timers.delete(projectId);
7570
+ for (const emit of [...listeners.get(projectId) ?? []])
7571
+ emit(cursors.get(projectId) ?? emptyChanges());
7572
+ })
7573
+ );
7574
+ };
7575
+ const stops = [.../* @__PURE__ */ new Set([...WORK_EVENTS, ...HANDBOOK_EVENTS])].map(
7576
+ (type) => ctx.events.onRaw(
7577
+ type,
7578
+ (event) => changed(event, WORK_EVENTS.has(type), HANDBOOK_EVENTS.has(type))
7579
+ )
7580
+ );
7581
+ stops.push(
7582
+ ctx.events.onRaw("plugin.event-appended", (event) => {
7583
+ const fact = decodeFact(event.payload);
7584
+ if (isNone2(fact)) return;
7585
+ const name = fact.value.name;
7586
+ const board = name === "team/goal-started" || name === "team/roster-set";
7587
+ const handbook = name === "team/goal-started" || name === "team/handbook-acknowledged" || name === "team/handbook-publication-completed";
7588
+ if (board || handbook) changed(event, board, handbook);
7589
+ })
7590
+ );
7591
+ ctx.stream({
7592
+ name: "watch",
7593
+ scope: "read",
7594
+ payloadSchema: payloadSchemaOf(ReadBoardInput),
7595
+ buffer: { latest: 1 },
7596
+ handle: async (payload, emit, signal) => {
7597
+ if (signal.aborted || disposed) return;
7598
+ const { projectId } = decodeRead(payload);
7599
+ const subscribers = listeners.get(projectId) ?? /* @__PURE__ */ new Set();
7600
+ const ring = (value) => {
7601
+ if (!signal.aborted && !disposed) emit(value);
7602
+ };
7603
+ listeners.set(projectId, subscribers);
7604
+ subscribers.add(ring);
7605
+ try {
7606
+ ring(cursors.get(projectId) ?? emptyChanges());
7607
+ await new Promise((resolve2) => {
7608
+ if (signal.aborted) resolve2();
7609
+ else signal.addEventListener("abort", () => resolve2(), { once: true });
7610
+ });
7611
+ } finally {
7612
+ subscribers.delete(ring);
7613
+ if (subscribers.size === 0) {
7614
+ listeners.delete(projectId);
7615
+ timers.get(projectId)?.();
7616
+ timers.delete(projectId);
7617
+ cursors.delete(projectId);
7618
+ }
7619
+ }
7620
+ }
7621
+ });
7622
+ ctx.onDispose(() => {
7623
+ disposed = true;
7624
+ for (const stop of stops) stop();
7625
+ for (const cancel of timers.values()) cancel();
7626
+ timers.clear();
7627
+ listeners.clear();
7628
+ cursors.clear();
7629
+ });
7630
+ }
7631
+
7632
+ // src/workReads.ts
7633
+ var PAGE_SIZE = 200;
7634
+ async function readPages(maximum, read, keep = "oldest") {
7635
+ let items = [];
7636
+ let truncated = false;
7637
+ let cursor = null;
7638
+ do {
7639
+ const page = await read({ limit: PAGE_SIZE, ...cursor === null ? {} : { cursor } });
7640
+ items.push(...page.items);
7641
+ cursor = page.nextCursor;
7642
+ if (keep === "oldest" && items.length >= maximum) {
7643
+ return {
7644
+ items: items.slice(0, maximum),
7645
+ truncated: items.length > maximum || cursor !== null
7646
+ };
7647
+ }
7648
+ if (items.length > maximum) {
7649
+ truncated = true;
7650
+ items = items.slice(-maximum);
7651
+ }
7652
+ } while (cursor !== null);
7653
+ return { items, truncated };
7654
+ }
7655
+
7656
+ // src/workWrites.ts
7657
+ var decodeHostRefusal = decodeUnknownOption2(
7658
+ Struct({
7659
+ rejection: Struct({
7660
+ code: String4,
7661
+ details: optionalKey2(Unknown2)
7662
+ })
7663
+ })
7664
+ );
7665
+ var rejectWork = (commandType, code) => ({ _tag: "TeamBoardRejection", commandType, code });
7666
+ async function writeWork(invocation, commandType, write) {
7667
+ if (invocation.asPerson === void 0) return rejectWork(commandType, "needs-person");
7668
+ try {
7669
+ return await write(invocation.asPerson.work);
7670
+ } catch (error) {
7671
+ const refusal = decodeHostRefusal(error);
7672
+ if (isNone2(refusal)) throw error;
7673
+ const { code, details } = refusal.value.rejection;
7674
+ return {
7675
+ ...rejectWork(
7676
+ commandType,
7677
+ commandType !== "postComment" && code === "version-conflict" ? "stale-version" : code
7678
+ ),
7679
+ ...details === void 0 ? {} : { details }
7680
+ };
7681
+ }
7682
+ }
7683
+
7684
+ // src/board.ts
7685
+ var ITEM_LIMIT = 500;
7686
+ var PEOPLE_LIMIT = 200;
7687
+ var REMOTE_AGENT_LIMIT = 1e3;
7688
+ var decodeRead2 = decodeUnknownSync(ReadBoardInput);
7689
+ var decodeCreate = decodeUnknownOption2(CreateItemInput);
7690
+ var decodeUpdate = decodeUnknownOption2(UpdateItemInput);
7691
+ function registerBoardCommands(ctx, goals, rosters, options = {}) {
7692
+ const isMember2 = async (projectId, threadId) => goalExecution(await goals.read(projectId)).members.some(
7693
+ (member) => "threadId" in member && member.threadId === threadId
7694
+ );
7320
7695
  ctx.command({
7321
7696
  name: "board",
7322
7697
  scope: "read",
7323
7698
  payloadSchema: payloadSchemaOf(ReadBoardInput),
7324
7699
  handle: async (payload, invocation) => {
7325
- const { projectId } = decodeRead(payload);
7700
+ const { projectId } = decodeRead2(payload);
7326
7701
  const [stateList, goal, roster, toolkitStates, people, remoteAgents, viewer] = await Promise.all([
7327
7702
  ctx.work.states.list({ projectId }),
7328
7703
  goals.read(projectId),
@@ -7447,74 +7822,7 @@ function registerBoardCommands(ctx, goals, rosters, options = {}) {
7447
7822
  return "code" in result2 ? result2 : { workItemId: result2.result.workItemId, version: result2.result.version };
7448
7823
  }
7449
7824
  });
7450
- const listeners = /* @__PURE__ */ new Map();
7451
- const cursors = /* @__PURE__ */ new Map();
7452
- const timers = /* @__PURE__ */ new Map();
7453
- let disposed = false;
7454
- const changed = (event) => {
7455
- const payload = decodeProject(event.payload);
7456
- const projectId = isSome2(payload) ? payload.value.projectId : event.aggregateKind === "project" ? event.aggregateId : null;
7457
- if (projectId === null || disposed || !listeners.has(projectId)) return;
7458
- cursors.set(projectId, Math.max(cursors.get(projectId) ?? 0, event.sequence));
7459
- if (timers.has(projectId)) return;
7460
- timers.set(
7461
- projectId,
7462
- after(50, () => {
7463
- timers.delete(projectId);
7464
- for (const emit of [...listeners.get(projectId) ?? []]) {
7465
- emit({ cursor: cursors.get(projectId) ?? 0 });
7466
- }
7467
- })
7468
- );
7469
- };
7470
- const stops = WORK_EVENTS.map((type) => ctx.events.onRaw(type, changed));
7471
- stops.push(
7472
- ctx.events.onRaw("plugin.event-appended", (event) => {
7473
- const fact = decodeFact(event.payload);
7474
- if (isSome2(fact) && ["team/goal-started", "team/roster-set"].includes(fact.value.name)) {
7475
- changed(event);
7476
- }
7477
- })
7478
- );
7479
- ctx.stream({
7480
- name: "watch",
7481
- scope: "read",
7482
- payloadSchema: payloadSchemaOf(ReadBoardInput),
7483
- buffer: { latest: 1 },
7484
- handle: async (payload, emit, signal) => {
7485
- if (signal.aborted || disposed) return;
7486
- const { projectId } = decodeRead(payload);
7487
- const subscribers = listeners.get(projectId) ?? /* @__PURE__ */ new Set();
7488
- const ring = (value) => {
7489
- if (!signal.aborted && !disposed) emit(value);
7490
- };
7491
- listeners.set(projectId, subscribers);
7492
- subscribers.add(ring);
7493
- try {
7494
- ring({ cursor: cursors.get(projectId) ?? 0 });
7495
- await new Promise((resolve2) => {
7496
- if (signal.aborted) resolve2();
7497
- else signal.addEventListener("abort", () => resolve2(), { once: true });
7498
- });
7499
- } finally {
7500
- subscribers.delete(ring);
7501
- if (subscribers.size === 0) {
7502
- listeners.delete(projectId);
7503
- timers.get(projectId)?.();
7504
- timers.delete(projectId);
7505
- cursors.delete(projectId);
7506
- }
7507
- }
7508
- }
7509
- });
7510
- ctx.onDispose(() => {
7511
- disposed = true;
7512
- for (const stop of stops) stop();
7513
- for (const cancel of timers.values()) cancel();
7514
- timers.clear();
7515
- listeners.clear();
7516
- cursors.clear();
7517
- });
7825
+ registerTeamWatch(ctx, options);
7518
7826
  }
7519
7827
 
7520
7828
  // src/detailContracts.ts
@@ -7576,9 +7884,9 @@ var ItemDetailSnapshot = Struct({
7576
7884
  blockedBy: ArraySchema(Dependency),
7577
7885
  blocking: ArraySchema(Dependency),
7578
7886
  comments: ArraySchema(DetailComment),
7579
- commentsTruncated: Boolean2,
7887
+ commentsTruncated: Boolean3,
7580
7888
  history: ArraySchema(DetailActivity),
7581
- historyTruncated: Boolean2
7889
+ historyTruncated: Boolean3
7582
7890
  });
7583
7891
  var ReadItemDetailResult = NullOr(ItemDetailSnapshot);
7584
7892
 
@@ -7587,7 +7895,7 @@ var RELATION_LIMIT = 100;
7587
7895
  var COMMENT_LIMIT = 200;
7588
7896
  var HISTORY_LIMIT = 200;
7589
7897
  var GET_BATCH_SIZE = 50;
7590
- var decodeRead2 = decodeUnknownSync(ReadItemDetailInput);
7898
+ var decodeRead3 = decodeUnknownSync(ReadItemDetailInput);
7591
7899
  var decodePost = decodeUnknownOption2(PostCommentInput);
7592
7900
  function registerDetailCommands(ctx) {
7593
7901
  ctx.command({
@@ -7595,7 +7903,7 @@ function registerDetailCommands(ctx) {
7595
7903
  scope: "read",
7596
7904
  payloadSchema: payloadSchemaOf(ReadItemDetailInput),
7597
7905
  handle: async (payload) => {
7598
- const { projectId, workItemId } = decodeRead2(payload);
7906
+ const { projectId, workItemId } = decodeRead3(payload);
7599
7907
  const result2 = await ctx.work.items.get({ projectId, workItemIds: [workItemId] });
7600
7908
  const item = result2.items.find((row) => row.workItemId === workItemId);
7601
7909
  if (item === void 0) return null;
@@ -7771,8 +8079,38 @@ function createRosterService(ctx, options = {}) {
7771
8079
  };
7772
8080
  }
7773
8081
 
7774
- // src/runtime/wake.ts
8082
+ // src/runtime/deliver.ts
7775
8083
  import * as NodeCrypto from "node:crypto";
8084
+ var stableHash = (key) => NodeCrypto.createHash("sha256").update(key).digest("hex").slice(0, 32);
8085
+ async function deliverWake(ctx, input) {
8086
+ const { recipient } = input;
8087
+ if ("principalId" in recipient) {
8088
+ await ctx.communication.records.send({
8089
+ projectId: input.projectId,
8090
+ requestId: input.requestId,
8091
+ to: { principalId: recipient.principalId },
8092
+ text: typeof input.text === "string" ? input.text : await input.text(),
8093
+ delivery: "wake"
8094
+ });
8095
+ } else {
8096
+ const execution = await ctx.projections.getThreadExecution(recipient.threadId);
8097
+ if (execution === null || execution.archivedAt !== null) return false;
8098
+ await ctx.threads.turns.start({
8099
+ requestId: input.requestId,
8100
+ threadId: recipient.threadId,
8101
+ message: {
8102
+ messageId: input.messageId,
8103
+ role: "user",
8104
+ text: typeof input.text === "string" ? input.text : await input.text(),
8105
+ attachments: []
8106
+ },
8107
+ modelSelection: execution.modelSelection,
8108
+ runtimeMode: execution.runtimeMode,
8109
+ interactionMode: execution.interactionMode
8110
+ });
8111
+ }
8112
+ return true;
8113
+ }
7776
8114
 
7777
8115
  // src/runtime/wakeState.ts
7778
8116
  var WAKE_STATE_KEY = "wakes";
@@ -7806,14 +8144,14 @@ var emptyProject = () => ({
7806
8144
  });
7807
8145
  var itemWakeKey = (workItemId, threadId, generation) => generation === 0 ? `item:${workItemId}:${threadId}` : `item:${workItemId}:${threadId}#${generation}`;
7808
8146
  var unblockedWakeKey = (workItemId, threadId, generation) => `unblocked:${workItemId}:${threadId}#${generation}`;
7809
- var hasOpenBlocker = (project, workItemId) => (readFact(project.blockers, workItemId) ?? []).some(
7810
- (blocker) => readFact(project.items, blocker)?.closed !== true
8147
+ var hasOpenBlocker = (project2, workItemId) => (readFact(project2.blockers, workItemId) ?? []).some(
8148
+ (blocker) => readFact(project2.items, blocker)?.closed !== true
7811
8149
  );
7812
- var markBlockedAfterClaim = (project, workItemId) => ({
7813
- ...project,
8150
+ var markBlockedAfterClaim = (project2, workItemId) => ({
8151
+ ...project2,
7814
8152
  blockedAfterClaim: {
7815
- ...project.blockedAfterClaim,
7816
- [workItemId]: (readFact(project.blockedAfterClaim, workItemId) ?? 0) + 1
8153
+ ...project2.blockedAfterClaim,
8154
+ [workItemId]: (readFact(project2.blockedAfterClaim, workItemId) ?? 0) + 1
7817
8155
  }
7818
8156
  });
7819
8157
  var decodeEnvelope2 = decodeUnknownOption2(
@@ -7898,8 +8236,8 @@ var decodeWorkEvent = decodeUnknownOption2(
7898
8236
  var agentThreads = (principals) => [
7899
8237
  ...new Set(principals.flatMap((p) => p.kind === "agent" ? [p.threadId] : []))
7900
8238
  ];
7901
- var isMember = (project, recipient) => "threadId" in recipient ? project.threads.includes(recipient.threadId) : project.remoteMembers.includes(recipient.principalId);
7902
- var commentRecipients = (project, actor, people) => {
8239
+ var isMember = (project2, recipient) => "threadId" in recipient ? project2.threads.includes(recipient.threadId) : project2.remoteMembers.includes(recipient.principalId);
8240
+ var commentRecipients = (project2, actor, people) => {
7903
8241
  const recipients = /* @__PURE__ */ new Map();
7904
8242
  for (const person of people) {
7905
8243
  if (person.kind === "agent" && !(actor.kind === "agent" && actor.threadId === person.threadId))
@@ -7907,7 +8245,7 @@ var commentRecipients = (project, actor, people) => {
7907
8245
  if (person.kind === "remote-agent" && !(actor.kind === "remote-agent" && actor.principalId === person.principalId))
7908
8246
  recipients.set(`principal:${person.principalId}`, { principalId: person.principalId });
7909
8247
  }
7910
- return [...recipients.values()].filter((recipient) => isMember(project, recipient));
8248
+ return [...recipients.values()].filter((recipient) => isMember(project2, recipient));
7911
8249
  };
7912
8250
  var commentWakeKey = (commentId, recipient) => "threadId" in recipient ? `comment:${commentId}:${recipient.threadId}` : `comment:${commentId}:principal:${recipient.principalId}`;
7913
8251
  function foldWakes(state, event) {
@@ -7917,48 +8255,53 @@ function foldWakes(state, event) {
7917
8255
  const fact = decoded2.value;
7918
8256
  if (fact.name === "team/goal-started") {
7919
8257
  const { goal } = fact.data;
7920
- const project3 = readFact(state, goal.projectId) ?? emptyProject();
7921
- const threads = [...new Set(goal.threads.map((thread) => thread.threadId))];
8258
+ const project4 = readFact(state, goal.projectId) ?? emptyProject();
8259
+ const { members } = goalExecution(goal);
8260
+ const threads = [
8261
+ ...new Set(members.flatMap((member) => "threadId" in member ? [member.threadId] : []))
8262
+ ];
7922
8263
  const remoteMembers = [
7923
- ...new Set((goal.remoteMembers ?? []).map((member) => member.principalId))
8264
+ ...new Set(
8265
+ members.flatMap((member) => "principalId" in member ? [member.principalId] : [])
8266
+ )
7924
8267
  ];
7925
8268
  const leadThreads = goal.threads.filter((thread) => thread.memberId === goal.leadMemberId).map((thread) => thread.threadId);
7926
- const next2 = { ...project3, threads, leadThreads, remoteMembers };
8269
+ const next2 = { ...project4, threads, leadThreads, remoteMembers };
7927
8270
  return {
7928
8271
  ...state,
7929
8272
  [goal.projectId]: {
7930
8273
  ...next2,
7931
- pendingComments: project3.pendingComments.filter((comment) => isMember(next2, comment))
8274
+ pendingComments: project4.pendingComments.filter((comment) => isMember(next2, comment))
7932
8275
  }
7933
8276
  };
7934
8277
  }
7935
8278
  const { projectId: projectId2, key } = fact.data;
7936
- const project2 = readFact(state, projectId2) ?? emptyProject();
7937
- if (project2.pendingComments.some((comment) => comment.key === key)) {
8279
+ const project3 = readFact(state, projectId2) ?? emptyProject();
8280
+ if (project3.pendingComments.some((comment) => comment.key === key)) {
7938
8281
  return {
7939
8282
  ...state,
7940
8283
  [projectId2]: {
7941
- ...project2,
7942
- pendingComments: project2.pendingComments.filter((comment) => comment.key !== key)
8284
+ ...project3,
8285
+ pendingComments: project3.pendingComments.filter((comment) => comment.key !== key)
7943
8286
  }
7944
8287
  };
7945
8288
  }
7946
- if (project2.woken.includes(key)) return state;
7947
- return { ...state, [projectId2]: { ...project2, woken: [...project2.woken, key] } };
8289
+ if (project3.woken.includes(key)) return state;
8290
+ return { ...state, [projectId2]: { ...project3, woken: [...project3.woken, key] } };
7948
8291
  }
7949
8292
  const decoded = decodeWorkEvent(event);
7950
8293
  if (isNone2(decoded)) return state;
7951
8294
  const work = decoded.value;
7952
8295
  const { projectId, workItemId } = work.payload;
7953
- const project = readFact(state, projectId) ?? emptyProject();
7954
- const currentItem = readFact(project.items, workItemId);
7955
- let next = project;
8296
+ const project2 = readFact(state, projectId) ?? emptyProject();
8297
+ const currentItem = readFact(project2.items, workItemId);
8298
+ let next = project2;
7956
8299
  switch (work.type) {
7957
8300
  case "work.item-created":
7958
8301
  next = {
7959
- ...project,
8302
+ ...project2,
7960
8303
  items: {
7961
- ...project.items,
8304
+ ...project2.items,
7962
8305
  [workItemId]: {
7963
8306
  sequenceId: work.payload.sequenceId,
7964
8307
  assigneeThreads: agentThreads(work.payload.assignees),
@@ -7978,9 +8321,9 @@ function foldWakes(state, event) {
7978
8321
  ])
7979
8322
  ];
7980
8323
  next = {
7981
- ...project,
8324
+ ...project2,
7982
8325
  items: {
7983
- ...project.items,
8326
+ ...project2.items,
7984
8327
  [workItemId]: {
7985
8328
  ...currentItem,
7986
8329
  assigneeThreads,
@@ -7996,18 +8339,18 @@ function foldWakes(state, event) {
7996
8339
  case "work.relation-added":
7997
8340
  case "work.relation-removed": {
7998
8341
  if (work.payload.relationKind !== "blocked_by") return state;
7999
- const blockers = readFact(project.blockers, workItemId) ?? [];
8342
+ const blockers = readFact(project2.blockers, workItemId) ?? [];
8000
8343
  const related = work.payload.relatedWorkItemId;
8001
8344
  next = {
8002
- ...project,
8345
+ ...project2,
8003
8346
  blockers: {
8004
- ...project.blockers,
8347
+ ...project2.blockers,
8005
8348
  [workItemId]: work.type === "work.relation-added" ? [.../* @__PURE__ */ new Set([...blockers, related])] : blockers.filter((blocker) => blocker !== related)
8006
8349
  }
8007
8350
  };
8008
- if (work.type === "work.relation-added" && currentItem !== null && readFact(project.items, related)?.closed !== true) {
8009
- const woken = new Set(project.woken);
8010
- const rearms = { ...project.rearms };
8351
+ if (work.type === "work.relation-added" && currentItem !== null && readFact(project2.items, related)?.closed !== true) {
8352
+ const woken = new Set(project2.woken);
8353
+ const rearms = { ...project2.rearms };
8011
8354
  for (const threadId of currentItem.assigneeThreads) {
8012
8355
  if (currentItem.claimedBy.includes(threadId)) continue;
8013
8356
  const generation = rearms[`${workItemId}:${threadId}`] ?? 0;
@@ -8015,14 +8358,14 @@ function foldWakes(state, event) {
8015
8358
  rearms[`${workItemId}:${threadId}`] = generation + 1;
8016
8359
  }
8017
8360
  next = { ...next, rearms };
8018
- if (currentItem.claimedBy.length > 0 && !hasOpenBlocker(project, workItemId))
8361
+ if (currentItem.claimedBy.length > 0 && !hasOpenBlocker(project2, workItemId))
8019
8362
  next = markBlockedAfterClaim(next, workItemId);
8020
8363
  }
8021
8364
  break;
8022
8365
  }
8023
8366
  case "work.comment-posted": {
8024
8367
  const { actor, parentAuthor, mentions, commentId } = work.payload;
8025
- const comments = commentRecipients(project, actor, [
8368
+ const comments = commentRecipients(project2, actor, [
8026
8369
  ...parentAuthor === void 0 ? [] : [parentAuthor],
8027
8370
  ...mentions
8028
8371
  ]).map((recipient) => ({
@@ -8032,10 +8375,10 @@ function foldWakes(state, event) {
8032
8375
  workItemId,
8033
8376
  commentId
8034
8377
  })).filter(
8035
- (comment) => !project.pendingComments.some((pending) => pending.key === comment.key)
8378
+ (comment) => !project2.pendingComments.some((pending) => pending.key === comment.key)
8036
8379
  );
8037
8380
  if (comments.length === 0) return state;
8038
- next = { ...project, pendingComments: [...project.pendingComments, ...comments] };
8381
+ next = { ...project2, pendingComments: [...project2.pendingComments, ...comments] };
8039
8382
  break;
8040
8383
  }
8041
8384
  }
@@ -8044,13 +8387,13 @@ function foldWakes(state, event) {
8044
8387
  var compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
8045
8388
  function dueWakes(state) {
8046
8389
  const due = [];
8047
- for (const [projectId, project] of Object.entries(state)) {
8048
- const woken = new Set(project.woken);
8049
- const threads = new Set(project.threads);
8050
- const leads = new Set(project.leadThreads ?? []);
8051
- for (const [workItemId, item] of Object.entries(project.items)) {
8052
- if (item.closed || hasOpenBlocker(project, workItemId)) continue;
8053
- const unblocked = readFact(project.blockedAfterClaim, workItemId) ?? 0;
8390
+ for (const [projectId, project2] of Object.entries(state)) {
8391
+ const woken = new Set(project2.woken);
8392
+ const threads = new Set(project2.threads);
8393
+ const leads = new Set(project2.leadThreads ?? []);
8394
+ for (const [workItemId, item] of Object.entries(project2.items)) {
8395
+ if (item.closed || hasOpenBlocker(project2, workItemId)) continue;
8396
+ const unblocked = readFact(project2.blockedAfterClaim, workItemId) ?? 0;
8054
8397
  const recipients = /* @__PURE__ */ new Set([
8055
8398
  ...item.assigneeThreads,
8056
8399
  ...unblocked > 0 ? item.claimedBy : []
@@ -8061,7 +8404,7 @@ function dueWakes(state) {
8061
8404
  const key = finishing ? unblockedWakeKey(workItemId, threadId, unblocked) : itemWakeKey(
8062
8405
  workItemId,
8063
8406
  threadId,
8064
- readFact(project.rearms, `${workItemId}:${threadId}`) ?? 0
8407
+ readFact(project2.rearms, `${workItemId}:${threadId}`) ?? 0
8065
8408
  );
8066
8409
  if (woken.has(key)) continue;
8067
8410
  due.push({
@@ -8075,8 +8418,8 @@ function dueWakes(state) {
8075
8418
  });
8076
8419
  }
8077
8420
  }
8078
- for (const comment of project.pendingComments) {
8079
- const item = readFact(project.items, comment.workItemId);
8421
+ for (const comment of project2.pendingComments) {
8422
+ const item = readFact(project2.items, comment.workItemId);
8080
8423
  due.push({
8081
8424
  ...comment,
8082
8425
  kind: "comment",
@@ -8090,12 +8433,11 @@ function dueWakes(state) {
8090
8433
  // src/runtime/wake.ts
8091
8434
  var workLabel = (workItemId, sequenceId) => sequenceId === void 0 ? `the work item with workItemId ${workItemId}` : `work item W${sequenceId} (workItemId ${workItemId})`;
8092
8435
  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}`;
8436
+ 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
8437
  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
8438
  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
8439
  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
8440
  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
8441
  function startWakeRuntime(ctx, options = {}) {
8100
8442
  const wakes = ctx.derivedState({
8101
8443
  key: WAKE_STATE_KEY,
@@ -8115,35 +8457,18 @@ function startWakeRuntime(ctx, options = {}) {
8115
8457
  for (const wake of dueWakes(state)) {
8116
8458
  try {
8117
8459
  const id = hash2(wake.key);
8118
- if ("principalId" in wake) {
8119
- await ctx.communication.records.send({
8120
- projectId: wake.projectId,
8121
- requestId: `team-wake:${id}`,
8122
- to: { principalId: wake.principalId },
8123
- text: commentText2(wake.workItemId, wake.sequenceId),
8124
- delivery: "wake"
8125
- });
8126
- } else {
8127
- const execution = await ctx.projections.getThreadExecution(wake.threadId);
8128
- if (execution === null || execution.archivedAt !== null) continue;
8129
- await ctx.threads.turns.start({
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
- }
8460
+ const delivered = await deliverWake(ctx, {
8461
+ projectId: wake.projectId,
8462
+ recipient: wake,
8463
+ requestId: `team-wake:${id}`,
8464
+ messageId: `team-wake-msg:${id}`,
8465
+ 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)(
8466
+ wake.workItemId,
8467
+ wake.sequenceId,
8468
+ await readTitle(wake.projectId, wake.workItemId)
8469
+ )
8470
+ });
8471
+ if (!delivered) continue;
8147
8472
  const receipt = await ctx.events.append({
8148
8473
  commandId: `team-woken:${id}`,
8149
8474
  name: "member-woken",
@@ -8175,10 +8500,707 @@ function startWakeRuntime(ctx, options = {}) {
8175
8500
  return { flush, ready };
8176
8501
  }
8177
8502
 
8503
+ // src/handbook/content.ts
8504
+ async function releaseRefs(memory, projectId, release) {
8505
+ return release === 0 ? [] : (await memory.handbook.releases.get({ projectId, release })).pages;
8506
+ }
8507
+ async function readBody(memory, projectId, ref) {
8508
+ let offset = 0;
8509
+ let body = "";
8510
+ while (true) {
8511
+ const page = await memory.pageRevisions.get({
8512
+ projectId,
8513
+ pageId: ref.pageId,
8514
+ revision: ref.revision,
8515
+ textOffset: offset
8516
+ });
8517
+ if (page.bodyState !== "present" || page.body === null)
8518
+ throw new Error("handbook-content-unavailable");
8519
+ body += page.body;
8520
+ const next = page.bodyWindow?.nextOffset;
8521
+ if (next === void 0 || next === null) break;
8522
+ offset = next;
8523
+ }
8524
+ return body;
8525
+ }
8526
+ async function publicationRequiresAck(memory, projectId, input) {
8527
+ const kinds = input.requireAck ? ["handbook_rules", "handbook_decisions"] : ["handbook_rules"];
8528
+ if (input.operation === "publish" && !input.pages.some((page) => kinds.includes(page.kind)))
8529
+ return false;
8530
+ const current = await releaseRefs(memory, projectId, input.expectedRelease);
8531
+ const target = input.operation === "revert" ? await releaseRefs(memory, projectId, input.toRelease) : [];
8532
+ for (const kind of kinds) {
8533
+ const previous = current.find((page) => page.kind === kind);
8534
+ if (input.operation === "publish") {
8535
+ const page = input.pages.find((row) => row.kind === kind);
8536
+ if (page === void 0) continue;
8537
+ if (previous === void 0) return true;
8538
+ if (await readBody(memory, projectId, previous) !== page.body) return true;
8539
+ } else {
8540
+ const next = target.find((page) => page.kind === kind);
8541
+ if (previous === void 0 || next === void 0) {
8542
+ if (previous !== next) return true;
8543
+ continue;
8544
+ }
8545
+ if (previous.pageId === next.pageId && previous.revision === next.revision) continue;
8546
+ if (await readBody(memory, projectId, previous) !== await readBody(memory, projectId, next))
8547
+ return true;
8548
+ }
8549
+ }
8550
+ return false;
8551
+ }
8552
+
8553
+ // src/handbook/contracts.ts
8554
+ var Release = Int.check(isGreaterThanOrEqualTo3(1));
8555
+ var HandbookPageKind = Literals([
8556
+ "handbook_decisions",
8557
+ "handbook_rules",
8558
+ "handbook_progress"
8559
+ ]);
8560
+ var Page = Struct({
8561
+ kind: HandbookPageKind,
8562
+ baseRevision: NullOr(Finite),
8563
+ title: optionalKey2(String4),
8564
+ body: String4,
8565
+ changeSummary: optionalKey2(String4)
8566
+ });
8567
+ var WriteFields = {
8568
+ requestId: String4,
8569
+ expectedRelease: Finite,
8570
+ requireAck: optionalKey2(Boolean3)
8571
+ };
8572
+ var PublishInput = Struct({
8573
+ ...WriteFields,
8574
+ pages: ArraySchema(Page),
8575
+ sourceExperience: optionalKey2(
8576
+ Struct({ experienceId: String4, revision: Finite })
8577
+ )
8578
+ });
8579
+ var RevertInput = Struct({ ...WriteFields, toRelease: Finite });
8580
+ var AcknowledgeInput = Struct({ release: Release });
8581
+ var ReadHandbookInput = Struct({ projectId: String4 });
8582
+ var PublicationIntent = Struct({
8583
+ version: Literal2(1),
8584
+ projectId: String4,
8585
+ memberId: String4,
8586
+ requestId: String4,
8587
+ op: Literals(["publish", "revert"]),
8588
+ requireAckChoice: optionalKey2(Boolean3),
8589
+ requiresAck: optionalKey2(Boolean3)
8590
+ });
8591
+ var PublicationCompleted = Struct({
8592
+ version: Literal2(1),
8593
+ projectId: String4,
8594
+ requestId: String4,
8595
+ producedRelease: optionalKey2(Boolean3),
8596
+ outcome: Union2([
8597
+ Struct({ status: Literal2("succeeded"), release: Release }),
8598
+ Struct({ status: Literal2("rejected") }),
8599
+ Struct({ status: Literal2("voided") })
8600
+ ])
8601
+ });
8602
+ var Acknowledged = Struct({
8603
+ version: Literal2(1),
8604
+ projectId: String4,
8605
+ release: Release,
8606
+ memberId: String4
8607
+ });
8608
+ var ReminderSent = Struct({
8609
+ ...Acknowledged.fields
8610
+ });
8611
+
8612
+ // src/handbook/state.ts
8613
+ var HANDBOOK_EVENTS2 = ["plugin.event-appended", "wiki.handbook-released"];
8614
+ var emptyHandbookState = () => ({
8615
+ projects: {},
8616
+ intents: {},
8617
+ completed: {},
8618
+ publishers: {},
8619
+ pendingPublications: {},
8620
+ pendingAcknowledgements: {},
8621
+ pendingReminders: {}
8622
+ });
8623
+ var emptyHandbookProject = () => ({
8624
+ goal: null,
8625
+ currentRelease: null,
8626
+ requiredRelease: null,
8627
+ acknowledgements: {},
8628
+ reminded: {}
8629
+ });
8630
+ var envelope = (name, data) => Struct({ pluginId: Literal2("team"), name: Literal2(`team/${name}`), data });
8631
+ var decodeFact3 = decodeUnknownOption2(
8632
+ Union2([
8633
+ envelope("goal-started", GoalStartedFact),
8634
+ envelope("handbook-publication-intended", PublicationIntent),
8635
+ envelope("handbook-publication-completed", PublicationCompleted),
8636
+ envelope("handbook-acknowledged", Acknowledged),
8637
+ envelope("handbook-reminder-sent", ReminderSent)
8638
+ ])
8639
+ );
8640
+ var decodeRelease = decodeUnknownSync(
8641
+ Struct({
8642
+ projectId: String4,
8643
+ release: Number5,
8644
+ requiresAck: Boolean3
8645
+ })
8646
+ );
8647
+ function callerMember(project2, caller) {
8648
+ if (caller === void 0) return void 0;
8649
+ return goalExecution(project2.goal).members.find(
8650
+ (member) => caller.kind === "thread" ? "threadId" in member && member.threadId === caller.threadId : "principalId" in member && member.principalId === caller.principalId
8651
+ );
8652
+ }
8653
+ var hasAcknowledged = (project2, memberId, release) => readFact(readFact(project2.acknowledgements, memberId)?.releases ?? {}, String(release)) === true;
8654
+ var reminderKey = (projectId, release, memberId) => JSON.stringify([projectId, release, memberId]);
8655
+ var without = (rows, key) => {
8656
+ if (!Object.hasOwn(rows, key)) return rows;
8657
+ const next = { ...rows };
8658
+ delete next[key];
8659
+ return next;
8660
+ };
8661
+ function foldHandbook(state, event) {
8662
+ if (event.type === "wiki.handbook-released") {
8663
+ const fact2 = decodeRelease(event.payload);
8664
+ const project3 = readFact(state.projects, fact2.projectId) ?? emptyHandbookProject();
8665
+ const pendingReminders = { ...state.pendingReminders };
8666
+ if (fact2.requiresAck)
8667
+ for (const { memberId, ...recipient } of goalExecution(project3.goal).members) {
8668
+ const key = reminderKey(fact2.projectId, fact2.release, memberId);
8669
+ if (readFact(project3.reminded, key) === null)
8670
+ pendingReminders[key] = {
8671
+ projectId: fact2.projectId,
8672
+ release: fact2.release,
8673
+ member: { memberId, recipient }
8674
+ };
8675
+ }
8676
+ return {
8677
+ ...state,
8678
+ pendingReminders,
8679
+ projects: {
8680
+ ...state.projects,
8681
+ [fact2.projectId]: {
8682
+ ...project3,
8683
+ currentRelease: fact2.release,
8684
+ requiredRelease: fact2.requiresAck ? fact2.release : project3.requiredRelease
8685
+ }
8686
+ }
8687
+ };
8688
+ }
8689
+ if (event.type !== "plugin.event-appended") return state;
8690
+ const decoded = decodeFact3(event.payload);
8691
+ if (isNone2(decoded)) return state;
8692
+ const fact = decoded.value;
8693
+ const projectId = fact.name === "team/goal-started" ? fact.data.goal.projectId : fact.data.projectId;
8694
+ const project2 = readFact(state.projects, projectId) ?? emptyHandbookProject();
8695
+ let next = project2;
8696
+ switch (fact.name) {
8697
+ case "team/goal-started":
8698
+ next = { ...project2, goal: fact.data.goal };
8699
+ break;
8700
+ case "team/handbook-publication-intended": {
8701
+ const intent = fact.data;
8702
+ if (readFact(state.intents, intent.requestId) !== null) return state;
8703
+ return {
8704
+ ...state,
8705
+ intents: { ...state.intents, [intent.requestId]: intent },
8706
+ pendingPublications: { ...state.pendingPublications, [intent.requestId]: intent }
8707
+ };
8708
+ }
8709
+ case "team/handbook-publication-completed": {
8710
+ const completed = fact.data;
8711
+ if (readFact(state.completed, completed.requestId) !== null) return state;
8712
+ const intent = readFact(state.pendingPublications, completed.requestId);
8713
+ let publishers = state.publishers;
8714
+ if (intent !== null && completed.producedRelease === true && completed.outcome.status === "succeeded") {
8715
+ const releases = readFact(publishers ?? {}, projectId) ?? {};
8716
+ const release = String(completed.outcome.release);
8717
+ if (readFact(releases, release) === null)
8718
+ publishers = {
8719
+ ...publishers,
8720
+ [projectId]: { ...releases, [release]: intent.memberId }
8721
+ };
8722
+ }
8723
+ let pendingAcknowledgements = state.pendingAcknowledgements;
8724
+ let pendingReminders = state.pendingReminders;
8725
+ if (intent !== null && intent.requiresAck && completed.outcome.status === "succeeded") {
8726
+ const { release } = completed.outcome;
8727
+ const key = reminderKey(projectId, release, intent.memberId);
8728
+ pendingReminders = without(pendingReminders, key);
8729
+ if (!hasAcknowledged(project2, intent.memberId, release))
8730
+ pendingAcknowledgements = {
8731
+ ...pendingAcknowledgements,
8732
+ [key]: { version: 1, projectId, release, memberId: intent.memberId }
8733
+ };
8734
+ }
8735
+ return {
8736
+ ...state,
8737
+ completed: { ...state.completed, [completed.requestId]: completed },
8738
+ ...publishers === void 0 ? {} : { publishers },
8739
+ pendingPublications: without(state.pendingPublications, completed.requestId),
8740
+ pendingAcknowledgements,
8741
+ pendingReminders
8742
+ };
8743
+ }
8744
+ case "team/handbook-acknowledged": {
8745
+ const { memberId, release } = fact.data;
8746
+ if (hasAcknowledged(project2, memberId, release)) return state;
8747
+ const previous = readFact(project2.acknowledgements, memberId);
8748
+ next = {
8749
+ ...project2,
8750
+ acknowledgements: {
8751
+ ...project2.acknowledgements,
8752
+ [memberId]: {
8753
+ releases: { ...previous?.releases, [release]: true },
8754
+ latestRelease: Math.max(previous?.latestRelease ?? 0, release)
8755
+ }
8756
+ }
8757
+ };
8758
+ return {
8759
+ ...state,
8760
+ pendingAcknowledgements: without(
8761
+ state.pendingAcknowledgements,
8762
+ reminderKey(projectId, release, memberId)
8763
+ ),
8764
+ projects: { ...state.projects, [projectId]: next }
8765
+ };
8766
+ }
8767
+ case "team/handbook-reminder-sent": {
8768
+ const key = reminderKey(projectId, fact.data.release, fact.data.memberId);
8769
+ if (readFact(project2.reminded, key) !== null) return state;
8770
+ next = { ...project2, reminded: { ...project2.reminded, [key]: true } };
8771
+ return {
8772
+ ...state,
8773
+ pendingReminders: without(state.pendingReminders, key),
8774
+ projects: { ...state.projects, [projectId]: next }
8775
+ };
8776
+ }
8777
+ }
8778
+ return { ...state, projects: { ...state.projects, [projectId]: next } };
8779
+ }
8780
+
8781
+ // src/handbook/runtime.ts
8782
+ var terminalFailure = (error) => {
8783
+ if (typeof error !== "object" || error === null || !("_tag" in error)) return null;
8784
+ const reason = "reason" in error ? error.reason : void 0;
8785
+ const rejected = error._tag === "DataCommandError" && ["rejected", "request-conflict", "command-id-conflict", "not-qualified"].includes(
8786
+ String(reason)
8787
+ );
8788
+ const invalid2 = error._tag === "PluginCapabilityError" && reason === void 0 && "capability" in error && ["memory.handbook.publish", "memory.handbook.revert"].includes(String(error.capability));
8789
+ if (!rejected && !invalid2) return null;
8790
+ return error instanceof Error ? error.message : "detail" in error ? String(error.detail) : String(reason);
8791
+ };
8792
+ function startHandbookRuntime(ctx) {
8793
+ const state = ctx.derivedState({
8794
+ key: "handbook",
8795
+ version: 5,
8796
+ events: HANDBOOK_EVENTS2,
8797
+ init: emptyHandbookState,
8798
+ apply: foldHandbook
8799
+ });
8800
+ let pending = Promise.resolve();
8801
+ let disposed = false;
8802
+ const serial = (execute2) => {
8803
+ const result2 = pending.then(() => {
8804
+ if (disposed) throw new Error("team-handbook-disposed");
8805
+ return execute2();
8806
+ });
8807
+ pending = result2.then(
8808
+ () => void 0,
8809
+ () => void 0
8810
+ );
8811
+ return result2;
8812
+ };
8813
+ const append2 = async (projectId, name, key, data) => {
8814
+ const receipt = await ctx.events.append({
8815
+ commandId: `handbook:${name}:${stableHash(key)}`,
8816
+ name,
8817
+ aggregate: { kind: "project", id: projectId },
8818
+ data
8819
+ });
8820
+ return state.get({ throughSequence: receipt.sequence });
8821
+ };
8822
+ const acknowledge = async (projectId, release, memberId) => {
8823
+ const project2 = readFact((await state.get()).projects, projectId) ?? emptyHandbookProject();
8824
+ if (hasAcknowledged(project2, memberId, release)) return;
8825
+ await append2(
8826
+ projectId,
8827
+ "handbook-acknowledged",
8828
+ JSON.stringify([projectId, release, memberId]),
8829
+ {
8830
+ version: 1,
8831
+ projectId,
8832
+ release,
8833
+ memberId
8834
+ }
8835
+ );
8836
+ };
8837
+ const complete = async (intent, outcome, producedRelease) => {
8838
+ await append2(intent.projectId, "handbook-publication-completed", intent.requestId, {
8839
+ version: 1,
8840
+ requestId: intent.requestId,
8841
+ projectId: intent.projectId,
8842
+ outcome,
8843
+ ...producedRelease === void 0 ? {} : { producedRelease }
8844
+ });
8845
+ if (outcome.status === "succeeded" && intent.requiresAck) {
8846
+ await acknowledge(intent.projectId, outcome.release, intent.memberId);
8847
+ }
8848
+ return outcome;
8849
+ };
8850
+ const reconcile = async (intent) => {
8851
+ const completed = readFact((await state.get()).completed, intent.requestId);
8852
+ if (completed !== null) return completed.outcome;
8853
+ const receipt = await ctx.commands.getReceipt({
8854
+ commandId: intent.requestId,
8855
+ commandType: `wiki.handbook.${intent.op}`
8856
+ });
8857
+ if (receipt.status === "absent") return complete(intent, { status: "voided" });
8858
+ if (receipt.status === "rejected") return complete(intent, { status: "rejected" });
8859
+ const result2 = receipt.result;
8860
+ if (typeof result2 !== "object" || result2 === null || !("release" in result2) || typeof result2.release !== "number" || !Number.isSafeInteger(result2.release) || result2.release < 1)
8861
+ throw new Error("handbook-receipt-result-unavailable");
8862
+ await state.get({ throughSequence: receipt.sequence });
8863
+ return complete(
8864
+ intent,
8865
+ { status: "succeeded", release: result2.release },
8866
+ "changed" in result2 && result2.changed === true
8867
+ );
8868
+ };
8869
+ const remind = async () => {
8870
+ const snapshot = await state.get();
8871
+ for (const [key, reminder] of Object.entries(snapshot.pendingReminders)) {
8872
+ const { projectId, release, member } = reminder;
8873
+ try {
8874
+ const id = stableHash(key);
8875
+ const delivered = await deliverWake(ctx, {
8876
+ projectId,
8877
+ recipient: member.recipient,
8878
+ requestId: `team-handbook:${id}`,
8879
+ messageId: `team-handbook-msg:${id}`,
8880
+ text: handbookMessage("reminder", release)
8881
+ });
8882
+ if (!delivered) continue;
8883
+ const previousKey = JSON.stringify([release, member.memberId]);
8884
+ await append2(
8885
+ projectId,
8886
+ "handbook-reminder-sent",
8887
+ JSON.stringify([projectId, previousKey]),
8888
+ {
8889
+ version: 1,
8890
+ projectId,
8891
+ release,
8892
+ memberId: member.memberId
8893
+ }
8894
+ );
8895
+ } catch (error) {
8896
+ ctx.log.warn("team handbook reminder failed", { projectId, key, error });
8897
+ }
8898
+ }
8899
+ };
8900
+ const execute = async () => {
8901
+ for (const intent of Object.values((await state.get()).pendingPublications)) {
8902
+ try {
8903
+ await reconcile(intent);
8904
+ } catch (error) {
8905
+ ctx.log.warn("team handbook publication catch-up failed", {
8906
+ requestId: intent.requestId,
8907
+ error
8908
+ });
8909
+ }
8910
+ }
8911
+ for (const ack of Object.values((await state.get()).pendingAcknowledgements)) {
8912
+ try {
8913
+ await acknowledge(ack.projectId, ack.release, ack.memberId);
8914
+ } catch (error) {
8915
+ ctx.log.warn("team handbook publisher acknowledgement failed", { ...ack, error });
8916
+ }
8917
+ }
8918
+ await remind();
8919
+ };
8920
+ const flush = () => serial(execute);
8921
+ ctx.reactor({ name: "team-handbook", events: HANDBOOK_EVENTS2, handle: () => flush() });
8922
+ ctx.onDispose(async () => {
8923
+ disposed = true;
8924
+ await pending;
8925
+ });
8926
+ const ready = flush();
8927
+ void ready.catch((error) => ctx.log.warn("team handbook catch-up failed", { error }));
8928
+ return {
8929
+ ready,
8930
+ flush,
8931
+ async publisherMembers(projectId, releases) {
8932
+ const snapshot = await state.get();
8933
+ const publishers = readFact(snapshot.publishers ?? {}, projectId) ?? {};
8934
+ return releases.flatMap((release) => {
8935
+ const memberId = readFact(publishers, String(release));
8936
+ return memberId === null ? [] : [{ release, memberId }];
8937
+ });
8938
+ },
8939
+ async read(projectId) {
8940
+ const project2 = readFact((await state.get()).projects, projectId) ?? emptyHandbookProject();
8941
+ return {
8942
+ currentRelease: project2.currentRelease,
8943
+ requiredRelease: project2.requiredRelease,
8944
+ members: goalExecution(project2.goal).members.map(({ memberId }) => ({
8945
+ memberId,
8946
+ acknowledgedRelease: readFact(project2.acknowledgements, memberId)?.latestRelease ?? null,
8947
+ acknowledgedRequiredRelease: project2.requiredRelease === null || hasAcknowledged(project2, memberId, project2.requiredRelease)
8948
+ }))
8949
+ };
8950
+ },
8951
+ ack(release, invocation) {
8952
+ return serial(async () => {
8953
+ const projectId = invocation.projectId;
8954
+ const project2 = projectId === null ? emptyHandbookProject() : readFact((await state.get()).projects, projectId) ?? emptyHandbookProject();
8955
+ const member = callerMember(project2, invocation.caller);
8956
+ if (member === void 0 || projectId === null)
8957
+ throw new Error(handbookMessage("unknownMember"));
8958
+ if (project2.requiredRelease === null) throw new Error(handbookMessage("noRequirement"));
8959
+ if (release !== project2.requiredRelease)
8960
+ throw new Error(handbookMessage("wrongRelease", project2.requiredRelease));
8961
+ await acknowledge(projectId, release, member.memberId);
8962
+ return { release, memberId: member.memberId };
8963
+ });
8964
+ },
8965
+ publish(input, invocation) {
8966
+ return serial(async () => {
8967
+ const projectId = invocation.projectId;
8968
+ const project2 = projectId === null ? emptyHandbookProject() : readFact((await state.get()).projects, projectId) ?? emptyHandbookProject();
8969
+ const member = callerMember(project2, invocation.caller);
8970
+ const execution = goalExecution(project2.goal);
8971
+ if (execution.leadMemberId === null) throw new Error(handbookMessage("noGoal"));
8972
+ if (projectId === null || invocation.caller?.kind !== "thread" || member === void 0 || member.memberId !== execution.leadMemberId)
8973
+ throw new Error(handbookMessage("leadOnly"));
8974
+ const requestId = `team-handbook:${stableHash(JSON.stringify([projectId, input.requestId]))}`;
8975
+ const current = await state.get();
8976
+ const existing = readFact(current.intents, requestId);
8977
+ const done2 = readFact(current.completed, requestId);
8978
+ if (existing !== null) {
8979
+ if (existing.op !== input.operation || existing.memberId !== member.memberId || (existing.requireAckChoice ?? false) !== (input.requireAck ?? false))
8980
+ throw new Error(handbookMessage("requestConflict"));
8981
+ if (done2?.outcome.status === "voided") throw new Error(handbookMessage("requestVoided"));
8982
+ }
8983
+ const requireAck = existing?.requiresAck ?? await publicationRequiresAck(ctx.memory, projectId, input);
8984
+ const intent = existing ?? {
8985
+ version: 1,
8986
+ projectId,
8987
+ memberId: member.memberId,
8988
+ requestId,
8989
+ op: input.operation,
8990
+ requireAckChoice: input.requireAck ?? false,
8991
+ requiresAck: requireAck
8992
+ };
8993
+ if (existing === null)
8994
+ await append2(projectId, "handbook-publication-intended", requestId, intent);
8995
+ let outcome;
8996
+ let producedRelease;
8997
+ let detail = null;
8998
+ try {
8999
+ const command = { ...input, projectId, requestId, requireAck };
9000
+ const result2 = command.operation === "publish" ? await ctx.memory.handbook.publish(command) : await ctx.memory.handbook.revert(command);
9001
+ await state.get({ throughSequence: result2.sequence });
9002
+ outcome = { status: "succeeded", release: result2.result.release };
9003
+ producedRelease = result2.result.changed;
9004
+ } catch (error) {
9005
+ detail = terminalFailure(error);
9006
+ if (detail === null) throw error;
9007
+ outcome = { status: "rejected" };
9008
+ }
9009
+ if (done2 === null) await complete(intent, outcome, producedRelease);
9010
+ await remind();
9011
+ return detail === null ? outcome : { ...outcome, detail };
9012
+ });
9013
+ },
9014
+ async check(input) {
9015
+ const project2 = readFact(
9016
+ (await state.get({ throughSequence: input.throughSequence })).projects,
9017
+ input.projectId
9018
+ );
9019
+ if (project2 === null || project2.requiredRelease === null) return { allowed: true };
9020
+ for (const actor of input.added) {
9021
+ const caller = actor.kind === "human" ? void 0 : actor.kind === "agent" ? { kind: "thread", threadId: actor.threadId } : actor;
9022
+ const member = callerMember(project2, caller);
9023
+ if (member !== void 0 && !hasAcknowledged(project2, member.memberId, project2.requiredRelease)) {
9024
+ return {
9025
+ allowed: false,
9026
+ detail: handbookMessage("guard", project2.requiredRelease)
9027
+ };
9028
+ }
9029
+ }
9030
+ return { allowed: true };
9031
+ }
9032
+ };
9033
+ }
9034
+
9035
+ // src/handbook/entry.ts
9036
+ var decodeAcknowledgement = decodeUnknownSync(AcknowledgeInput);
9037
+ var decodePublish = decodeUnknownSync(PublishInput);
9038
+ var decodeRevert = decodeUnknownSync(RevertInput);
9039
+ var decodeRead4 = decodeUnknownSync(ReadHandbookInput);
9040
+ var toolSchema = (schema) => ({
9041
+ ...payloadSchemaOf(schema),
9042
+ type: "object",
9043
+ additionalProperties: false
9044
+ });
9045
+ var run2 = async (operation) => {
9046
+ try {
9047
+ const result2 = await operation();
9048
+ const rejected = typeof result2 === "object" && result2 !== null && "status" in result2 && (result2.status === "rejected" || result2.status === "voided");
9049
+ return { text: JSON.stringify(result2), ...rejected ? { isError: true } : {} };
9050
+ } catch (error) {
9051
+ return { text: error instanceof Error ? error.message : String(error), isError: true };
9052
+ }
9053
+ };
9054
+ function registerHandbook(ctx, runtime) {
9055
+ ctx.toolkit({
9056
+ name: "handbook",
9057
+ tools: [
9058
+ {
9059
+ name: "acknowledge_handbook",
9060
+ remoteAvailable: true,
9061
+ description: handbookMessage("ackTool"),
9062
+ inputSchema: toolSchema(AcknowledgeInput),
9063
+ handle: (input, invocation) => run2(() => runtime.ack(decodeAcknowledgement(input).release, invocation))
9064
+ },
9065
+ {
9066
+ name: "publish_handbook",
9067
+ description: handbookMessage("publishTool"),
9068
+ inputSchema: toolSchema(PublishInput),
9069
+ handle: (input, invocation) => run2(() => runtime.publish({ operation: "publish", ...decodePublish(input) }, invocation))
9070
+ },
9071
+ {
9072
+ name: "revert_handbook",
9073
+ description: handbookMessage("revertTool"),
9074
+ inputSchema: toolSchema(RevertInput),
9075
+ handle: (input, invocation) => run2(() => runtime.publish({ operation: "revert", ...decodeRevert(input) }, invocation))
9076
+ }
9077
+ ]
9078
+ });
9079
+ ctx.command({
9080
+ name: "handbook",
9081
+ scope: "read",
9082
+ payloadSchema: payloadSchemaOf(ReadHandbookInput),
9083
+ handle: (input) => runtime.read(decodeRead4(input).projectId)
9084
+ });
9085
+ ctx.operations.guard({ operations: ["work.assign"], check: runtime.check });
9086
+ }
9087
+
9088
+ // src/handbook/panelContracts.ts
9089
+ var project = { projectId: String4 };
9090
+ var ReadPanelInput = Struct(project);
9091
+ var ReadReleasesInput = Struct({
9092
+ ...project,
9093
+ cursor: optionalKey2(String4)
9094
+ });
9095
+ var ReadPageInput = Struct({
9096
+ ...project,
9097
+ pageId: String4,
9098
+ revision: Int,
9099
+ textOffset: optionalKey2(Int)
9100
+ });
9101
+ var PublishPanelInput = Struct({ ...project, ...PublishInput.fields });
9102
+ var RevertPanelInput = Struct({ ...project, ...RevertInput.fields });
9103
+ var HandbookWriteResult = Union2([
9104
+ Struct({ status: Literal2("succeeded"), release: Int }),
9105
+ Struct({ status: Literal2("rejected"), code: String4, detail: String4 })
9106
+ ]);
9107
+
9108
+ // src/handbook/panel.ts
9109
+ var decodePanel = decodeUnknownSync(ReadPanelInput);
9110
+ var decodeList = decodeUnknownSync(ReadReleasesInput);
9111
+ var decodePage = decodeUnknownSync(ReadPageInput);
9112
+ var decodePublish2 = decodeUnknownSync(PublishPanelInput);
9113
+ var decodeRevert2 = decodeUnknownSync(RevertPanelInput);
9114
+ function registerHandbookPanel(ctx, runtime) {
9115
+ ctx.command({
9116
+ name: "handbookPanel",
9117
+ scope: "read",
9118
+ payloadSchema: payloadSchemaOf(ReadPanelInput),
9119
+ handle: async (payload, invocation) => {
9120
+ const input = decodePanel(payload);
9121
+ const [result2, status] = await Promise.all([
9122
+ ctx.memory.handbook.get(input),
9123
+ runtime.read(input.projectId)
9124
+ ]);
9125
+ return { head: result2.head, status, canOperate: invocation.scope === "operate" };
9126
+ }
9127
+ });
9128
+ ctx.command({
9129
+ name: "handbookReleases",
9130
+ scope: "read",
9131
+ payloadSchema: payloadSchemaOf(ReadReleasesInput),
9132
+ handle: async (payload) => {
9133
+ const input = decodeList(payload);
9134
+ const result2 = await ctx.memory.handbook.releases.list({ ...input, limit: 20 });
9135
+ const publishers = await runtime.publisherMembers(
9136
+ input.projectId,
9137
+ result2.items.map((row) => row.release)
9138
+ );
9139
+ return {
9140
+ ...result2,
9141
+ items: result2.items.map((row) => ({
9142
+ ...row,
9143
+ publisherMemberId: row.publishedBy.kind === "plugin" && row.publishedBy.pluginId === "team" ? publishers.find((publisher) => publisher.release === row.release)?.memberId ?? null : null
9144
+ }))
9145
+ };
9146
+ }
9147
+ });
9148
+ ctx.command({
9149
+ name: "handbookPage",
9150
+ scope: "read",
9151
+ payloadSchema: payloadSchemaOf(ReadPageInput),
9152
+ handle: (payload) => ctx.memory.pageRevisions.get(decodePage(payload))
9153
+ });
9154
+ const write = async (input, invocation) => {
9155
+ if (invocation.scope !== "operate" || invocation.asPerson === void 0)
9156
+ return { status: "rejected", code: "needs-person", detail: "needs-person" };
9157
+ try {
9158
+ const requireAck = await publicationRequiresAck(ctx.memory, input.projectId, input);
9159
+ const handbook = invocation.asPerson.memory.handbook;
9160
+ const command = {
9161
+ projectId: input.projectId,
9162
+ requestId: input.requestId,
9163
+ expectedRelease: input.expectedRelease,
9164
+ requireAck
9165
+ };
9166
+ const result2 = input.operation === "publish" ? await handbook.publish({
9167
+ ...command,
9168
+ pages: input.pages,
9169
+ ...input.sourceExperience === void 0 ? {} : { sourceExperience: input.sourceExperience }
9170
+ }) : await handbook.revert({ ...command, toRelease: input.toRelease });
9171
+ return { status: "succeeded", release: result2.result.release };
9172
+ } catch (error) {
9173
+ const refusal = decodeHostRefusal(error);
9174
+ if (isNone2(refusal)) throw error;
9175
+ const { code, details } = refusal.value.rejection;
9176
+ return {
9177
+ status: "rejected",
9178
+ code,
9179
+ detail: [
9180
+ error instanceof Error ? error.message : code,
9181
+ details === void 0 ? null : JSON.stringify(details)
9182
+ ].filter(Boolean).join("\n")
9183
+ };
9184
+ }
9185
+ };
9186
+ ctx.command({
9187
+ name: "publishHandbook",
9188
+ scope: "operate",
9189
+ payloadSchema: payloadSchemaOf(PublishPanelInput),
9190
+ handle: (payload, invocation) => write({ ...decodePublish2(payload), operation: "publish" }, invocation)
9191
+ });
9192
+ ctx.command({
9193
+ name: "revertHandbook",
9194
+ scope: "operate",
9195
+ payloadSchema: payloadSchemaOf(RevertPanelInput),
9196
+ handle: (payload, invocation) => write({ ...decodeRevert2(payload), operation: "revert" }, invocation)
9197
+ });
9198
+ }
9199
+
8178
9200
  // src/index.ts
8179
9201
  var index_default = definePlugin({
8180
9202
  id: "team",
8181
- apiVersion: 12,
9203
+ apiVersion: 14,
8182
9204
  activate(ctx) {
8183
9205
  const rosters = createRosterService(ctx);
8184
9206
  registerRosterCommands(ctx, rosters);
@@ -8187,6 +9209,9 @@ var index_default = definePlugin({
8187
9209
  registerBoardCommands(ctx, goals, rosters);
8188
9210
  registerDetailCommands(ctx);
8189
9211
  startWakeRuntime(ctx);
9212
+ const handbook = startHandbookRuntime(ctx);
9213
+ registerHandbook(ctx, handbook);
9214
+ registerHandbookPanel(ctx, handbook);
8190
9215
  }
8191
9216
  });
8192
9217
  export {