@ycodium-ai/plugin-team 0.2.3122 → 0.2.3158

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) {
@@ -154,6 +151,7 @@ function isObjectKeyword(input) {
154
151
  return typeof input === "object" && input !== null || isFunction(input);
155
152
  }
156
153
  var hasProperty = /* @__PURE__ */ dual(2, (self, property) => isObjectKeyword(self) && property in self);
154
+ var isTagged = /* @__PURE__ */ dual(2, (self, tag2) => hasProperty(self, "_tag") && self["_tag"] === tag2);
157
155
 
158
156
  // ../../node_modules/.pnpm/effect@4.0.0-beta.103_patch_hash=78523ded7c3dfc3efd3cec0247f4960d00eaa3d3c96a2077457694a4d8b453c6/node_modules/effect/dist/Hash.js
159
157
  var symbol = "~effect/interfaces/Hash";
@@ -3843,7 +3841,7 @@ function numberToJson(checks) {
3843
3841
  return new Link(new Union([encodedFinite, nonFiniteLiterals], "anyOf"), new Transformation(Number3(), transform((n) => globalThis.Number.isFinite(n) ? n : globalThis.String(n))));
3844
3842
  }
3845
3843
  var number2 = /* @__PURE__ */ new Number4();
3846
- var Boolean = class extends Base2 {
3844
+ var Boolean2 = class extends Base2 {
3847
3845
  _tag = "Boolean";
3848
3846
  /** @internal */
3849
3847
  getParser() {
@@ -3854,7 +3852,7 @@ var Boolean = class extends Base2 {
3854
3852
  return "boolean";
3855
3853
  }
3856
3854
  };
3857
- var boolean = /* @__PURE__ */ new Boolean();
3855
+ var boolean = /* @__PURE__ */ new Boolean2();
3858
3856
  var Arrays = class _Arrays extends Base2 {
3859
3857
  _tag = "Arrays";
3860
3858
  isMutable;
@@ -6265,7 +6263,7 @@ var Null2 = /* @__PURE__ */ make8(null_);
6265
6263
  var Undefined2 = /* @__PURE__ */ make8(undefined_2);
6266
6264
  var String4 = /* @__PURE__ */ make8(string2);
6267
6265
  var Number5 = /* @__PURE__ */ make8(number2);
6268
- var Boolean2 = /* @__PURE__ */ make8(boolean);
6266
+ var Boolean3 = /* @__PURE__ */ make8(boolean);
6269
6267
  function makeStruct(ast, fields) {
6270
6268
  return make8(ast, {
6271
6269
  fields,
@@ -7000,6 +6998,8 @@ var en_default = {
7000
6998
  confirmStart: "Start new goal",
7001
6999
  cancel: "Cancel",
7002
7000
  noRoster: "No saved roster was found. Save the roster and choose a lead first.",
7001
+ noDefaultModel: "The lead's thread didn't start: this project has no default model. Choose one in the project settings under New threads \u2192 Model, then retry.",
7002
+ modelUnavailable: "The lead's thread didn't start: this project's default model isn't available on this machine. Check the provider, or choose another model in the project settings under New threads \u2192 Model, then retry.",
7003
7003
  invalidPayload: "Check the goal fields and try again.",
7004
7004
  invalidGoal: "The goal must be 1\u20134000 characters after trimming spaces.",
7005
7005
  invalidRequestId: "The request ID must be 1\u201364 letters, numbers, underscores, or hyphens.",
@@ -7045,6 +7045,68 @@ var en_default = {
7045
7045
  remoteAgentsUnavailable: "Could not read the remote agents on this machine. Try again later.",
7046
7046
  remoteSource: "Remote agent: {{label}}"
7047
7047
  },
7048
+ handbookPanel: {
7049
+ title: "Handbook",
7050
+ open: "View handbook",
7051
+ close: "Close handbook",
7052
+ current: "Current version {{version}}",
7053
+ required: "Version {{version}} currently requires acknowledgement",
7054
+ noCurrent: "No handbook has been published",
7055
+ noRequired: "No version has required acknowledgement",
7056
+ handbook_decisions: "Decisions",
7057
+ handbook_rules: "Rules",
7058
+ handbook_progress: "Progress",
7059
+ missingPage: "This page is absent from the current version",
7060
+ erased: "Erased",
7061
+ unavailable: "Content is unavailable",
7062
+ uneditable: "This page cannot be edited because its content is erased or unavailable",
7063
+ loading: "Loading handbook\u2026",
7064
+ loadFailed: "Could not read the handbook. Reload to try again.",
7065
+ reload: "Reload handbook",
7066
+ moreBody: "Read more",
7067
+ moreBodyFailed: "Couldn't load the rest of this page. Click Read more to try again.",
7068
+ releases: "Version history",
7069
+ noReleases: "No versions yet",
7070
+ version: "Version {{version}}",
7071
+ publishedBy: "Published by {{author}}",
7072
+ requiresAck: "Acknowledgement required",
7073
+ noAck: "Acknowledgement not required",
7074
+ restoredFrom: "Restored from version {{version}}",
7075
+ moreReleases: "Load older versions",
7076
+ members: "Member acknowledgements",
7077
+ noMembers: "No executing members yet. Start a goal to assign team seats.",
7078
+ member: "Member {{id}}",
7079
+ neverAck: "Has not acknowledged a version",
7080
+ acked: "Acknowledged version {{version}}",
7081
+ pendingAck: "Needs to acknowledge version {{version}}",
7082
+ edit: "Edit handbook",
7083
+ pageTitle: "{{page}} \u2014 title (optional)",
7084
+ pageBody: "{{page}} \u2014 body",
7085
+ pageSummary: "{{page}} \u2014 change summary (optional)",
7086
+ ackChoice: "Require acknowledgement for changes to Decisions",
7087
+ ackPolicy: "Rules changes always require acknowledgement; Progress-only changes don't; Decisions changes follow this choice.",
7088
+ cancel: "Cancel",
7089
+ publish: "Save and publish",
7090
+ saved: "Handbook published",
7091
+ conflict: "Someone else changed the handbook. Your edits are kept. Review the latest version shown below before saving again.",
7092
+ 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.",
7093
+ revert: "Revert to this version",
7094
+ revertVersion: "Revert to version {{version}}",
7095
+ revertTitle: "Revert handbook to version {{version}}?",
7096
+ revertDescription: "Publish the content of version {{version}} as a new version, under your name. The current version remains in history.",
7097
+ confirmRevert: "Revert and publish",
7098
+ historyFailed: "Couldn't load the version history. Try again.",
7099
+ emptyPages: "Once the handbook is published, its three pages show here.",
7100
+ fieldTitle: "Title (optional)",
7101
+ fieldBody: "Body",
7102
+ fieldSummary: "Change summary (optional)",
7103
+ currentBadge: "Current",
7104
+ currentReadOnly: "Current handbook (read-only)",
7105
+ discardTitle: "Discard your unsaved edits?",
7106
+ discardDescription: "Your edits to the handbook pages will be lost.",
7107
+ keepEditing: "Keep editing",
7108
+ discard: "Discard edits"
7109
+ },
7048
7110
  remoteStatus: {
7049
7111
  title: "Remote members",
7050
7112
  none: "No remote members on the roster. Add one with \u201CAdd remote agent\u201D in the roster.",
@@ -7152,7 +7214,15 @@ var StartGoalInput = Struct({
7152
7214
  var GoalRejection = Struct({
7153
7215
  _tag: Literal2("TeamGoalRejection"),
7154
7216
  commandType: Literal2("startGoal"),
7155
- code: Literals(["invalid-payload", "invalid-goal", "invalid-request-id", "no-roster"])
7217
+ code: Literals([
7218
+ "invalid-payload",
7219
+ "invalid-goal",
7220
+ "invalid-request-id",
7221
+ "no-roster",
7222
+ // 负责人会话起不来:项目没设默认型号,或设了但解析不出来(provider 不在)。目标不落事实。
7223
+ "no-default-model",
7224
+ "model-unavailable"
7225
+ ])
7156
7226
  });
7157
7227
  var StartGoalResult = Union2([
7158
7228
  Struct({
@@ -7168,6 +7238,7 @@ var rejectGoal = (code) => ({
7168
7238
  commandType: "startGoal",
7169
7239
  code
7170
7240
  });
7241
+ var isSelectionUnavailable = isTagged("ProviderSelectionError");
7171
7242
  var decodeInput = decodeUnknownOption2(StartGoalInput);
7172
7243
  var decodeEnvelope = decodeUnknownOption2(
7173
7244
  Struct({
@@ -7219,6 +7290,13 @@ function createGoalService(ctx, rosterService, options = {}) {
7219
7290
  if (!REQUEST_ID_PATTERN.test(input.requestId)) return rejectGoal("invalid-request-id");
7220
7291
  const roster = await rosterService.read(input.projectId);
7221
7292
  if (roster === null) return rejectGoal("no-roster");
7293
+ const project2 = await ctx.projects.get({ projectId: input.projectId });
7294
+ if (project2.defaultModelSelection == null) return rejectGoal("no-default-model");
7295
+ const selection = await ctx.providers.selection.resolve({ base: project2.defaultModelSelection }).catch((error) => {
7296
+ if (isSelectionUnavailable(error)) return null;
7297
+ throw error;
7298
+ });
7299
+ if (selection === null) return rejectGoal("model-unavailable");
7222
7300
  const key = requestKey(input.projectId, input.requestId);
7223
7301
  const leadThreadId = teamThreadId(input.projectId, input.requestId, null);
7224
7302
  const remoteMembers = roster.members.flatMap(
@@ -7245,17 +7323,14 @@ function createGoalService(ctx, rosterService, options = {}) {
7245
7323
  });
7246
7324
  await goals.get({ throughSequence: receipt.sequence });
7247
7325
  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 });
7250
7326
  const orderedThreads = [
7251
7327
  ...goal.threads.filter((thread) => thread.memberId === goal.leadMemberId),
7252
7328
  ...goal.threads.filter((thread) => thread.memberId !== goal.leadMemberId)
7253
7329
  ];
7254
7330
  for (const thread of orderedThreads) {
7255
7331
  const name = roster.members.find((member) => member.memberId === thread.memberId)?.name ?? thread.memberId;
7256
- await ctx.commands.dispatch({
7257
- type: "thread.create",
7258
- commandId: `thread:${thread.threadId}`,
7332
+ await ctx.threads.create({
7333
+ requestId: `thread:${thread.threadId}`,
7259
7334
  threadId: thread.threadId,
7260
7335
  projectId: goal.projectId,
7261
7336
  title: `${name} \xB7 ${(goal.titleSeed ?? goal.goal).slice(0, 48)}`,
@@ -7272,8 +7347,7 @@ function createGoalService(ctx, rosterService, options = {}) {
7272
7347
  requestedBy: "agent",
7273
7348
  notificationPolicy: "activity-only"
7274
7349
  }
7275
- },
7276
- createdAt: goal.startedAt
7350
+ }
7277
7351
  });
7278
7352
  }
7279
7353
  await ctx.threads.turns.start({
@@ -7308,16 +7382,32 @@ function createGoalService(ctx, rosterService, options = {}) {
7308
7382
  // src/commands.ts
7309
7383
  var decodeReadRoster = decodeUnknownSync(ReadRosterInput);
7310
7384
  var decodeReadGoal = decodeUnknownSync(ReadGoalInput);
7311
- var mergeAllOf = (node) => {
7312
- if (Array.isArray(node)) return node.map(mergeAllOf);
7385
+ var commandSchema = (node) => {
7386
+ if (Array.isArray(node)) return node.map(commandSchema);
7313
7387
  if (typeof node !== "object" || node === null) return node;
7314
7388
  const { allOf, ...rest } = node;
7315
7389
  const merged = Object.fromEntries(
7316
- Object.entries(rest).map(([key, value]) => [key, mergeAllOf(value)])
7390
+ Object.entries(rest).map(([key, value]) => [key, commandSchema(value)])
7391
+ );
7392
+ if (Array.isArray(allOf)) Object.assign(merged, ...allOf.map(commandSchema));
7393
+ if (!Array.isArray(merged.anyOf) || merged.anyOf.length !== 2) return merged;
7394
+ const branches = merged.anyOf.filter(
7395
+ (branch) => typeof branch === "object" && branch !== null
7317
7396
  );
7318
- return Array.isArray(allOf) ? Object.assign(merged, ...allOf.map(mergeAllOf)) : merged;
7397
+ if (branches.length !== 2) return merged;
7398
+ const base = branches.find((branch) => branch.type !== "null");
7399
+ if (!branches.some((branch) => branch.type === "null") || base === void 0 || typeof base.type !== "string" || !["string", "number", "integer", "boolean"].includes(base.type))
7400
+ return merged;
7401
+ const { anyOf: _, ...common } = merged;
7402
+ const { const: literal, ...constraints } = base;
7403
+ return {
7404
+ ...common,
7405
+ ...constraints,
7406
+ type: [base.type, "null"],
7407
+ ...Object.hasOwn(base, "const") ? { enum: [literal, null] } : Array.isArray(base.enum) ? { enum: [...base.enum, null] } : {}
7408
+ };
7319
7409
  };
7320
- var payloadSchemaOf = (schema) => mergeAllOf(toJsonSchemaDocument2(schema).schema);
7410
+ var payloadSchemaOf = (schema) => commandSchema(toJsonSchemaDocument2(schema).schema);
7321
7411
  function registerRosterCommands(ctx, service2) {
7322
7412
  ctx.command({
7323
7413
  name: "roster",
@@ -7347,57 +7437,8 @@ function registerGoalCommands(ctx, service2) {
7347
7437
  });
7348
7438
  }
7349
7439
 
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
- }
7440
+ // src/watch.ts
7441
+ import * as NodeTimers from "node:timers";
7401
7442
 
7402
7443
  // src/boardContracts.ts
7403
7444
  var BoardState = Struct({
@@ -7435,7 +7476,7 @@ var BoardRemoteWork = Struct({
7435
7476
  pending: NullOr(
7436
7477
  Struct({
7437
7478
  count: Number5,
7438
- items: ArraySchema(Struct({ workItemId: String4, blocked: Boolean2 }))
7479
+ items: ArraySchema(Struct({ workItemId: String4, blocked: Boolean3 }))
7439
7480
  })
7440
7481
  )
7441
7482
  });
@@ -7461,8 +7502,8 @@ var BoardSnapshot = Struct({
7461
7502
  remoteWork: ArraySchema(BoardRemoteWork),
7462
7503
  viewerKey: optionalKey2(String4),
7463
7504
  // 负责人要用看板与项目记忆两组工具;开关由宿主控件拨,这里只读来提示。
7464
- toolkits: Struct({ board: Boolean2, memory: Boolean2 }),
7465
- truncated: Boolean2
7505
+ toolkits: Struct({ board: Boolean3, memory: Boolean3 }),
7506
+ truncated: Boolean3
7466
7507
  });
7467
7508
  var ReadBoardInput = Struct({ projectId: String4 });
7468
7509
  var CreateItemInput = Struct({
@@ -7478,7 +7519,7 @@ var UpdateItemInput = Struct({
7478
7519
  expectedVersion: Int.check(isGreaterThanOrEqualTo3(1)),
7479
7520
  stateId: optionalKey2(String4),
7480
7521
  assigneeThreadId: optionalKey2(String4),
7481
- unassign: optionalKey2(Boolean2)
7522
+ unassign: optionalKey2(Boolean3)
7482
7523
  });
7483
7524
  var BoardRejection = Struct({
7484
7525
  _tag: Literal2("TeamBoardRejection"),
@@ -7495,18 +7536,14 @@ var UpdateItemResult = Union2([
7495
7536
  BoardRejection
7496
7537
  ]);
7497
7538
 
7498
- // src/board.ts
7499
- var ITEM_LIMIT = 500;
7500
- var PEOPLE_LIMIT = 200;
7501
- var REMOTE_AGENT_LIMIT = 1e3;
7539
+ // src/watch.ts
7540
+ var emptyChanges = () => ({ cursor: 0, handbookCursor: 0 });
7502
7541
  var decodeRead = decodeUnknownSync(ReadBoardInput);
7503
- var decodeCreate = decodeUnknownOption2(CreateItemInput);
7504
- var decodeUpdate = decodeUnknownOption2(UpdateItemInput);
7505
7542
  var decodeProject = decodeUnknownOption2(Struct({ projectId: String4 }));
7506
7543
  var decodeFact = decodeUnknownOption2(
7507
7544
  Struct({ pluginId: Literal2("team"), name: String4 })
7508
7545
  );
7509
- var WORK_EVENTS = [
7546
+ var WORK_EVENTS = /* @__PURE__ */ new Set([
7510
7547
  "work.item-created",
7511
7548
  "work.item-updated",
7512
7549
  "work.item-claimed",
@@ -7514,15 +7551,159 @@ var WORK_EVENTS = [
7514
7551
  "work.relation-removed",
7515
7552
  "work.comment-posted",
7516
7553
  "work.states-seeded",
7517
- // 快照带看板与记忆两只开关的状态,拨一下就敲门。
7518
7554
  "board.toolkit-set",
7519
7555
  "memory.toolkit-set"
7520
- ];
7521
- function registerBoardCommands(ctx, goals, rosters, options = {}) {
7556
+ ]);
7557
+ var HANDBOOK_EVENTS = /* @__PURE__ */ new Set([
7558
+ "wiki.handbook-released",
7559
+ "wiki.page-erased",
7560
+ "memory.toolkit-set"
7561
+ ]);
7562
+ function registerTeamWatch(ctx, options) {
7522
7563
  const after = options.after ?? ((delay2, notify) => {
7523
7564
  const timer = NodeTimers.setTimeout(notify, delay2);
7524
7565
  return () => NodeTimers.clearTimeout(timer);
7525
7566
  });
7567
+ const listeners = /* @__PURE__ */ new Map();
7568
+ const cursors = /* @__PURE__ */ new Map();
7569
+ const timers = /* @__PURE__ */ new Map();
7570
+ let disposed = false;
7571
+ const changed = (event, board, handbook) => {
7572
+ const payload = decodeProject(event.payload);
7573
+ const projectId = isSome2(payload) ? payload.value.projectId : event.aggregateKind === "project" ? event.aggregateId : null;
7574
+ if (projectId === null || disposed || !listeners.has(projectId)) return;
7575
+ const previous = cursors.get(projectId) ?? emptyChanges();
7576
+ cursors.set(projectId, {
7577
+ cursor: board ? Math.max(previous.cursor, event.sequence) : previous.cursor,
7578
+ handbookCursor: handbook ? Math.max(previous.handbookCursor, event.sequence) : previous.handbookCursor
7579
+ });
7580
+ if (timers.has(projectId)) return;
7581
+ timers.set(
7582
+ projectId,
7583
+ after(50, () => {
7584
+ timers.delete(projectId);
7585
+ for (const emit of [...listeners.get(projectId) ?? []])
7586
+ emit(cursors.get(projectId) ?? emptyChanges());
7587
+ })
7588
+ );
7589
+ };
7590
+ const stops = [.../* @__PURE__ */ new Set([...WORK_EVENTS, ...HANDBOOK_EVENTS])].map(
7591
+ (type) => ctx.events.onRaw(
7592
+ type,
7593
+ (event) => changed(event, WORK_EVENTS.has(type), HANDBOOK_EVENTS.has(type))
7594
+ )
7595
+ );
7596
+ stops.push(
7597
+ ctx.events.onRaw("plugin.event-appended", (event) => {
7598
+ const fact = decodeFact(event.payload);
7599
+ if (isNone2(fact)) return;
7600
+ const name = fact.value.name;
7601
+ const board = name === "team/goal-started" || name === "team/roster-set";
7602
+ const handbook = name === "team/goal-started" || name === "team/handbook-acknowledged" || name === "team/handbook-publication-completed";
7603
+ if (board || handbook) changed(event, board, handbook);
7604
+ })
7605
+ );
7606
+ ctx.stream({
7607
+ name: "watch",
7608
+ scope: "read",
7609
+ payloadSchema: payloadSchemaOf(ReadBoardInput),
7610
+ buffer: { latest: 1 },
7611
+ handle: async (payload, emit, signal) => {
7612
+ if (signal.aborted || disposed) return;
7613
+ const { projectId } = decodeRead(payload);
7614
+ const subscribers = listeners.get(projectId) ?? /* @__PURE__ */ new Set();
7615
+ const ring = (value) => {
7616
+ if (!signal.aborted && !disposed) emit(value);
7617
+ };
7618
+ listeners.set(projectId, subscribers);
7619
+ subscribers.add(ring);
7620
+ try {
7621
+ ring(cursors.get(projectId) ?? emptyChanges());
7622
+ await new Promise((resolve2) => {
7623
+ if (signal.aborted) resolve2();
7624
+ else signal.addEventListener("abort", () => resolve2(), { once: true });
7625
+ });
7626
+ } finally {
7627
+ subscribers.delete(ring);
7628
+ if (subscribers.size === 0) {
7629
+ listeners.delete(projectId);
7630
+ timers.get(projectId)?.();
7631
+ timers.delete(projectId);
7632
+ cursors.delete(projectId);
7633
+ }
7634
+ }
7635
+ }
7636
+ });
7637
+ ctx.onDispose(() => {
7638
+ disposed = true;
7639
+ for (const stop of stops) stop();
7640
+ for (const cancel of timers.values()) cancel();
7641
+ timers.clear();
7642
+ listeners.clear();
7643
+ cursors.clear();
7644
+ });
7645
+ }
7646
+
7647
+ // src/workReads.ts
7648
+ var PAGE_SIZE = 200;
7649
+ async function readPages(maximum, read, keep = "oldest") {
7650
+ let items = [];
7651
+ let truncated = false;
7652
+ let cursor = null;
7653
+ do {
7654
+ const page = await read({ limit: PAGE_SIZE, ...cursor === null ? {} : { cursor } });
7655
+ items.push(...page.items);
7656
+ cursor = page.nextCursor;
7657
+ if (keep === "oldest" && items.length >= maximum) {
7658
+ return {
7659
+ items: items.slice(0, maximum),
7660
+ truncated: items.length > maximum || cursor !== null
7661
+ };
7662
+ }
7663
+ if (items.length > maximum) {
7664
+ truncated = true;
7665
+ items = items.slice(-maximum);
7666
+ }
7667
+ } while (cursor !== null);
7668
+ return { items, truncated };
7669
+ }
7670
+
7671
+ // src/workWrites.ts
7672
+ var decodeHostRefusal = decodeUnknownOption2(
7673
+ Struct({
7674
+ rejection: Struct({
7675
+ code: String4,
7676
+ details: optionalKey2(Unknown2)
7677
+ })
7678
+ })
7679
+ );
7680
+ var rejectWork = (commandType, code) => ({ _tag: "TeamBoardRejection", commandType, code });
7681
+ async function writeWork(invocation, commandType, write) {
7682
+ if (invocation.asPerson === void 0) return rejectWork(commandType, "needs-person");
7683
+ try {
7684
+ return await write(invocation.asPerson.work);
7685
+ } catch (error) {
7686
+ const refusal = decodeHostRefusal(error);
7687
+ if (isNone2(refusal)) throw error;
7688
+ const { code, details } = refusal.value.rejection;
7689
+ return {
7690
+ ...rejectWork(
7691
+ commandType,
7692
+ commandType !== "postComment" && code === "version-conflict" ? "stale-version" : code
7693
+ ),
7694
+ ...details === void 0 ? {} : { details }
7695
+ };
7696
+ }
7697
+ }
7698
+
7699
+ // src/board.ts
7700
+ var ITEM_LIMIT = 500;
7701
+ var PEOPLE_LIMIT = 200;
7702
+ var REMOTE_AGENT_LIMIT = 1e3;
7703
+ var decodeRead2 = decodeUnknownSync(ReadBoardInput);
7704
+ var decodeCreate = decodeUnknownOption2(CreateItemInput);
7705
+ var decodeUpdate = decodeUnknownOption2(UpdateItemInput);
7706
+ function registerBoardCommands(ctx, goals, rosters, options = {}) {
7526
7707
  const isMember2 = async (projectId, threadId) => goalExecution(await goals.read(projectId)).members.some(
7527
7708
  (member) => "threadId" in member && member.threadId === threadId
7528
7709
  );
@@ -7531,7 +7712,7 @@ function registerBoardCommands(ctx, goals, rosters, options = {}) {
7531
7712
  scope: "read",
7532
7713
  payloadSchema: payloadSchemaOf(ReadBoardInput),
7533
7714
  handle: async (payload, invocation) => {
7534
- const { projectId } = decodeRead(payload);
7715
+ const { projectId } = decodeRead2(payload);
7535
7716
  const [stateList, goal, roster, toolkitStates, people, remoteAgents, viewer] = await Promise.all([
7536
7717
  ctx.work.states.list({ projectId }),
7537
7718
  goals.read(projectId),
@@ -7656,74 +7837,7 @@ function registerBoardCommands(ctx, goals, rosters, options = {}) {
7656
7837
  return "code" in result2 ? result2 : { workItemId: result2.result.workItemId, version: result2.result.version };
7657
7838
  }
7658
7839
  });
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
- });
7840
+ registerTeamWatch(ctx, options);
7727
7841
  }
7728
7842
 
7729
7843
  // src/detailContracts.ts
@@ -7785,9 +7899,9 @@ var ItemDetailSnapshot = Struct({
7785
7899
  blockedBy: ArraySchema(Dependency),
7786
7900
  blocking: ArraySchema(Dependency),
7787
7901
  comments: ArraySchema(DetailComment),
7788
- commentsTruncated: Boolean2,
7902
+ commentsTruncated: Boolean3,
7789
7903
  history: ArraySchema(DetailActivity),
7790
- historyTruncated: Boolean2
7904
+ historyTruncated: Boolean3
7791
7905
  });
7792
7906
  var ReadItemDetailResult = NullOr(ItemDetailSnapshot);
7793
7907
 
@@ -7796,7 +7910,7 @@ var RELATION_LIMIT = 100;
7796
7910
  var COMMENT_LIMIT = 200;
7797
7911
  var HISTORY_LIMIT = 200;
7798
7912
  var GET_BATCH_SIZE = 50;
7799
- var decodeRead2 = decodeUnknownSync(ReadItemDetailInput);
7913
+ var decodeRead3 = decodeUnknownSync(ReadItemDetailInput);
7800
7914
  var decodePost = decodeUnknownOption2(PostCommentInput);
7801
7915
  function registerDetailCommands(ctx) {
7802
7916
  ctx.command({
@@ -7804,7 +7918,7 @@ function registerDetailCommands(ctx) {
7804
7918
  scope: "read",
7805
7919
  payloadSchema: payloadSchemaOf(ReadItemDetailInput),
7806
7920
  handle: async (payload) => {
7807
- const { projectId, workItemId } = decodeRead2(payload);
7921
+ const { projectId, workItemId } = decodeRead3(payload);
7808
7922
  const result2 = await ctx.work.items.get({ projectId, workItemIds: [workItemId] });
7809
7923
  const item = result2.items.find((row) => row.workItemId === workItemId);
7810
7924
  if (item === void 0) return null;
@@ -8045,14 +8159,14 @@ var emptyProject = () => ({
8045
8159
  });
8046
8160
  var itemWakeKey = (workItemId, threadId, generation) => generation === 0 ? `item:${workItemId}:${threadId}` : `item:${workItemId}:${threadId}#${generation}`;
8047
8161
  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
8162
+ var hasOpenBlocker = (project2, workItemId) => (readFact(project2.blockers, workItemId) ?? []).some(
8163
+ (blocker) => readFact(project2.items, blocker)?.closed !== true
8050
8164
  );
8051
- var markBlockedAfterClaim = (project, workItemId) => ({
8052
- ...project,
8165
+ var markBlockedAfterClaim = (project2, workItemId) => ({
8166
+ ...project2,
8053
8167
  blockedAfterClaim: {
8054
- ...project.blockedAfterClaim,
8055
- [workItemId]: (readFact(project.blockedAfterClaim, workItemId) ?? 0) + 1
8168
+ ...project2.blockedAfterClaim,
8169
+ [workItemId]: (readFact(project2.blockedAfterClaim, workItemId) ?? 0) + 1
8056
8170
  }
8057
8171
  });
8058
8172
  var decodeEnvelope2 = decodeUnknownOption2(
@@ -8137,8 +8251,8 @@ var decodeWorkEvent = decodeUnknownOption2(
8137
8251
  var agentThreads = (principals) => [
8138
8252
  ...new Set(principals.flatMap((p) => p.kind === "agent" ? [p.threadId] : []))
8139
8253
  ];
8140
- var isMember = (project, recipient) => "threadId" in recipient ? project.threads.includes(recipient.threadId) : project.remoteMembers.includes(recipient.principalId);
8141
- var commentRecipients = (project, actor, people) => {
8254
+ var isMember = (project2, recipient) => "threadId" in recipient ? project2.threads.includes(recipient.threadId) : project2.remoteMembers.includes(recipient.principalId);
8255
+ var commentRecipients = (project2, actor, people) => {
8142
8256
  const recipients = /* @__PURE__ */ new Map();
8143
8257
  for (const person of people) {
8144
8258
  if (person.kind === "agent" && !(actor.kind === "agent" && actor.threadId === person.threadId))
@@ -8146,7 +8260,7 @@ var commentRecipients = (project, actor, people) => {
8146
8260
  if (person.kind === "remote-agent" && !(actor.kind === "remote-agent" && actor.principalId === person.principalId))
8147
8261
  recipients.set(`principal:${person.principalId}`, { principalId: person.principalId });
8148
8262
  }
8149
- return [...recipients.values()].filter((recipient) => isMember(project, recipient));
8263
+ return [...recipients.values()].filter((recipient) => isMember(project2, recipient));
8150
8264
  };
8151
8265
  var commentWakeKey = (commentId, recipient) => "threadId" in recipient ? `comment:${commentId}:${recipient.threadId}` : `comment:${commentId}:principal:${recipient.principalId}`;
8152
8266
  function foldWakes(state, event) {
@@ -8156,7 +8270,7 @@ function foldWakes(state, event) {
8156
8270
  const fact = decoded2.value;
8157
8271
  if (fact.name === "team/goal-started") {
8158
8272
  const { goal } = fact.data;
8159
- const project3 = readFact(state, goal.projectId) ?? emptyProject();
8273
+ const project4 = readFact(state, goal.projectId) ?? emptyProject();
8160
8274
  const { members } = goalExecution(goal);
8161
8275
  const threads = [
8162
8276
  ...new Set(members.flatMap((member) => "threadId" in member ? [member.threadId] : []))
@@ -8167,42 +8281,42 @@ function foldWakes(state, event) {
8167
8281
  )
8168
8282
  ];
8169
8283
  const leadThreads = goal.threads.filter((thread) => thread.memberId === goal.leadMemberId).map((thread) => thread.threadId);
8170
- const next2 = { ...project3, threads, leadThreads, remoteMembers };
8284
+ const next2 = { ...project4, threads, leadThreads, remoteMembers };
8171
8285
  return {
8172
8286
  ...state,
8173
8287
  [goal.projectId]: {
8174
8288
  ...next2,
8175
- pendingComments: project3.pendingComments.filter((comment) => isMember(next2, comment))
8289
+ pendingComments: project4.pendingComments.filter((comment) => isMember(next2, comment))
8176
8290
  }
8177
8291
  };
8178
8292
  }
8179
8293
  const { projectId: projectId2, key } = fact.data;
8180
- const project2 = readFact(state, projectId2) ?? emptyProject();
8181
- if (project2.pendingComments.some((comment) => comment.key === key)) {
8294
+ const project3 = readFact(state, projectId2) ?? emptyProject();
8295
+ if (project3.pendingComments.some((comment) => comment.key === key)) {
8182
8296
  return {
8183
8297
  ...state,
8184
8298
  [projectId2]: {
8185
- ...project2,
8186
- pendingComments: project2.pendingComments.filter((comment) => comment.key !== key)
8299
+ ...project3,
8300
+ pendingComments: project3.pendingComments.filter((comment) => comment.key !== key)
8187
8301
  }
8188
8302
  };
8189
8303
  }
8190
- if (project2.woken.includes(key)) return state;
8191
- return { ...state, [projectId2]: { ...project2, woken: [...project2.woken, key] } };
8304
+ if (project3.woken.includes(key)) return state;
8305
+ return { ...state, [projectId2]: { ...project3, woken: [...project3.woken, key] } };
8192
8306
  }
8193
8307
  const decoded = decodeWorkEvent(event);
8194
8308
  if (isNone2(decoded)) return state;
8195
8309
  const work = decoded.value;
8196
8310
  const { projectId, workItemId } = work.payload;
8197
- const project = readFact(state, projectId) ?? emptyProject();
8198
- const currentItem = readFact(project.items, workItemId);
8199
- let next = project;
8311
+ const project2 = readFact(state, projectId) ?? emptyProject();
8312
+ const currentItem = readFact(project2.items, workItemId);
8313
+ let next = project2;
8200
8314
  switch (work.type) {
8201
8315
  case "work.item-created":
8202
8316
  next = {
8203
- ...project,
8317
+ ...project2,
8204
8318
  items: {
8205
- ...project.items,
8319
+ ...project2.items,
8206
8320
  [workItemId]: {
8207
8321
  sequenceId: work.payload.sequenceId,
8208
8322
  assigneeThreads: agentThreads(work.payload.assignees),
@@ -8222,9 +8336,9 @@ function foldWakes(state, event) {
8222
8336
  ])
8223
8337
  ];
8224
8338
  next = {
8225
- ...project,
8339
+ ...project2,
8226
8340
  items: {
8227
- ...project.items,
8341
+ ...project2.items,
8228
8342
  [workItemId]: {
8229
8343
  ...currentItem,
8230
8344
  assigneeThreads,
@@ -8240,18 +8354,18 @@ function foldWakes(state, event) {
8240
8354
  case "work.relation-added":
8241
8355
  case "work.relation-removed": {
8242
8356
  if (work.payload.relationKind !== "blocked_by") return state;
8243
- const blockers = readFact(project.blockers, workItemId) ?? [];
8357
+ const blockers = readFact(project2.blockers, workItemId) ?? [];
8244
8358
  const related = work.payload.relatedWorkItemId;
8245
8359
  next = {
8246
- ...project,
8360
+ ...project2,
8247
8361
  blockers: {
8248
- ...project.blockers,
8362
+ ...project2.blockers,
8249
8363
  [workItemId]: work.type === "work.relation-added" ? [.../* @__PURE__ */ new Set([...blockers, related])] : blockers.filter((blocker) => blocker !== related)
8250
8364
  }
8251
8365
  };
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 };
8366
+ if (work.type === "work.relation-added" && currentItem !== null && readFact(project2.items, related)?.closed !== true) {
8367
+ const woken = new Set(project2.woken);
8368
+ const rearms = { ...project2.rearms };
8255
8369
  for (const threadId of currentItem.assigneeThreads) {
8256
8370
  if (currentItem.claimedBy.includes(threadId)) continue;
8257
8371
  const generation = rearms[`${workItemId}:${threadId}`] ?? 0;
@@ -8259,14 +8373,14 @@ function foldWakes(state, event) {
8259
8373
  rearms[`${workItemId}:${threadId}`] = generation + 1;
8260
8374
  }
8261
8375
  next = { ...next, rearms };
8262
- if (currentItem.claimedBy.length > 0 && !hasOpenBlocker(project, workItemId))
8376
+ if (currentItem.claimedBy.length > 0 && !hasOpenBlocker(project2, workItemId))
8263
8377
  next = markBlockedAfterClaim(next, workItemId);
8264
8378
  }
8265
8379
  break;
8266
8380
  }
8267
8381
  case "work.comment-posted": {
8268
8382
  const { actor, parentAuthor, mentions, commentId } = work.payload;
8269
- const comments = commentRecipients(project, actor, [
8383
+ const comments = commentRecipients(project2, actor, [
8270
8384
  ...parentAuthor === void 0 ? [] : [parentAuthor],
8271
8385
  ...mentions
8272
8386
  ]).map((recipient) => ({
@@ -8276,10 +8390,10 @@ function foldWakes(state, event) {
8276
8390
  workItemId,
8277
8391
  commentId
8278
8392
  })).filter(
8279
- (comment) => !project.pendingComments.some((pending) => pending.key === comment.key)
8393
+ (comment) => !project2.pendingComments.some((pending) => pending.key === comment.key)
8280
8394
  );
8281
8395
  if (comments.length === 0) return state;
8282
- next = { ...project, pendingComments: [...project.pendingComments, ...comments] };
8396
+ next = { ...project2, pendingComments: [...project2.pendingComments, ...comments] };
8283
8397
  break;
8284
8398
  }
8285
8399
  }
@@ -8288,13 +8402,13 @@ function foldWakes(state, event) {
8288
8402
  var compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
8289
8403
  function dueWakes(state) {
8290
8404
  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;
8405
+ for (const [projectId, project2] of Object.entries(state)) {
8406
+ const woken = new Set(project2.woken);
8407
+ const threads = new Set(project2.threads);
8408
+ const leads = new Set(project2.leadThreads ?? []);
8409
+ for (const [workItemId, item] of Object.entries(project2.items)) {
8410
+ if (item.closed || hasOpenBlocker(project2, workItemId)) continue;
8411
+ const unblocked = readFact(project2.blockedAfterClaim, workItemId) ?? 0;
8298
8412
  const recipients = /* @__PURE__ */ new Set([
8299
8413
  ...item.assigneeThreads,
8300
8414
  ...unblocked > 0 ? item.claimedBy : []
@@ -8305,7 +8419,7 @@ function dueWakes(state) {
8305
8419
  const key = finishing ? unblockedWakeKey(workItemId, threadId, unblocked) : itemWakeKey(
8306
8420
  workItemId,
8307
8421
  threadId,
8308
- readFact(project.rearms, `${workItemId}:${threadId}`) ?? 0
8422
+ readFact(project2.rearms, `${workItemId}:${threadId}`) ?? 0
8309
8423
  );
8310
8424
  if (woken.has(key)) continue;
8311
8425
  due.push({
@@ -8319,8 +8433,8 @@ function dueWakes(state) {
8319
8433
  });
8320
8434
  }
8321
8435
  }
8322
- for (const comment of project.pendingComments) {
8323
- const item = readFact(project.items, comment.workItemId);
8436
+ for (const comment of project2.pendingComments) {
8437
+ const item = readFact(project2.items, comment.workItemId);
8324
8438
  due.push({
8325
8439
  ...comment,
8326
8440
  kind: "comment",
@@ -8453,8 +8567,13 @@ async function publicationRequiresAck(memory, projectId, input) {
8453
8567
 
8454
8568
  // src/handbook/contracts.ts
8455
8569
  var Release = Int.check(isGreaterThanOrEqualTo3(1));
8570
+ var HandbookPageKind = Literals([
8571
+ "handbook_decisions",
8572
+ "handbook_rules",
8573
+ "handbook_progress"
8574
+ ]);
8456
8575
  var Page = Struct({
8457
- kind: Literals(["handbook_decisions", "handbook_rules", "handbook_progress"]),
8576
+ kind: HandbookPageKind,
8458
8577
  baseRevision: NullOr(Finite),
8459
8578
  title: optionalKey2(String4),
8460
8579
  body: String4,
@@ -8463,7 +8582,7 @@ var Page = Struct({
8463
8582
  var WriteFields = {
8464
8583
  requestId: String4,
8465
8584
  expectedRelease: Finite,
8466
- requireAck: optionalKey2(Boolean2)
8585
+ requireAck: optionalKey2(Boolean3)
8467
8586
  };
8468
8587
  var PublishInput = Struct({
8469
8588
  ...WriteFields,
@@ -8481,13 +8600,14 @@ var PublicationIntent = Struct({
8481
8600
  memberId: String4,
8482
8601
  requestId: String4,
8483
8602
  op: Literals(["publish", "revert"]),
8484
- requireAckChoice: optionalKey2(Boolean2),
8485
- requiresAck: optionalKey2(Boolean2)
8603
+ requireAckChoice: optionalKey2(Boolean3),
8604
+ requiresAck: optionalKey2(Boolean3)
8486
8605
  });
8487
8606
  var PublicationCompleted = Struct({
8488
8607
  version: Literal2(1),
8489
8608
  projectId: String4,
8490
8609
  requestId: String4,
8610
+ producedRelease: optionalKey2(Boolean3),
8491
8611
  outcome: Union2([
8492
8612
  Struct({ status: Literal2("succeeded"), release: Release }),
8493
8613
  Struct({ status: Literal2("rejected") }),
@@ -8505,11 +8625,12 @@ var ReminderSent = Struct({
8505
8625
  });
8506
8626
 
8507
8627
  // src/handbook/state.ts
8508
- var HANDBOOK_EVENTS = ["plugin.event-appended", "wiki.handbook-released"];
8628
+ var HANDBOOK_EVENTS2 = ["plugin.event-appended", "wiki.handbook-released"];
8509
8629
  var emptyHandbookState = () => ({
8510
8630
  projects: {},
8511
8631
  intents: {},
8512
8632
  completed: {},
8633
+ publishers: {},
8513
8634
  pendingPublications: {},
8514
8635
  pendingAcknowledgements: {},
8515
8636
  pendingReminders: {}
@@ -8535,16 +8656,16 @@ var decodeRelease = decodeUnknownSync(
8535
8656
  Struct({
8536
8657
  projectId: String4,
8537
8658
  release: Number5,
8538
- requiresAck: Boolean2
8659
+ requiresAck: Boolean3
8539
8660
  })
8540
8661
  );
8541
- function callerMember(project, caller) {
8662
+ function callerMember(project2, caller) {
8542
8663
  if (caller === void 0) return void 0;
8543
- return goalExecution(project.goal).members.find(
8664
+ return goalExecution(project2.goal).members.find(
8544
8665
  (member) => caller.kind === "thread" ? "threadId" in member && member.threadId === caller.threadId : "principalId" in member && member.principalId === caller.principalId
8545
8666
  );
8546
8667
  }
8547
- var hasAcknowledged = (project, memberId, release) => readFact(readFact(project.acknowledgements, memberId)?.releases ?? {}, String(release)) === true;
8668
+ var hasAcknowledged = (project2, memberId, release) => readFact(readFact(project2.acknowledgements, memberId)?.releases ?? {}, String(release)) === true;
8548
8669
  var reminderKey = (projectId, release, memberId) => JSON.stringify([projectId, release, memberId]);
8549
8670
  var without = (rows, key) => {
8550
8671
  if (!Object.hasOwn(rows, key)) return rows;
@@ -8555,12 +8676,12 @@ var without = (rows, key) => {
8555
8676
  function foldHandbook(state, event) {
8556
8677
  if (event.type === "wiki.handbook-released") {
8557
8678
  const fact2 = decodeRelease(event.payload);
8558
- const project2 = readFact(state.projects, fact2.projectId) ?? emptyHandbookProject();
8679
+ const project3 = readFact(state.projects, fact2.projectId) ?? emptyHandbookProject();
8559
8680
  const pendingReminders = { ...state.pendingReminders };
8560
8681
  if (fact2.requiresAck)
8561
- for (const { memberId, ...recipient } of goalExecution(project2.goal).members) {
8682
+ for (const { memberId, ...recipient } of goalExecution(project3.goal).members) {
8562
8683
  const key = reminderKey(fact2.projectId, fact2.release, memberId);
8563
- if (readFact(project2.reminded, key) === null)
8684
+ if (readFact(project3.reminded, key) === null)
8564
8685
  pendingReminders[key] = {
8565
8686
  projectId: fact2.projectId,
8566
8687
  release: fact2.release,
@@ -8573,9 +8694,9 @@ function foldHandbook(state, event) {
8573
8694
  projects: {
8574
8695
  ...state.projects,
8575
8696
  [fact2.projectId]: {
8576
- ...project2,
8697
+ ...project3,
8577
8698
  currentRelease: fact2.release,
8578
- requiredRelease: fact2.requiresAck ? fact2.release : project2.requiredRelease
8699
+ requiredRelease: fact2.requiresAck ? fact2.release : project3.requiredRelease
8579
8700
  }
8580
8701
  }
8581
8702
  };
@@ -8585,11 +8706,11 @@ function foldHandbook(state, event) {
8585
8706
  if (isNone2(decoded)) return state;
8586
8707
  const fact = decoded.value;
8587
8708
  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;
8709
+ const project2 = readFact(state.projects, projectId) ?? emptyHandbookProject();
8710
+ let next = project2;
8590
8711
  switch (fact.name) {
8591
8712
  case "team/goal-started":
8592
- next = { ...project, goal: fact.data.goal };
8713
+ next = { ...project2, goal: fact.data.goal };
8593
8714
  break;
8594
8715
  case "team/handbook-publication-intended": {
8595
8716
  const intent = fact.data;
@@ -8604,13 +8725,23 @@ function foldHandbook(state, event) {
8604
8725
  const completed = fact.data;
8605
8726
  if (readFact(state.completed, completed.requestId) !== null) return state;
8606
8727
  const intent = readFact(state.pendingPublications, completed.requestId);
8728
+ let publishers = state.publishers;
8729
+ if (intent !== null && completed.producedRelease === true && completed.outcome.status === "succeeded") {
8730
+ const releases = readFact(publishers ?? {}, projectId) ?? {};
8731
+ const release = String(completed.outcome.release);
8732
+ if (readFact(releases, release) === null)
8733
+ publishers = {
8734
+ ...publishers,
8735
+ [projectId]: { ...releases, [release]: intent.memberId }
8736
+ };
8737
+ }
8607
8738
  let pendingAcknowledgements = state.pendingAcknowledgements;
8608
8739
  let pendingReminders = state.pendingReminders;
8609
8740
  if (intent !== null && intent.requiresAck && completed.outcome.status === "succeeded") {
8610
8741
  const { release } = completed.outcome;
8611
8742
  const key = reminderKey(projectId, release, intent.memberId);
8612
8743
  pendingReminders = without(pendingReminders, key);
8613
- if (!hasAcknowledged(project, intent.memberId, release))
8744
+ if (!hasAcknowledged(project2, intent.memberId, release))
8614
8745
  pendingAcknowledgements = {
8615
8746
  ...pendingAcknowledgements,
8616
8747
  [key]: { version: 1, projectId, release, memberId: intent.memberId }
@@ -8619,6 +8750,7 @@ function foldHandbook(state, event) {
8619
8750
  return {
8620
8751
  ...state,
8621
8752
  completed: { ...state.completed, [completed.requestId]: completed },
8753
+ ...publishers === void 0 ? {} : { publishers },
8622
8754
  pendingPublications: without(state.pendingPublications, completed.requestId),
8623
8755
  pendingAcknowledgements,
8624
8756
  pendingReminders
@@ -8626,12 +8758,12 @@ function foldHandbook(state, event) {
8626
8758
  }
8627
8759
  case "team/handbook-acknowledged": {
8628
8760
  const { memberId, release } = fact.data;
8629
- if (hasAcknowledged(project, memberId, release)) return state;
8630
- const previous = readFact(project.acknowledgements, memberId);
8761
+ if (hasAcknowledged(project2, memberId, release)) return state;
8762
+ const previous = readFact(project2.acknowledgements, memberId);
8631
8763
  next = {
8632
- ...project,
8764
+ ...project2,
8633
8765
  acknowledgements: {
8634
- ...project.acknowledgements,
8766
+ ...project2.acknowledgements,
8635
8767
  [memberId]: {
8636
8768
  releases: { ...previous?.releases, [release]: true },
8637
8769
  latestRelease: Math.max(previous?.latestRelease ?? 0, release)
@@ -8649,8 +8781,8 @@ function foldHandbook(state, event) {
8649
8781
  }
8650
8782
  case "team/handbook-reminder-sent": {
8651
8783
  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 } };
8784
+ if (readFact(project2.reminded, key) !== null) return state;
8785
+ next = { ...project2, reminded: { ...project2.reminded, [key]: true } };
8654
8786
  return {
8655
8787
  ...state,
8656
8788
  pendingReminders: without(state.pendingReminders, key),
@@ -8675,8 +8807,8 @@ var terminalFailure = (error) => {
8675
8807
  function startHandbookRuntime(ctx) {
8676
8808
  const state = ctx.derivedState({
8677
8809
  key: "handbook",
8678
- version: 4,
8679
- events: HANDBOOK_EVENTS,
8810
+ version: 5,
8811
+ events: HANDBOOK_EVENTS2,
8680
8812
  init: emptyHandbookState,
8681
8813
  apply: foldHandbook
8682
8814
  });
@@ -8703,8 +8835,8 @@ function startHandbookRuntime(ctx) {
8703
8835
  return state.get({ throughSequence: receipt.sequence });
8704
8836
  };
8705
8837
  const acknowledge = async (projectId, release, memberId) => {
8706
- const project = readFact((await state.get()).projects, projectId) ?? emptyHandbookProject();
8707
- if (hasAcknowledged(project, memberId, release)) return;
8838
+ const project2 = readFact((await state.get()).projects, projectId) ?? emptyHandbookProject();
8839
+ if (hasAcknowledged(project2, memberId, release)) return;
8708
8840
  await append2(
8709
8841
  projectId,
8710
8842
  "handbook-acknowledged",
@@ -8717,12 +8849,13 @@ function startHandbookRuntime(ctx) {
8717
8849
  }
8718
8850
  );
8719
8851
  };
8720
- const complete = async (intent, outcome) => {
8852
+ const complete = async (intent, outcome, producedRelease) => {
8721
8853
  await append2(intent.projectId, "handbook-publication-completed", intent.requestId, {
8722
8854
  version: 1,
8723
8855
  requestId: intent.requestId,
8724
8856
  projectId: intent.projectId,
8725
- outcome
8857
+ outcome,
8858
+ ...producedRelease === void 0 ? {} : { producedRelease }
8726
8859
  });
8727
8860
  if (outcome.status === "succeeded" && intent.requiresAck) {
8728
8861
  await acknowledge(intent.projectId, outcome.release, intent.memberId);
@@ -8742,7 +8875,11 @@ function startHandbookRuntime(ctx) {
8742
8875
  if (typeof result2 !== "object" || result2 === null || !("release" in result2) || typeof result2.release !== "number" || !Number.isSafeInteger(result2.release) || result2.release < 1)
8743
8876
  throw new Error("handbook-receipt-result-unavailable");
8744
8877
  await state.get({ throughSequence: receipt.sequence });
8745
- return complete(intent, { status: "succeeded", release: result2.release });
8878
+ return complete(
8879
+ intent,
8880
+ { status: "succeeded", release: result2.release },
8881
+ "changed" in result2 && result2.changed === true
8882
+ );
8746
8883
  };
8747
8884
  const remind = async () => {
8748
8885
  const snapshot = await state.get();
@@ -8796,7 +8933,7 @@ function startHandbookRuntime(ctx) {
8796
8933
  await remind();
8797
8934
  };
8798
8935
  const flush = () => serial(execute);
8799
- ctx.reactor({ name: "team-handbook", events: HANDBOOK_EVENTS, handle: () => flush() });
8936
+ ctx.reactor({ name: "team-handbook", events: HANDBOOK_EVENTS2, handle: () => flush() });
8800
8937
  ctx.onDispose(async () => {
8801
8938
  disposed = true;
8802
8939
  await pending;
@@ -8806,28 +8943,36 @@ function startHandbookRuntime(ctx) {
8806
8943
  return {
8807
8944
  ready,
8808
8945
  flush,
8946
+ async publisherMembers(projectId, releases) {
8947
+ const snapshot = await state.get();
8948
+ const publishers = readFact(snapshot.publishers ?? {}, projectId) ?? {};
8949
+ return releases.flatMap((release) => {
8950
+ const memberId = readFact(publishers, String(release));
8951
+ return memberId === null ? [] : [{ release, memberId }];
8952
+ });
8953
+ },
8809
8954
  async read(projectId) {
8810
- const project = readFact((await state.get()).projects, projectId) ?? emptyHandbookProject();
8955
+ const project2 = readFact((await state.get()).projects, projectId) ?? emptyHandbookProject();
8811
8956
  return {
8812
- currentRelease: project.currentRelease,
8813
- requiredRelease: project.requiredRelease,
8814
- members: goalExecution(project.goal).members.map(({ memberId }) => ({
8957
+ currentRelease: project2.currentRelease,
8958
+ requiredRelease: project2.requiredRelease,
8959
+ members: goalExecution(project2.goal).members.map(({ memberId }) => ({
8815
8960
  memberId,
8816
- acknowledgedRelease: readFact(project.acknowledgements, memberId)?.latestRelease ?? null,
8817
- acknowledgedRequiredRelease: project.requiredRelease === null || hasAcknowledged(project, memberId, project.requiredRelease)
8961
+ acknowledgedRelease: readFact(project2.acknowledgements, memberId)?.latestRelease ?? null,
8962
+ acknowledgedRequiredRelease: project2.requiredRelease === null || hasAcknowledged(project2, memberId, project2.requiredRelease)
8818
8963
  }))
8819
8964
  };
8820
8965
  },
8821
8966
  ack(release, invocation) {
8822
8967
  return serial(async () => {
8823
8968
  const projectId = invocation.projectId;
8824
- const project = projectId === null ? emptyHandbookProject() : readFact((await state.get()).projects, projectId) ?? emptyHandbookProject();
8825
- const member = callerMember(project, invocation.caller);
8969
+ const project2 = projectId === null ? emptyHandbookProject() : readFact((await state.get()).projects, projectId) ?? emptyHandbookProject();
8970
+ const member = callerMember(project2, invocation.caller);
8826
8971
  if (member === void 0 || projectId === null)
8827
8972
  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));
8973
+ if (project2.requiredRelease === null) throw new Error(handbookMessage("noRequirement"));
8974
+ if (release !== project2.requiredRelease)
8975
+ throw new Error(handbookMessage("wrongRelease", project2.requiredRelease));
8831
8976
  await acknowledge(projectId, release, member.memberId);
8832
8977
  return { release, memberId: member.memberId };
8833
8978
  });
@@ -8835,9 +8980,9 @@ function startHandbookRuntime(ctx) {
8835
8980
  publish(input, invocation) {
8836
8981
  return serial(async () => {
8837
8982
  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);
8983
+ const project2 = projectId === null ? emptyHandbookProject() : readFact((await state.get()).projects, projectId) ?? emptyHandbookProject();
8984
+ const member = callerMember(project2, invocation.caller);
8985
+ const execution = goalExecution(project2.goal);
8841
8986
  if (execution.leadMemberId === null) throw new Error(handbookMessage("noGoal"));
8842
8987
  if (projectId === null || invocation.caller?.kind !== "thread" || member === void 0 || member.memberId !== execution.leadMemberId)
8843
8988
  throw new Error(handbookMessage("leadOnly"));
@@ -8863,35 +9008,37 @@ function startHandbookRuntime(ctx) {
8863
9008
  if (existing === null)
8864
9009
  await append2(projectId, "handbook-publication-intended", requestId, intent);
8865
9010
  let outcome;
9011
+ let producedRelease;
8866
9012
  let detail = null;
8867
9013
  try {
8868
9014
  const command = { ...input, projectId, requestId, requireAck };
8869
9015
  const result2 = command.operation === "publish" ? await ctx.memory.handbook.publish(command) : await ctx.memory.handbook.revert(command);
8870
9016
  await state.get({ throughSequence: result2.sequence });
8871
9017
  outcome = { status: "succeeded", release: result2.result.release };
9018
+ producedRelease = result2.result.changed;
8872
9019
  } catch (error) {
8873
9020
  detail = terminalFailure(error);
8874
9021
  if (detail === null) throw error;
8875
9022
  outcome = { status: "rejected" };
8876
9023
  }
8877
- if (done2 === null) await complete(intent, outcome);
9024
+ if (done2 === null) await complete(intent, outcome, producedRelease);
8878
9025
  await remind();
8879
9026
  return detail === null ? outcome : { ...outcome, detail };
8880
9027
  });
8881
9028
  },
8882
9029
  async check(input) {
8883
- const project = readFact(
9030
+ const project2 = readFact(
8884
9031
  (await state.get({ throughSequence: input.throughSequence })).projects,
8885
9032
  input.projectId
8886
9033
  );
8887
- if (project === null || project.requiredRelease === null) return { allowed: true };
9034
+ if (project2 === null || project2.requiredRelease === null) return { allowed: true };
8888
9035
  for (const actor of input.added) {
8889
9036
  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)) {
9037
+ const member = callerMember(project2, caller);
9038
+ if (member !== void 0 && !hasAcknowledged(project2, member.memberId, project2.requiredRelease)) {
8892
9039
  return {
8893
9040
  allowed: false,
8894
- detail: handbookMessage("guard", project.requiredRelease)
9041
+ detail: handbookMessage("guard", project2.requiredRelease)
8895
9042
  };
8896
9043
  }
8897
9044
  }
@@ -8904,7 +9051,7 @@ function startHandbookRuntime(ctx) {
8904
9051
  var decodeAcknowledgement = decodeUnknownSync(AcknowledgeInput);
8905
9052
  var decodePublish = decodeUnknownSync(PublishInput);
8906
9053
  var decodeRevert = decodeUnknownSync(RevertInput);
8907
- var decodeRead3 = decodeUnknownSync(ReadHandbookInput);
9054
+ var decodeRead4 = decodeUnknownSync(ReadHandbookInput);
8908
9055
  var toolSchema = (schema) => ({
8909
9056
  ...payloadSchemaOf(schema),
8910
9057
  type: "object",
@@ -8948,15 +9095,127 @@ function registerHandbook(ctx, runtime) {
8948
9095
  name: "handbook",
8949
9096
  scope: "read",
8950
9097
  payloadSchema: payloadSchemaOf(ReadHandbookInput),
8951
- handle: (input) => runtime.read(decodeRead3(input).projectId)
9098
+ handle: (input) => runtime.read(decodeRead4(input).projectId)
8952
9099
  });
8953
9100
  ctx.operations.guard({ operations: ["work.assign"], check: runtime.check });
8954
9101
  }
8955
9102
 
9103
+ // src/handbook/panelContracts.ts
9104
+ var project = { projectId: String4 };
9105
+ var ReadPanelInput = Struct(project);
9106
+ var ReadReleasesInput = Struct({
9107
+ ...project,
9108
+ cursor: optionalKey2(String4)
9109
+ });
9110
+ var ReadPageInput = Struct({
9111
+ ...project,
9112
+ pageId: String4,
9113
+ revision: Int,
9114
+ textOffset: optionalKey2(Int)
9115
+ });
9116
+ var PublishPanelInput = Struct({ ...project, ...PublishInput.fields });
9117
+ var RevertPanelInput = Struct({ ...project, ...RevertInput.fields });
9118
+ var HandbookWriteResult = Union2([
9119
+ Struct({ status: Literal2("succeeded"), release: Int }),
9120
+ Struct({ status: Literal2("rejected"), code: String4, detail: String4 })
9121
+ ]);
9122
+
9123
+ // src/handbook/panel.ts
9124
+ var decodePanel = decodeUnknownSync(ReadPanelInput);
9125
+ var decodeList = decodeUnknownSync(ReadReleasesInput);
9126
+ var decodePage = decodeUnknownSync(ReadPageInput);
9127
+ var decodePublish2 = decodeUnknownSync(PublishPanelInput);
9128
+ var decodeRevert2 = decodeUnknownSync(RevertPanelInput);
9129
+ function registerHandbookPanel(ctx, runtime) {
9130
+ ctx.command({
9131
+ name: "handbookPanel",
9132
+ scope: "read",
9133
+ payloadSchema: payloadSchemaOf(ReadPanelInput),
9134
+ handle: async (payload, invocation) => {
9135
+ const input = decodePanel(payload);
9136
+ const [result2, status] = await Promise.all([
9137
+ ctx.memory.handbook.get(input),
9138
+ runtime.read(input.projectId)
9139
+ ]);
9140
+ return { head: result2.head, status, canOperate: invocation.scope === "operate" };
9141
+ }
9142
+ });
9143
+ ctx.command({
9144
+ name: "handbookReleases",
9145
+ scope: "read",
9146
+ payloadSchema: payloadSchemaOf(ReadReleasesInput),
9147
+ handle: async (payload) => {
9148
+ const input = decodeList(payload);
9149
+ const result2 = await ctx.memory.handbook.releases.list({ ...input, limit: 20 });
9150
+ const publishers = await runtime.publisherMembers(
9151
+ input.projectId,
9152
+ result2.items.map((row) => row.release)
9153
+ );
9154
+ return {
9155
+ ...result2,
9156
+ items: result2.items.map((row) => ({
9157
+ ...row,
9158
+ publisherMemberId: row.publishedBy.kind === "plugin" && row.publishedBy.pluginId === "team" ? publishers.find((publisher) => publisher.release === row.release)?.memberId ?? null : null
9159
+ }))
9160
+ };
9161
+ }
9162
+ });
9163
+ ctx.command({
9164
+ name: "handbookPage",
9165
+ scope: "read",
9166
+ payloadSchema: payloadSchemaOf(ReadPageInput),
9167
+ handle: (payload) => ctx.memory.pageRevisions.get(decodePage(payload))
9168
+ });
9169
+ const write = async (input, invocation) => {
9170
+ if (invocation.scope !== "operate" || invocation.asPerson === void 0)
9171
+ return { status: "rejected", code: "needs-person", detail: "needs-person" };
9172
+ try {
9173
+ const requireAck = await publicationRequiresAck(ctx.memory, input.projectId, input);
9174
+ const handbook = invocation.asPerson.memory.handbook;
9175
+ const command = {
9176
+ projectId: input.projectId,
9177
+ requestId: input.requestId,
9178
+ expectedRelease: input.expectedRelease,
9179
+ requireAck
9180
+ };
9181
+ const result2 = input.operation === "publish" ? await handbook.publish({
9182
+ ...command,
9183
+ pages: input.pages,
9184
+ ...input.sourceExperience === void 0 ? {} : { sourceExperience: input.sourceExperience }
9185
+ }) : await handbook.revert({ ...command, toRelease: input.toRelease });
9186
+ return { status: "succeeded", release: result2.result.release };
9187
+ } catch (error) {
9188
+ const refusal = decodeHostRefusal(error);
9189
+ if (isNone2(refusal)) throw error;
9190
+ const { code, details } = refusal.value.rejection;
9191
+ return {
9192
+ status: "rejected",
9193
+ code,
9194
+ detail: [
9195
+ error instanceof Error ? error.message : code,
9196
+ details === void 0 ? null : JSON.stringify(details)
9197
+ ].filter(Boolean).join("\n")
9198
+ };
9199
+ }
9200
+ };
9201
+ ctx.command({
9202
+ name: "publishHandbook",
9203
+ scope: "operate",
9204
+ payloadSchema: payloadSchemaOf(PublishPanelInput),
9205
+ handle: (payload, invocation) => write({ ...decodePublish2(payload), operation: "publish" }, invocation)
9206
+ });
9207
+ ctx.command({
9208
+ name: "revertHandbook",
9209
+ scope: "operate",
9210
+ payloadSchema: payloadSchemaOf(RevertPanelInput),
9211
+ handle: (payload, invocation) => write({ ...decodeRevert2(payload), operation: "revert" }, invocation)
9212
+ });
9213
+ }
9214
+
8956
9215
  // src/index.ts
8957
9216
  var index_default = definePlugin({
8958
9217
  id: "team",
8959
- apiVersion: 13,
9218
+ apiVersion: 15,
8960
9219
  activate(ctx) {
8961
9220
  const rosters = createRosterService(ctx);
8962
9221
  registerRosterCommands(ctx, rosters);
@@ -8965,7 +9224,9 @@ var index_default = definePlugin({
8965
9224
  registerBoardCommands(ctx, goals, rosters);
8966
9225
  registerDetailCommands(ctx);
8967
9226
  startWakeRuntime(ctx);
8968
- registerHandbook(ctx, startHandbookRuntime(ctx));
9227
+ const handbook = startHandbookRuntime(ctx);
9228
+ registerHandbook(ctx, handbook);
9229
+ registerHandbookPanel(ctx, handbook);
8969
9230
  }
8970
9231
  });
8971
9232
  export {