@kubitnodes/stellaris 0.3.11 → 0.3.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +287 -28
- package/dist/server/index.js +542 -63
- package/package.json +1 -1
- package/web/dist/assets/{BackLink-C-vad6K2.js → BackLink-d0xz7ZLb.js} +1 -1
- package/web/dist/assets/ChannelForms-BIRwT9CY.js +1 -0
- package/web/dist/assets/ChannelView-BvMlzMiN.js +1 -0
- package/web/dist/assets/CitizenView-DNQwjpIp.js +2 -0
- package/web/dist/assets/CitizensView-Bv04iH-y.js +1 -0
- package/web/dist/assets/CliIcon-tYTwI_RQ.js +1 -0
- package/web/dist/assets/KnowledgeView-L0pdaM_2.js +1 -0
- package/web/dist/assets/{Markdown-J2pAPfCa.js → Markdown-BLiIulgZ.js} +4 -4
- package/web/dist/assets/MessageItem-CHrmjF7P.js +1 -0
- package/web/dist/assets/MetricsView-DG1alTgs.js +1 -0
- package/web/dist/assets/NeedsYouView-Doc0fKPi.js +1 -0
- package/web/dist/assets/Overview-ChsOgIEW.js +1 -0
- package/web/dist/assets/{Pane-caYWCiyo.js → Pane-MJgGXQBb.js} +1 -1
- package/web/dist/assets/ProjectView-CQTn7yDo.js +1 -0
- package/web/dist/assets/ProposalView-C92OJ_w1.js +1 -0
- package/web/dist/assets/ProposalsView-CSA8neXR.js +1 -0
- package/web/dist/assets/RunnersView-Dx21LDFu.js +1 -0
- package/web/dist/assets/SocietyView-CdVW2DgG.js +1 -0
- package/web/dist/assets/{SubjectThread-D_L3vB-A.js → SubjectThread-egXeFhT1.js} +1 -1
- package/web/dist/assets/TaskFileView-BqqzxJKb.js +4 -0
- package/web/dist/assets/TaskView-rks26vuF.js +3 -0
- package/web/dist/assets/TasksView-Ba5fPAvy.js +1 -0
- package/web/dist/assets/ThreadCard-D6i69_15.js +1 -0
- package/web/dist/assets/ThreadForms-ChTFRu-P.js +1 -0
- package/web/dist/assets/ThreadView-CnJPNS_8.js +1 -0
- package/web/dist/assets/{citizen-BtOZq62_.js → citizen-D-dxX00V.js} +1 -1
- package/web/dist/assets/compose-BgpUWGYE.js +1 -0
- package/web/dist/assets/{format-EfoceZkm.js → format-C3aawMM6.js} +1 -1
- package/web/dist/assets/governance-Di4MijNG.js +1 -0
- package/web/dist/assets/index-VVnGdAXc.css +2 -0
- package/web/dist/assets/index-krHkeIbX.js +23 -0
- package/web/dist/assets/link-DAMxBItI.js +1 -0
- package/web/dist/assets/mentions-BCdV0bMK.js +1 -0
- package/web/dist/assets/{tasks-Duke4U7N.js → tasks-bwj_EdwF.js} +1 -1
- package/web/dist/assets/useMatch-B-8IcuU6.js +1 -0
- package/web/dist/assets/{useStickyScroll-n5d7sdPZ.js → useStickyScroll-BILPUgGj.js} +1 -1
- package/web/dist/index.html +10 -4
- package/web/dist/assets/ChannelForms-jNbbNnIC.js +0 -1
- package/web/dist/assets/ChannelView-kLoegYUQ.js +0 -1
- package/web/dist/assets/CitizenView-Cb8f2mB7.js +0 -2
- package/web/dist/assets/CitizensView-Ds2rojfa.js +0 -1
- package/web/dist/assets/KnowledgeView-k10TU0jc.js +0 -1
- package/web/dist/assets/MessageItem-BjM8BdpK.js +0 -1
- package/web/dist/assets/MetricsView-D9N54rV7.js +0 -1
- package/web/dist/assets/NeedsYouView-ML8i7Pl7.js +0 -1
- package/web/dist/assets/Overview-BBXvRhfR.js +0 -1
- package/web/dist/assets/ProjectView-sDpFNjcW.js +0 -1
- package/web/dist/assets/ProposalView-Bc91NjKC.js +0 -1
- package/web/dist/assets/ProposalsView-Dg_bEryX.js +0 -1
- package/web/dist/assets/RunnersView-Dv5nbwYv.js +0 -1
- package/web/dist/assets/SocietyView-ClaEWgOq.js +0 -1
- package/web/dist/assets/TaskFileView-B19YtrtW.js +0 -4
- package/web/dist/assets/TaskView-FYSoCb3T.js +0 -1
- package/web/dist/assets/TasksView-CniXTU3R.js +0 -1
- package/web/dist/assets/ThreadCard-DCpvhSlc.js +0 -1
- package/web/dist/assets/ThreadForms-DUGkAwTp.js +0 -1
- package/web/dist/assets/ThreadView-D8eXlVbC.js +0 -1
- package/web/dist/assets/index-BeOGtAz0.css +0 -2
- package/web/dist/assets/index-D75IxLme.js +0 -23
package/dist/cli/index.js
CHANGED
|
@@ -59,7 +59,8 @@ var VERB_NAMES = [
|
|
|
59
59
|
"archive_project",
|
|
60
60
|
"remove_knowledge",
|
|
61
61
|
"create_channel",
|
|
62
|
-
"archive_channel"
|
|
62
|
+
"archive_channel",
|
|
63
|
+
"update_dashboard"
|
|
63
64
|
];
|
|
64
65
|
var VerbNameSchema = z2.enum(VERB_NAMES);
|
|
65
66
|
var CharterTriggerSchema = z2.enum(["user_post", "ops_event", "heartbeat"]);
|
|
@@ -105,7 +106,8 @@ var MEMBER_VERBS = [
|
|
|
105
106
|
"write_knowledge",
|
|
106
107
|
"remove_knowledge",
|
|
107
108
|
"plan_task",
|
|
108
|
-
"advance_task"
|
|
109
|
+
"advance_task",
|
|
110
|
+
"update_dashboard"
|
|
109
111
|
];
|
|
110
112
|
var GOVERNANCE_VERBS = ["approve", "reject"];
|
|
111
113
|
var FRONT_DESK_VERBS = ["create_project"];
|
|
@@ -185,7 +187,44 @@ var SocietySchema = z3.object({
|
|
|
185
187
|
channels: z3.array(NameSchema),
|
|
186
188
|
archivedChannels: z3.array(ArchivedChannelSchema).default([])
|
|
187
189
|
});
|
|
188
|
-
var CompletionEffectSchema = z3.enum(["none", "merge"]);
|
|
190
|
+
var CompletionEffectSchema = z3.enum(["none", "merge", "ghpr"]);
|
|
191
|
+
var GITHUB_REMOTE = [
|
|
192
|
+
/^https:\/\/github\.com\/([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?\/?$/,
|
|
193
|
+
/^git@github\.com:([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?$/,
|
|
194
|
+
/^ssh:\/\/git@github\.com\/([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?\/?$/
|
|
195
|
+
];
|
|
196
|
+
function githubRepository(remote) {
|
|
197
|
+
for (const pattern of GITHUB_REMOTE) {
|
|
198
|
+
const match = remote === null ? null : pattern.exec(remote);
|
|
199
|
+
if (match?.[1] !== void 0 && match[2] !== void 0) {
|
|
200
|
+
return { owner: match[1], name: match[2] };
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
var PULL_REQUEST_URL = /^https:\/\/github\.com\/([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+)\/pull\/([1-9]\d*)\/?$/;
|
|
206
|
+
var PullRequestUrlSchema = z3.string().trim().regex(PULL_REQUEST_URL, "a pull request's address, https://github.com/<owner>/<repo>/pull/<number>");
|
|
207
|
+
function parsePullRequestUrl(url) {
|
|
208
|
+
const match = PULL_REQUEST_URL.exec(url.trim());
|
|
209
|
+
return match?.[1] === void 0 || match[2] === void 0 || match[3] === void 0 ? null : { owner: match[1], name: match[2], number: Number(match[3]) };
|
|
210
|
+
}
|
|
211
|
+
function sameRepository(a, b) {
|
|
212
|
+
return a.owner.toLowerCase() === b.owner.toLowerCase() && a.name.toLowerCase() === b.name.toLowerCase();
|
|
213
|
+
}
|
|
214
|
+
var MergeSubjectSchema = z3.string().trim().min(1).max(300).regex(/^[^\n]+$/, "a merge commit's subject is one line");
|
|
215
|
+
var PullRequestLinkSchema = z3.object({
|
|
216
|
+
url: PullRequestUrlSchema,
|
|
217
|
+
number: z3.number().int().positive(),
|
|
218
|
+
subject: MergeSubjectSchema.optional(),
|
|
219
|
+
body: z3.string().max(2e4).optional(),
|
|
220
|
+
linkedBy: NameSchema,
|
|
221
|
+
linkedAt: IsoDateTimeSchema,
|
|
222
|
+
/**
|
|
223
|
+
* Once its `ghpr` task was abandoned, how the board's closing of it on GitHub went: `ok` false
|
|
224
|
+
* leaves it open, for the reason `detail` gives.
|
|
225
|
+
*/
|
|
226
|
+
closed: z3.object({ at: IsoDateTimeSchema, ok: z3.boolean(), detail: z3.string() }).optional()
|
|
227
|
+
});
|
|
189
228
|
var DefaultBranchSchema = z3.string().trim().min(1).max(100).regex(/^[A-Za-z0-9][A-Za-z0-9._/-]*$/, "a branch name is letters, digits, and . _ / -").refine((branch) => !branch.includes("..") && !branch.includes("//") && !branch.endsWith("/") && !branch.endsWith(".") && !branch.endsWith(".lock"), "not a branch name git takes").refine((branch) => !branch.startsWith("task/") && !branch.startsWith("agent/"), "task/ and agent/ hold the board's own branches");
|
|
190
229
|
var StageIdSchema = z3.string().regex(/^s[1-9][0-9]*$/, "expected a stage id such as s1");
|
|
191
230
|
var stageFields = {
|
|
@@ -300,6 +339,8 @@ var TaskFrontmatterSchema = z3.object({
|
|
|
300
339
|
/** The highest stage number issued, so a removed stage's id is never reused. */
|
|
301
340
|
stageSeq: z3.number().int().positive(),
|
|
302
341
|
onDone: CompletionEffectSchema.default("none"),
|
|
342
|
+
/** The pull request a `ghpr` task lands through. */
|
|
343
|
+
pullRequest: PullRequestLinkSchema.optional(),
|
|
303
344
|
/** The last stage is complete and the completion effect is running. */
|
|
304
345
|
completing: z3.boolean().default(false),
|
|
305
346
|
/** The latest send-back while its rework lasts: cleared once the task reaches that stage again. */
|
|
@@ -392,6 +433,8 @@ var RunnerSchema = z3.object({
|
|
|
392
433
|
os: RunnerOsSchema,
|
|
393
434
|
clis: z3.array(CliKindSchema),
|
|
394
435
|
capabilities: z3.array(z3.string()),
|
|
436
|
+
/** The release the runner reported when it last registered; absent until it has. */
|
|
437
|
+
version: z3.string().optional(),
|
|
395
438
|
status: z3.enum(["connected", "disconnected"]),
|
|
396
439
|
lastSeen: IsoDateTimeSchema.optional()
|
|
397
440
|
});
|
|
@@ -570,12 +613,27 @@ var RemovedKnowledgeSchema = z3.object({
|
|
|
570
613
|
removedBy: NameSchema,
|
|
571
614
|
removedAt: IsoDateTimeSchema
|
|
572
615
|
});
|
|
616
|
+
var DashboardSchema = z3.object({
|
|
617
|
+
project: NameSchema,
|
|
618
|
+
revision: z3.number().int().min(0),
|
|
619
|
+
/** Absent until someone updates the dashboard the project began with. */
|
|
620
|
+
updatedBy: NameSchema.optional(),
|
|
621
|
+
updatedAt: IsoDateTimeSchema.optional(),
|
|
622
|
+
body: z3.string()
|
|
623
|
+
});
|
|
624
|
+
var ProposalOriginSchema = z3.object({
|
|
625
|
+
scope: NameSchema,
|
|
626
|
+
thread: UlidSchema.optional(),
|
|
627
|
+
channel: NameSchema.optional()
|
|
628
|
+
});
|
|
573
629
|
var ProposalFrontmatterSchema = z3.object({
|
|
574
630
|
id: UlidSchema,
|
|
575
631
|
kind: ProposalKindSchema,
|
|
576
632
|
proposedBy: NameSchema,
|
|
577
633
|
status: ProposalStatusSchema,
|
|
578
634
|
createdAt: IsoDateTimeSchema,
|
|
635
|
+
/** Absent for a proposal filed outside a turn, or before proposals recorded it. */
|
|
636
|
+
filedFrom: ProposalOriginSchema.optional(),
|
|
579
637
|
decidedBy: NameSchema.optional(),
|
|
580
638
|
decidedAt: IsoDateTimeSchema.optional(),
|
|
581
639
|
reason: z3.string().optional(),
|
|
@@ -641,7 +699,16 @@ var VerbInputs = {
|
|
|
641
699
|
status: z4.enum(["abandoned"]).optional(),
|
|
642
700
|
/** Posted to the task's thread as the caller, marked with the step when the task moved. */
|
|
643
701
|
note: z4.string().min(1).optional(),
|
|
644
|
-
blocked_by: z4.array(UlidSchema).optional()
|
|
702
|
+
blocked_by: z4.array(UlidSchema).optional(),
|
|
703
|
+
/**
|
|
704
|
+
* The pull request a `ghpr` task lands through, on its project's repository, with the merge
|
|
705
|
+
* commit's subject and body; null unlinks it.
|
|
706
|
+
*/
|
|
707
|
+
pull_request: z4.object({
|
|
708
|
+
url: PullRequestUrlSchema,
|
|
709
|
+
merge_subject: MergeSubjectSchema.optional(),
|
|
710
|
+
merge_body: z4.string().max(2e4).optional()
|
|
711
|
+
}).nullable().optional()
|
|
645
712
|
}),
|
|
646
713
|
get_task: z4.object({ task_id: UlidSchema }),
|
|
647
714
|
subscribe: z4.object({ channel: ChannelRefSchema }),
|
|
@@ -695,7 +762,14 @@ var VerbInputs = {
|
|
|
695
762
|
name: NameSchema,
|
|
696
763
|
purpose: z4.string().min(1)
|
|
697
764
|
}),
|
|
698
|
-
archive_channel: z4.object({ channel: ChannelRefSchema, reason: z4.string().min(1) })
|
|
765
|
+
archive_channel: z4.object({ channel: ChannelRefSchema, reason: z4.string().min(1) }),
|
|
766
|
+
update_dashboard: z4.object({
|
|
767
|
+
project: NameSchema,
|
|
768
|
+
/** The whole dashboard as markdown; the board keeps its frontmatter. */
|
|
769
|
+
body: z4.string().min(1),
|
|
770
|
+
/** The `revision` in the frontmatter of the dashboard you read, 0 when it names none. */
|
|
771
|
+
revision: z4.number().int().min(0)
|
|
772
|
+
})
|
|
699
773
|
};
|
|
700
774
|
|
|
701
775
|
// packages/shared/dist/events.js
|
|
@@ -735,6 +809,7 @@ var BOARD_EVENT_TYPES = [
|
|
|
735
809
|
"agent.left",
|
|
736
810
|
"knowledge.written",
|
|
737
811
|
"knowledge.removed",
|
|
812
|
+
"dashboard.updated",
|
|
738
813
|
"skill.promoted",
|
|
739
814
|
"runner.added",
|
|
740
815
|
"user.signed_in",
|
|
@@ -751,6 +826,8 @@ var BOARD_EVENT_TYPES = [
|
|
|
751
826
|
"turn.failed",
|
|
752
827
|
"merge.completed",
|
|
753
828
|
"merge.failed",
|
|
829
|
+
"pull_request.closed",
|
|
830
|
+
"pull_request.close_failed",
|
|
754
831
|
"workspace.leftovers"
|
|
755
832
|
];
|
|
756
833
|
var BoardEventTypeSchema = z5.enum(BOARD_EVENT_TYPES);
|
|
@@ -898,7 +975,12 @@ var PendingEnrollmentSchema = z6.object({
|
|
|
898
975
|
var ProjectRepoSchema = z6.object({
|
|
899
976
|
slug: NameSchema,
|
|
900
977
|
origin: z6.string().nullable(),
|
|
901
|
-
defaultBranch: z6.string().min(1)
|
|
978
|
+
defaultBranch: z6.string().min(1),
|
|
979
|
+
/**
|
|
980
|
+
* The board lands finished tasks on the default branch, itself or through their pull requests,
|
|
981
|
+
* so the runner lets nothing else move it.
|
|
982
|
+
*/
|
|
983
|
+
boardLands: z6.boolean()
|
|
902
984
|
});
|
|
903
985
|
var TurnWorkspaceSchema = z6.discriminatedUnion("kind", [
|
|
904
986
|
z6.object({
|
|
@@ -972,7 +1054,22 @@ var TurnOutcomeSchema = z6.object({
|
|
|
972
1054
|
var TurnAckSchema = z6.object({ dropWorkspace: z6.boolean() });
|
|
973
1055
|
var LandRequestSchema = z6.object({
|
|
974
1056
|
repo: ProjectRepoSchema,
|
|
975
|
-
branch: z6.string().min(1)
|
|
1057
|
+
branch: z6.string().min(1),
|
|
1058
|
+
/**
|
|
1059
|
+
* For a `ghpr` task, the pull request to merge in place of the branch, which must hold the
|
|
1060
|
+
* branch's files, and the merge commit's subject and body, the pull request's title and an
|
|
1061
|
+
* empty body when left out.
|
|
1062
|
+
*/
|
|
1063
|
+
pullRequest: z6.object({
|
|
1064
|
+
url: z6.string().min(1),
|
|
1065
|
+
subject: z6.string().min(1).optional(),
|
|
1066
|
+
body: z6.string().optional()
|
|
1067
|
+
}).optional()
|
|
1068
|
+
});
|
|
1069
|
+
var CloseRequestSchema = z6.object({
|
|
1070
|
+
repo: ProjectRepoSchema,
|
|
1071
|
+
url: z6.string().min(1),
|
|
1072
|
+
comment: z6.string().min(1)
|
|
976
1073
|
});
|
|
977
1074
|
var MergeOutcomeSchema = z6.object({ ok: z6.boolean(), detail: z6.string() });
|
|
978
1075
|
var ModelListSchema = z6.array(ModelOptionSchema);
|
|
@@ -1041,6 +1138,8 @@ var TurnSteerSchema = z6.object({ id: z6.uuid(), text: z6.string().min(1) });
|
|
|
1041
1138
|
var RunnerMessageSchema = z6.discriminatedUnion("type", [
|
|
1042
1139
|
z6.object({ type: z6.literal("turn"), job: TurnJobSchema }),
|
|
1043
1140
|
z6.object({ type: z6.literal("land"), request: z6.string().min(1), land: LandRequestSchema }),
|
|
1141
|
+
/** Answered with a `MergeOutcome`. */
|
|
1142
|
+
z6.object({ type: z6.literal("close"), request: z6.string().min(1), close: CloseRequestSchema }),
|
|
1044
1143
|
z6.object({ type: z6.literal("models"), request: z6.string().min(1), cli: CliKindSchema }),
|
|
1045
1144
|
/** Answered with a `BranchFile`, or null when the branch or the path does not exist. */
|
|
1046
1145
|
z6.object({ type: z6.literal("file"), request: z6.string().min(1), read: BranchReadSchema }),
|
|
@@ -1385,7 +1484,7 @@ var RunnerConfigSchema = z10.object({
|
|
|
1385
1484
|
});
|
|
1386
1485
|
|
|
1387
1486
|
// packages/shared/dist/version.js
|
|
1388
|
-
var STELLARIS_VERSION = "0.3.
|
|
1487
|
+
var STELLARIS_VERSION = "0.3.13";
|
|
1389
1488
|
|
|
1390
1489
|
// packages/board-core/dist/errors.js
|
|
1391
1490
|
var BoardError = class extends Error {
|
|
@@ -2566,6 +2665,9 @@ var THREAD_CONVERSATIONS = "thread-conversations";
|
|
|
2566
2665
|
var ASKS_CHANNEL_OPENED = "asks-channel-opened";
|
|
2567
2666
|
var KNOWLEDGE_REMOVAL = "knowledge-removal-granted";
|
|
2568
2667
|
var CHANNEL_CONVERSATIONS = "channel-conversations";
|
|
2668
|
+
var DASHBOARD_UPDATES = "dashboard-updates-granted";
|
|
2669
|
+
var OLD_DASHBOARD_LINE = "Agents may edit this file. It is rendered by the board UI.";
|
|
2670
|
+
var DASHBOARD_LINE = "The project's members keep this page current with update_dashboard.";
|
|
2569
2671
|
var PLANNING_ROLES = [USER_ROLE, "steward", "concierge"];
|
|
2570
2672
|
var REALLOCATING_ROLES = [USER_ROLE, "steward", "concierge"];
|
|
2571
2673
|
var PROFILE_TEMPLATE = "# Profile\n\nOne short paragraph, kept current: what I do well, what I am working on, and what to send my way. The board projects this into the roster the front desk reads.\n";
|
|
@@ -2602,6 +2704,11 @@ function memberToAgentInput(member) {
|
|
|
2602
2704
|
...member.seedInstructions === void 0 ? {} : { seedInstructions: member.seedInstructions }
|
|
2603
2705
|
};
|
|
2604
2706
|
}
|
|
2707
|
+
function assertLandsByPullRequest(effect, repo, where) {
|
|
2708
|
+
if (effect === "ghpr" && githubRepository(repo) === null) {
|
|
2709
|
+
throw new BoardError("VALIDATION", `${where} has ${repo === null ? "no remote" : `its remote at ${repo}, not on GitHub`}, so its tasks cannot land through pull requests (ghpr)`);
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2605
2712
|
function stageFrom(planned, id) {
|
|
2606
2713
|
return {
|
|
2607
2714
|
id,
|
|
@@ -2759,7 +2866,8 @@ var Board = class _Board {
|
|
|
2759
2866
|
THREAD_CONVERSATIONS,
|
|
2760
2867
|
ASKS_CHANNEL_OPENED,
|
|
2761
2868
|
KNOWLEDGE_REMOVAL,
|
|
2762
|
-
CHANNEL_CONVERSATIONS
|
|
2869
|
+
CHANNEL_CONVERSATIONS,
|
|
2870
|
+
DASHBOARD_UPDATES
|
|
2763
2871
|
].filter((name) => !applied.includes(name));
|
|
2764
2872
|
if (pending.length === 0) {
|
|
2765
2873
|
return;
|
|
@@ -2789,21 +2897,38 @@ var Board = class _Board {
|
|
|
2789
2897
|
await this.openAsksChannel();
|
|
2790
2898
|
}
|
|
2791
2899
|
if (pending.includes(KNOWLEDGE_REMOVAL)) {
|
|
2792
|
-
await this.
|
|
2900
|
+
await this.grantBesideKnowledge("remove_knowledge");
|
|
2793
2901
|
}
|
|
2794
2902
|
if (pending.includes(CHANNEL_CONVERSATIONS)) {
|
|
2795
2903
|
await this.startChannelCursors();
|
|
2796
2904
|
}
|
|
2905
|
+
if (pending.includes(DASHBOARD_UPDATES)) {
|
|
2906
|
+
await this.grantBesideKnowledge("update_dashboard");
|
|
2907
|
+
await this.rewordDashboards();
|
|
2908
|
+
}
|
|
2797
2909
|
await writeJson(file, { applied: [...applied, ...pending] });
|
|
2798
2910
|
}
|
|
2799
2911
|
/**
|
|
2800
|
-
*
|
|
2801
|
-
*
|
|
2912
|
+
* Grants `verb` once to every charter granting `write_knowledge`, the work roles' charters in
|
|
2913
|
+
* practice; seed roles have it from their seed.
|
|
2802
2914
|
*/
|
|
2803
|
-
async
|
|
2915
|
+
async grantBesideKnowledge(verb) {
|
|
2804
2916
|
for (const charter of await this.listRoles()) {
|
|
2805
|
-
if (charter.verbs.includes("write_knowledge") && !charter.verbs.includes(
|
|
2806
|
-
await this.writeRoleUnlocked(USER_NAME, { ...charter, verbs: [...charter.verbs,
|
|
2917
|
+
if (charter.verbs.includes("write_knowledge") && !charter.verbs.includes(verb)) {
|
|
2918
|
+
await this.writeRoleUnlocked(USER_NAME, { ...charter, verbs: [...charter.verbs, verb] }, { verbsAdded: [verb] });
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
/** A dashboard still saying agents may edit its file says how it changes now. */
|
|
2923
|
+
async rewordDashboards() {
|
|
2924
|
+
for (const slug of await listDirs(this.paths.projects())) {
|
|
2925
|
+
const file = this.paths.dashboard(slug);
|
|
2926
|
+
if (!await exists(file)) {
|
|
2927
|
+
continue;
|
|
2928
|
+
}
|
|
2929
|
+
const { data, body } = await readLooseMarkdown(file);
|
|
2930
|
+
if (body.includes(OLD_DASHBOARD_LINE)) {
|
|
2931
|
+
await writeMarkdown(file, data, body.replace(OLD_DASHBOARD_LINE, DASHBOARD_LINE));
|
|
2807
2932
|
}
|
|
2808
2933
|
}
|
|
2809
2934
|
}
|
|
@@ -3565,6 +3690,7 @@ ${charter.purpose}
|
|
|
3565
3690
|
if (await exists(file)) {
|
|
3566
3691
|
throw new BoardError("ALREADY_EXISTS", `project ${input.slug} already exists`);
|
|
3567
3692
|
}
|
|
3693
|
+
assertLandsByPullRequest(input.onDone ?? "none", input.repo ?? null, `project ${input.slug}`);
|
|
3568
3694
|
const project2 = ProjectSchema.parse({
|
|
3569
3695
|
slug: input.slug,
|
|
3570
3696
|
name: input.name ?? input.slug,
|
|
@@ -3585,9 +3711,9 @@ ${charter.purpose}
|
|
|
3585
3711
|
await ensureDir(this.paths.tasks(project2.slug));
|
|
3586
3712
|
await ensureDir(this.paths.threads(project2.slug));
|
|
3587
3713
|
await ensureDir(this.paths.projectKnowledge(project2.slug));
|
|
3588
|
-
await writeMarkdown(this.paths.dashboard(project2.slug), { project: project2.slug, updatedAt: this.now().toISOString() }, `# ${project2.name} dashboard
|
|
3714
|
+
await writeMarkdown(this.paths.dashboard(project2.slug), { project: project2.slug, revision: 0, updatedAt: this.now().toISOString() }, `# ${project2.name} dashboard
|
|
3589
3715
|
|
|
3590
|
-
|
|
3716
|
+
${DASHBOARD_LINE}
|
|
3591
3717
|
`);
|
|
3592
3718
|
await this.updateAgent(USER_NAME, (user2) => ({
|
|
3593
3719
|
...user2,
|
|
@@ -3692,6 +3818,7 @@ Agents may edit this file. It is rendered by the board UI.
|
|
|
3692
3818
|
...patch.os === void 0 ? {} : { os: patch.os },
|
|
3693
3819
|
...patch.clis === void 0 ? {} : { clis: [...patch.clis] },
|
|
3694
3820
|
...patch.capabilities === void 0 ? {} : { capabilities: [...patch.capabilities] },
|
|
3821
|
+
...patch.version === void 0 ? {} : { version: patch.version },
|
|
3695
3822
|
lastSeen: this.now().toISOString()
|
|
3696
3823
|
});
|
|
3697
3824
|
await writeMarkdown(file, next, doc.body);
|
|
@@ -4166,6 +4293,42 @@ ${task2.body}`;
|
|
|
4166
4293
|
throw new BoardError("FORBIDDEN", `${actor.name} is not a member of ${project2}`);
|
|
4167
4294
|
}
|
|
4168
4295
|
}
|
|
4296
|
+
/**
|
|
4297
|
+
* Replaces a project's dashboard, by a member or the user. A turn reads the dashboard from the
|
|
4298
|
+
* copy of the projection pulled when it started, so a revision other than the current one means
|
|
4299
|
+
* someone updated it since; the refusal carries the current text, which that copy lacks.
|
|
4300
|
+
*/
|
|
4301
|
+
async updateDashboard(actor, input) {
|
|
4302
|
+
const args = VerbInputs.update_dashboard.parse(input);
|
|
4303
|
+
await this.authorize(actor, "update_dashboard");
|
|
4304
|
+
return this.mutex.run(async () => {
|
|
4305
|
+
const project2 = await this.readActiveProject(args.project);
|
|
4306
|
+
if (actor.role !== USER_ROLE && !project2.members.includes(actor.name)) {
|
|
4307
|
+
throw new BoardError("FORBIDDEN", `${actor.name} is not a member of ${args.project}`);
|
|
4308
|
+
}
|
|
4309
|
+
const current = await this.dashboardOf(args.project);
|
|
4310
|
+
if (args.revision !== current.revision) {
|
|
4311
|
+
const by = current.updatedBy === void 0 ? "" : `, updated by ${current.updatedBy} at ${current.updatedAt ?? "an unknown time"}`;
|
|
4312
|
+
throw new BoardError("INVALID_STATE", `the dashboard of ${args.project} is at revision ${current.revision}${by}, not ${args.revision}. Merge your changes into its current text, below, and update again with revision ${current.revision}.
|
|
4313
|
+
|
|
4314
|
+
${current.body}`);
|
|
4315
|
+
}
|
|
4316
|
+
const updated = DashboardSchema.parse({
|
|
4317
|
+
project: args.project,
|
|
4318
|
+
revision: current.revision + 1,
|
|
4319
|
+
updatedBy: actor.name,
|
|
4320
|
+
updatedAt: this.now().toISOString(),
|
|
4321
|
+
body: args.body
|
|
4322
|
+
});
|
|
4323
|
+
const { body, ...frontmatter } = updated;
|
|
4324
|
+
await writeMarkdown(this.paths.dashboard(args.project), frontmatter, body);
|
|
4325
|
+
await this.events.append("dashboard.updated", actor.name, {
|
|
4326
|
+
project: args.project,
|
|
4327
|
+
revision: updated.revision
|
|
4328
|
+
});
|
|
4329
|
+
return updated;
|
|
4330
|
+
});
|
|
4331
|
+
}
|
|
4169
4332
|
async openThread(actor, input) {
|
|
4170
4333
|
const args = VerbInputs.open_thread.parse(input);
|
|
4171
4334
|
await this.authorize(actor, "open_thread");
|
|
@@ -4547,6 +4710,9 @@ ${args.summary}`);
|
|
|
4547
4710
|
completedAt: ts
|
|
4548
4711
|
};
|
|
4549
4712
|
const next = current.stages[index + 1];
|
|
4713
|
+
if (next === void 0 && current.onDone === "ghpr" && current.pullRequest === void 0) {
|
|
4714
|
+
throw new BoardError("INVALID_STATE", `task ${current.id} lands through its pull request: link it with update_task pull_request before the last stage completes, or have the user, the steward, or the concierge set the task's on_done to none`);
|
|
4715
|
+
}
|
|
4550
4716
|
if (next === void 0 && current.onDone === "none") {
|
|
4551
4717
|
this.assertNoTurnCut(actor, `task ${current.id} cannot end`, (turn2) => turn2.thread === current.id);
|
|
4552
4718
|
}
|
|
@@ -4663,6 +4829,7 @@ ${args.summary}`);
|
|
|
4663
4829
|
const guarded = gateChanges(ahead, replanned);
|
|
4664
4830
|
if (onDone !== current.onDone) {
|
|
4665
4831
|
guarded.push("change the completion effect");
|
|
4832
|
+
assertLandsByPullRequest(onDone, (await this.readProject(current.project)).repo, `project ${current.project}`);
|
|
4666
4833
|
}
|
|
4667
4834
|
if (guarded.length > 0) {
|
|
4668
4835
|
this.assertMayGate(actor, guarded.join(", "));
|
|
@@ -4751,6 +4918,12 @@ ${args.summary}`);
|
|
|
4751
4918
|
}
|
|
4752
4919
|
next = { ...next, blockedBy: [...args.blocked_by] };
|
|
4753
4920
|
}
|
|
4921
|
+
if (args.pull_request !== void 0) {
|
|
4922
|
+
next = {
|
|
4923
|
+
...next,
|
|
4924
|
+
pullRequest: await this.pullRequestLink(actor, current, args.pull_request)
|
|
4925
|
+
};
|
|
4926
|
+
}
|
|
4754
4927
|
if (args.note !== void 0) {
|
|
4755
4928
|
const step = moved !== null ? { action: "returned", stage: moved.from, to: moved.to } : next.status === "abandoned" ? { action: "abandoned", stage: current.stage, to: null } : void 0;
|
|
4756
4929
|
await this.postTaskNote(actor.name, current, args.note, step);
|
|
@@ -4776,12 +4949,46 @@ ${args.summary}`);
|
|
|
4776
4949
|
project: location.project,
|
|
4777
4950
|
from: current.status,
|
|
4778
4951
|
to: task2.status,
|
|
4779
|
-
note: args.note ?? null
|
|
4952
|
+
note: args.note ?? null,
|
|
4953
|
+
...args.pull_request === void 0 ? {} : { pullRequest: task2.pullRequest?.url ?? null }
|
|
4780
4954
|
});
|
|
4781
4955
|
}
|
|
4782
4956
|
return task2;
|
|
4783
4957
|
});
|
|
4784
4958
|
}
|
|
4959
|
+
/**
|
|
4960
|
+
* The pull request a task is linked to, or none for `null`: one on the repository of its project,
|
|
4961
|
+
* by a member of the project or a planner, while the task is in play and before it lands.
|
|
4962
|
+
*/
|
|
4963
|
+
async pullRequestLink(actor, task2, link) {
|
|
4964
|
+
this.assertInPlay(task2);
|
|
4965
|
+
if (task2.completing) {
|
|
4966
|
+
throw new BoardError("INVALID_STATE", `task ${task2.id} is landing, so its pull request no longer changes`);
|
|
4967
|
+
}
|
|
4968
|
+
if (!PLANNING_ROLES.includes(actor.role)) {
|
|
4969
|
+
const agent2 = await this.readAgent(actor.name);
|
|
4970
|
+
if (!agent2.memberships.includes(task2.project)) {
|
|
4971
|
+
throw new BoardError("FORBIDDEN", `${actor.name} is not a member of ${task2.project}`);
|
|
4972
|
+
}
|
|
4973
|
+
}
|
|
4974
|
+
if (link === null) {
|
|
4975
|
+
return void 0;
|
|
4976
|
+
}
|
|
4977
|
+
const project2 = await this.readProject(task2.project);
|
|
4978
|
+
const repository = githubRepository(project2.repo);
|
|
4979
|
+
const named = parsePullRequestUrl(link.url);
|
|
4980
|
+
if (repository === null || named === null || !sameRepository(repository, named)) {
|
|
4981
|
+
throw new BoardError("VALIDATION", `${link.url} is not a pull request of ${project2.slug}'s repository${project2.repo === null ? ", since it has no remote" : ` at ${project2.repo}`}`);
|
|
4982
|
+
}
|
|
4983
|
+
return PullRequestLinkSchema.parse({
|
|
4984
|
+
url: link.url,
|
|
4985
|
+
number: named.number,
|
|
4986
|
+
...link.merge_subject === void 0 ? {} : { subject: link.merge_subject },
|
|
4987
|
+
...link.merge_body === void 0 ? {} : { body: link.merge_body },
|
|
4988
|
+
linkedBy: actor.name,
|
|
4989
|
+
linkedAt: this.now().toISOString()
|
|
4990
|
+
});
|
|
4991
|
+
}
|
|
4785
4992
|
async getTask(actor, input) {
|
|
4786
4993
|
const args = VerbInputs.get_task.parse(input);
|
|
4787
4994
|
await this.authorize(actor, "get_task");
|
|
@@ -4839,6 +5046,13 @@ ${args.summary}`);
|
|
|
4839
5046
|
proposedBy: actor.name,
|
|
4840
5047
|
status: "proposed",
|
|
4841
5048
|
createdAt: this.now().toISOString(),
|
|
5049
|
+
...actor.scope === void 0 ? {} : {
|
|
5050
|
+
filedFrom: {
|
|
5051
|
+
scope: actor.scope,
|
|
5052
|
+
...actor.thread === void 0 ? {} : { thread: actor.thread },
|
|
5053
|
+
...actor.channel === void 0 ? {} : { channel: actor.channel }
|
|
5054
|
+
}
|
|
5055
|
+
},
|
|
4842
5056
|
charter: parsed.data
|
|
4843
5057
|
}),
|
|
4844
5058
|
body: args.rationale
|
|
@@ -4892,6 +5106,9 @@ ${rationale}`}`);
|
|
|
4892
5106
|
}
|
|
4893
5107
|
return this.mutex.run(async () => {
|
|
4894
5108
|
const previous = await this.readActiveProject(args.project);
|
|
5109
|
+
if (args.on_done !== void 0) {
|
|
5110
|
+
assertLandsByPullRequest(args.on_done, previous.repo, `project ${previous.slug}`);
|
|
5111
|
+
}
|
|
4895
5112
|
const project2 = await this.updateProject(args.project, (current) => ({
|
|
4896
5113
|
...current,
|
|
4897
5114
|
...args.on_done === void 0 ? {} : { onDone: args.on_done },
|
|
@@ -5073,6 +5290,8 @@ ${rationale}`}`);
|
|
|
5073
5290
|
return this.createChannel(actor, VerbInputs.create_channel.parse(input));
|
|
5074
5291
|
case "archive_channel":
|
|
5075
5292
|
return this.archiveChannel(actor, VerbInputs.archive_channel.parse(input));
|
|
5293
|
+
case "update_dashboard":
|
|
5294
|
+
return this.updateDashboard(actor, VerbInputs.update_dashboard.parse(input));
|
|
5076
5295
|
default:
|
|
5077
5296
|
throw new BoardError("VALIDATION", `unknown verb ${String(verb)}`);
|
|
5078
5297
|
}
|
|
@@ -5334,6 +5553,29 @@ ${rationale}`}`);
|
|
|
5334
5553
|
});
|
|
5335
5554
|
});
|
|
5336
5555
|
}
|
|
5556
|
+
/**
|
|
5557
|
+
* Records how closing an abandoned `ghpr` task's pull request on GitHub went, once: the task's
|
|
5558
|
+
* link keeps it, so a restart does not try again.
|
|
5559
|
+
*/
|
|
5560
|
+
async recordPullRequestClosed(actor, input) {
|
|
5561
|
+
return this.mutex.run(async () => {
|
|
5562
|
+
const location = await this.findTask(input.taskId);
|
|
5563
|
+
const current = location.task;
|
|
5564
|
+
const pull = current.pullRequest;
|
|
5565
|
+
if (pull === void 0 || pull.closed !== void 0) {
|
|
5566
|
+
return current;
|
|
5567
|
+
}
|
|
5568
|
+
const task2 = await this.writeTask(location.project, {
|
|
5569
|
+
...current,
|
|
5570
|
+
pullRequest: {
|
|
5571
|
+
...pull,
|
|
5572
|
+
closed: { at: this.now().toISOString(), ok: input.ok, detail: input.detail }
|
|
5573
|
+
}
|
|
5574
|
+
});
|
|
5575
|
+
await this.events.append(input.ok ? "pull_request.closed" : "pull_request.close_failed", actor.name, { project: location.project, taskId: task2.id, url: pull.url, detail: input.detail });
|
|
5576
|
+
return task2;
|
|
5577
|
+
});
|
|
5578
|
+
}
|
|
5337
5579
|
/**
|
|
5338
5580
|
* Records that a citizen's workspace in a conversation that has ended holds work on no branch, so
|
|
5339
5581
|
* the scheduler gives the citizen a closing turn there to decide about it. False, recording
|
|
@@ -5377,8 +5619,8 @@ ${rationale}`}`);
|
|
|
5377
5619
|
}
|
|
5378
5620
|
/**
|
|
5379
5621
|
* Records the outcome of a completing task's effect: done, or waiting again at its last stage so
|
|
5380
|
-
* its participants can reshape the plan. A
|
|
5381
|
-
* landed one announced in the
|
|
5622
|
+
* its participants can reshape the plan. A landing's outcome, a merge's or a pull request's, is
|
|
5623
|
+
* posted to the task's thread, and a landed one announced in the task's channel.
|
|
5382
5624
|
*/
|
|
5383
5625
|
async finishCompletion(actor, input) {
|
|
5384
5626
|
return this.mutex.run(async () => {
|
|
@@ -5388,8 +5630,8 @@ ${rationale}`}`);
|
|
|
5388
5630
|
throw new BoardError("INVALID_STATE", `task ${current.id} is not completing`);
|
|
5389
5631
|
}
|
|
5390
5632
|
const ts = this.now().toISOString();
|
|
5391
|
-
const
|
|
5392
|
-
if (
|
|
5633
|
+
const landed = current.onDone !== "none";
|
|
5634
|
+
if (landed) {
|
|
5393
5635
|
await this.postTaskNote(actor.name, current, input.ok ? `${sentence(input.detail)}.` : `Landing failed: ${sentence(input.detail)}. The task waits at its last stage for its participants to reshape the plan.`, input.ok ? { action: "landed", stage: current.stage, to: null } : { action: "reopened", stage: current.stage, to: current.stage });
|
|
5394
5636
|
}
|
|
5395
5637
|
if (input.ok) {
|
|
@@ -5409,7 +5651,7 @@ ${rationale}`}`);
|
|
|
5409
5651
|
for (const name of new Set(task3.stages.flatMap((stage) => stage.completedBy ?? []))) {
|
|
5410
5652
|
await this.refreshMember(name);
|
|
5411
5653
|
}
|
|
5412
|
-
if (
|
|
5654
|
+
if (landed) {
|
|
5413
5655
|
await this.appendMessage(actor.name, channelRef(location.project, task3.channel), `Task ${task3.id} "${task3.title}" is done: ${sentence(input.detail)}.`);
|
|
5414
5656
|
}
|
|
5415
5657
|
return task3;
|
|
@@ -5439,15 +5681,31 @@ ${rationale}`}`);
|
|
|
5439
5681
|
async writeState(name, value) {
|
|
5440
5682
|
await this.mutex.run(() => writeJson(path6.join(this.paths.state(), `${name}.json`), value));
|
|
5441
5683
|
}
|
|
5442
|
-
/** The project's
|
|
5684
|
+
/** The project's dashboard, empty at revision 0 when it has none. */
|
|
5443
5685
|
async readDashboard(slug) {
|
|
5444
5686
|
await this.readProject(slug);
|
|
5687
|
+
return this.dashboardOf(slug);
|
|
5688
|
+
}
|
|
5689
|
+
/**
|
|
5690
|
+
* Read loosely, since agents wrote dashboards by hand before they had a verb: a revision or an
|
|
5691
|
+
* author that does not parse counts as none.
|
|
5692
|
+
*/
|
|
5693
|
+
async dashboardOf(slug) {
|
|
5445
5694
|
const file = this.paths.dashboard(slug);
|
|
5446
5695
|
if (!await exists(file)) {
|
|
5447
|
-
return {
|
|
5696
|
+
return { project: slug, revision: 0, body: "" };
|
|
5448
5697
|
}
|
|
5449
5698
|
const { data, body } = await readLooseMarkdown(file);
|
|
5450
|
-
|
|
5699
|
+
const revision = z12.number().int().min(0).safeParse(data["revision"]);
|
|
5700
|
+
const updatedBy = NameSchema.safeParse(data["updatedBy"]);
|
|
5701
|
+
const updatedAt = IsoDateTimeSchema.safeParse(data["updatedAt"]);
|
|
5702
|
+
return {
|
|
5703
|
+
project: slug,
|
|
5704
|
+
revision: revision.success ? revision.data : 0,
|
|
5705
|
+
...updatedBy.success ? { updatedBy: updatedBy.data } : {},
|
|
5706
|
+
...updatedAt.success ? { updatedAt: updatedAt.data } : {},
|
|
5707
|
+
body
|
|
5708
|
+
};
|
|
5451
5709
|
}
|
|
5452
5710
|
async listRunners() {
|
|
5453
5711
|
const runners = [];
|
|
@@ -6550,7 +6808,8 @@ ${charter.purpose}
|
|
|
6550
6808
|
proposalId,
|
|
6551
6809
|
outcome,
|
|
6552
6810
|
kind: proposal2.kind,
|
|
6553
|
-
proposedBy: proposal2.proposedBy
|
|
6811
|
+
proposedBy: proposal2.proposedBy,
|
|
6812
|
+
...proposal2.filedFrom === void 0 ? {} : { filedFrom: proposal2.filedFrom }
|
|
6554
6813
|
});
|
|
6555
6814
|
if (provision !== void 0) {
|
|
6556
6815
|
await this.events.append("proposal.provisioned", actor.name, {
|
|
@@ -6794,7 +7053,7 @@ runner.command("add <name>").description("Register a runner and print its token
|
|
|
6794
7053
|
runner.command("list").description("List runners and what they offer").action(async () => {
|
|
6795
7054
|
const board = await open();
|
|
6796
7055
|
const runners = await board.listRunners();
|
|
6797
|
-
print(runners, () => runners.length === 0 ? "No runners yet; add one with `runner add <name>`." : runners.map((each) => `${each.name} ${each.status} ${each.os} ${each.clis.join(",") || "no CLIs yet"}${each.capabilities.length === 0 ? "" : ` ${each.capabilities.join(",")}`}`).join("\n"));
|
|
7056
|
+
print(runners, () => runners.length === 0 ? "No runners yet; add one with `runner add <name>`." : runners.map((each) => `${each.name} ${each.status} ${each.os}${each.version === void 0 ? "" : ` v${each.version}`} ${each.clis.join(",") || "no CLIs yet"}${each.capabilities.length === 0 ? "" : ` ${each.capabilities.join(",")}`}`).join("\n"));
|
|
6798
7057
|
});
|
|
6799
7058
|
var agent = program.command("agent").description("Manage agents");
|
|
6800
7059
|
agent.command("add <name>").description("Add an agent and print its board token once").requiredOption("--role <role>", "role charter name").option("--cli <cli>", "claude or codex").option("--model <model>", "model to run the CLI with; the CLI's own default otherwise").option("--runner <name>", "runner preferred for its turns; any with its CLI otherwise").option("-p, --project <slug...>", "project memberships").action(async (name, opts) => {
|