@ycodium-ai/plugin-team 0.2.3122 → 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) {
@@ -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;
@@ -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,
@@ -7045,6 +7042,68 @@ var en_default = {
7045
7042
  remoteAgentsUnavailable: "Could not read the remote agents on this machine. Try again later.",
7046
7043
  remoteSource: "Remote agent: {{label}}"
7047
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
+ },
7048
7107
  remoteStatus: {
7049
7108
  title: "Remote members",
7050
7109
  none: "No remote members on the roster. Add one with \u201CAdd remote agent\u201D in the roster.",
@@ -7245,8 +7304,8 @@ function createGoalService(ctx, rosterService, options = {}) {
7245
7304
  });
7246
7305
  await goals.get({ throughSequence: receipt.sequence });
7247
7306
  goal = readFact(await requests.get({ throughSequence: receipt.sequence }), key) ?? goal;
7248
- const project = await ctx.projects.get({ projectId: goal.projectId });
7249
- 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 });
7250
7309
  const orderedThreads = [
7251
7310
  ...goal.threads.filter((thread) => thread.memberId === goal.leadMemberId),
7252
7311
  ...goal.threads.filter((thread) => thread.memberId !== goal.leadMemberId)
@@ -7308,16 +7367,32 @@ function createGoalService(ctx, rosterService, options = {}) {
7308
7367
  // src/commands.ts
7309
7368
  var decodeReadRoster = decodeUnknownSync(ReadRosterInput);
7310
7369
  var decodeReadGoal = decodeUnknownSync(ReadGoalInput);
7311
- var mergeAllOf = (node) => {
7312
- if (Array.isArray(node)) return node.map(mergeAllOf);
7370
+ var commandSchema = (node) => {
7371
+ if (Array.isArray(node)) return node.map(commandSchema);
7313
7372
  if (typeof node !== "object" || node === null) return node;
7314
7373
  const { allOf, ...rest } = node;
7315
7374
  const merged = Object.fromEntries(
7316
- Object.entries(rest).map(([key, value]) => [key, mergeAllOf(value)])
7375
+ Object.entries(rest).map(([key, value]) => [key, commandSchema(value)])
7317
7376
  );
7318
- 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
+ };
7319
7394
  };
7320
- var payloadSchemaOf = (schema) => mergeAllOf(toJsonSchemaDocument2(schema).schema);
7395
+ var payloadSchemaOf = (schema) => commandSchema(toJsonSchemaDocument2(schema).schema);
7321
7396
  function registerRosterCommands(ctx, service2) {
7322
7397
  ctx.command({
7323
7398
  name: "roster",
@@ -7347,57 +7422,8 @@ function registerGoalCommands(ctx, service2) {
7347
7422
  });
7348
7423
  }
7349
7424
 
7350
- // src/workReads.ts
7351
- var PAGE_SIZE = 200;
7352
- async function readPages(maximum, read, keep = "oldest") {
7353
- let items = [];
7354
- let truncated = false;
7355
- let cursor = null;
7356
- do {
7357
- const page = await read({ limit: PAGE_SIZE, ...cursor === null ? {} : { cursor } });
7358
- items.push(...page.items);
7359
- cursor = page.nextCursor;
7360
- if (keep === "oldest" && items.length >= maximum) {
7361
- return {
7362
- items: items.slice(0, maximum),
7363
- truncated: items.length > maximum || cursor !== null
7364
- };
7365
- }
7366
- if (items.length > maximum) {
7367
- truncated = true;
7368
- items = items.slice(-maximum);
7369
- }
7370
- } while (cursor !== null);
7371
- return { items, truncated };
7372
- }
7373
-
7374
- // src/workWrites.ts
7375
- var decodeWorkRefusal = decodeUnknownOption2(
7376
- Struct({
7377
- rejection: Struct({
7378
- code: String4,
7379
- details: optionalKey2(Unknown2)
7380
- })
7381
- })
7382
- );
7383
- var rejectWork = (commandType, code) => ({ _tag: "TeamBoardRejection", commandType, code });
7384
- async function writeWork(invocation, commandType, write) {
7385
- if (invocation.asPerson === void 0) return rejectWork(commandType, "needs-person");
7386
- try {
7387
- return await write(invocation.asPerson.work);
7388
- } catch (error) {
7389
- const refusal = decodeWorkRefusal(error);
7390
- if (isNone2(refusal)) throw error;
7391
- const { code, details } = refusal.value.rejection;
7392
- return {
7393
- ...rejectWork(
7394
- commandType,
7395
- commandType !== "postComment" && code === "version-conflict" ? "stale-version" : code
7396
- ),
7397
- ...details === void 0 ? {} : { details }
7398
- };
7399
- }
7400
- }
7425
+ // src/watch.ts
7426
+ import * as NodeTimers from "node:timers";
7401
7427
 
7402
7428
  // src/boardContracts.ts
7403
7429
  var BoardState = Struct({
@@ -7435,7 +7461,7 @@ var BoardRemoteWork = Struct({
7435
7461
  pending: NullOr(
7436
7462
  Struct({
7437
7463
  count: Number5,
7438
- items: ArraySchema(Struct({ workItemId: String4, blocked: Boolean2 }))
7464
+ items: ArraySchema(Struct({ workItemId: String4, blocked: Boolean3 }))
7439
7465
  })
7440
7466
  )
7441
7467
  });
@@ -7461,8 +7487,8 @@ var BoardSnapshot = Struct({
7461
7487
  remoteWork: ArraySchema(BoardRemoteWork),
7462
7488
  viewerKey: optionalKey2(String4),
7463
7489
  // 负责人要用看板与项目记忆两组工具;开关由宿主控件拨,这里只读来提示。
7464
- toolkits: Struct({ board: Boolean2, memory: Boolean2 }),
7465
- truncated: Boolean2
7490
+ toolkits: Struct({ board: Boolean3, memory: Boolean3 }),
7491
+ truncated: Boolean3
7466
7492
  });
7467
7493
  var ReadBoardInput = Struct({ projectId: String4 });
7468
7494
  var CreateItemInput = Struct({
@@ -7478,7 +7504,7 @@ var UpdateItemInput = Struct({
7478
7504
  expectedVersion: Int.check(isGreaterThanOrEqualTo3(1)),
7479
7505
  stateId: optionalKey2(String4),
7480
7506
  assigneeThreadId: optionalKey2(String4),
7481
- unassign: optionalKey2(Boolean2)
7507
+ unassign: optionalKey2(Boolean3)
7482
7508
  });
7483
7509
  var BoardRejection = Struct({
7484
7510
  _tag: Literal2("TeamBoardRejection"),
@@ -7495,18 +7521,14 @@ var UpdateItemResult = Union2([
7495
7521
  BoardRejection
7496
7522
  ]);
7497
7523
 
7498
- // src/board.ts
7499
- var ITEM_LIMIT = 500;
7500
- var PEOPLE_LIMIT = 200;
7501
- var REMOTE_AGENT_LIMIT = 1e3;
7524
+ // src/watch.ts
7525
+ var emptyChanges = () => ({ cursor: 0, handbookCursor: 0 });
7502
7526
  var decodeRead = decodeUnknownSync(ReadBoardInput);
7503
- var decodeCreate = decodeUnknownOption2(CreateItemInput);
7504
- var decodeUpdate = decodeUnknownOption2(UpdateItemInput);
7505
7527
  var decodeProject = decodeUnknownOption2(Struct({ projectId: String4 }));
7506
7528
  var decodeFact = decodeUnknownOption2(
7507
7529
  Struct({ pluginId: Literal2("team"), name: String4 })
7508
7530
  );
7509
- var WORK_EVENTS = [
7531
+ var WORK_EVENTS = /* @__PURE__ */ new Set([
7510
7532
  "work.item-created",
7511
7533
  "work.item-updated",
7512
7534
  "work.item-claimed",
@@ -7514,15 +7536,159 @@ var WORK_EVENTS = [
7514
7536
  "work.relation-removed",
7515
7537
  "work.comment-posted",
7516
7538
  "work.states-seeded",
7517
- // 快照带看板与记忆两只开关的状态,拨一下就敲门。
7518
7539
  "board.toolkit-set",
7519
7540
  "memory.toolkit-set"
7520
- ];
7521
- 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) {
7522
7548
  const after = options.after ?? ((delay2, notify) => {
7523
7549
  const timer = NodeTimers.setTimeout(notify, delay2);
7524
7550
  return () => NodeTimers.clearTimeout(timer);
7525
7551
  });
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 = {}) {
7526
7692
  const isMember2 = async (projectId, threadId) => goalExecution(await goals.read(projectId)).members.some(
7527
7693
  (member) => "threadId" in member && member.threadId === threadId
7528
7694
  );
@@ -7531,7 +7697,7 @@ function registerBoardCommands(ctx, goals, rosters, options = {}) {
7531
7697
  scope: "read",
7532
7698
  payloadSchema: payloadSchemaOf(ReadBoardInput),
7533
7699
  handle: async (payload, invocation) => {
7534
- const { projectId } = decodeRead(payload);
7700
+ const { projectId } = decodeRead2(payload);
7535
7701
  const [stateList, goal, roster, toolkitStates, people, remoteAgents, viewer] = await Promise.all([
7536
7702
  ctx.work.states.list({ projectId }),
7537
7703
  goals.read(projectId),
@@ -7656,74 +7822,7 @@ function registerBoardCommands(ctx, goals, rosters, options = {}) {
7656
7822
  return "code" in result2 ? result2 : { workItemId: result2.result.workItemId, version: result2.result.version };
7657
7823
  }
7658
7824
  });
7659
- const listeners = /* @__PURE__ */ new Map();
7660
- const cursors = /* @__PURE__ */ new Map();
7661
- const timers = /* @__PURE__ */ new Map();
7662
- let disposed = false;
7663
- const changed = (event) => {
7664
- const payload = decodeProject(event.payload);
7665
- const projectId = isSome2(payload) ? payload.value.projectId : event.aggregateKind === "project" ? event.aggregateId : null;
7666
- if (projectId === null || disposed || !listeners.has(projectId)) return;
7667
- cursors.set(projectId, Math.max(cursors.get(projectId) ?? 0, event.sequence));
7668
- if (timers.has(projectId)) return;
7669
- timers.set(
7670
- projectId,
7671
- after(50, () => {
7672
- timers.delete(projectId);
7673
- for (const emit of [...listeners.get(projectId) ?? []]) {
7674
- emit({ cursor: cursors.get(projectId) ?? 0 });
7675
- }
7676
- })
7677
- );
7678
- };
7679
- const stops = WORK_EVENTS.map((type) => ctx.events.onRaw(type, changed));
7680
- stops.push(
7681
- ctx.events.onRaw("plugin.event-appended", (event) => {
7682
- const fact = decodeFact(event.payload);
7683
- if (isSome2(fact) && ["team/goal-started", "team/roster-set"].includes(fact.value.name)) {
7684
- changed(event);
7685
- }
7686
- })
7687
- );
7688
- ctx.stream({
7689
- name: "watch",
7690
- scope: "read",
7691
- payloadSchema: payloadSchemaOf(ReadBoardInput),
7692
- buffer: { latest: 1 },
7693
- handle: async (payload, emit, signal) => {
7694
- if (signal.aborted || disposed) return;
7695
- const { projectId } = decodeRead(payload);
7696
- const subscribers = listeners.get(projectId) ?? /* @__PURE__ */ new Set();
7697
- const ring = (value) => {
7698
- if (!signal.aborted && !disposed) emit(value);
7699
- };
7700
- listeners.set(projectId, subscribers);
7701
- subscribers.add(ring);
7702
- try {
7703
- ring({ cursor: cursors.get(projectId) ?? 0 });
7704
- await new Promise((resolve2) => {
7705
- if (signal.aborted) resolve2();
7706
- else signal.addEventListener("abort", () => resolve2(), { once: true });
7707
- });
7708
- } finally {
7709
- subscribers.delete(ring);
7710
- if (subscribers.size === 0) {
7711
- listeners.delete(projectId);
7712
- timers.get(projectId)?.();
7713
- timers.delete(projectId);
7714
- cursors.delete(projectId);
7715
- }
7716
- }
7717
- }
7718
- });
7719
- ctx.onDispose(() => {
7720
- disposed = true;
7721
- for (const stop of stops) stop();
7722
- for (const cancel of timers.values()) cancel();
7723
- timers.clear();
7724
- listeners.clear();
7725
- cursors.clear();
7726
- });
7825
+ registerTeamWatch(ctx, options);
7727
7826
  }
7728
7827
 
7729
7828
  // src/detailContracts.ts
@@ -7785,9 +7884,9 @@ var ItemDetailSnapshot = Struct({
7785
7884
  blockedBy: ArraySchema(Dependency),
7786
7885
  blocking: ArraySchema(Dependency),
7787
7886
  comments: ArraySchema(DetailComment),
7788
- commentsTruncated: Boolean2,
7887
+ commentsTruncated: Boolean3,
7789
7888
  history: ArraySchema(DetailActivity),
7790
- historyTruncated: Boolean2
7889
+ historyTruncated: Boolean3
7791
7890
  });
7792
7891
  var ReadItemDetailResult = NullOr(ItemDetailSnapshot);
7793
7892
 
@@ -7796,7 +7895,7 @@ var RELATION_LIMIT = 100;
7796
7895
  var COMMENT_LIMIT = 200;
7797
7896
  var HISTORY_LIMIT = 200;
7798
7897
  var GET_BATCH_SIZE = 50;
7799
- var decodeRead2 = decodeUnknownSync(ReadItemDetailInput);
7898
+ var decodeRead3 = decodeUnknownSync(ReadItemDetailInput);
7800
7899
  var decodePost = decodeUnknownOption2(PostCommentInput);
7801
7900
  function registerDetailCommands(ctx) {
7802
7901
  ctx.command({
@@ -7804,7 +7903,7 @@ function registerDetailCommands(ctx) {
7804
7903
  scope: "read",
7805
7904
  payloadSchema: payloadSchemaOf(ReadItemDetailInput),
7806
7905
  handle: async (payload) => {
7807
- const { projectId, workItemId } = decodeRead2(payload);
7906
+ const { projectId, workItemId } = decodeRead3(payload);
7808
7907
  const result2 = await ctx.work.items.get({ projectId, workItemIds: [workItemId] });
7809
7908
  const item = result2.items.find((row) => row.workItemId === workItemId);
7810
7909
  if (item === void 0) return null;
@@ -8045,14 +8144,14 @@ var emptyProject = () => ({
8045
8144
  });
8046
8145
  var itemWakeKey = (workItemId, threadId, generation) => generation === 0 ? `item:${workItemId}:${threadId}` : `item:${workItemId}:${threadId}#${generation}`;
8047
8146
  var unblockedWakeKey = (workItemId, threadId, generation) => `unblocked:${workItemId}:${threadId}#${generation}`;
8048
- var hasOpenBlocker = (project, workItemId) => (readFact(project.blockers, workItemId) ?? []).some(
8049
- (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
8050
8149
  );
8051
- var markBlockedAfterClaim = (project, workItemId) => ({
8052
- ...project,
8150
+ var markBlockedAfterClaim = (project2, workItemId) => ({
8151
+ ...project2,
8053
8152
  blockedAfterClaim: {
8054
- ...project.blockedAfterClaim,
8055
- [workItemId]: (readFact(project.blockedAfterClaim, workItemId) ?? 0) + 1
8153
+ ...project2.blockedAfterClaim,
8154
+ [workItemId]: (readFact(project2.blockedAfterClaim, workItemId) ?? 0) + 1
8056
8155
  }
8057
8156
  });
8058
8157
  var decodeEnvelope2 = decodeUnknownOption2(
@@ -8137,8 +8236,8 @@ var decodeWorkEvent = decodeUnknownOption2(
8137
8236
  var agentThreads = (principals) => [
8138
8237
  ...new Set(principals.flatMap((p) => p.kind === "agent" ? [p.threadId] : []))
8139
8238
  ];
8140
- var isMember = (project, recipient) => "threadId" in recipient ? project.threads.includes(recipient.threadId) : project.remoteMembers.includes(recipient.principalId);
8141
- 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) => {
8142
8241
  const recipients = /* @__PURE__ */ new Map();
8143
8242
  for (const person of people) {
8144
8243
  if (person.kind === "agent" && !(actor.kind === "agent" && actor.threadId === person.threadId))
@@ -8146,7 +8245,7 @@ var commentRecipients = (project, actor, people) => {
8146
8245
  if (person.kind === "remote-agent" && !(actor.kind === "remote-agent" && actor.principalId === person.principalId))
8147
8246
  recipients.set(`principal:${person.principalId}`, { principalId: person.principalId });
8148
8247
  }
8149
- return [...recipients.values()].filter((recipient) => isMember(project, recipient));
8248
+ return [...recipients.values()].filter((recipient) => isMember(project2, recipient));
8150
8249
  };
8151
8250
  var commentWakeKey = (commentId, recipient) => "threadId" in recipient ? `comment:${commentId}:${recipient.threadId}` : `comment:${commentId}:principal:${recipient.principalId}`;
8152
8251
  function foldWakes(state, event) {
@@ -8156,7 +8255,7 @@ function foldWakes(state, event) {
8156
8255
  const fact = decoded2.value;
8157
8256
  if (fact.name === "team/goal-started") {
8158
8257
  const { goal } = fact.data;
8159
- const project3 = readFact(state, goal.projectId) ?? emptyProject();
8258
+ const project4 = readFact(state, goal.projectId) ?? emptyProject();
8160
8259
  const { members } = goalExecution(goal);
8161
8260
  const threads = [
8162
8261
  ...new Set(members.flatMap((member) => "threadId" in member ? [member.threadId] : []))
@@ -8167,42 +8266,42 @@ function foldWakes(state, event) {
8167
8266
  )
8168
8267
  ];
8169
8268
  const leadThreads = goal.threads.filter((thread) => thread.memberId === goal.leadMemberId).map((thread) => thread.threadId);
8170
- const next2 = { ...project3, threads, leadThreads, remoteMembers };
8269
+ const next2 = { ...project4, threads, leadThreads, remoteMembers };
8171
8270
  return {
8172
8271
  ...state,
8173
8272
  [goal.projectId]: {
8174
8273
  ...next2,
8175
- pendingComments: project3.pendingComments.filter((comment) => isMember(next2, comment))
8274
+ pendingComments: project4.pendingComments.filter((comment) => isMember(next2, comment))
8176
8275
  }
8177
8276
  };
8178
8277
  }
8179
8278
  const { projectId: projectId2, key } = fact.data;
8180
- const project2 = readFact(state, projectId2) ?? emptyProject();
8181
- if (project2.pendingComments.some((comment) => comment.key === key)) {
8279
+ const project3 = readFact(state, projectId2) ?? emptyProject();
8280
+ if (project3.pendingComments.some((comment) => comment.key === key)) {
8182
8281
  return {
8183
8282
  ...state,
8184
8283
  [projectId2]: {
8185
- ...project2,
8186
- pendingComments: project2.pendingComments.filter((comment) => comment.key !== key)
8284
+ ...project3,
8285
+ pendingComments: project3.pendingComments.filter((comment) => comment.key !== key)
8187
8286
  }
8188
8287
  };
8189
8288
  }
8190
- if (project2.woken.includes(key)) return state;
8191
- 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] } };
8192
8291
  }
8193
8292
  const decoded = decodeWorkEvent(event);
8194
8293
  if (isNone2(decoded)) return state;
8195
8294
  const work = decoded.value;
8196
8295
  const { projectId, workItemId } = work.payload;
8197
- const project = readFact(state, projectId) ?? emptyProject();
8198
- const currentItem = readFact(project.items, workItemId);
8199
- let next = project;
8296
+ const project2 = readFact(state, projectId) ?? emptyProject();
8297
+ const currentItem = readFact(project2.items, workItemId);
8298
+ let next = project2;
8200
8299
  switch (work.type) {
8201
8300
  case "work.item-created":
8202
8301
  next = {
8203
- ...project,
8302
+ ...project2,
8204
8303
  items: {
8205
- ...project.items,
8304
+ ...project2.items,
8206
8305
  [workItemId]: {
8207
8306
  sequenceId: work.payload.sequenceId,
8208
8307
  assigneeThreads: agentThreads(work.payload.assignees),
@@ -8222,9 +8321,9 @@ function foldWakes(state, event) {
8222
8321
  ])
8223
8322
  ];
8224
8323
  next = {
8225
- ...project,
8324
+ ...project2,
8226
8325
  items: {
8227
- ...project.items,
8326
+ ...project2.items,
8228
8327
  [workItemId]: {
8229
8328
  ...currentItem,
8230
8329
  assigneeThreads,
@@ -8240,18 +8339,18 @@ function foldWakes(state, event) {
8240
8339
  case "work.relation-added":
8241
8340
  case "work.relation-removed": {
8242
8341
  if (work.payload.relationKind !== "blocked_by") return state;
8243
- const blockers = readFact(project.blockers, workItemId) ?? [];
8342
+ const blockers = readFact(project2.blockers, workItemId) ?? [];
8244
8343
  const related = work.payload.relatedWorkItemId;
8245
8344
  next = {
8246
- ...project,
8345
+ ...project2,
8247
8346
  blockers: {
8248
- ...project.blockers,
8347
+ ...project2.blockers,
8249
8348
  [workItemId]: work.type === "work.relation-added" ? [.../* @__PURE__ */ new Set([...blockers, related])] : blockers.filter((blocker) => blocker !== related)
8250
8349
  }
8251
8350
  };
8252
- if (work.type === "work.relation-added" && currentItem !== null && readFact(project.items, related)?.closed !== true) {
8253
- const woken = new Set(project.woken);
8254
- 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 };
8255
8354
  for (const threadId of currentItem.assigneeThreads) {
8256
8355
  if (currentItem.claimedBy.includes(threadId)) continue;
8257
8356
  const generation = rearms[`${workItemId}:${threadId}`] ?? 0;
@@ -8259,14 +8358,14 @@ function foldWakes(state, event) {
8259
8358
  rearms[`${workItemId}:${threadId}`] = generation + 1;
8260
8359
  }
8261
8360
  next = { ...next, rearms };
8262
- if (currentItem.claimedBy.length > 0 && !hasOpenBlocker(project, workItemId))
8361
+ if (currentItem.claimedBy.length > 0 && !hasOpenBlocker(project2, workItemId))
8263
8362
  next = markBlockedAfterClaim(next, workItemId);
8264
8363
  }
8265
8364
  break;
8266
8365
  }
8267
8366
  case "work.comment-posted": {
8268
8367
  const { actor, parentAuthor, mentions, commentId } = work.payload;
8269
- const comments = commentRecipients(project, actor, [
8368
+ const comments = commentRecipients(project2, actor, [
8270
8369
  ...parentAuthor === void 0 ? [] : [parentAuthor],
8271
8370
  ...mentions
8272
8371
  ]).map((recipient) => ({
@@ -8276,10 +8375,10 @@ function foldWakes(state, event) {
8276
8375
  workItemId,
8277
8376
  commentId
8278
8377
  })).filter(
8279
- (comment) => !project.pendingComments.some((pending) => pending.key === comment.key)
8378
+ (comment) => !project2.pendingComments.some((pending) => pending.key === comment.key)
8280
8379
  );
8281
8380
  if (comments.length === 0) return state;
8282
- next = { ...project, pendingComments: [...project.pendingComments, ...comments] };
8381
+ next = { ...project2, pendingComments: [...project2.pendingComments, ...comments] };
8283
8382
  break;
8284
8383
  }
8285
8384
  }
@@ -8288,13 +8387,13 @@ function foldWakes(state, event) {
8288
8387
  var compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
8289
8388
  function dueWakes(state) {
8290
8389
  const due = [];
8291
- for (const [projectId, project] of Object.entries(state)) {
8292
- const woken = new Set(project.woken);
8293
- const threads = new Set(project.threads);
8294
- const leads = new Set(project.leadThreads ?? []);
8295
- for (const [workItemId, item] of Object.entries(project.items)) {
8296
- if (item.closed || hasOpenBlocker(project, workItemId)) continue;
8297
- 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;
8298
8397
  const recipients = /* @__PURE__ */ new Set([
8299
8398
  ...item.assigneeThreads,
8300
8399
  ...unblocked > 0 ? item.claimedBy : []
@@ -8305,7 +8404,7 @@ function dueWakes(state) {
8305
8404
  const key = finishing ? unblockedWakeKey(workItemId, threadId, unblocked) : itemWakeKey(
8306
8405
  workItemId,
8307
8406
  threadId,
8308
- readFact(project.rearms, `${workItemId}:${threadId}`) ?? 0
8407
+ readFact(project2.rearms, `${workItemId}:${threadId}`) ?? 0
8309
8408
  );
8310
8409
  if (woken.has(key)) continue;
8311
8410
  due.push({
@@ -8319,8 +8418,8 @@ function dueWakes(state) {
8319
8418
  });
8320
8419
  }
8321
8420
  }
8322
- for (const comment of project.pendingComments) {
8323
- const item = readFact(project.items, comment.workItemId);
8421
+ for (const comment of project2.pendingComments) {
8422
+ const item = readFact(project2.items, comment.workItemId);
8324
8423
  due.push({
8325
8424
  ...comment,
8326
8425
  kind: "comment",
@@ -8453,8 +8552,13 @@ async function publicationRequiresAck(memory, projectId, input) {
8453
8552
 
8454
8553
  // src/handbook/contracts.ts
8455
8554
  var Release = Int.check(isGreaterThanOrEqualTo3(1));
8555
+ var HandbookPageKind = Literals([
8556
+ "handbook_decisions",
8557
+ "handbook_rules",
8558
+ "handbook_progress"
8559
+ ]);
8456
8560
  var Page = Struct({
8457
- kind: Literals(["handbook_decisions", "handbook_rules", "handbook_progress"]),
8561
+ kind: HandbookPageKind,
8458
8562
  baseRevision: NullOr(Finite),
8459
8563
  title: optionalKey2(String4),
8460
8564
  body: String4,
@@ -8463,7 +8567,7 @@ var Page = Struct({
8463
8567
  var WriteFields = {
8464
8568
  requestId: String4,
8465
8569
  expectedRelease: Finite,
8466
- requireAck: optionalKey2(Boolean2)
8570
+ requireAck: optionalKey2(Boolean3)
8467
8571
  };
8468
8572
  var PublishInput = Struct({
8469
8573
  ...WriteFields,
@@ -8481,13 +8585,14 @@ var PublicationIntent = Struct({
8481
8585
  memberId: String4,
8482
8586
  requestId: String4,
8483
8587
  op: Literals(["publish", "revert"]),
8484
- requireAckChoice: optionalKey2(Boolean2),
8485
- requiresAck: optionalKey2(Boolean2)
8588
+ requireAckChoice: optionalKey2(Boolean3),
8589
+ requiresAck: optionalKey2(Boolean3)
8486
8590
  });
8487
8591
  var PublicationCompleted = Struct({
8488
8592
  version: Literal2(1),
8489
8593
  projectId: String4,
8490
8594
  requestId: String4,
8595
+ producedRelease: optionalKey2(Boolean3),
8491
8596
  outcome: Union2([
8492
8597
  Struct({ status: Literal2("succeeded"), release: Release }),
8493
8598
  Struct({ status: Literal2("rejected") }),
@@ -8505,11 +8610,12 @@ var ReminderSent = Struct({
8505
8610
  });
8506
8611
 
8507
8612
  // src/handbook/state.ts
8508
- var HANDBOOK_EVENTS = ["plugin.event-appended", "wiki.handbook-released"];
8613
+ var HANDBOOK_EVENTS2 = ["plugin.event-appended", "wiki.handbook-released"];
8509
8614
  var emptyHandbookState = () => ({
8510
8615
  projects: {},
8511
8616
  intents: {},
8512
8617
  completed: {},
8618
+ publishers: {},
8513
8619
  pendingPublications: {},
8514
8620
  pendingAcknowledgements: {},
8515
8621
  pendingReminders: {}
@@ -8535,16 +8641,16 @@ var decodeRelease = decodeUnknownSync(
8535
8641
  Struct({
8536
8642
  projectId: String4,
8537
8643
  release: Number5,
8538
- requiresAck: Boolean2
8644
+ requiresAck: Boolean3
8539
8645
  })
8540
8646
  );
8541
- function callerMember(project, caller) {
8647
+ function callerMember(project2, caller) {
8542
8648
  if (caller === void 0) return void 0;
8543
- return goalExecution(project.goal).members.find(
8649
+ return goalExecution(project2.goal).members.find(
8544
8650
  (member) => caller.kind === "thread" ? "threadId" in member && member.threadId === caller.threadId : "principalId" in member && member.principalId === caller.principalId
8545
8651
  );
8546
8652
  }
8547
- var hasAcknowledged = (project, memberId, release) => readFact(readFact(project.acknowledgements, memberId)?.releases ?? {}, String(release)) === true;
8653
+ var hasAcknowledged = (project2, memberId, release) => readFact(readFact(project2.acknowledgements, memberId)?.releases ?? {}, String(release)) === true;
8548
8654
  var reminderKey = (projectId, release, memberId) => JSON.stringify([projectId, release, memberId]);
8549
8655
  var without = (rows, key) => {
8550
8656
  if (!Object.hasOwn(rows, key)) return rows;
@@ -8555,12 +8661,12 @@ var without = (rows, key) => {
8555
8661
  function foldHandbook(state, event) {
8556
8662
  if (event.type === "wiki.handbook-released") {
8557
8663
  const fact2 = decodeRelease(event.payload);
8558
- const project2 = readFact(state.projects, fact2.projectId) ?? emptyHandbookProject();
8664
+ const project3 = readFact(state.projects, fact2.projectId) ?? emptyHandbookProject();
8559
8665
  const pendingReminders = { ...state.pendingReminders };
8560
8666
  if (fact2.requiresAck)
8561
- for (const { memberId, ...recipient } of goalExecution(project2.goal).members) {
8667
+ for (const { memberId, ...recipient } of goalExecution(project3.goal).members) {
8562
8668
  const key = reminderKey(fact2.projectId, fact2.release, memberId);
8563
- if (readFact(project2.reminded, key) === null)
8669
+ if (readFact(project3.reminded, key) === null)
8564
8670
  pendingReminders[key] = {
8565
8671
  projectId: fact2.projectId,
8566
8672
  release: fact2.release,
@@ -8573,9 +8679,9 @@ function foldHandbook(state, event) {
8573
8679
  projects: {
8574
8680
  ...state.projects,
8575
8681
  [fact2.projectId]: {
8576
- ...project2,
8682
+ ...project3,
8577
8683
  currentRelease: fact2.release,
8578
- requiredRelease: fact2.requiresAck ? fact2.release : project2.requiredRelease
8684
+ requiredRelease: fact2.requiresAck ? fact2.release : project3.requiredRelease
8579
8685
  }
8580
8686
  }
8581
8687
  };
@@ -8585,11 +8691,11 @@ function foldHandbook(state, event) {
8585
8691
  if (isNone2(decoded)) return state;
8586
8692
  const fact = decoded.value;
8587
8693
  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;
8694
+ const project2 = readFact(state.projects, projectId) ?? emptyHandbookProject();
8695
+ let next = project2;
8590
8696
  switch (fact.name) {
8591
8697
  case "team/goal-started":
8592
- next = { ...project, goal: fact.data.goal };
8698
+ next = { ...project2, goal: fact.data.goal };
8593
8699
  break;
8594
8700
  case "team/handbook-publication-intended": {
8595
8701
  const intent = fact.data;
@@ -8604,13 +8710,23 @@ function foldHandbook(state, event) {
8604
8710
  const completed = fact.data;
8605
8711
  if (readFact(state.completed, completed.requestId) !== null) return state;
8606
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
+ }
8607
8723
  let pendingAcknowledgements = state.pendingAcknowledgements;
8608
8724
  let pendingReminders = state.pendingReminders;
8609
8725
  if (intent !== null && intent.requiresAck && completed.outcome.status === "succeeded") {
8610
8726
  const { release } = completed.outcome;
8611
8727
  const key = reminderKey(projectId, release, intent.memberId);
8612
8728
  pendingReminders = without(pendingReminders, key);
8613
- if (!hasAcknowledged(project, intent.memberId, release))
8729
+ if (!hasAcknowledged(project2, intent.memberId, release))
8614
8730
  pendingAcknowledgements = {
8615
8731
  ...pendingAcknowledgements,
8616
8732
  [key]: { version: 1, projectId, release, memberId: intent.memberId }
@@ -8619,6 +8735,7 @@ function foldHandbook(state, event) {
8619
8735
  return {
8620
8736
  ...state,
8621
8737
  completed: { ...state.completed, [completed.requestId]: completed },
8738
+ ...publishers === void 0 ? {} : { publishers },
8622
8739
  pendingPublications: without(state.pendingPublications, completed.requestId),
8623
8740
  pendingAcknowledgements,
8624
8741
  pendingReminders
@@ -8626,12 +8743,12 @@ function foldHandbook(state, event) {
8626
8743
  }
8627
8744
  case "team/handbook-acknowledged": {
8628
8745
  const { memberId, release } = fact.data;
8629
- if (hasAcknowledged(project, memberId, release)) return state;
8630
- const previous = readFact(project.acknowledgements, memberId);
8746
+ if (hasAcknowledged(project2, memberId, release)) return state;
8747
+ const previous = readFact(project2.acknowledgements, memberId);
8631
8748
  next = {
8632
- ...project,
8749
+ ...project2,
8633
8750
  acknowledgements: {
8634
- ...project.acknowledgements,
8751
+ ...project2.acknowledgements,
8635
8752
  [memberId]: {
8636
8753
  releases: { ...previous?.releases, [release]: true },
8637
8754
  latestRelease: Math.max(previous?.latestRelease ?? 0, release)
@@ -8649,8 +8766,8 @@ function foldHandbook(state, event) {
8649
8766
  }
8650
8767
  case "team/handbook-reminder-sent": {
8651
8768
  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 } };
8769
+ if (readFact(project2.reminded, key) !== null) return state;
8770
+ next = { ...project2, reminded: { ...project2.reminded, [key]: true } };
8654
8771
  return {
8655
8772
  ...state,
8656
8773
  pendingReminders: without(state.pendingReminders, key),
@@ -8675,8 +8792,8 @@ var terminalFailure = (error) => {
8675
8792
  function startHandbookRuntime(ctx) {
8676
8793
  const state = ctx.derivedState({
8677
8794
  key: "handbook",
8678
- version: 4,
8679
- events: HANDBOOK_EVENTS,
8795
+ version: 5,
8796
+ events: HANDBOOK_EVENTS2,
8680
8797
  init: emptyHandbookState,
8681
8798
  apply: foldHandbook
8682
8799
  });
@@ -8703,8 +8820,8 @@ function startHandbookRuntime(ctx) {
8703
8820
  return state.get({ throughSequence: receipt.sequence });
8704
8821
  };
8705
8822
  const acknowledge = async (projectId, release, memberId) => {
8706
- const project = readFact((await state.get()).projects, projectId) ?? emptyHandbookProject();
8707
- if (hasAcknowledged(project, memberId, release)) return;
8823
+ const project2 = readFact((await state.get()).projects, projectId) ?? emptyHandbookProject();
8824
+ if (hasAcknowledged(project2, memberId, release)) return;
8708
8825
  await append2(
8709
8826
  projectId,
8710
8827
  "handbook-acknowledged",
@@ -8717,12 +8834,13 @@ function startHandbookRuntime(ctx) {
8717
8834
  }
8718
8835
  );
8719
8836
  };
8720
- const complete = async (intent, outcome) => {
8837
+ const complete = async (intent, outcome, producedRelease) => {
8721
8838
  await append2(intent.projectId, "handbook-publication-completed", intent.requestId, {
8722
8839
  version: 1,
8723
8840
  requestId: intent.requestId,
8724
8841
  projectId: intent.projectId,
8725
- outcome
8842
+ outcome,
8843
+ ...producedRelease === void 0 ? {} : { producedRelease }
8726
8844
  });
8727
8845
  if (outcome.status === "succeeded" && intent.requiresAck) {
8728
8846
  await acknowledge(intent.projectId, outcome.release, intent.memberId);
@@ -8742,7 +8860,11 @@ function startHandbookRuntime(ctx) {
8742
8860
  if (typeof result2 !== "object" || result2 === null || !("release" in result2) || typeof result2.release !== "number" || !Number.isSafeInteger(result2.release) || result2.release < 1)
8743
8861
  throw new Error("handbook-receipt-result-unavailable");
8744
8862
  await state.get({ throughSequence: receipt.sequence });
8745
- return complete(intent, { status: "succeeded", release: result2.release });
8863
+ return complete(
8864
+ intent,
8865
+ { status: "succeeded", release: result2.release },
8866
+ "changed" in result2 && result2.changed === true
8867
+ );
8746
8868
  };
8747
8869
  const remind = async () => {
8748
8870
  const snapshot = await state.get();
@@ -8796,7 +8918,7 @@ function startHandbookRuntime(ctx) {
8796
8918
  await remind();
8797
8919
  };
8798
8920
  const flush = () => serial(execute);
8799
- ctx.reactor({ name: "team-handbook", events: HANDBOOK_EVENTS, handle: () => flush() });
8921
+ ctx.reactor({ name: "team-handbook", events: HANDBOOK_EVENTS2, handle: () => flush() });
8800
8922
  ctx.onDispose(async () => {
8801
8923
  disposed = true;
8802
8924
  await pending;
@@ -8806,28 +8928,36 @@ function startHandbookRuntime(ctx) {
8806
8928
  return {
8807
8929
  ready,
8808
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
+ },
8809
8939
  async read(projectId) {
8810
- const project = readFact((await state.get()).projects, projectId) ?? emptyHandbookProject();
8940
+ const project2 = readFact((await state.get()).projects, projectId) ?? emptyHandbookProject();
8811
8941
  return {
8812
- currentRelease: project.currentRelease,
8813
- requiredRelease: project.requiredRelease,
8814
- members: goalExecution(project.goal).members.map(({ memberId }) => ({
8942
+ currentRelease: project2.currentRelease,
8943
+ requiredRelease: project2.requiredRelease,
8944
+ members: goalExecution(project2.goal).members.map(({ memberId }) => ({
8815
8945
  memberId,
8816
- acknowledgedRelease: readFact(project.acknowledgements, memberId)?.latestRelease ?? null,
8817
- acknowledgedRequiredRelease: project.requiredRelease === null || hasAcknowledged(project, memberId, project.requiredRelease)
8946
+ acknowledgedRelease: readFact(project2.acknowledgements, memberId)?.latestRelease ?? null,
8947
+ acknowledgedRequiredRelease: project2.requiredRelease === null || hasAcknowledged(project2, memberId, project2.requiredRelease)
8818
8948
  }))
8819
8949
  };
8820
8950
  },
8821
8951
  ack(release, invocation) {
8822
8952
  return serial(async () => {
8823
8953
  const projectId = invocation.projectId;
8824
- const project = projectId === null ? emptyHandbookProject() : readFact((await state.get()).projects, projectId) ?? emptyHandbookProject();
8825
- const member = callerMember(project, invocation.caller);
8954
+ const project2 = projectId === null ? emptyHandbookProject() : readFact((await state.get()).projects, projectId) ?? emptyHandbookProject();
8955
+ const member = callerMember(project2, invocation.caller);
8826
8956
  if (member === void 0 || projectId === null)
8827
8957
  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));
8958
+ if (project2.requiredRelease === null) throw new Error(handbookMessage("noRequirement"));
8959
+ if (release !== project2.requiredRelease)
8960
+ throw new Error(handbookMessage("wrongRelease", project2.requiredRelease));
8831
8961
  await acknowledge(projectId, release, member.memberId);
8832
8962
  return { release, memberId: member.memberId };
8833
8963
  });
@@ -8835,9 +8965,9 @@ function startHandbookRuntime(ctx) {
8835
8965
  publish(input, invocation) {
8836
8966
  return serial(async () => {
8837
8967
  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);
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);
8841
8971
  if (execution.leadMemberId === null) throw new Error(handbookMessage("noGoal"));
8842
8972
  if (projectId === null || invocation.caller?.kind !== "thread" || member === void 0 || member.memberId !== execution.leadMemberId)
8843
8973
  throw new Error(handbookMessage("leadOnly"));
@@ -8863,35 +8993,37 @@ function startHandbookRuntime(ctx) {
8863
8993
  if (existing === null)
8864
8994
  await append2(projectId, "handbook-publication-intended", requestId, intent);
8865
8995
  let outcome;
8996
+ let producedRelease;
8866
8997
  let detail = null;
8867
8998
  try {
8868
8999
  const command = { ...input, projectId, requestId, requireAck };
8869
9000
  const result2 = command.operation === "publish" ? await ctx.memory.handbook.publish(command) : await ctx.memory.handbook.revert(command);
8870
9001
  await state.get({ throughSequence: result2.sequence });
8871
9002
  outcome = { status: "succeeded", release: result2.result.release };
9003
+ producedRelease = result2.result.changed;
8872
9004
  } catch (error) {
8873
9005
  detail = terminalFailure(error);
8874
9006
  if (detail === null) throw error;
8875
9007
  outcome = { status: "rejected" };
8876
9008
  }
8877
- if (done2 === null) await complete(intent, outcome);
9009
+ if (done2 === null) await complete(intent, outcome, producedRelease);
8878
9010
  await remind();
8879
9011
  return detail === null ? outcome : { ...outcome, detail };
8880
9012
  });
8881
9013
  },
8882
9014
  async check(input) {
8883
- const project = readFact(
9015
+ const project2 = readFact(
8884
9016
  (await state.get({ throughSequence: input.throughSequence })).projects,
8885
9017
  input.projectId
8886
9018
  );
8887
- if (project === null || project.requiredRelease === null) return { allowed: true };
9019
+ if (project2 === null || project2.requiredRelease === null) return { allowed: true };
8888
9020
  for (const actor of input.added) {
8889
9021
  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)) {
9022
+ const member = callerMember(project2, caller);
9023
+ if (member !== void 0 && !hasAcknowledged(project2, member.memberId, project2.requiredRelease)) {
8892
9024
  return {
8893
9025
  allowed: false,
8894
- detail: handbookMessage("guard", project.requiredRelease)
9026
+ detail: handbookMessage("guard", project2.requiredRelease)
8895
9027
  };
8896
9028
  }
8897
9029
  }
@@ -8904,7 +9036,7 @@ function startHandbookRuntime(ctx) {
8904
9036
  var decodeAcknowledgement = decodeUnknownSync(AcknowledgeInput);
8905
9037
  var decodePublish = decodeUnknownSync(PublishInput);
8906
9038
  var decodeRevert = decodeUnknownSync(RevertInput);
8907
- var decodeRead3 = decodeUnknownSync(ReadHandbookInput);
9039
+ var decodeRead4 = decodeUnknownSync(ReadHandbookInput);
8908
9040
  var toolSchema = (schema) => ({
8909
9041
  ...payloadSchemaOf(schema),
8910
9042
  type: "object",
@@ -8948,15 +9080,127 @@ function registerHandbook(ctx, runtime) {
8948
9080
  name: "handbook",
8949
9081
  scope: "read",
8950
9082
  payloadSchema: payloadSchemaOf(ReadHandbookInput),
8951
- handle: (input) => runtime.read(decodeRead3(input).projectId)
9083
+ handle: (input) => runtime.read(decodeRead4(input).projectId)
8952
9084
  });
8953
9085
  ctx.operations.guard({ operations: ["work.assign"], check: runtime.check });
8954
9086
  }
8955
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
+
8956
9200
  // src/index.ts
8957
9201
  var index_default = definePlugin({
8958
9202
  id: "team",
8959
- apiVersion: 13,
9203
+ apiVersion: 14,
8960
9204
  activate(ctx) {
8961
9205
  const rosters = createRosterService(ctx);
8962
9206
  registerRosterCommands(ctx, rosters);
@@ -8965,7 +9209,9 @@ var index_default = definePlugin({
8965
9209
  registerBoardCommands(ctx, goals, rosters);
8966
9210
  registerDetailCommands(ctx);
8967
9211
  startWakeRuntime(ctx);
8968
- registerHandbook(ctx, startHandbookRuntime(ctx));
9212
+ const handbook = startHandbookRuntime(ctx);
9213
+ registerHandbook(ctx, handbook);
9214
+ registerHandbookPanel(ctx, handbook);
8969
9215
  }
8970
9216
  });
8971
9217
  export {