@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/assets/ui.js +30 -10
- package/dist/index.js +519 -258
- package/locales/en.json +64 -0
- package/locales/zh-CN.json +64 -0
- package/package.json +2 -2
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
|
|
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
|
|
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
|
|
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([
|
|
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.
|
|
7257
|
-
|
|
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
|
|
7312
|
-
if (Array.isArray(node)) return node.map(
|
|
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,
|
|
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
|
-
|
|
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) =>
|
|
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/
|
|
7351
|
-
|
|
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:
|
|
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:
|
|
7465
|
-
truncated:
|
|
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(
|
|
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/
|
|
7499
|
-
var
|
|
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
|
-
|
|
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 } =
|
|
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
|
-
|
|
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:
|
|
7902
|
+
commentsTruncated: Boolean3,
|
|
7789
7903
|
history: ArraySchema(DetailActivity),
|
|
7790
|
-
historyTruncated:
|
|
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
|
|
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 } =
|
|
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 = (
|
|
8049
|
-
(blocker) => readFact(
|
|
8162
|
+
var hasOpenBlocker = (project2, workItemId) => (readFact(project2.blockers, workItemId) ?? []).some(
|
|
8163
|
+
(blocker) => readFact(project2.items, blocker)?.closed !== true
|
|
8050
8164
|
);
|
|
8051
|
-
var markBlockedAfterClaim = (
|
|
8052
|
-
...
|
|
8165
|
+
var markBlockedAfterClaim = (project2, workItemId) => ({
|
|
8166
|
+
...project2,
|
|
8053
8167
|
blockedAfterClaim: {
|
|
8054
|
-
...
|
|
8055
|
-
[workItemId]: (readFact(
|
|
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 = (
|
|
8141
|
-
var commentRecipients = (
|
|
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(
|
|
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
|
|
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 = { ...
|
|
8284
|
+
const next2 = { ...project4, threads, leadThreads, remoteMembers };
|
|
8171
8285
|
return {
|
|
8172
8286
|
...state,
|
|
8173
8287
|
[goal.projectId]: {
|
|
8174
8288
|
...next2,
|
|
8175
|
-
pendingComments:
|
|
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
|
|
8181
|
-
if (
|
|
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
|
-
...
|
|
8186
|
-
pendingComments:
|
|
8299
|
+
...project3,
|
|
8300
|
+
pendingComments: project3.pendingComments.filter((comment) => comment.key !== key)
|
|
8187
8301
|
}
|
|
8188
8302
|
};
|
|
8189
8303
|
}
|
|
8190
|
-
if (
|
|
8191
|
-
return { ...state, [projectId2]: { ...
|
|
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
|
|
8198
|
-
const currentItem = readFact(
|
|
8199
|
-
let next =
|
|
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
|
-
...
|
|
8317
|
+
...project2,
|
|
8204
8318
|
items: {
|
|
8205
|
-
...
|
|
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
|
-
...
|
|
8339
|
+
...project2,
|
|
8226
8340
|
items: {
|
|
8227
|
-
...
|
|
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(
|
|
8357
|
+
const blockers = readFact(project2.blockers, workItemId) ?? [];
|
|
8244
8358
|
const related = work.payload.relatedWorkItemId;
|
|
8245
8359
|
next = {
|
|
8246
|
-
...
|
|
8360
|
+
...project2,
|
|
8247
8361
|
blockers: {
|
|
8248
|
-
...
|
|
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(
|
|
8253
|
-
const woken = new Set(
|
|
8254
|
-
const 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(
|
|
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(
|
|
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) => !
|
|
8393
|
+
(comment) => !project2.pendingComments.some((pending) => pending.key === comment.key)
|
|
8280
8394
|
);
|
|
8281
8395
|
if (comments.length === 0) return state;
|
|
8282
|
-
next = { ...
|
|
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,
|
|
8292
|
-
const woken = new Set(
|
|
8293
|
-
const threads = new Set(
|
|
8294
|
-
const leads = new Set(
|
|
8295
|
-
for (const [workItemId, item] of Object.entries(
|
|
8296
|
-
if (item.closed || hasOpenBlocker(
|
|
8297
|
-
const unblocked = readFact(
|
|
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(
|
|
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
|
|
8323
|
-
const item = readFact(
|
|
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:
|
|
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(
|
|
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(
|
|
8485
|
-
requiresAck: optionalKey2(
|
|
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
|
|
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:
|
|
8659
|
+
requiresAck: Boolean3
|
|
8539
8660
|
})
|
|
8540
8661
|
);
|
|
8541
|
-
function callerMember(
|
|
8662
|
+
function callerMember(project2, caller) {
|
|
8542
8663
|
if (caller === void 0) return void 0;
|
|
8543
|
-
return goalExecution(
|
|
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 = (
|
|
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
|
|
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(
|
|
8682
|
+
for (const { memberId, ...recipient } of goalExecution(project3.goal).members) {
|
|
8562
8683
|
const key = reminderKey(fact2.projectId, fact2.release, memberId);
|
|
8563
|
-
if (readFact(
|
|
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
|
-
...
|
|
8697
|
+
...project3,
|
|
8577
8698
|
currentRelease: fact2.release,
|
|
8578
|
-
requiredRelease: fact2.requiresAck ? fact2.release :
|
|
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
|
|
8589
|
-
let next =
|
|
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 = { ...
|
|
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(
|
|
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(
|
|
8630
|
-
const previous = readFact(
|
|
8761
|
+
if (hasAcknowledged(project2, memberId, release)) return state;
|
|
8762
|
+
const previous = readFact(project2.acknowledgements, memberId);
|
|
8631
8763
|
next = {
|
|
8632
|
-
...
|
|
8764
|
+
...project2,
|
|
8633
8765
|
acknowledgements: {
|
|
8634
|
-
...
|
|
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(
|
|
8653
|
-
next = { ...
|
|
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:
|
|
8679
|
-
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
|
|
8707
|
-
if (hasAcknowledged(
|
|
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(
|
|
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:
|
|
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
|
|
8955
|
+
const project2 = readFact((await state.get()).projects, projectId) ?? emptyHandbookProject();
|
|
8811
8956
|
return {
|
|
8812
|
-
currentRelease:
|
|
8813
|
-
requiredRelease:
|
|
8814
|
-
members: goalExecution(
|
|
8957
|
+
currentRelease: project2.currentRelease,
|
|
8958
|
+
requiredRelease: project2.requiredRelease,
|
|
8959
|
+
members: goalExecution(project2.goal).members.map(({ memberId }) => ({
|
|
8815
8960
|
memberId,
|
|
8816
|
-
acknowledgedRelease: readFact(
|
|
8817
|
-
acknowledgedRequiredRelease:
|
|
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
|
|
8825
|
-
const member = callerMember(
|
|
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 (
|
|
8829
|
-
if (release !==
|
|
8830
|
-
throw new Error(handbookMessage("wrongRelease",
|
|
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
|
|
8839
|
-
const member = callerMember(
|
|
8840
|
-
const execution = goalExecution(
|
|
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
|
|
9030
|
+
const project2 = readFact(
|
|
8884
9031
|
(await state.get({ throughSequence: input.throughSequence })).projects,
|
|
8885
9032
|
input.projectId
|
|
8886
9033
|
);
|
|
8887
|
-
if (
|
|
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(
|
|
8891
|
-
if (member !== void 0 && !hasAcknowledged(
|
|
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",
|
|
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
|
|
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(
|
|
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:
|
|
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
|
-
|
|
9227
|
+
const handbook = startHandbookRuntime(ctx);
|
|
9228
|
+
registerHandbook(ctx, handbook);
|
|
9229
|
+
registerHandbookPanel(ctx, handbook);
|
|
8969
9230
|
}
|
|
8970
9231
|
});
|
|
8971
9232
|
export {
|