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