@kubitnodes/stellaris 0.3.22 → 0.3.24
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/README.md +4 -4
- package/dist/cli/index.js +1029 -571
- package/dist/server/index.js +1256 -656
- package/package.json +1 -1
- package/web/dist/assets/BackLink-DKMw_rHu.js +1 -0
- package/web/dist/assets/{Button-Yoe21NQ2.js → Button-BYJGqZ3Q.js} +1 -1
- package/web/dist/assets/{ChannelForms-55otlg-m.js → ChannelForms-CkYupEL2.js} +1 -1
- package/web/dist/assets/ChannelView-BIXouQMX.js +2 -0
- package/web/dist/assets/CitizenView-C3AMFixP.js +2 -0
- package/web/dist/assets/CitizensView-CYFS3f9U.js +1 -0
- package/web/dist/assets/{CliIcon-DbMYJdlU.js → CliIcon-DVX5iLhG.js} +1 -1
- package/web/dist/assets/CronsView-BGCcO7bH.js +1 -0
- package/web/dist/assets/KnowledgeView-DLLYjd1G.js +1 -0
- package/web/dist/assets/Markdown--whKJBYV.js +30 -0
- package/web/dist/assets/MessageItem-BvDOeJci.js +1 -0
- package/web/dist/assets/MetricsView-B-KUNOZm.js +1 -0
- package/web/dist/assets/NeedsYouView-Dee_bOKj.js +1 -0
- package/web/dist/assets/{Overview-BveU1yJj.js → Overview-DFiHDFnA.js} +1 -1
- package/web/dist/assets/{Pane-CLjdfBMM.js → Pane-DTmByqgA.js} +1 -1
- package/web/dist/assets/{Picker-CXOe5Xdu.js → Picker-DsHm4f7s.js} +1 -1
- package/web/dist/assets/{ProjectView-oa4Z46ED.js → ProjectView-DsRXW-qk.js} +1 -1
- package/web/dist/assets/ProposalView-B-4P1S-s.js +1 -0
- package/web/dist/assets/{ProposalsView-BZHivSEm.js → ProposalsView-uEuj-ooV.js} +1 -1
- package/web/dist/assets/Quota-D8WDWp5H.js +1 -0
- package/web/dist/assets/{RunnersView-BrJF-aLU.js → RunnersView-BB3q65rG.js} +1 -1
- package/web/dist/assets/SkillFileList-DhdEcFQA.js +2 -0
- package/web/dist/assets/SkillView-vAv7zvtJ.js +1 -0
- package/web/dist/assets/SocietyView-Dv4phSkz.js +1 -0
- package/web/dist/assets/{StickyList-7tAlUDCN.js → StickyList-C4fC_KJv.js} +1 -1
- package/web/dist/assets/SubjectThread-BEXt-tub.js +1 -0
- package/web/dist/assets/{TaskFileView-BEXQJQQ6.js → TaskFileView-B9sex0tS.js} +2 -2
- package/web/dist/assets/{TaskView-Cfhb7NuO.js → TaskView-BCT388kb.js} +2 -2
- package/web/dist/assets/{TasksView-3XljXcoi.js → TasksView-CuPay34-.js} +1 -1
- package/web/dist/assets/{ThreadForms-D2y5dXSC.js → ThreadForms-DzoRDeBh.js} +1 -1
- package/web/dist/assets/ThreadItem-CDVppcOZ.js +1 -0
- package/web/dist/assets/ThreadView-B4F10_j1.js +1 -0
- package/web/dist/assets/citizen-BPIJzKdn.js +1 -0
- package/web/dist/assets/{compose-DNc3_c5x.js → compose-BUhbVoI-.js} +1 -1
- package/web/dist/assets/format-CvfmDNoY.js +55 -0
- package/web/dist/assets/{governance-RBQ4Lam0.js → governance-Bba0OHvJ.js} +1 -1
- package/web/dist/assets/index-Bj8MlyAn.css +2 -0
- package/web/dist/assets/index-DD3J11PT.js +23 -0
- package/web/dist/assets/{mentions-B2Qchd_6.js → mentions-D838wlfQ.js} +1 -1
- package/web/dist/assets/{tasks-DCUj5w_d.js → tasks-CTaxXtGL.js} +1 -1
- package/web/dist/assets/useMatch-B8j0hm1q.js +1 -0
- package/web/dist/index.html +9 -9
- package/web/dist/assets/BackLink-CD-iuy-h.js +0 -1
- package/web/dist/assets/ChannelView-DJvLzg0N.js +0 -2
- package/web/dist/assets/CitizenView-m1K2aTvK.js +0 -2
- package/web/dist/assets/CitizensView-BeTgmrKE.js +0 -1
- package/web/dist/assets/CronsView-B4kXk5Ld.js +0 -1
- package/web/dist/assets/KnowledgeView-aSGxDDKy.js +0 -1
- package/web/dist/assets/Markdown-c9bCCLHg.js +0 -30
- package/web/dist/assets/MessageItem-DKJKMqs-.js +0 -1
- package/web/dist/assets/MetricsView-D2TTry5O.js +0 -1
- package/web/dist/assets/NeedsYouView-C3celRei.js +0 -1
- package/web/dist/assets/ProposalView-B4T-EoX9.js +0 -2
- package/web/dist/assets/Quota-BC7dpnwd.js +0 -1
- package/web/dist/assets/SocietyView-C1yosPuf.js +0 -1
- package/web/dist/assets/SubjectThread-CCq6ZAnR.js +0 -1
- package/web/dist/assets/ThreadItem-DeIhhmyu.js +0 -1
- package/web/dist/assets/ThreadView-CbUnt9WL.js +0 -1
- package/web/dist/assets/citizen--bqux6i_.js +0 -1
- package/web/dist/assets/format-B6fjC0_a.js +0 -55
- package/web/dist/assets/index-CyCty7a2.js +0 -23
- package/web/dist/assets/index-GRqxKN35.css +0 -2
- package/web/dist/assets/useMatch-CfbBQirn.js +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
// apps/cli/dist/index.js
|
|
4
4
|
import { readFile as readFile5 } from "node:fs/promises";
|
|
5
|
-
import
|
|
5
|
+
import path8 from "node:path";
|
|
6
6
|
import { Command } from "commander";
|
|
7
7
|
|
|
8
8
|
// packages/board-core/dist/board.js
|
|
9
|
-
import { chmod as chmod2, cp, mkdir as mkdir4, readdir as
|
|
10
|
-
import
|
|
9
|
+
import { chmod as chmod2, cp, mkdir as mkdir4, readdir as readdir4, readFile as readFile4, rename as rename3, rm as rm2, stat as stat3, writeFile as writeFile4 } from "node:fs/promises";
|
|
10
|
+
import path7 from "node:path";
|
|
11
11
|
import { decodeTime, monotonicFactory } from "ulid";
|
|
12
|
-
import { z as
|
|
12
|
+
import { z as z16 } from "zod";
|
|
13
13
|
|
|
14
14
|
// packages/shared/dist/ids.js
|
|
15
15
|
import { z } from "zod";
|
|
@@ -64,7 +64,8 @@ var VERB_NAMES = [
|
|
|
64
64
|
"create_cron",
|
|
65
65
|
"update_cron",
|
|
66
66
|
"remove_cron",
|
|
67
|
-
"remove_skill"
|
|
67
|
+
"remove_skill",
|
|
68
|
+
"upload_image"
|
|
68
69
|
];
|
|
69
70
|
var VerbNameSchema = z2.enum(VERB_NAMES);
|
|
70
71
|
var CharterTriggerSchema = z2.enum(["user_post", "ops_event", "heartbeat"]);
|
|
@@ -93,6 +94,7 @@ var USER_ROLE = "user";
|
|
|
93
94
|
var USER_NAME = "user";
|
|
94
95
|
var MEMBER_VERBS = [
|
|
95
96
|
"post_message",
|
|
97
|
+
"upload_image",
|
|
96
98
|
"read_inbox",
|
|
97
99
|
"search",
|
|
98
100
|
"open_thread",
|
|
@@ -232,6 +234,7 @@ var CronFrontmatterSchema = z3.object({
|
|
|
232
234
|
scope: NameSchema,
|
|
233
235
|
thread: UlidSchema.optional(),
|
|
234
236
|
channel: NameSchema.optional(),
|
|
237
|
+
place: NameSchema.optional(),
|
|
235
238
|
schedule: CronScheduleSchema,
|
|
236
239
|
createdBy: NameSchema,
|
|
237
240
|
createdAt: IsoDateTimeSchema,
|
|
@@ -937,7 +940,8 @@ var DashboardSchema = z5.object({
|
|
|
937
940
|
var ProposalOriginSchema = z5.object({
|
|
938
941
|
scope: NameSchema,
|
|
939
942
|
thread: UlidSchema.optional(),
|
|
940
|
-
channel: NameSchema.optional()
|
|
943
|
+
channel: NameSchema.optional(),
|
|
944
|
+
place: NameSchema.optional()
|
|
941
945
|
});
|
|
942
946
|
var SKILL_FILES_LIMIT = 100;
|
|
943
947
|
var SKILL_BYTES_LIMIT = 10 * 1024 * 1024;
|
|
@@ -985,130 +989,169 @@ var DecisionSchema = z5.object({
|
|
|
985
989
|
});
|
|
986
990
|
|
|
987
991
|
// packages/shared/dist/verbs.js
|
|
992
|
+
import { z as z7 } from "zod";
|
|
993
|
+
|
|
994
|
+
// packages/shared/dist/images.js
|
|
988
995
|
import { z as z6 } from "zod";
|
|
989
|
-
var
|
|
990
|
-
var
|
|
991
|
-
|
|
996
|
+
var IMAGE_LIMIT_BYTES = 8 * 1024 * 1024;
|
|
997
|
+
var ImageTypeSchema = z6.enum(["png", "jpeg", "gif", "webp"]);
|
|
998
|
+
function startsWith(bytes, prefix, at = 0) {
|
|
999
|
+
for (let index = 0; index < prefix.length; index += 1) {
|
|
1000
|
+
if (bytes[at + index] !== prefix[index]) {
|
|
1001
|
+
return false;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
return true;
|
|
1005
|
+
}
|
|
1006
|
+
var ascii = (text) => new TextEncoder().encode(text);
|
|
1007
|
+
function imageTypeOf(bytes) {
|
|
1008
|
+
if (startsWith(bytes, [137, 80, 78, 71, 13, 10, 26, 10])) {
|
|
1009
|
+
return "png";
|
|
1010
|
+
}
|
|
1011
|
+
if (startsWith(bytes, [255, 216, 255])) {
|
|
1012
|
+
return "jpeg";
|
|
1013
|
+
}
|
|
1014
|
+
if (startsWith(bytes, ascii("GIF87a")) || startsWith(bytes, ascii("GIF89a"))) {
|
|
1015
|
+
return "gif";
|
|
1016
|
+
}
|
|
1017
|
+
if (startsWith(bytes, ascii("RIFF")) && startsWith(bytes, ascii("WEBP"), 8)) {
|
|
1018
|
+
return "webp";
|
|
1019
|
+
}
|
|
1020
|
+
return null;
|
|
1021
|
+
}
|
|
1022
|
+
var ImagePathSchema = z6.string().min(1).max(4096).refine((value) => !value.includes("\0"), { message: "a path with no NUL character" });
|
|
1023
|
+
var IMAGE_SCHEME = "image:";
|
|
1024
|
+
var UploadedImageSchema = z6.object({
|
|
1025
|
+
id: UlidSchema,
|
|
1026
|
+
type: ImageTypeSchema,
|
|
1027
|
+
size: z6.number().int().positive(),
|
|
1028
|
+
markdown: z6.string()
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
// packages/shared/dist/verbs.js
|
|
1032
|
+
var MomentSchema = z7.iso.datetime({ offset: true });
|
|
1033
|
+
var CronTitleSchema = z7.string().trim().min(1).max(120);
|
|
1034
|
+
var CronNoteSchema = z7.string().trim().min(1).max(4e3);
|
|
992
1035
|
var VerbInputs = {
|
|
993
|
-
post_message:
|
|
1036
|
+
post_message: z7.object({
|
|
994
1037
|
/** Required outside a thread; in one it may be left out, and must otherwise be the thread's. */
|
|
995
1038
|
channel: ChannelRefSchema.optional(),
|
|
996
|
-
body:
|
|
1039
|
+
body: z7.string().min(1),
|
|
997
1040
|
thread_id: UlidSchema.optional()
|
|
998
1041
|
}),
|
|
999
|
-
read_inbox:
|
|
1042
|
+
read_inbox: z7.object({
|
|
1000
1043
|
since_cursor: UlidSchema.nullable().optional(),
|
|
1001
|
-
limit:
|
|
1002
|
-
advance:
|
|
1044
|
+
limit: z7.number().int().positive().max(200).default(50),
|
|
1045
|
+
advance: z7.boolean().default(true)
|
|
1003
1046
|
}),
|
|
1004
|
-
search:
|
|
1005
|
-
query:
|
|
1047
|
+
search: z7.object({
|
|
1048
|
+
query: z7.string().min(1),
|
|
1006
1049
|
project: NameSchema.optional(),
|
|
1007
1050
|
channel: ChannelRefSchema.optional(),
|
|
1008
|
-
limit:
|
|
1051
|
+
limit: z7.number().int().positive().max(100).default(20)
|
|
1009
1052
|
}),
|
|
1010
1053
|
/** On a task or a proposal, whose id the thread takes, or on a channel with a title. */
|
|
1011
|
-
open_thread:
|
|
1054
|
+
open_thread: z7.object({
|
|
1012
1055
|
task_id: UlidSchema.optional(),
|
|
1013
1056
|
proposal_id: UlidSchema.optional(),
|
|
1014
1057
|
channel: ChannelRefSchema.optional(),
|
|
1015
|
-
title:
|
|
1058
|
+
title: z7.string().min(1).max(200).optional()
|
|
1016
1059
|
}),
|
|
1017
|
-
close_thread:
|
|
1018
|
-
create_task:
|
|
1060
|
+
close_thread: z7.object({ thread_id: UlidSchema, summary: z7.string().min(1) }),
|
|
1061
|
+
create_task: z7.object({
|
|
1019
1062
|
project: NameSchema,
|
|
1020
|
-
title:
|
|
1021
|
-
body:
|
|
1063
|
+
title: z7.string().min(1).max(200),
|
|
1064
|
+
body: z7.string().default(""),
|
|
1022
1065
|
parent_id: UlidSchema.optional(),
|
|
1023
|
-
required_capabilities:
|
|
1066
|
+
required_capabilities: z7.array(z7.string()).default([]),
|
|
1024
1067
|
/** The plan; without it the task gets one stage, work, that anyone in the project may take. */
|
|
1025
|
-
stages:
|
|
1068
|
+
stages: z7.array(PlanStageSchema).min(1).optional(),
|
|
1026
1069
|
/** A channel of the project the task belongs to, such as a release's; its thread opens there. */
|
|
1027
1070
|
channel: NameSchema.default("general")
|
|
1028
1071
|
}),
|
|
1029
|
-
claim_task:
|
|
1030
|
-
release_task:
|
|
1031
|
-
update_task:
|
|
1072
|
+
claim_task: z7.object({ task_id: UlidSchema }),
|
|
1073
|
+
release_task: z7.object({ task_id: UlidSchema }),
|
|
1074
|
+
update_task: z7.object({
|
|
1032
1075
|
task_id: UlidSchema,
|
|
1033
1076
|
/** Move the task back to this earlier stage. */
|
|
1034
1077
|
stage: StageIdSchema.optional(),
|
|
1035
|
-
status:
|
|
1078
|
+
status: z7.enum(["abandoned"]).optional(),
|
|
1036
1079
|
/** Posted to the task's thread as the caller, marked with the step when the task moved. */
|
|
1037
|
-
note:
|
|
1038
|
-
blocked_by:
|
|
1080
|
+
note: z7.string().min(1).optional(),
|
|
1081
|
+
blocked_by: z7.array(UlidSchema).optional(),
|
|
1039
1082
|
/**
|
|
1040
1083
|
* The pull request a `ghpr` task lands through, on its project's repository, with the merge
|
|
1041
1084
|
* commit's subject and body; null unlinks it.
|
|
1042
1085
|
*/
|
|
1043
|
-
pull_request:
|
|
1086
|
+
pull_request: z7.object({
|
|
1044
1087
|
url: PullRequestUrlSchema,
|
|
1045
1088
|
merge_subject: MergeSubjectSchema.optional(),
|
|
1046
|
-
merge_body:
|
|
1089
|
+
merge_body: z7.string().max(2e4).optional()
|
|
1047
1090
|
}).nullable().optional()
|
|
1048
1091
|
}),
|
|
1049
|
-
get_task:
|
|
1050
|
-
subscribe:
|
|
1051
|
-
unsubscribe:
|
|
1052
|
-
propose:
|
|
1092
|
+
get_task: z7.object({ task_id: UlidSchema }),
|
|
1093
|
+
subscribe: z7.object({ channel: ChannelRefSchema }),
|
|
1094
|
+
unsubscribe: z7.object({ channel: ChannelRefSchema }),
|
|
1095
|
+
propose: z7.object({
|
|
1053
1096
|
kind: ProposalKindSchema,
|
|
1054
|
-
charter:
|
|
1055
|
-
rationale:
|
|
1097
|
+
charter: z7.record(z7.string(), z7.unknown()),
|
|
1098
|
+
rationale: z7.string().default("")
|
|
1056
1099
|
}),
|
|
1057
|
-
approve:
|
|
1058
|
-
reject:
|
|
1059
|
-
create_project:
|
|
1100
|
+
approve: z7.object({ proposal_id: UlidSchema, reason: z7.string().optional() }),
|
|
1101
|
+
reject: z7.object({ proposal_id: UlidSchema, reason: z7.string().min(1) }),
|
|
1102
|
+
create_project: z7.object({
|
|
1060
1103
|
slug: NameSchema,
|
|
1061
|
-
name:
|
|
1062
|
-
repo:
|
|
1104
|
+
name: z7.string().min(1).optional(),
|
|
1105
|
+
repo: z7.string().min(1).nullable().default(null),
|
|
1063
1106
|
default_branch: DefaultBranchSchema.default("main"),
|
|
1064
1107
|
on_done: CompletionEffectSchema.optional()
|
|
1065
1108
|
}),
|
|
1066
|
-
join_project:
|
|
1067
|
-
leave_project:
|
|
1068
|
-
write_knowledge:
|
|
1109
|
+
join_project: z7.object({ project: NameSchema, agent: NameSchema.optional() }),
|
|
1110
|
+
leave_project: z7.object({ project: NameSchema, agent: NameSchema.optional() }),
|
|
1111
|
+
write_knowledge: z7.object({
|
|
1069
1112
|
/** A project you belong to, or null for society knowledge, which the steward and the user curate. */
|
|
1070
1113
|
project: NameSchema.nullable().default(null),
|
|
1071
1114
|
topic: NameSchema,
|
|
1072
|
-
body:
|
|
1115
|
+
body: z7.string().min(1),
|
|
1073
1116
|
/** The `revision` in the frontmatter of the topic you read, 0 when it names none or for a new topic. */
|
|
1074
|
-
revision:
|
|
1117
|
+
revision: z7.number().int().min(0)
|
|
1075
1118
|
}),
|
|
1076
|
-
remove_knowledge:
|
|
1119
|
+
remove_knowledge: z7.object({
|
|
1077
1120
|
/** As for write_knowledge: a project you belong to, or null for society knowledge. */
|
|
1078
1121
|
project: NameSchema.nullable().default(null),
|
|
1079
1122
|
topic: NameSchema
|
|
1080
1123
|
}),
|
|
1081
|
-
plan_task:
|
|
1124
|
+
plan_task: z7.object({
|
|
1082
1125
|
task_id: UlidSchema,
|
|
1083
|
-
stages:
|
|
1126
|
+
stages: z7.array(PlanEditStageSchema),
|
|
1084
1127
|
on_done: CompletionEffectSchema.optional()
|
|
1085
1128
|
}),
|
|
1086
1129
|
/** The note is posted to the task's thread as the caller, marked with the step. */
|
|
1087
|
-
advance_task:
|
|
1088
|
-
configure_project:
|
|
1130
|
+
advance_task: z7.object({ task_id: UlidSchema, note: z7.string().min(1).optional() }),
|
|
1131
|
+
configure_project: z7.object({
|
|
1089
1132
|
project: NameSchema,
|
|
1090
1133
|
on_done: CompletionEffectSchema.optional(),
|
|
1091
1134
|
/** A new display name; the slug, which everything refers to the project by, never changes. */
|
|
1092
|
-
name:
|
|
1135
|
+
name: z7.string().trim().min(1).max(120).optional(),
|
|
1093
1136
|
/** The branch tasks start from and land on from now; tasks in play keep their branches. */
|
|
1094
1137
|
default_branch: DefaultBranchSchema.optional()
|
|
1095
1138
|
}),
|
|
1096
|
-
archive_project:
|
|
1097
|
-
create_channel:
|
|
1139
|
+
archive_project: z7.object({ project: NameSchema, reason: z7.string().min(1) }),
|
|
1140
|
+
create_channel: z7.object({
|
|
1098
1141
|
/** A project, or null for a channel of the society. */
|
|
1099
1142
|
project: NameSchema.nullable().default(null),
|
|
1100
1143
|
name: NameSchema,
|
|
1101
|
-
purpose:
|
|
1144
|
+
purpose: z7.string().min(1)
|
|
1102
1145
|
}),
|
|
1103
|
-
archive_channel:
|
|
1104
|
-
update_dashboard:
|
|
1146
|
+
archive_channel: z7.object({ channel: ChannelRefSchema, reason: z7.string().min(1) }),
|
|
1147
|
+
update_dashboard: z7.object({
|
|
1105
1148
|
project: NameSchema,
|
|
1106
1149
|
/** The whole dashboard as markdown; the board keeps its frontmatter. */
|
|
1107
|
-
body:
|
|
1150
|
+
body: z7.string().min(1),
|
|
1108
1151
|
/** The `revision` in the frontmatter of the dashboard you read, 0 when it names none. */
|
|
1109
|
-
revision:
|
|
1152
|
+
revision: z7.number().int().min(0)
|
|
1110
1153
|
}),
|
|
1111
|
-
create_cron:
|
|
1154
|
+
create_cron: z7.object({
|
|
1112
1155
|
title: CronTitleSchema,
|
|
1113
1156
|
/** What to do when it fires; quoted in the prompt of every turn it starts. */
|
|
1114
1157
|
note: CronNoteSchema,
|
|
@@ -1127,10 +1170,10 @@ var VerbInputs = {
|
|
|
1127
1170
|
/** An open channel of that place other than general, whose conversation it fires in. */
|
|
1128
1171
|
channel: NameSchema.optional()
|
|
1129
1172
|
}),
|
|
1130
|
-
update_cron:
|
|
1173
|
+
update_cron: z7.object({
|
|
1131
1174
|
cron_id: UlidSchema,
|
|
1132
1175
|
/** Pause it, or resume it, which starts its schedule afresh from now. */
|
|
1133
|
-
paused:
|
|
1176
|
+
paused: z7.boolean().optional(),
|
|
1134
1177
|
title: CronTitleSchema.optional(),
|
|
1135
1178
|
note: CronNoteSchema.optional(),
|
|
1136
1179
|
/** A new expression, or `at` for one time; either starts the schedule afresh. */
|
|
@@ -1139,12 +1182,16 @@ var VerbInputs = {
|
|
|
1139
1182
|
timezone: TimeZoneSchema.nullable().optional(),
|
|
1140
1183
|
at: MomentSchema.optional()
|
|
1141
1184
|
}),
|
|
1142
|
-
remove_cron:
|
|
1143
|
-
remove_skill:
|
|
1185
|
+
remove_cron: z7.object({ cron_id: UlidSchema, reason: z7.string().trim().min(1) }),
|
|
1186
|
+
remove_skill: z7.object({ name: NameSchema, reason: z7.string().trim().min(1) }),
|
|
1187
|
+
upload_image: z7.object({
|
|
1188
|
+
/** The image file on the machine your turn runs on: absolute, or relative to your working directory. */
|
|
1189
|
+
path: ImagePathSchema
|
|
1190
|
+
})
|
|
1144
1191
|
};
|
|
1145
1192
|
|
|
1146
1193
|
// packages/shared/dist/events.js
|
|
1147
|
-
import { z as
|
|
1194
|
+
import { z as z8 } from "zod";
|
|
1148
1195
|
var BOARD_EVENT_TYPES = [
|
|
1149
1196
|
"society.initialized",
|
|
1150
1197
|
"project.added",
|
|
@@ -1208,21 +1255,22 @@ var BOARD_EVENT_TYPES = [
|
|
|
1208
1255
|
"cron.updated",
|
|
1209
1256
|
"cron.fired",
|
|
1210
1257
|
"cron.skipped",
|
|
1211
|
-
"cron.ended"
|
|
1258
|
+
"cron.ended",
|
|
1259
|
+
"image.uploaded"
|
|
1212
1260
|
];
|
|
1213
|
-
var BoardEventTypeSchema =
|
|
1214
|
-
var BoardEventSchema =
|
|
1261
|
+
var BoardEventTypeSchema = z8.enum(BOARD_EVENT_TYPES);
|
|
1262
|
+
var BoardEventSchema = z8.object({
|
|
1215
1263
|
id: UlidSchema,
|
|
1216
1264
|
ts: IsoDateTimeSchema,
|
|
1217
1265
|
type: BoardEventTypeSchema,
|
|
1218
1266
|
actor: NameSchema,
|
|
1219
|
-
payload:
|
|
1267
|
+
payload: z8.record(z8.string(), z8.unknown())
|
|
1220
1268
|
});
|
|
1221
|
-
var UsageSchema =
|
|
1222
|
-
inputTokens:
|
|
1223
|
-
outputTokens:
|
|
1224
|
-
cacheReadTokens:
|
|
1225
|
-
cacheWriteTokens:
|
|
1269
|
+
var UsageSchema = z8.object({
|
|
1270
|
+
inputTokens: z8.number().int().nonnegative(),
|
|
1271
|
+
outputTokens: z8.number().int().nonnegative(),
|
|
1272
|
+
cacheReadTokens: z8.number().int().nonnegative().default(0),
|
|
1273
|
+
cacheWriteTokens: z8.number().int().nonnegative().default(0)
|
|
1226
1274
|
});
|
|
1227
1275
|
function addUsage(a, b) {
|
|
1228
1276
|
return {
|
|
@@ -1232,14 +1280,14 @@ function addUsage(a, b) {
|
|
|
1232
1280
|
cacheWriteTokens: a.cacheWriteTokens + b.cacheWriteTokens
|
|
1233
1281
|
};
|
|
1234
1282
|
}
|
|
1235
|
-
var TurnStatusSchema =
|
|
1236
|
-
summary:
|
|
1237
|
-
claimsHeld:
|
|
1238
|
-
blockedOn:
|
|
1239
|
-
needsUserDecision:
|
|
1240
|
-
memoryUpdated:
|
|
1241
|
-
});
|
|
1242
|
-
var TurnExitReasonSchema =
|
|
1283
|
+
var TurnStatusSchema = z8.object({
|
|
1284
|
+
summary: z8.string().min(1),
|
|
1285
|
+
claimsHeld: z8.array(UlidSchema).default([]),
|
|
1286
|
+
blockedOn: z8.array(z8.string()).default([]),
|
|
1287
|
+
needsUserDecision: z8.boolean().default(false),
|
|
1288
|
+
memoryUpdated: z8.boolean().default(false)
|
|
1289
|
+
});
|
|
1290
|
+
var TurnExitReasonSchema = z8.enum([
|
|
1243
1291
|
"completed",
|
|
1244
1292
|
"timeout",
|
|
1245
1293
|
"error",
|
|
@@ -1247,50 +1295,51 @@ var TurnExitReasonSchema = z7.enum([
|
|
|
1247
1295
|
"blocked",
|
|
1248
1296
|
"stopped"
|
|
1249
1297
|
]);
|
|
1250
|
-
var
|
|
1251
|
-
|
|
1252
|
-
|
|
1298
|
+
var ResumeFailureSchema = z8.enum(["lost", "retry"]);
|
|
1299
|
+
var AgentEventSchema = z8.discriminatedUnion("type", [
|
|
1300
|
+
z8.object({
|
|
1301
|
+
type: z8.literal("turn_started"),
|
|
1253
1302
|
agent: NameSchema,
|
|
1254
|
-
session:
|
|
1303
|
+
session: z8.string(),
|
|
1255
1304
|
runner: NameSchema,
|
|
1256
1305
|
/** The model the CLI reports running, when it reports one. */
|
|
1257
|
-
model:
|
|
1306
|
+
model: z8.string().optional()
|
|
1258
1307
|
}),
|
|
1259
1308
|
/**
|
|
1260
1309
|
* The CLI took a steer into the running turn. Adapters name the steer; the server adds the
|
|
1261
1310
|
* messages it delivered and the text the CLI was given.
|
|
1262
1311
|
*/
|
|
1263
|
-
|
|
1264
|
-
type:
|
|
1265
|
-
steer:
|
|
1266
|
-
messages:
|
|
1267
|
-
text:
|
|
1312
|
+
z8.object({
|
|
1313
|
+
type: z8.literal("steered"),
|
|
1314
|
+
steer: z8.string().min(1),
|
|
1315
|
+
messages: z8.array(UlidSchema).optional(),
|
|
1316
|
+
text: z8.string().optional()
|
|
1268
1317
|
}),
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
type:
|
|
1273
|
-
name:
|
|
1274
|
-
ok:
|
|
1318
|
+
z8.object({ type: z8.literal("text"), delta: z8.string() }),
|
|
1319
|
+
z8.object({ type: z8.literal("tool_call"), name: z8.string(), input: z8.unknown() }),
|
|
1320
|
+
z8.object({
|
|
1321
|
+
type: z8.literal("tool_result"),
|
|
1322
|
+
name: z8.string(),
|
|
1323
|
+
ok: z8.boolean(),
|
|
1275
1324
|
/** What the tool returned, cut to `OUTPUT_LIMIT` characters by `capOutput`. */
|
|
1276
|
-
output:
|
|
1325
|
+
output: z8.string().optional()
|
|
1277
1326
|
}),
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
type:
|
|
1327
|
+
z8.object({ type: z8.literal("approval_requested"), kind: z8.string(), detail: z8.unknown() }),
|
|
1328
|
+
z8.object({
|
|
1329
|
+
type: z8.literal("turn_completed"),
|
|
1281
1330
|
usage: UsageSchema,
|
|
1282
|
-
costUsd:
|
|
1331
|
+
costUsd: z8.number().nonnegative(),
|
|
1283
1332
|
status: TurnStatusSchema.nullable(),
|
|
1284
1333
|
exitReason: TurnExitReasonSchema
|
|
1285
1334
|
}),
|
|
1286
|
-
|
|
1335
|
+
z8.object({ type: z8.literal("error"), message: z8.string() })
|
|
1287
1336
|
]);
|
|
1288
|
-
var TranscriptEntrySchema =
|
|
1337
|
+
var TranscriptEntrySchema = z8.object({
|
|
1289
1338
|
ts: IsoDateTimeSchema,
|
|
1290
1339
|
event: AgentEventSchema
|
|
1291
1340
|
});
|
|
1292
|
-
var LiveTurnEventSchema =
|
|
1293
|
-
seq:
|
|
1341
|
+
var LiveTurnEventSchema = z8.object({
|
|
1342
|
+
seq: z8.number().int().positive(),
|
|
1294
1343
|
ts: IsoDateTimeSchema,
|
|
1295
1344
|
agent: NameSchema,
|
|
1296
1345
|
project: NameSchema,
|
|
@@ -1298,243 +1347,280 @@ var LiveTurnEventSchema = z7.object({
|
|
|
1298
1347
|
thread: UlidSchema.optional(),
|
|
1299
1348
|
/** The channel, never general, whose conversation the turn is in. */
|
|
1300
1349
|
channel: NameSchema.optional(),
|
|
1350
|
+
/** The project whose channels' conversation the turn is in, for a turn in the society scope. */
|
|
1351
|
+
place: NameSchema.optional(),
|
|
1301
1352
|
event: AgentEventSchema
|
|
1302
1353
|
});
|
|
1303
1354
|
|
|
1304
1355
|
// packages/shared/dist/turns.js
|
|
1305
|
-
import { z as
|
|
1356
|
+
import { z as z10 } from "zod";
|
|
1306
1357
|
|
|
1307
1358
|
// packages/shared/dist/runner.js
|
|
1308
|
-
import { z as
|
|
1359
|
+
import { z as z9 } from "zod";
|
|
1309
1360
|
var HOME_SCRATCH = "scratch";
|
|
1310
1361
|
var HOME_FILE_LIMIT_BYTES = 8 * 1024 * 1024;
|
|
1311
|
-
var RunnerHelloSchema =
|
|
1312
|
-
protocol:
|
|
1313
|
-
version:
|
|
1362
|
+
var RunnerHelloSchema = z9.object({
|
|
1363
|
+
protocol: z9.number().int().positive(),
|
|
1364
|
+
version: z9.string().min(1),
|
|
1314
1365
|
os: RunnerOsSchema,
|
|
1315
|
-
clis:
|
|
1366
|
+
clis: z9.array(CliKindSchema),
|
|
1316
1367
|
/** The CLIs whose adapter can keep a session warm between turns. */
|
|
1317
|
-
residentClis:
|
|
1368
|
+
residentClis: z9.array(CliKindSchema).default([]),
|
|
1318
1369
|
/** The CLIs whose adapter can take input into a running turn; the server steers only these. */
|
|
1319
|
-
steerableClis:
|
|
1370
|
+
steerableClis: z9.array(CliKindSchema).default([]),
|
|
1320
1371
|
/** The CLIs whose adapter can stop a running turn; the server stops only these. */
|
|
1321
|
-
stoppableClis:
|
|
1372
|
+
stoppableClis: z9.array(CliKindSchema).default([]),
|
|
1322
1373
|
/** Whether the runner answers file reads on its projects' branches; one from before never does. */
|
|
1323
|
-
branchReads:
|
|
1374
|
+
branchReads: z9.boolean().default(false),
|
|
1324
1375
|
/** Whether the runner lists what a branch changed; one from before never does. */
|
|
1325
|
-
branchChanges:
|
|
1376
|
+
branchChanges: z9.boolean().default(false),
|
|
1326
1377
|
/** Whether the runner answers a read of a CLI's quota; one from before never does. */
|
|
1327
|
-
quotaReads:
|
|
1328
|
-
|
|
1378
|
+
quotaReads: z9.boolean().default(false),
|
|
1379
|
+
/** Whether the runner reads an image a turn running there uploads; one from before never does. */
|
|
1380
|
+
imageReads: z9.boolean().default(false),
|
|
1381
|
+
capabilities: z9.array(z9.string()).default([]),
|
|
1329
1382
|
/**
|
|
1330
1383
|
* What the runner's account lacks of the git identity citizens' own commits carry, checked
|
|
1331
1384
|
* before each hello outside any repository; empty when it has both. Absent from a runner from
|
|
1332
1385
|
* before the check, which no default fills in, so the board marks only what a runner reported.
|
|
1333
1386
|
*/
|
|
1334
|
-
gitIdentityMissing:
|
|
1387
|
+
gitIdentityMissing: z9.array(GitIdentityPartSchema).optional(),
|
|
1335
1388
|
/** Turns the runner runs at once, or null for no limit. */
|
|
1336
|
-
slots:
|
|
1389
|
+
slots: z9.number().int().positive().nullable(),
|
|
1337
1390
|
/** Turns the runner is still running, when it registers again; the server fails the rest of its turns. */
|
|
1338
|
-
turns:
|
|
1391
|
+
turns: z9.array(UlidSchema).default([])
|
|
1339
1392
|
});
|
|
1340
|
-
var RunnerWelcomeSchema =
|
|
1341
|
-
var RunnerEnrollRequestSchema =
|
|
1342
|
-
protocol:
|
|
1343
|
-
version:
|
|
1344
|
-
hostname:
|
|
1393
|
+
var RunnerWelcomeSchema = z9.object({ name: NameSchema, version: z9.string() });
|
|
1394
|
+
var RunnerEnrollRequestSchema = z9.object({
|
|
1395
|
+
protocol: z9.number().int().positive(),
|
|
1396
|
+
version: z9.string().min(1).max(64),
|
|
1397
|
+
hostname: z9.string().min(1).max(253),
|
|
1345
1398
|
os: RunnerOsSchema,
|
|
1346
|
-
clis:
|
|
1347
|
-
capabilities:
|
|
1399
|
+
clis: z9.array(CliKindSchema).max(8),
|
|
1400
|
+
capabilities: z9.array(z9.string().min(1).max(64)).max(32).default([])
|
|
1348
1401
|
});
|
|
1349
|
-
var RunnerEnrollmentSchema =
|
|
1350
|
-
deviceCode:
|
|
1351
|
-
userCode:
|
|
1402
|
+
var RunnerEnrollmentSchema = z9.object({
|
|
1403
|
+
deviceCode: z9.string().min(1),
|
|
1404
|
+
userCode: z9.string().min(1),
|
|
1352
1405
|
expiresAt: IsoDateTimeSchema,
|
|
1353
|
-
intervalMs:
|
|
1406
|
+
intervalMs: z9.number().int().positive()
|
|
1354
1407
|
});
|
|
1355
|
-
var RunnerEnrollPollSchema =
|
|
1356
|
-
var RunnerEnrollStatusSchema =
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1408
|
+
var RunnerEnrollPollSchema = z9.object({ deviceCode: z9.string().min(1) });
|
|
1409
|
+
var RunnerEnrollStatusSchema = z9.discriminatedUnion("status", [
|
|
1410
|
+
z9.object({ status: z9.literal("pending") }),
|
|
1411
|
+
z9.object({ status: z9.literal("approved"), name: NameSchema, token: z9.string().min(1) }),
|
|
1412
|
+
z9.object({ status: z9.literal("denied") })
|
|
1360
1413
|
]);
|
|
1361
|
-
var PendingEnrollmentSchema =
|
|
1362
|
-
userCode:
|
|
1363
|
-
hostname:
|
|
1414
|
+
var PendingEnrollmentSchema = z9.object({
|
|
1415
|
+
userCode: z9.string().min(1),
|
|
1416
|
+
hostname: z9.string(),
|
|
1364
1417
|
os: RunnerOsSchema,
|
|
1365
|
-
clis:
|
|
1366
|
-
capabilities:
|
|
1367
|
-
version:
|
|
1418
|
+
clis: z9.array(CliKindSchema),
|
|
1419
|
+
capabilities: z9.array(z9.string()),
|
|
1420
|
+
version: z9.string(),
|
|
1368
1421
|
requestedAt: IsoDateTimeSchema,
|
|
1369
1422
|
expiresAt: IsoDateTimeSchema
|
|
1370
1423
|
});
|
|
1371
|
-
var ProjectRepoSchema =
|
|
1424
|
+
var ProjectRepoSchema = z9.object({
|
|
1372
1425
|
slug: NameSchema,
|
|
1373
|
-
origin:
|
|
1374
|
-
defaultBranch:
|
|
1426
|
+
origin: z9.string().nullable(),
|
|
1427
|
+
defaultBranch: z9.string().min(1),
|
|
1375
1428
|
/**
|
|
1376
1429
|
* The board lands finished tasks on the default branch, itself or through their pull requests,
|
|
1377
1430
|
* so the runner lets nothing else move it.
|
|
1378
1431
|
*/
|
|
1379
|
-
boardLands:
|
|
1432
|
+
boardLands: z9.boolean()
|
|
1380
1433
|
});
|
|
1381
|
-
var TurnWorkspaceSchema =
|
|
1382
|
-
|
|
1383
|
-
kind:
|
|
1434
|
+
var TurnWorkspaceSchema = z9.discriminatedUnion("kind", [
|
|
1435
|
+
z9.object({
|
|
1436
|
+
kind: z9.literal("home"),
|
|
1384
1437
|
thread: UlidSchema.optional(),
|
|
1385
|
-
channel: NameSchema.optional()
|
|
1438
|
+
channel: NameSchema.optional(),
|
|
1439
|
+
place: NameSchema.optional()
|
|
1386
1440
|
}),
|
|
1387
|
-
|
|
1388
|
-
kind:
|
|
1441
|
+
z9.object({
|
|
1442
|
+
kind: z9.literal("project"),
|
|
1389
1443
|
repo: ProjectRepoSchema,
|
|
1390
|
-
branches:
|
|
1444
|
+
branches: z9.array(z9.string().min(1)),
|
|
1391
1445
|
thread: UlidSchema.optional(),
|
|
1392
1446
|
channel: NameSchema.optional()
|
|
1393
1447
|
}),
|
|
1394
|
-
|
|
1448
|
+
z9.object({
|
|
1449
|
+
kind: z9.literal("task"),
|
|
1450
|
+
repo: ProjectRepoSchema,
|
|
1451
|
+
taskId: UlidSchema,
|
|
1452
|
+
/**
|
|
1453
|
+
* The citizen holding the task's current stage, when someone does: a turn of anyone else
|
|
1454
|
+
* works detached at the branch's tip, so the holder's turns find the branch free.
|
|
1455
|
+
*/
|
|
1456
|
+
holder: NameSchema.optional()
|
|
1457
|
+
})
|
|
1395
1458
|
]);
|
|
1396
|
-
var TurnJobSchema =
|
|
1459
|
+
var TurnJobSchema = z9.object({
|
|
1397
1460
|
turnId: UlidSchema,
|
|
1398
1461
|
agent: NameSchema,
|
|
1399
1462
|
role: NameSchema,
|
|
1400
1463
|
cli: CliKindSchema,
|
|
1401
|
-
model:
|
|
1464
|
+
model: z9.string().optional(),
|
|
1402
1465
|
/** The reasoning effort set on the agent; absent for the model's own default. */
|
|
1403
|
-
effort:
|
|
1466
|
+
effort: z9.string().optional(),
|
|
1404
1467
|
/** A project slug, or the society scope. */
|
|
1405
1468
|
scope: NameSchema,
|
|
1406
1469
|
/** The thread whose conversation the turn is in; absent for the home conversation. */
|
|
1407
1470
|
thread: UlidSchema.optional(),
|
|
1408
1471
|
/** The channel, never general, whose conversation the turn is in. */
|
|
1409
1472
|
channel: NameSchema.optional(),
|
|
1473
|
+
/** The project whose channels' conversation the turn is in, for a turn in the society scope. */
|
|
1474
|
+
place: NameSchema.optional(),
|
|
1410
1475
|
/**
|
|
1411
1476
|
* The session to resume, or null to start one: the CLI's adapter picks a fresh session's id, and
|
|
1412
1477
|
* the outcome reports it.
|
|
1413
1478
|
*/
|
|
1414
|
-
session:
|
|
1479
|
+
session: z9.string().min(1).nullable(),
|
|
1415
1480
|
/** The running total the session reported after its last turn, which a resumed session starts from. */
|
|
1416
|
-
costSoFarUsd:
|
|
1481
|
+
costSoFarUsd: z9.number().nonnegative(),
|
|
1417
1482
|
/** The rendered charter, norms, memory core, and skills index, with `PATH_TOKENS` for places. */
|
|
1418
|
-
instructions:
|
|
1419
|
-
prompt:
|
|
1420
|
-
mcp:
|
|
1421
|
-
limits:
|
|
1422
|
-
timeoutMs:
|
|
1423
|
-
maxTurns:
|
|
1483
|
+
instructions: z9.string(),
|
|
1484
|
+
prompt: z9.string(),
|
|
1485
|
+
mcp: z9.object({ url: z9.string().min(1), token: z9.string().min(1) }),
|
|
1486
|
+
limits: z9.object({
|
|
1487
|
+
timeoutMs: z9.number().int().positive().nullable(),
|
|
1488
|
+
maxTurns: z9.number().int().positive().nullable()
|
|
1424
1489
|
}),
|
|
1425
1490
|
/** Present when the runner keeps the conversation's session warm between turns, under `key`. */
|
|
1426
|
-
resident:
|
|
1491
|
+
resident: z9.object({ key: z9.string().min(1), idleMs: z9.number().int().positive() }).optional(),
|
|
1427
1492
|
workspace: TurnWorkspaceSchema
|
|
1428
1493
|
});
|
|
1429
|
-
var TurnWorkSchema =
|
|
1430
|
-
var
|
|
1431
|
-
|
|
1494
|
+
var TurnWorkSchema = z9.object({ branch: z9.string().min(1), head: z9.string().min(1) });
|
|
1495
|
+
var StuckWorkSchema = z9.object({
|
|
1496
|
+
branch: z9.string().min(1),
|
|
1497
|
+
agent: NameSchema,
|
|
1498
|
+
commits: z9.array(z9.string()),
|
|
1499
|
+
total: z9.number().int().positive()
|
|
1500
|
+
});
|
|
1501
|
+
var TurnEventsSchema = z9.object({ entries: z9.array(TranscriptEntrySchema) });
|
|
1502
|
+
var TurnOutcomeSchema = z9.object({
|
|
1432
1503
|
exitReason: TurnExitReasonSchema,
|
|
1433
1504
|
status: TurnStatusSchema.nullable(),
|
|
1434
|
-
error:
|
|
1505
|
+
error: z9.string().nullable(),
|
|
1435
1506
|
usage: UsageSchema,
|
|
1436
|
-
costUsd:
|
|
1507
|
+
costUsd: z9.number().nonnegative(),
|
|
1437
1508
|
/** The session's running total after this turn, for CLIs that report one. */
|
|
1438
|
-
sessionCostUsd:
|
|
1439
|
-
/**
|
|
1440
|
-
|
|
1509
|
+
sessionCostUsd: z9.number().nonnegative().optional(),
|
|
1510
|
+
/**
|
|
1511
|
+
* The session the CLI used, which differs from the job's for CLIs that assign their own ids. A
|
|
1512
|
+
* turn whose CLI never started, because it failed or was stopped first, reports the job's own,
|
|
1513
|
+
* null in a conversation's first turn, so a session never started is not recorded.
|
|
1514
|
+
*/
|
|
1515
|
+
session: z9.string().min(1).nullable(),
|
|
1516
|
+
/**
|
|
1517
|
+
* Set when the CLI could not resume the job's session, which failed the turn before any model
|
|
1518
|
+
* call; the server then drops a `lost` session, and one that failed `retry` too often in a row. A
|
|
1519
|
+
* runner from before never sends it.
|
|
1520
|
+
*/
|
|
1521
|
+
resumeFailed: ResumeFailureSchema.optional(),
|
|
1441
1522
|
/** The model the CLI reported, when it reported one. */
|
|
1442
|
-
model:
|
|
1523
|
+
model: z9.string().nullable(),
|
|
1443
1524
|
work: TurnWorkSchema.nullable(),
|
|
1444
1525
|
/**
|
|
1445
1526
|
* Whether the turn's own workspace, a thread's, a channel's, or a task's, holds work on no
|
|
1446
1527
|
* branch after the hand-back: uncommitted changes, or commits no branch contains.
|
|
1447
1528
|
*/
|
|
1448
|
-
leftovers:
|
|
1529
|
+
leftovers: z9.boolean().default(false),
|
|
1530
|
+
/**
|
|
1531
|
+
* Commits the hand-back found its task's worktrees holding that no fast-forward of the task's
|
|
1532
|
+
* branch can take, each told once; a runner from before never sends it.
|
|
1533
|
+
*/
|
|
1534
|
+
stuck: z9.array(StuckWorkSchema).optional(),
|
|
1449
1535
|
/**
|
|
1450
1536
|
* The CLI's plan quota as the turn's stream last reported it, when it reported any; a runner from
|
|
1451
1537
|
* before never sends it.
|
|
1452
1538
|
*/
|
|
1453
1539
|
quota: CliQuotaSchema.optional()
|
|
1454
1540
|
});
|
|
1455
|
-
var TurnAckSchema =
|
|
1456
|
-
var LandRequestSchema =
|
|
1541
|
+
var TurnAckSchema = z9.object({ dropWorkspace: z9.boolean() });
|
|
1542
|
+
var LandRequestSchema = z9.object({
|
|
1457
1543
|
repo: ProjectRepoSchema,
|
|
1458
|
-
branch:
|
|
1544
|
+
branch: z9.string().min(1),
|
|
1459
1545
|
/**
|
|
1460
1546
|
* For a `ghpr` task, the pull request to merge in place of the branch, which must hold the
|
|
1461
1547
|
* branch's files, and the merge commit's subject and body, the pull request's title and an
|
|
1462
1548
|
* empty body when left out.
|
|
1463
1549
|
*/
|
|
1464
|
-
pullRequest:
|
|
1465
|
-
url:
|
|
1466
|
-
subject:
|
|
1467
|
-
body:
|
|
1550
|
+
pullRequest: z9.object({
|
|
1551
|
+
url: z9.string().min(1),
|
|
1552
|
+
subject: z9.string().min(1).optional(),
|
|
1553
|
+
body: z9.string().optional()
|
|
1468
1554
|
}).optional()
|
|
1469
1555
|
});
|
|
1470
|
-
var CloseRequestSchema =
|
|
1556
|
+
var CloseRequestSchema = z9.object({
|
|
1471
1557
|
repo: ProjectRepoSchema,
|
|
1472
|
-
url:
|
|
1473
|
-
comment:
|
|
1558
|
+
url: z9.string().min(1),
|
|
1559
|
+
comment: z9.string().min(1)
|
|
1474
1560
|
});
|
|
1475
|
-
var MergeOutcomeSchema =
|
|
1476
|
-
var ModelListSchema =
|
|
1561
|
+
var MergeOutcomeSchema = z9.object({ ok: z9.boolean(), detail: z9.string() });
|
|
1562
|
+
var ModelListSchema = z9.array(ModelOptionSchema);
|
|
1477
1563
|
var BRANCH_FILE_LIMIT_BYTES = 8 * 1024 * 1024;
|
|
1478
|
-
var BranchPathSchema =
|
|
1479
|
-
var BranchNameSchema =
|
|
1480
|
-
var BranchReadSchema =
|
|
1564
|
+
var BranchPathSchema = z9.string().max(4096).refine((value) => value === "" || value.split("/").every((segment) => segment !== "" && segment !== "." && segment !== ".." && !segment.includes("\0")), { message: "a path inside the branch, with no empty, `.`, or `..` segment" });
|
|
1565
|
+
var BranchNameSchema = z9.string().regex(/^[A-Za-z0-9][A-Za-z0-9._/-]*$/);
|
|
1566
|
+
var BranchReadSchema = z9.object({
|
|
1481
1567
|
repo: ProjectRepoSchema,
|
|
1482
1568
|
branch: BranchNameSchema,
|
|
1483
1569
|
path: BranchPathSchema
|
|
1484
1570
|
});
|
|
1485
|
-
var BranchCommitSchema =
|
|
1486
|
-
id:
|
|
1571
|
+
var BranchCommitSchema = z9.object({
|
|
1572
|
+
id: z9.string().min(1),
|
|
1487
1573
|
at: IsoDateTimeSchema,
|
|
1488
|
-
author:
|
|
1574
|
+
author: z9.string()
|
|
1489
1575
|
});
|
|
1490
|
-
var BranchFileSchema =
|
|
1491
|
-
|
|
1492
|
-
kind:
|
|
1576
|
+
var BranchFileSchema = z9.discriminatedUnion("kind", [
|
|
1577
|
+
z9.object({
|
|
1578
|
+
kind: z9.literal("file"),
|
|
1493
1579
|
path: BranchPathSchema,
|
|
1494
1580
|
commit: BranchCommitSchema,
|
|
1495
|
-
size:
|
|
1496
|
-
content:
|
|
1581
|
+
size: z9.number().int().nonnegative(),
|
|
1582
|
+
content: z9.string().nullable()
|
|
1497
1583
|
}),
|
|
1498
|
-
|
|
1499
|
-
kind:
|
|
1584
|
+
z9.object({
|
|
1585
|
+
kind: z9.literal("dir"),
|
|
1500
1586
|
path: BranchPathSchema,
|
|
1501
1587
|
commit: BranchCommitSchema,
|
|
1502
|
-
entries:
|
|
1503
|
-
name:
|
|
1504
|
-
kind:
|
|
1505
|
-
size:
|
|
1588
|
+
entries: z9.array(z9.object({
|
|
1589
|
+
name: z9.string().min(1),
|
|
1590
|
+
kind: z9.enum(["file", "dir"]),
|
|
1591
|
+
size: z9.number().int().nonnegative().nullable()
|
|
1506
1592
|
}))
|
|
1507
1593
|
})
|
|
1508
1594
|
]);
|
|
1509
|
-
var BranchChangesReadSchema =
|
|
1595
|
+
var BranchChangesReadSchema = z9.object({
|
|
1510
1596
|
repo: ProjectRepoSchema,
|
|
1511
1597
|
branch: BranchNameSchema
|
|
1512
1598
|
});
|
|
1513
|
-
var BranchFileChangeSchema =
|
|
1514
|
-
path:
|
|
1515
|
-
status:
|
|
1599
|
+
var BranchFileChangeSchema = z9.object({
|
|
1600
|
+
path: z9.string().min(1),
|
|
1601
|
+
status: z9.enum(["added", "modified", "deleted"]),
|
|
1516
1602
|
/** Lines added and removed, or null for a binary file. */
|
|
1517
|
-
added:
|
|
1518
|
-
removed:
|
|
1603
|
+
added: z9.number().int().nonnegative().nullable(),
|
|
1604
|
+
removed: z9.number().int().nonnegative().nullable(),
|
|
1519
1605
|
/** Absent when the file changed only through a merge of another branch. */
|
|
1520
|
-
lastChange:
|
|
1606
|
+
lastChange: z9.object({ author: z9.string(), at: IsoDateTimeSchema }).optional()
|
|
1521
1607
|
});
|
|
1522
|
-
var BranchChangesSchema =
|
|
1608
|
+
var BranchChangesSchema = z9.object({
|
|
1523
1609
|
head: BranchCommitSchema,
|
|
1524
|
-
files:
|
|
1525
|
-
total:
|
|
1610
|
+
files: z9.array(BranchFileChangeSchema),
|
|
1611
|
+
total: z9.number().int().nonnegative()
|
|
1526
1612
|
});
|
|
1527
|
-
var BranchDiffReadSchema =
|
|
1613
|
+
var BranchDiffReadSchema = z9.object({
|
|
1528
1614
|
repo: ProjectRepoSchema,
|
|
1529
1615
|
branch: BranchNameSchema,
|
|
1530
1616
|
path: BranchPathSchema.min(1)
|
|
1531
1617
|
});
|
|
1532
|
-
var BranchFileDiffSchema =
|
|
1618
|
+
var BranchFileDiffSchema = z9.object({
|
|
1533
1619
|
head: BranchCommitSchema,
|
|
1534
1620
|
path: BranchPathSchema,
|
|
1535
1621
|
status: BranchFileChangeSchema.shape.status,
|
|
1536
|
-
patch:
|
|
1537
|
-
truncated:
|
|
1622
|
+
patch: z9.string().nullable(),
|
|
1623
|
+
truncated: z9.boolean()
|
|
1538
1624
|
});
|
|
1539
1625
|
function patchHunks(patch, limit) {
|
|
1540
1626
|
if (/^Binary files .* differ$/m.test(patch)) {
|
|
@@ -1548,69 +1634,89 @@ function patchHunks(patch, limit) {
|
|
|
1548
1634
|
const cut = hunks.lastIndexOf("\n", limit);
|
|
1549
1635
|
return { patch: hunks.slice(0, cut === -1 ? limit : cut), truncated: true };
|
|
1550
1636
|
}
|
|
1551
|
-
var
|
|
1552
|
-
|
|
1553
|
-
|
|
1637
|
+
var TurnImageReadSchema = z9.object({
|
|
1638
|
+
turnId: UlidSchema,
|
|
1639
|
+
path: ImagePathSchema
|
|
1640
|
+
});
|
|
1641
|
+
var ImageRefusalSchema = z9.enum(["missing", "outside", "not_image", "too_big"]);
|
|
1642
|
+
var TurnImageSchema = z9.discriminatedUnion("kind", [
|
|
1643
|
+
z9.object({
|
|
1644
|
+
kind: z9.literal("image"),
|
|
1645
|
+
type: ImageTypeSchema,
|
|
1646
|
+
size: z9.number().int().positive(),
|
|
1647
|
+
content: z9.string()
|
|
1648
|
+
}),
|
|
1649
|
+
z9.object({ kind: z9.literal("refused"), why: ImageRefusalSchema, detail: z9.string() })
|
|
1554
1650
|
]);
|
|
1555
|
-
var
|
|
1651
|
+
var WorkspaceConversationSchema = z9.discriminatedUnion("kind", [
|
|
1652
|
+
z9.object({ kind: z9.literal("thread"), id: UlidSchema }),
|
|
1653
|
+
z9.object({ kind: z9.literal("channel"), scope: NameSchema, name: NameSchema }),
|
|
1654
|
+
z9.object({ kind: z9.literal("place"), project: NameSchema })
|
|
1655
|
+
]);
|
|
1656
|
+
var HeldWorkspaceSchema = z9.object({
|
|
1556
1657
|
agent: NameSchema,
|
|
1557
1658
|
scope: NameSchema,
|
|
1558
1659
|
conversation: WorkspaceConversationSchema
|
|
1559
1660
|
});
|
|
1560
|
-
var HeldWorkspacesSchema =
|
|
1561
|
-
var SweepResultSchema =
|
|
1562
|
-
var TurnSteerSchema =
|
|
1563
|
-
var RunnerMessageSchema =
|
|
1564
|
-
|
|
1565
|
-
|
|
1661
|
+
var HeldWorkspacesSchema = z9.object({ workspaces: z9.array(HeldWorkspaceSchema) });
|
|
1662
|
+
var SweepResultSchema = z9.object({ kept: z9.array(HeldWorkspaceSchema) });
|
|
1663
|
+
var TurnSteerSchema = z9.object({ id: z9.uuid(), text: z9.string().min(1) });
|
|
1664
|
+
var RunnerMessageSchema = z9.discriminatedUnion("type", [
|
|
1665
|
+
z9.object({ type: z9.literal("turn"), job: TurnJobSchema }),
|
|
1666
|
+
z9.object({ type: z9.literal("land"), request: z9.string().min(1), land: LandRequestSchema }),
|
|
1566
1667
|
/** Answered with a `MergeOutcome`. */
|
|
1567
|
-
|
|
1568
|
-
|
|
1668
|
+
z9.object({ type: z9.literal("close"), request: z9.string().min(1), close: CloseRequestSchema }),
|
|
1669
|
+
z9.object({ type: z9.literal("models"), request: z9.string().min(1), cli: CliKindSchema }),
|
|
1569
1670
|
/**
|
|
1570
1671
|
* Answered with a `CliQuota` read from the CLI without a turn, or null when its adapter cannot
|
|
1571
1672
|
* read one. Sent only to runners whose hello says `quotaReads`.
|
|
1572
1673
|
*/
|
|
1573
|
-
|
|
1674
|
+
z9.object({ type: z9.literal("quota"), request: z9.string().min(1), cli: CliKindSchema }),
|
|
1574
1675
|
/** Answered with a `BranchFile`, or null when the branch or the path does not exist. */
|
|
1575
|
-
|
|
1676
|
+
z9.object({ type: z9.literal("file"), request: z9.string().min(1), read: BranchReadSchema }),
|
|
1576
1677
|
/** Answered with `BranchChanges`, or null when the repository or the branch does not exist. */
|
|
1577
|
-
|
|
1578
|
-
type:
|
|
1579
|
-
request:
|
|
1678
|
+
z9.object({
|
|
1679
|
+
type: z9.literal("changes"),
|
|
1680
|
+
request: z9.string().min(1),
|
|
1580
1681
|
read: BranchChangesReadSchema
|
|
1581
1682
|
}),
|
|
1582
1683
|
/** Answered with a `BranchFileDiff`, or null when the branch does not exist or left the path alone. */
|
|
1583
|
-
|
|
1684
|
+
z9.object({ type: z9.literal("diff"), request: z9.string().min(1), read: BranchDiffReadSchema }),
|
|
1685
|
+
/**
|
|
1686
|
+
* Answered with a `TurnImage`, or null when the turn is not running there. Sent only to runners
|
|
1687
|
+
* whose hello says `imageReads`.
|
|
1688
|
+
*/
|
|
1689
|
+
z9.object({ type: z9.literal("image"), request: z9.string().min(1), read: TurnImageReadSchema }),
|
|
1584
1690
|
/** Answered with whether the CLI accepted it; that it took it comes as a `steered` event. */
|
|
1585
|
-
|
|
1586
|
-
type:
|
|
1587
|
-
request:
|
|
1691
|
+
z9.object({
|
|
1692
|
+
type: z9.literal("steer"),
|
|
1693
|
+
request: z9.string().min(1),
|
|
1588
1694
|
turnId: UlidSchema,
|
|
1589
1695
|
steer: TurnSteerSchema
|
|
1590
1696
|
}),
|
|
1591
1697
|
/** Answered with whether the turn was there to stop; it ends `stopped`. */
|
|
1592
|
-
|
|
1698
|
+
z9.object({ type: z9.literal("stop"), request: z9.string().min(1), turnId: UlidSchema }),
|
|
1593
1699
|
/**
|
|
1594
1700
|
* A conversation ended: the runner removes every citizen's workspace for it but those a turn
|
|
1595
1701
|
* still uses, and answers with a `SweepResult` naming those it kept for holding work on no branch.
|
|
1596
1702
|
*/
|
|
1597
|
-
|
|
1598
|
-
type:
|
|
1599
|
-
request:
|
|
1703
|
+
z9.object({
|
|
1704
|
+
type: z9.literal("sweep"),
|
|
1705
|
+
request: z9.string().min(1),
|
|
1600
1706
|
conversation: WorkspaceConversationSchema
|
|
1601
1707
|
})
|
|
1602
1708
|
]);
|
|
1603
|
-
var RunnerAnswerSchema =
|
|
1604
|
-
|
|
1605
|
-
|
|
1709
|
+
var RunnerAnswerSchema = z9.discriminatedUnion("ok", [
|
|
1710
|
+
z9.object({ ok: z9.literal(true), value: z9.unknown() }),
|
|
1711
|
+
z9.object({ ok: z9.literal(false), error: z9.string() })
|
|
1606
1712
|
]);
|
|
1607
|
-
var WarmSessionsSchema =
|
|
1608
|
-
var FileManifestSchema =
|
|
1713
|
+
var WarmSessionsSchema = z9.object({ warm: z9.array(z9.string()) });
|
|
1714
|
+
var FileManifestSchema = z9.object({ files: z9.record(z9.string(), z9.string()) });
|
|
1609
1715
|
function fileVersion(sha, executable) {
|
|
1610
1716
|
return executable ? `${sha}+x` : sha;
|
|
1611
1717
|
}
|
|
1612
|
-
var FileReadSchema =
|
|
1613
|
-
var FileContentsSchema =
|
|
1718
|
+
var FileReadSchema = z9.object({ paths: z9.array(z9.string()) });
|
|
1719
|
+
var FileContentsSchema = z9.object({ files: z9.record(z9.string(), z9.string()) });
|
|
1614
1720
|
function isSafeRelativePath(file) {
|
|
1615
1721
|
if (file.length === 0 || file.startsWith("/") || file.includes("\\") || file.includes("\0")) {
|
|
1616
1722
|
return false;
|
|
@@ -1619,7 +1725,7 @@ function isSafeRelativePath(file) {
|
|
|
1619
1725
|
}
|
|
1620
1726
|
|
|
1621
1727
|
// packages/shared/dist/turns.js
|
|
1622
|
-
var TriggerKindSchema =
|
|
1728
|
+
var TriggerKindSchema = z10.enum([
|
|
1623
1729
|
"mention",
|
|
1624
1730
|
"stage",
|
|
1625
1731
|
"task_done",
|
|
@@ -1633,11 +1739,11 @@ var TriggerKindSchema = z9.enum([
|
|
|
1633
1739
|
"closing",
|
|
1634
1740
|
"cron"
|
|
1635
1741
|
]);
|
|
1636
|
-
var TriggerSchema =
|
|
1742
|
+
var TriggerSchema = z10.object({
|
|
1637
1743
|
kind: TriggerKindSchema,
|
|
1638
1744
|
from: NameSchema.optional(),
|
|
1639
|
-
fromUser:
|
|
1640
|
-
reason:
|
|
1745
|
+
fromUser: z10.boolean().default(false),
|
|
1746
|
+
reason: z10.string().default(""),
|
|
1641
1747
|
taskId: UlidSchema.optional(),
|
|
1642
1748
|
messageId: UlidSchema.optional(),
|
|
1643
1749
|
/** The channel the waking message was posted in. */
|
|
@@ -1645,20 +1751,27 @@ var TriggerSchema = z9.object({
|
|
|
1645
1751
|
/** The cron that came due, for a `cron` wake. */
|
|
1646
1752
|
cronId: UlidSchema.optional()
|
|
1647
1753
|
});
|
|
1648
|
-
var TurnThreadSchema =
|
|
1649
|
-
var TurnDispatchSchema =
|
|
1754
|
+
var TurnThreadSchema = z10.object({ id: UlidSchema, task: z10.boolean() });
|
|
1755
|
+
var TurnDispatchSchema = z10.object({
|
|
1650
1756
|
agent: NameSchema,
|
|
1651
1757
|
project: NameSchema,
|
|
1652
1758
|
thread: TurnThreadSchema.optional(),
|
|
1653
1759
|
channel: NameSchema.optional(),
|
|
1760
|
+
place: NameSchema.optional(),
|
|
1654
1761
|
trigger: TriggerSchema,
|
|
1655
|
-
priority:
|
|
1656
|
-
onboarding:
|
|
1762
|
+
priority: z10.number().int().min(0).max(2),
|
|
1763
|
+
onboarding: z10.boolean().default(false),
|
|
1657
1764
|
/** Every cron that came due for this turn, whatever wake it merged into; its prompt quotes each. */
|
|
1658
|
-
crons:
|
|
1765
|
+
crons: z10.array(UlidSchema).optional()
|
|
1659
1766
|
});
|
|
1660
1767
|
function conversationPart(conversation) {
|
|
1661
|
-
|
|
1768
|
+
if (conversation.thread !== void 0) {
|
|
1769
|
+
return conversation.thread;
|
|
1770
|
+
}
|
|
1771
|
+
if (conversation.channel !== void 0) {
|
|
1772
|
+
return `#${conversation.channel}`;
|
|
1773
|
+
}
|
|
1774
|
+
return conversation.place === void 0 ? void 0 : `~${conversation.place}`;
|
|
1662
1775
|
}
|
|
1663
1776
|
function channelConversation(scope, channel) {
|
|
1664
1777
|
const at = channel.indexOf("/");
|
|
@@ -1666,7 +1779,11 @@ function channelConversation(scope, channel) {
|
|
|
1666
1779
|
const name = at < 0 ? channel : channel.slice(at + 1);
|
|
1667
1780
|
return place === scope && name !== "general" ? name : void 0;
|
|
1668
1781
|
}
|
|
1669
|
-
|
|
1782
|
+
function placeConversation(scope, channel) {
|
|
1783
|
+
const at = channel.indexOf("/");
|
|
1784
|
+
return scope === SOCIETY_SCOPE && at >= 0 ? channel.slice(0, at) : void 0;
|
|
1785
|
+
}
|
|
1786
|
+
var TurnRecordSchema = z10.object({
|
|
1670
1787
|
/** Assigned by the board when the turn begins; the turn's transcript is filed under it. */
|
|
1671
1788
|
id: UlidSchema.optional(),
|
|
1672
1789
|
agent: NameSchema,
|
|
@@ -1675,83 +1792,94 @@ var TurnRecordSchema = z9.object({
|
|
|
1675
1792
|
thread: UlidSchema.optional(),
|
|
1676
1793
|
/** The channel, never general, whose conversation the turn was in. */
|
|
1677
1794
|
channel: NameSchema.optional(),
|
|
1795
|
+
/** The project whose channels' conversation the turn was in, for a turn in the society scope. */
|
|
1796
|
+
place: NameSchema.optional(),
|
|
1678
1797
|
runner: NameSchema,
|
|
1679
1798
|
cli: CliKindSchema.nullable(),
|
|
1680
|
-
session:
|
|
1799
|
+
session: z10.string().nullable(),
|
|
1681
1800
|
trigger: TriggerSchema,
|
|
1682
1801
|
startedAt: IsoDateTimeSchema,
|
|
1683
1802
|
endedAt: IsoDateTimeSchema.nullable(),
|
|
1684
1803
|
exitReason: TurnExitReasonSchema.nullable(),
|
|
1685
1804
|
status: TurnStatusSchema.nullable(),
|
|
1686
|
-
error:
|
|
1805
|
+
error: z10.string().nullable(),
|
|
1687
1806
|
usage: UsageSchema.nullable(),
|
|
1688
|
-
costUsd:
|
|
1807
|
+
costUsd: z10.number().nonnegative().default(0),
|
|
1689
1808
|
/**
|
|
1690
1809
|
* The running total the CLI reported for the session after this turn, when it reports one. A
|
|
1691
1810
|
* resumed session starts from it, so the next turn's own cost is measured from here.
|
|
1692
1811
|
*/
|
|
1693
|
-
sessionCostUsd:
|
|
1694
|
-
toolCalls:
|
|
1812
|
+
sessionCostUsd: z10.number().nonnegative().optional(),
|
|
1813
|
+
toolCalls: z10.number().int().nonnegative().default(0),
|
|
1695
1814
|
/** The model the CLI reported for this turn, else the model configured for the agent, else null. */
|
|
1696
|
-
model:
|
|
1815
|
+
model: z10.string().nullable().default(null),
|
|
1697
1816
|
/** The task branch the turn left work on, with its commit, as the runner reported it. */
|
|
1698
1817
|
work: TurnWorkSchema.optional(),
|
|
1699
1818
|
/** Who stopped the turn, for one that ended `stopped`. */
|
|
1700
1819
|
stoppedBy: NameSchema.optional(),
|
|
1820
|
+
/** Why the CLI could not resume the turn's session, for a turn that failed so. */
|
|
1821
|
+
resumeFailed: ResumeFailureSchema.optional(),
|
|
1822
|
+
/** How many turns in a row, this one included, could not resume the session for a `retry`. */
|
|
1823
|
+
resumeRetries: z10.number().int().positive().optional(),
|
|
1824
|
+
/** Whether the server dropped the conversation's session after this turn, which the next starts anew. */
|
|
1825
|
+
sessionDropped: z10.boolean().optional(),
|
|
1701
1826
|
/** The crons whose fire started the turn, which record how it ended. */
|
|
1702
|
-
crons:
|
|
1827
|
+
crons: z10.array(UlidSchema).optional()
|
|
1703
1828
|
});
|
|
1704
|
-
var RunningTurnSchema =
|
|
1829
|
+
var RunningTurnSchema = z10.object({
|
|
1705
1830
|
turnId: UlidSchema,
|
|
1706
1831
|
agent: NameSchema,
|
|
1707
1832
|
scope: NameSchema,
|
|
1708
1833
|
thread: UlidSchema.optional(),
|
|
1709
1834
|
channel: NameSchema.optional(),
|
|
1835
|
+
place: NameSchema.optional(),
|
|
1710
1836
|
cli: CliKindSchema,
|
|
1711
1837
|
askedIn: ChannelRefSchema.optional(),
|
|
1712
|
-
steerable:
|
|
1713
|
-
stoppable:
|
|
1838
|
+
steerable: z10.boolean(),
|
|
1839
|
+
stoppable: z10.boolean()
|
|
1714
1840
|
});
|
|
1715
|
-
var SessionsFileSchema =
|
|
1716
|
-
claude:
|
|
1717
|
-
codex:
|
|
1841
|
+
var SessionsFileSchema = z10.object({
|
|
1842
|
+
claude: z10.string().optional(),
|
|
1843
|
+
codex: z10.string().optional(),
|
|
1718
1844
|
runner: NameSchema.optional()
|
|
1719
1845
|
});
|
|
1720
|
-
var WakeRequestSchema =
|
|
1846
|
+
var WakeRequestSchema = z10.object({
|
|
1721
1847
|
agent: NameSchema,
|
|
1722
1848
|
project: NameSchema,
|
|
1723
|
-
reason:
|
|
1849
|
+
reason: z10.string().default("manual wake"),
|
|
1724
1850
|
/** A manual wake, or a reflection turn requested ahead of the cadence. */
|
|
1725
|
-
kind:
|
|
1851
|
+
kind: z10.enum(["manual", "reflection"]).default("manual")
|
|
1726
1852
|
});
|
|
1727
|
-
var TurnHistoryEntrySchema =
|
|
1853
|
+
var TurnHistoryEntrySchema = z10.object({
|
|
1728
1854
|
/** The id of the event that ended the turn. */
|
|
1729
1855
|
id: UlidSchema,
|
|
1730
1856
|
/** The turn's own id, when it has a transcript to open; turns before transcripts have none. */
|
|
1731
1857
|
turnId: UlidSchema.optional(),
|
|
1732
1858
|
ts: IsoDateTimeSchema,
|
|
1733
|
-
outcome:
|
|
1859
|
+
outcome: z10.enum(["completed", "failed"]),
|
|
1734
1860
|
/** A project slug, or the society scope. */
|
|
1735
1861
|
project: NameSchema,
|
|
1736
1862
|
/** The thread whose conversation the turn was in; absent for the home conversation. */
|
|
1737
1863
|
thread: UlidSchema.optional(),
|
|
1738
1864
|
/** The channel, never general, whose conversation the turn was in. */
|
|
1739
1865
|
channel: NameSchema.optional(),
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1866
|
+
/** The project whose channels' conversation the turn was in, for a turn in the society scope. */
|
|
1867
|
+
place: NameSchema.optional(),
|
|
1868
|
+
trigger: z10.string(),
|
|
1869
|
+
exitReason: z10.string().nullable(),
|
|
1870
|
+
costUsd: z10.number(),
|
|
1743
1871
|
/** The tokens the turn used; absent for turns logged before turns recorded them. */
|
|
1744
1872
|
usage: UsageSchema.optional(),
|
|
1745
|
-
model:
|
|
1746
|
-
summary:
|
|
1747
|
-
error:
|
|
1873
|
+
model: z10.string().nullable(),
|
|
1874
|
+
summary: z10.string().nullable(),
|
|
1875
|
+
error: z10.string().nullable(),
|
|
1748
1876
|
/** When the log recorded the turn's start; absent when the start is missing from the log. */
|
|
1749
1877
|
startedAt: IsoDateTimeSchema.optional(),
|
|
1750
|
-
toolCalls:
|
|
1878
|
+
toolCalls: z10.number().int().nonnegative().optional()
|
|
1751
1879
|
});
|
|
1752
1880
|
|
|
1753
1881
|
// packages/shared/dist/ops.js
|
|
1754
|
-
import { z as
|
|
1882
|
+
import { z as z11 } from "zod";
|
|
1755
1883
|
var OPS_SIGNAL_KINDS = [
|
|
1756
1884
|
"waiting_stage",
|
|
1757
1885
|
"backlog",
|
|
@@ -1767,16 +1895,16 @@ var OPS_SIGNAL_KINDS = [
|
|
|
1767
1895
|
"stuck_workspace",
|
|
1768
1896
|
"cron_failing"
|
|
1769
1897
|
];
|
|
1770
|
-
var OpsSignalKindSchema =
|
|
1771
|
-
var OpsSignalSchema =
|
|
1898
|
+
var OpsSignalKindSchema = z11.enum(OPS_SIGNAL_KINDS);
|
|
1899
|
+
var OpsSignalSchema = z11.object({
|
|
1772
1900
|
kind: OpsSignalKindSchema,
|
|
1773
1901
|
/** Identifies the condition, for example `backlog:demo:engineer`, so a persisting one is not logged again every pass. */
|
|
1774
|
-
key:
|
|
1902
|
+
key: z11.string().min(1),
|
|
1775
1903
|
/** One line a reader can act on. */
|
|
1776
|
-
summary:
|
|
1904
|
+
summary: z11.string().min(1),
|
|
1777
1905
|
/** The counter or timer value behind the signal. */
|
|
1778
|
-
value:
|
|
1779
|
-
threshold:
|
|
1906
|
+
value: z11.number(),
|
|
1907
|
+
threshold: z11.number().optional(),
|
|
1780
1908
|
project: NameSchema.optional(),
|
|
1781
1909
|
agent: NameSchema.optional(),
|
|
1782
1910
|
role: NameSchema.optional(),
|
|
@@ -1784,8 +1912,8 @@ var OpsSignalSchema = z10.object({
|
|
|
1784
1912
|
});
|
|
1785
1913
|
|
|
1786
1914
|
// packages/shared/dist/metrics.js
|
|
1787
|
-
import { z as
|
|
1788
|
-
var MetricsWindowSchema =
|
|
1915
|
+
import { z as z12 } from "zod";
|
|
1916
|
+
var MetricsWindowSchema = z12.enum(["24h", "7d", "all"]);
|
|
1789
1917
|
var METRICS_WINDOW_MS = {
|
|
1790
1918
|
"24h": 24 * 36e5,
|
|
1791
1919
|
"7d": 7 * 24 * 36e5,
|
|
@@ -1796,9 +1924,9 @@ function isBoardAction(tool) {
|
|
|
1796
1924
|
const verb = /^mcp__board__(.+)$/.exec(tool)?.[1];
|
|
1797
1925
|
return verb !== void 0 && !READ_VERBS.some((read2) => read2 === verb);
|
|
1798
1926
|
}
|
|
1799
|
-
var CountSchema =
|
|
1800
|
-
var MsSchema =
|
|
1801
|
-
var MetricsSchema =
|
|
1927
|
+
var CountSchema = z12.number().int().nonnegative();
|
|
1928
|
+
var MsSchema = z12.number().nonnegative();
|
|
1929
|
+
var MetricsSchema = z12.object({
|
|
1802
1930
|
window: MetricsWindowSchema,
|
|
1803
1931
|
since: IsoDateTimeSchema.nullable(),
|
|
1804
1932
|
until: IsoDateTimeSchema,
|
|
@@ -1806,12 +1934,12 @@ var MetricsSchema = z11.object({
|
|
|
1806
1934
|
* Finished turns that changed nothing on the board. Reflections are left out, since they write
|
|
1807
1935
|
* memory, not the board; turns without a transcript are counted apart, their actions unknown.
|
|
1808
1936
|
*/
|
|
1809
|
-
idle:
|
|
1937
|
+
idle: z12.object({
|
|
1810
1938
|
turns: CountSchema,
|
|
1811
1939
|
idle: CountSchema,
|
|
1812
1940
|
unknown: CountSchema,
|
|
1813
|
-
byTrigger:
|
|
1814
|
-
byAgent:
|
|
1941
|
+
byTrigger: z12.array(z12.object({ trigger: z12.string(), turns: CountSchema, idle: CountSchema })),
|
|
1942
|
+
byAgent: z12.array(z12.object({
|
|
1815
1943
|
agent: NameSchema,
|
|
1816
1944
|
role: NameSchema.nullable(),
|
|
1817
1945
|
turns: CountSchema,
|
|
@@ -1819,33 +1947,33 @@ var MetricsSchema = z11.object({
|
|
|
1819
1947
|
}))
|
|
1820
1948
|
}),
|
|
1821
1949
|
/** Work sent back to an earlier stage: over the tasks finished in the window, and by where. */
|
|
1822
|
-
sentBack:
|
|
1950
|
+
sentBack: z12.object({
|
|
1823
1951
|
finished: CountSchema,
|
|
1824
1952
|
sendBacks: CountSchema,
|
|
1825
1953
|
tasksSentBack: CountSchema,
|
|
1826
|
-
byStage:
|
|
1827
|
-
bySender:
|
|
1954
|
+
byStage: z12.array(z12.object({ project: NameSchema, stage: z12.string(), count: CountSchema })),
|
|
1955
|
+
bySender: z12.array(z12.object({ agent: NameSchema, count: CountSchema }))
|
|
1828
1956
|
}),
|
|
1829
1957
|
/** Posts in the threads of the tasks finished in the window, the board's own notices left out. */
|
|
1830
|
-
messages:
|
|
1958
|
+
messages: z12.object({
|
|
1831
1959
|
finished: CountSchema,
|
|
1832
1960
|
messages: CountSchema,
|
|
1833
|
-
byProject:
|
|
1834
|
-
busiest:
|
|
1961
|
+
byProject: z12.array(z12.object({ project: NameSchema, finished: CountSchema, messages: CountSchema })),
|
|
1962
|
+
busiest: z12.array(z12.object({
|
|
1835
1963
|
taskId: UlidSchema,
|
|
1836
|
-
title:
|
|
1964
|
+
title: z12.string(),
|
|
1837
1965
|
project: NameSchema,
|
|
1838
1966
|
messages: CountSchema
|
|
1839
1967
|
}))
|
|
1840
1968
|
}),
|
|
1841
1969
|
/** Time from a mention to the start of the turn it woke, in the conversation it was made in. */
|
|
1842
|
-
latency:
|
|
1970
|
+
latency: z12.object({
|
|
1843
1971
|
mentions: CountSchema,
|
|
1844
1972
|
/** Mentions no turn has followed yet. */
|
|
1845
1973
|
unanswered: CountSchema,
|
|
1846
1974
|
medianMs: MsSchema.nullable(),
|
|
1847
1975
|
slowestMs: MsSchema.nullable(),
|
|
1848
|
-
byAgent:
|
|
1976
|
+
byAgent: z12.array(z12.object({
|
|
1849
1977
|
agent: NameSchema,
|
|
1850
1978
|
mentions: CountSchema,
|
|
1851
1979
|
medianMs: MsSchema,
|
|
@@ -1853,93 +1981,93 @@ var MetricsSchema = z11.object({
|
|
|
1853
1981
|
}))
|
|
1854
1982
|
}),
|
|
1855
1983
|
/** The user's decisions: proposals decided, questions answered, and stages passed or sent back. */
|
|
1856
|
-
decisions:
|
|
1984
|
+
decisions: z12.object({
|
|
1857
1985
|
total: CountSchema,
|
|
1858
|
-
byDay:
|
|
1859
|
-
day:
|
|
1986
|
+
byDay: z12.array(z12.object({
|
|
1987
|
+
day: z12.string(),
|
|
1860
1988
|
proposals: CountSchema,
|
|
1861
1989
|
answers: CountSchema,
|
|
1862
1990
|
stages: CountSchema
|
|
1863
1991
|
}))
|
|
1864
1992
|
}),
|
|
1865
1993
|
/** Tasks that needed a capability no connected runner offered, and whether they still do. */
|
|
1866
|
-
blocked:
|
|
1994
|
+
blocked: z12.array(z12.object({
|
|
1867
1995
|
taskId: UlidSchema,
|
|
1868
|
-
title:
|
|
1996
|
+
title: z12.string().nullable(),
|
|
1869
1997
|
project: NameSchema.nullable(),
|
|
1870
|
-
summary:
|
|
1998
|
+
summary: z12.string(),
|
|
1871
1999
|
firstAt: IsoDateTimeSchema,
|
|
1872
2000
|
lastAt: IsoDateTimeSchema,
|
|
1873
|
-
holds:
|
|
2001
|
+
holds: z12.boolean()
|
|
1874
2002
|
}))
|
|
1875
2003
|
});
|
|
1876
|
-
var CitizenTokensSchema =
|
|
2004
|
+
var CitizenTokensSchema = z12.object({
|
|
1877
2005
|
agent: NameSchema,
|
|
1878
2006
|
turns: CountSchema,
|
|
1879
2007
|
withUsage: CountSchema,
|
|
1880
2008
|
usage: UsageSchema.nullable()
|
|
1881
2009
|
});
|
|
1882
|
-
var TokenUseSchema =
|
|
2010
|
+
var TokenUseSchema = z12.object({
|
|
1883
2011
|
window: MetricsWindowSchema,
|
|
1884
2012
|
since: IsoDateTimeSchema.nullable(),
|
|
1885
2013
|
until: IsoDateTimeSchema,
|
|
1886
|
-
citizens:
|
|
2014
|
+
citizens: z12.array(CitizenTokensSchema)
|
|
1887
2015
|
});
|
|
1888
2016
|
|
|
1889
2017
|
// packages/shared/dist/config.js
|
|
1890
|
-
import { z as
|
|
1891
|
-
var LogLevelSchema =
|
|
1892
|
-
var GithubSignInSchema =
|
|
1893
|
-
clientId:
|
|
1894
|
-
clientSecret:
|
|
2018
|
+
import { z as z13 } from "zod";
|
|
2019
|
+
var LogLevelSchema = z13.enum(["trace", "debug", "info", "warn", "error", "fatal"]);
|
|
2020
|
+
var GithubSignInSchema = z13.object({
|
|
2021
|
+
clientId: z13.string().min(1),
|
|
2022
|
+
clientSecret: z13.string().min(1),
|
|
1895
2023
|
/** Logins, lowercased; GitHub logins are case-insensitive. */
|
|
1896
|
-
users:
|
|
2024
|
+
users: z13.array(z13.string().min(1)).min(1)
|
|
1897
2025
|
});
|
|
1898
|
-
var ServerConfigSchema =
|
|
1899
|
-
dataDir:
|
|
1900
|
-
host:
|
|
1901
|
-
port:
|
|
2026
|
+
var ServerConfigSchema = z13.object({
|
|
2027
|
+
dataDir: z13.string().min(1),
|
|
2028
|
+
host: z13.string().min(1),
|
|
2029
|
+
port: z13.number().int().positive().max(65535),
|
|
1902
2030
|
/**
|
|
1903
2031
|
* An address every runner's agents reach this server at, for example https://board.example, or
|
|
1904
2032
|
* null for each runner to use the address it reaches the server at itself.
|
|
1905
2033
|
*/
|
|
1906
|
-
publicUrl:
|
|
2034
|
+
publicUrl: z13.string().min(1).nullable(),
|
|
1907
2035
|
logLevel: LogLevelSchema,
|
|
1908
2036
|
/** Turns running at once across every runner, or null for no limit beyond each runner's own. */
|
|
1909
|
-
concurrency:
|
|
2037
|
+
concurrency: z13.number().int().positive().nullable(),
|
|
1910
2038
|
/** How long one turn may run before it is stopped, or null for no limit. */
|
|
1911
|
-
turnTimeoutMs:
|
|
2039
|
+
turnTimeoutMs: z13.number().int().positive().nullable(),
|
|
1912
2040
|
/** Rounds of tool calls one Claude turn may take, or null for no limit. */
|
|
1913
|
-
toolRounds:
|
|
2041
|
+
toolRounds: z13.number().int().positive().nullable(),
|
|
1914
2042
|
/** How long a resident session stays warm after its last turn. */
|
|
1915
|
-
residentIdleMs:
|
|
2043
|
+
residentIdleMs: z13.number().int().positive(),
|
|
1916
2044
|
/** Signing in to the board with GitHub, or null when only a token lets anyone in. */
|
|
1917
2045
|
github: GithubSignInSchema.nullable(),
|
|
1918
2046
|
/**
|
|
1919
2047
|
* Reverse proxies in front of the server whose X-Forwarded-For entries name the client, for the
|
|
1920
2048
|
* rate limits on what anyone may ask without a token: 0 when clients connect directly.
|
|
1921
2049
|
*/
|
|
1922
|
-
trustedProxies:
|
|
2050
|
+
trustedProxies: z13.number().int().min(0).max(16)
|
|
1923
2051
|
});
|
|
1924
|
-
var RunnerConfigSchema =
|
|
2052
|
+
var RunnerConfigSchema = z13.object({
|
|
1925
2053
|
/** The board server, for example http://127.0.0.1:4700. */
|
|
1926
|
-
serverUrl:
|
|
2054
|
+
serverUrl: z13.string().url(),
|
|
1927
2055
|
/**
|
|
1928
2056
|
* The runner's token, from `runner add`, or null for the one it saved when it was enrolled, or
|
|
1929
2057
|
* to enroll it.
|
|
1930
2058
|
*/
|
|
1931
|
-
token:
|
|
2059
|
+
token: z13.string().min(1).nullable(),
|
|
1932
2060
|
/** The runner's own data directory: the board mirror, agent homes, repositories, worktrees. */
|
|
1933
|
-
dataDir:
|
|
2061
|
+
dataDir: z13.string().min(1),
|
|
1934
2062
|
logLevel: LogLevelSchema,
|
|
1935
2063
|
/** Turns this machine runs at once, or null for no limit. */
|
|
1936
|
-
slots:
|
|
1937
|
-
clis:
|
|
1938
|
-
capabilities:
|
|
2064
|
+
slots: z13.number().int().positive().nullable(),
|
|
2065
|
+
clis: z13.array(CliKindSchema).min(1),
|
|
2066
|
+
capabilities: z13.array(z13.string())
|
|
1939
2067
|
});
|
|
1940
2068
|
|
|
1941
2069
|
// packages/shared/dist/version.js
|
|
1942
|
-
var STELLARIS_VERSION = "0.3.
|
|
2070
|
+
var STELLARIS_VERSION = "0.3.24";
|
|
1943
2071
|
|
|
1944
2072
|
// packages/shared/dist/mentions.js
|
|
1945
2073
|
import { parse, postprocess, preprocess } from "micromark";
|
|
@@ -2320,6 +2448,7 @@ var HOME_GITIGNORE = [
|
|
|
2320
2448
|
"/projects/*/last-turn.json",
|
|
2321
2449
|
"/projects/*/threads/",
|
|
2322
2450
|
"/projects/*/channels/",
|
|
2451
|
+
"/projects/*/places/",
|
|
2323
2452
|
`/${HOME_SCRATCH}/`,
|
|
2324
2453
|
"*.tmp",
|
|
2325
2454
|
"node_modules/",
|
|
@@ -2337,7 +2466,7 @@ var HOME_GITIGNORE = [
|
|
|
2337
2466
|
var IGNORE_FILE = `${HOME_GITIGNORE.join("\n")}
|
|
2338
2467
|
`;
|
|
2339
2468
|
var PRE_RECEIVE = `#!/bin/sh
|
|
2340
|
-
forbidden='^(agent\\.json|cursors\\.json|role\\.md|turns/|\\.claude/|\\.codex/|projects/[^/]+/(sessions[^/]*\\.json|last-turn\\.json|threads/|channels/))'
|
|
2469
|
+
forbidden='^(agent\\.json|cursors\\.json|role\\.md|turns/|\\.claude/|\\.codex/|projects/[^/]+/(sessions[^/]*\\.json|last-turn\\.json|threads/|channels/|places/))'
|
|
2341
2470
|
while read old new ref; do
|
|
2342
2471
|
if [ "$ref" != "refs/heads/main" ]; then
|
|
2343
2472
|
echo "a home takes pushes to main only" >&2
|
|
@@ -2657,32 +2786,33 @@ function postMentions(body) {
|
|
|
2657
2786
|
}
|
|
2658
2787
|
|
|
2659
2788
|
// packages/board-core/dist/metrics.js
|
|
2660
|
-
import { z as
|
|
2661
|
-
var TurnPayload =
|
|
2662
|
-
turnId:
|
|
2663
|
-
project:
|
|
2664
|
-
thread:
|
|
2665
|
-
channel:
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
var
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2789
|
+
import { z as z14 } from "zod";
|
|
2790
|
+
var TurnPayload = z14.object({
|
|
2791
|
+
turnId: z14.string().optional(),
|
|
2792
|
+
project: z14.string(),
|
|
2793
|
+
thread: z14.string().optional(),
|
|
2794
|
+
channel: z14.string().optional(),
|
|
2795
|
+
place: z14.string().optional(),
|
|
2796
|
+
trigger: z14.string()
|
|
2797
|
+
});
|
|
2798
|
+
var TaskPayload = z14.object({
|
|
2799
|
+
taskId: z14.string(),
|
|
2800
|
+
project: z14.string(),
|
|
2801
|
+
from: z14.string().optional()
|
|
2802
|
+
});
|
|
2803
|
+
var MessagePayload = z14.object({
|
|
2804
|
+
id: z14.string().optional(),
|
|
2805
|
+
channel: z14.string(),
|
|
2806
|
+
thread: z14.string().nullable().optional(),
|
|
2807
|
+
mentions: z14.array(z14.string()).default([])
|
|
2808
|
+
});
|
|
2809
|
+
var SteeredPayload = z14.object({ messages: z14.array(z14.string()) });
|
|
2810
|
+
var SignalPayload = z14.object({
|
|
2811
|
+
kind: z14.string(),
|
|
2812
|
+
key: z14.string(),
|
|
2813
|
+
summary: z14.string(),
|
|
2814
|
+
taskId: z14.string().optional(),
|
|
2815
|
+
project: z14.string().optional()
|
|
2686
2816
|
});
|
|
2687
2817
|
function median(values) {
|
|
2688
2818
|
const sorted = values.toSorted((a, b) => a - b);
|
|
@@ -2708,11 +2838,16 @@ function answers(mention, turn2) {
|
|
|
2708
2838
|
if (!mention.scopes.includes(turn2.project)) {
|
|
2709
2839
|
return false;
|
|
2710
2840
|
}
|
|
2711
|
-
const
|
|
2841
|
+
const topLevel = mention.thread === null;
|
|
2842
|
+
const own = topLevel ? channelConversation(turn2.project, mention.channel) : void 0;
|
|
2843
|
+
const place = topLevel ? placeConversation(turn2.project, mention.channel) : void 0;
|
|
2712
2844
|
if (turn2.channel !== null) {
|
|
2713
|
-
return
|
|
2845
|
+
return topLevel && own === turn2.channel;
|
|
2714
2846
|
}
|
|
2715
|
-
|
|
2847
|
+
if (turn2.place !== null) {
|
|
2848
|
+
return topLevel && place === turn2.place;
|
|
2849
|
+
}
|
|
2850
|
+
return topLevel && own === void 0 && place === void 0 || turn2.trigger === "mention";
|
|
2716
2851
|
}
|
|
2717
2852
|
function computeMetrics(input) {
|
|
2718
2853
|
const { events, now, window } = input;
|
|
@@ -2778,6 +2913,7 @@ function computeMetrics(input) {
|
|
|
2778
2913
|
at,
|
|
2779
2914
|
thread: turn2.data.thread ?? null,
|
|
2780
2915
|
channel: turn2.data.channel ?? null,
|
|
2916
|
+
place: turn2.data.place ?? null,
|
|
2781
2917
|
project: turn2.data.project,
|
|
2782
2918
|
trigger: turn2.data.trigger
|
|
2783
2919
|
};
|
|
@@ -2972,19 +3108,20 @@ function computeMetrics(input) {
|
|
|
2972
3108
|
}
|
|
2973
3109
|
|
|
2974
3110
|
// packages/board-core/dist/turn-history.js
|
|
2975
|
-
import { z as
|
|
2976
|
-
var TurnHistoryPayloadSchema =
|
|
3111
|
+
import { z as z15 } from "zod";
|
|
3112
|
+
var TurnHistoryPayloadSchema = z15.object({
|
|
2977
3113
|
project: NameSchema,
|
|
2978
3114
|
thread: UlidSchema.optional(),
|
|
2979
3115
|
channel: NameSchema.optional(),
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
3116
|
+
place: NameSchema.optional(),
|
|
3117
|
+
trigger: z15.string().default("manual"),
|
|
3118
|
+
exitReason: z15.string().nullable().default(null),
|
|
3119
|
+
costUsd: z15.number().default(0),
|
|
2983
3120
|
usage: UsageSchema.optional(),
|
|
2984
|
-
model:
|
|
2985
|
-
summary:
|
|
2986
|
-
error:
|
|
2987
|
-
toolCalls:
|
|
3121
|
+
model: z15.string().nullable().default(null),
|
|
3122
|
+
summary: z15.string().nullable().default(null),
|
|
3123
|
+
error: z15.string().nullable().default(null),
|
|
3124
|
+
toolCalls: z15.number().int().nonnegative().optional(),
|
|
2988
3125
|
turnId: UlidSchema.optional()
|
|
2989
3126
|
});
|
|
2990
3127
|
function tokenUse(events, window, now) {
|
|
@@ -3215,11 +3352,14 @@ var BoardPaths = class {
|
|
|
3215
3352
|
return path5.join(this.agentProjects(name), slug);
|
|
3216
3353
|
}
|
|
3217
3354
|
/**
|
|
3218
|
-
* A thread's
|
|
3219
|
-
* conversation; `part` is a thread's id or
|
|
3355
|
+
* A thread's, a channel's, or, in the society scope, a project's conversation's session and last
|
|
3356
|
+
* turn, beside the scope's home conversation; `part` is a thread's id, `#channel`, or `~project`.
|
|
3220
3357
|
*/
|
|
3221
3358
|
agentConversation(name, slug, part) {
|
|
3222
|
-
|
|
3359
|
+
if (part.startsWith("#")) {
|
|
3360
|
+
return path5.join(this.agentProject(name, slug), "channels", part.slice(1));
|
|
3361
|
+
}
|
|
3362
|
+
return part.startsWith("~") ? path5.join(this.agentProject(name, slug), "places", part.slice(1)) : path5.join(this.agentProject(name, slug), "threads", part);
|
|
3223
3363
|
}
|
|
3224
3364
|
agentCursors(name) {
|
|
3225
3365
|
return path5.join(this.agent(name), "cursors.json");
|
|
@@ -3230,6 +3370,13 @@ var BoardPaths = class {
|
|
|
3230
3370
|
eventLog() {
|
|
3231
3371
|
return path5.join(this.events(), "log.jsonl");
|
|
3232
3372
|
}
|
|
3373
|
+
/** Images citizens uploaded, by id: outside the projection, which every runner mirrors. */
|
|
3374
|
+
images() {
|
|
3375
|
+
return path5.join(this.dataDir, "images");
|
|
3376
|
+
}
|
|
3377
|
+
image(id) {
|
|
3378
|
+
return path5.join(this.images(), id);
|
|
3379
|
+
}
|
|
3233
3380
|
state() {
|
|
3234
3381
|
return path5.join(this.dataDir, "state");
|
|
3235
3382
|
}
|
|
@@ -3246,6 +3393,101 @@ var BoardPaths = class {
|
|
|
3246
3393
|
}
|
|
3247
3394
|
};
|
|
3248
3395
|
|
|
3396
|
+
// packages/board-core/dist/skill-folder.js
|
|
3397
|
+
import { constants } from "node:fs";
|
|
3398
|
+
import { lstat, open as open2, readdir as readdir3, realpath } from "node:fs/promises";
|
|
3399
|
+
import path6 from "node:path";
|
|
3400
|
+
var SKILL_TEXT_LIMIT_CHARS = 1e5;
|
|
3401
|
+
var SKILL_TEXT_LIMIT_BYTES = SKILL_TEXT_LIMIT_CHARS * 4;
|
|
3402
|
+
function skillFileText(bytes) {
|
|
3403
|
+
const text = bytes.includes(0) ? null : bytes.toString("utf8");
|
|
3404
|
+
return {
|
|
3405
|
+
text: text === null ? null : text.slice(0, SKILL_TEXT_LIMIT_CHARS),
|
|
3406
|
+
truncated: text !== null && text.length > SKILL_TEXT_LIMIT_CHARS
|
|
3407
|
+
};
|
|
3408
|
+
}
|
|
3409
|
+
function inside(root, file) {
|
|
3410
|
+
const relative = path6.relative(root, file);
|
|
3411
|
+
return relative !== ".." && !relative.startsWith(`..${path6.sep}`) && !path6.isAbsolute(relative);
|
|
3412
|
+
}
|
|
3413
|
+
async function walk(root, prefix, found) {
|
|
3414
|
+
const entries = await readdir3(path6.join(root, ...prefix === "" ? [] : prefix.split("/")), {
|
|
3415
|
+
withFileTypes: true
|
|
3416
|
+
}).catch(() => []);
|
|
3417
|
+
for (const entry of entries.toSorted((a, b) => a.name < b.name ? -1 : 1)) {
|
|
3418
|
+
if (found.length >= SKILL_FILES_LIMIT) {
|
|
3419
|
+
return;
|
|
3420
|
+
}
|
|
3421
|
+
const relative = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
3422
|
+
if (!isSafeRelativePath(relative)) {
|
|
3423
|
+
continue;
|
|
3424
|
+
}
|
|
3425
|
+
if (entry.isDirectory()) {
|
|
3426
|
+
await walk(root, relative, found);
|
|
3427
|
+
} else if (entry.isFile() && !entry.name.endsWith(".tmp")) {
|
|
3428
|
+
found.push(relative);
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
3432
|
+
async function readEntry(root, relative) {
|
|
3433
|
+
let real;
|
|
3434
|
+
try {
|
|
3435
|
+
real = await realpath(path6.join(root, ...relative.split("/")));
|
|
3436
|
+
} catch {
|
|
3437
|
+
return null;
|
|
3438
|
+
}
|
|
3439
|
+
if (!inside(root, real)) {
|
|
3440
|
+
return null;
|
|
3441
|
+
}
|
|
3442
|
+
const handle = await open2(real, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK).catch(() => null);
|
|
3443
|
+
if (handle === null) {
|
|
3444
|
+
return null;
|
|
3445
|
+
}
|
|
3446
|
+
try {
|
|
3447
|
+
const stats = await handle.stat();
|
|
3448
|
+
if (!stats.isFile()) {
|
|
3449
|
+
return null;
|
|
3450
|
+
}
|
|
3451
|
+
const buffer = Buffer.alloc(Math.min(stats.size, SKILL_TEXT_LIMIT_BYTES));
|
|
3452
|
+
let size = 0;
|
|
3453
|
+
while (size < buffer.length) {
|
|
3454
|
+
const { bytesRead } = await handle.read(buffer, size, buffer.length - size, size);
|
|
3455
|
+
if (bytesRead === 0) {
|
|
3456
|
+
break;
|
|
3457
|
+
}
|
|
3458
|
+
size += bytesRead;
|
|
3459
|
+
}
|
|
3460
|
+
return {
|
|
3461
|
+
path: relative,
|
|
3462
|
+
size: stats.size,
|
|
3463
|
+
executable: (stats.mode & 73) !== 0,
|
|
3464
|
+
...skillFileText(buffer.subarray(0, size))
|
|
3465
|
+
};
|
|
3466
|
+
} finally {
|
|
3467
|
+
await handle.close();
|
|
3468
|
+
}
|
|
3469
|
+
}
|
|
3470
|
+
async function readSkillFolder(dir) {
|
|
3471
|
+
const info = await lstat(dir).catch(() => null);
|
|
3472
|
+
if (info === null || !info.isDirectory()) {
|
|
3473
|
+
return null;
|
|
3474
|
+
}
|
|
3475
|
+
const root = await realpath(dir).catch(() => null);
|
|
3476
|
+
if (root === null) {
|
|
3477
|
+
return null;
|
|
3478
|
+
}
|
|
3479
|
+
const found = [];
|
|
3480
|
+
await walk(root, "", found);
|
|
3481
|
+
const files = [];
|
|
3482
|
+
for (const relative of found) {
|
|
3483
|
+
const file = await readEntry(root, relative);
|
|
3484
|
+
if (file !== null) {
|
|
3485
|
+
files.push(file);
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
return files.some((file) => file.path === "SKILL.md") ? files : null;
|
|
3489
|
+
}
|
|
3490
|
+
|
|
3249
3491
|
// packages/board-core/dist/tokens.js
|
|
3250
3492
|
import { createHash as createHash2, randomBytes } from "node:crypto";
|
|
3251
3493
|
function mintToken() {
|
|
@@ -3259,48 +3501,62 @@ function hashToken(token) {
|
|
|
3259
3501
|
var SYSTEM_ACTOR = { name: "board", role: USER_ROLE };
|
|
3260
3502
|
var CLOSING_ATTEMPTS = 3;
|
|
3261
3503
|
function ownTurn(actor, turn2) {
|
|
3262
|
-
return turn2.agent === actor.name && turn2.scope === actor.scope && turn2.thread === actor.thread && turn2.channel === actor.channel;
|
|
3504
|
+
return turn2.agent === actor.name && turn2.scope === actor.scope && turn2.thread === actor.thread && turn2.channel === actor.channel && turn2.place === actor.place;
|
|
3263
3505
|
}
|
|
3264
3506
|
function namesList(names) {
|
|
3265
3507
|
return names.length <= 1 ? names[0] ?? "" : `${names.slice(0, -1).join(", ")} and ${names.at(-1) ?? ""}`;
|
|
3266
3508
|
}
|
|
3267
3509
|
var SKIPPED_IN_HOME = /* @__PURE__ */ new Set([".git", "turns", ".claude", ".codex"]);
|
|
3268
|
-
var RunnerTokensSchema =
|
|
3269
|
-
runners:
|
|
3510
|
+
var RunnerTokensSchema = z16.object({
|
|
3511
|
+
runners: z16.record(z16.string(), z16.object({ tokenHash: z16.string().min(1) }))
|
|
3270
3512
|
});
|
|
3271
|
-
var SignInsSchema =
|
|
3272
|
-
signIns:
|
|
3273
|
-
tokenHash:
|
|
3274
|
-
login:
|
|
3513
|
+
var SignInsSchema = z16.object({
|
|
3514
|
+
signIns: z16.array(z16.object({
|
|
3515
|
+
tokenHash: z16.string().min(1),
|
|
3516
|
+
login: z16.string().min(1),
|
|
3275
3517
|
/** The account's picture at the identity provider; sign-ins from before it have none. */
|
|
3276
|
-
avatarUrl:
|
|
3518
|
+
avatarUrl: z16.url().optional(),
|
|
3277
3519
|
createdAt: IsoDateTimeSchema,
|
|
3278
3520
|
expiresAt: IsoDateTimeSchema
|
|
3279
3521
|
})).default([])
|
|
3280
3522
|
});
|
|
3281
|
-
var TurnStartPayloadSchema =
|
|
3282
|
-
var CursorsSchema =
|
|
3283
|
-
digest:
|
|
3284
|
-
scopes:
|
|
3285
|
-
threadsFrom:
|
|
3286
|
-
channelsFrom:
|
|
3523
|
+
var TurnStartPayloadSchema = z16.object({ project: NameSchema, turnId: UlidSchema.optional() });
|
|
3524
|
+
var CursorsSchema = z16.object({
|
|
3525
|
+
digest: z16.string().nullable(),
|
|
3526
|
+
scopes: z16.record(z16.string(), z16.string()).default({}),
|
|
3527
|
+
threadsFrom: z16.record(z16.string(), z16.string().nullable()).default({}),
|
|
3528
|
+
channelsFrom: z16.record(z16.string(), z16.string().nullable()).default({}),
|
|
3529
|
+
placesFrom: z16.record(z16.string(), z16.string().nullable()).default({}),
|
|
3287
3530
|
/** How far a work role has read news, which goes to whichever of its general conversations reads first. */
|
|
3288
|
-
news:
|
|
3289
|
-
});
|
|
3290
|
-
var NO_CURSORS = {
|
|
3531
|
+
news: z16.string().optional()
|
|
3532
|
+
});
|
|
3533
|
+
var NO_CURSORS = {
|
|
3534
|
+
digest: null,
|
|
3535
|
+
scopes: {},
|
|
3536
|
+
threadsFrom: {},
|
|
3537
|
+
channelsFrom: {},
|
|
3538
|
+
placesFrom: {}
|
|
3539
|
+
};
|
|
3291
3540
|
function conversationKey(scope, part) {
|
|
3292
3541
|
return part === null ? scope : `${scope}/${part}`;
|
|
3293
3542
|
}
|
|
3294
3543
|
function channelOfPart(part) {
|
|
3295
3544
|
return part?.startsWith("#") === true ? part.slice(1) : void 0;
|
|
3296
3545
|
}
|
|
3546
|
+
function placeOfPart(part) {
|
|
3547
|
+
return part?.startsWith("~") === true ? part.slice(1) : void 0;
|
|
3548
|
+
}
|
|
3297
3549
|
function threadOfPart(part) {
|
|
3298
|
-
return part === null || part === void 0 || part.startsWith("#") ? void 0 : part;
|
|
3550
|
+
return part === null || part === void 0 || part.startsWith("#") || part.startsWith("~") ? void 0 : part;
|
|
3299
3551
|
}
|
|
3300
3552
|
function channelStart(cursors, scope) {
|
|
3301
3553
|
const from = cursors.channelsFrom[scope];
|
|
3302
3554
|
return from === void 0 ? cursors.digest : from;
|
|
3303
3555
|
}
|
|
3556
|
+
function placeStart(cursors, scope) {
|
|
3557
|
+
const from = cursors.placesFrom[scope];
|
|
3558
|
+
return from === void 0 ? cursors.digest : from;
|
|
3559
|
+
}
|
|
3304
3560
|
function threadStart(cursors, scope) {
|
|
3305
3561
|
const from = cursors.threadsFrom[scope];
|
|
3306
3562
|
return from === void 0 ? cursors.digest : from;
|
|
@@ -3316,7 +3572,7 @@ function cursorOf(cursors, scope, part = null) {
|
|
|
3316
3572
|
if (part === null) {
|
|
3317
3573
|
return cursors.scopes[scope] ?? cursors.digest;
|
|
3318
3574
|
}
|
|
3319
|
-
return cursors.scopes[conversationKey(scope, part)] ?? (part.startsWith("#") ? channelStart(cursors, scope) : threadStart(cursors, scope));
|
|
3575
|
+
return cursors.scopes[conversationKey(scope, part)] ?? (part.startsWith("#") ? channelStart(cursors, scope) : part.startsWith("~") ? placeStart(cursors, scope) : threadStart(cursors, scope));
|
|
3320
3576
|
}
|
|
3321
3577
|
function byId(a, b) {
|
|
3322
3578
|
return a.message.id < b.message.id ? -1 : a.message.id > b.message.id ? 1 : 0;
|
|
@@ -3324,12 +3580,14 @@ function byId(a, b) {
|
|
|
3324
3580
|
function turnTime(record) {
|
|
3325
3581
|
return record.endedAt ?? record.startedAt;
|
|
3326
3582
|
}
|
|
3327
|
-
|
|
3583
|
+
function turnConversation(record) {
|
|
3584
|
+
return record.thread !== void 0 ? ` in thread ${record.thread}` : record.channel !== void 0 ? ` in #${record.channel}` : record.place !== void 0 ? ` in ${record.place}'s channels` : "";
|
|
3585
|
+
}
|
|
3586
|
+
var PausedSchema = z16.object({ paused: z16.boolean() });
|
|
3328
3587
|
var DEFAULT_LEASE_MS = 30 * 60 * 1e3;
|
|
3329
3588
|
function withoutMentions(text) {
|
|
3330
3589
|
return text.replaceAll(MENTION_PATTERN, "$1$2");
|
|
3331
3590
|
}
|
|
3332
|
-
var SKILL_TEXT_LIMIT_CHARS = 1e5;
|
|
3333
3591
|
function skillFolderOf(proposal2) {
|
|
3334
3592
|
if (proposal2.kind !== "skill") {
|
|
3335
3593
|
return null;
|
|
@@ -3338,7 +3596,7 @@ function skillFolderOf(proposal2) {
|
|
|
3338
3596
|
return parsed.success && parsed.data.body === void 0 ? parsed.data.name : null;
|
|
3339
3597
|
}
|
|
3340
3598
|
function proposedFrom(origin, where) {
|
|
3341
|
-
return origin !== void 0 && origin.scope === where.scope && origin.thread === where.thread && origin.channel === where.channel;
|
|
3599
|
+
return origin !== void 0 && origin.scope === where.scope && origin.thread === where.thread && origin.channel === where.channel && origin.place === where.place;
|
|
3342
3600
|
}
|
|
3343
3601
|
function megabytes(bytes) {
|
|
3344
3602
|
return (bytes / (1024 * 1024)).toFixed(1).replace(/\.0$/, "");
|
|
@@ -3349,7 +3607,7 @@ var OPS_WAKE_TRIGGER = "ops_event";
|
|
|
3349
3607
|
var FRONT_DESK_TRIGGER = "user_post";
|
|
3350
3608
|
var OPS_CHANNELS = ["governance"];
|
|
3351
3609
|
var FRONT_DESK_CHANNELS = [ASK_CHANNEL];
|
|
3352
|
-
var AlignmentsSchema =
|
|
3610
|
+
var AlignmentsSchema = z16.object({ applied: z16.array(z16.string()).default([]) });
|
|
3353
3611
|
var FOLLOW_DECISIONS = "ops-readers-follow-decisions";
|
|
3354
3612
|
var THREAD_RECORDS = "threads-as-records";
|
|
3355
3613
|
var DIGEST_CURSORS = "digest-replaces-inbox";
|
|
@@ -3360,13 +3618,15 @@ var THREAD_CONVERSATIONS = "thread-conversations";
|
|
|
3360
3618
|
var ASKS_CHANNEL_OPENED = "asks-channel-opened";
|
|
3361
3619
|
var KNOWLEDGE_REMOVAL = "knowledge-removal-granted";
|
|
3362
3620
|
var CHANNEL_CONVERSATIONS = "channel-conversations";
|
|
3621
|
+
var PLACE_CONVERSATIONS = "place-conversations";
|
|
3363
3622
|
var DASHBOARD_UPDATES = "dashboard-updates-granted";
|
|
3364
3623
|
var CRON_VERBS = "cron-verbs-granted";
|
|
3365
3624
|
var KNOWLEDGE_NOTICES = "knowledge-notices";
|
|
3366
|
-
var
|
|
3625
|
+
var IMAGE_UPLOADS = "image-uploads-granted";
|
|
3626
|
+
var KnowledgeEventSchema = z16.object({
|
|
3367
3627
|
topic: NameSchema,
|
|
3368
3628
|
project: NameSchema.nullable(),
|
|
3369
|
-
replaced:
|
|
3629
|
+
replaced: z16.boolean().optional()
|
|
3370
3630
|
});
|
|
3371
3631
|
var CRON_VERB_NAMES = ["create_cron", "update_cron", "remove_cron"];
|
|
3372
3632
|
var OLD_DASHBOARD_LINE = "Agents may edit this file. It is rendered by the board UI.";
|
|
@@ -3433,8 +3693,8 @@ function restage(existing, edit) {
|
|
|
3433
3693
|
};
|
|
3434
3694
|
}
|
|
3435
3695
|
function reopenStage(stage) {
|
|
3436
|
-
const { completedBy: _by, completedAt: _at, ...
|
|
3437
|
-
return
|
|
3696
|
+
const { completedBy: _by, completedAt: _at, ...open4 } = stage;
|
|
3697
|
+
return open4;
|
|
3438
3698
|
}
|
|
3439
3699
|
function gateChanges(before, after) {
|
|
3440
3700
|
const changes = [];
|
|
@@ -3498,6 +3758,7 @@ var Board = class _Board {
|
|
|
3498
3758
|
mutex = new Mutex();
|
|
3499
3759
|
/** The turns in flight, which the server hosting them supplies; none until it does. */
|
|
3500
3760
|
turnsInFlight = () => [];
|
|
3761
|
+
imageSource = null;
|
|
3501
3762
|
newId = monotonicFactory();
|
|
3502
3763
|
events;
|
|
3503
3764
|
actionCounts = /* @__PURE__ */ new Map();
|
|
@@ -3514,7 +3775,7 @@ var Board = class _Board {
|
|
|
3514
3775
|
homes;
|
|
3515
3776
|
constructor(dataDir, options) {
|
|
3516
3777
|
this.paths = new BoardPaths(dataDir);
|
|
3517
|
-
this.homes = new HomeRepos(
|
|
3778
|
+
this.homes = new HomeRepos(path7.join(this.paths.state(), "home-hooks"));
|
|
3518
3779
|
this.leaseMs = options.leaseMs ?? DEFAULT_LEASE_MS;
|
|
3519
3780
|
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
3520
3781
|
this.events = new EventLog(this.paths.eventLog(), () => this.newId(), this.now);
|
|
@@ -3547,7 +3808,7 @@ var Board = class _Board {
|
|
|
3547
3808
|
* repeated, so a member that later chooses otherwise keeps its choice.
|
|
3548
3809
|
*/
|
|
3549
3810
|
async applyAlignments() {
|
|
3550
|
-
const file =
|
|
3811
|
+
const file = path7.join(this.paths.state(), "alignments.json");
|
|
3551
3812
|
const { applied } = await exists(file) ? await readJson(file, AlignmentsSchema) : { applied: [] };
|
|
3552
3813
|
const pending = [
|
|
3553
3814
|
FOLLOW_DECISIONS,
|
|
@@ -3562,7 +3823,9 @@ var Board = class _Board {
|
|
|
3562
3823
|
CHANNEL_CONVERSATIONS,
|
|
3563
3824
|
DASHBOARD_UPDATES,
|
|
3564
3825
|
CRON_VERBS,
|
|
3565
|
-
KNOWLEDGE_NOTICES
|
|
3826
|
+
KNOWLEDGE_NOTICES,
|
|
3827
|
+
IMAGE_UPLOADS,
|
|
3828
|
+
PLACE_CONVERSATIONS
|
|
3566
3829
|
].filter((name) => !applied.includes(name));
|
|
3567
3830
|
if (pending.length === 0) {
|
|
3568
3831
|
return;
|
|
@@ -3592,13 +3855,13 @@ var Board = class _Board {
|
|
|
3592
3855
|
await this.openAsksChannel();
|
|
3593
3856
|
}
|
|
3594
3857
|
if (pending.includes(KNOWLEDGE_REMOVAL)) {
|
|
3595
|
-
await this.
|
|
3858
|
+
await this.grantBeside("write_knowledge", "remove_knowledge");
|
|
3596
3859
|
}
|
|
3597
3860
|
if (pending.includes(CHANNEL_CONVERSATIONS)) {
|
|
3598
3861
|
await this.startChannelCursors();
|
|
3599
3862
|
}
|
|
3600
3863
|
if (pending.includes(DASHBOARD_UPDATES)) {
|
|
3601
|
-
await this.
|
|
3864
|
+
await this.grantBeside("write_knowledge", "update_dashboard");
|
|
3602
3865
|
await this.rewordDashboards();
|
|
3603
3866
|
}
|
|
3604
3867
|
if (pending.includes(CRON_VERBS)) {
|
|
@@ -3607,6 +3870,12 @@ var Board = class _Board {
|
|
|
3607
3870
|
if (pending.includes(KNOWLEDGE_NOTICES)) {
|
|
3608
3871
|
await this.noticeKnowledgeNotes();
|
|
3609
3872
|
}
|
|
3873
|
+
if (pending.includes(IMAGE_UPLOADS)) {
|
|
3874
|
+
await this.grantBeside("post_message", "upload_image");
|
|
3875
|
+
}
|
|
3876
|
+
if (pending.includes(PLACE_CONVERSATIONS)) {
|
|
3877
|
+
await this.startPlaceCursors();
|
|
3878
|
+
}
|
|
3610
3879
|
await writeJson(file, { applied: [...applied, ...pending] });
|
|
3611
3880
|
}
|
|
3612
3881
|
/**
|
|
@@ -3648,12 +3917,12 @@ var Board = class _Board {
|
|
|
3648
3917
|
}
|
|
3649
3918
|
}
|
|
3650
3919
|
/**
|
|
3651
|
-
* Grants `verb` once to every charter granting `
|
|
3652
|
-
*
|
|
3920
|
+
* Grants `verb` once to every charter granting `beside`, the work roles' charters in practice;
|
|
3921
|
+
* seed roles have it from their seed.
|
|
3653
3922
|
*/
|
|
3654
|
-
async
|
|
3923
|
+
async grantBeside(beside, verb) {
|
|
3655
3924
|
for (const charter of await this.listRoles()) {
|
|
3656
|
-
if (charter.verbs.includes(
|
|
3925
|
+
if (charter.verbs.includes(beside) && !charter.verbs.includes(verb)) {
|
|
3657
3926
|
await this.writeRoleUnlocked(USER_NAME, { ...charter, verbs: [...charter.verbs, verb] }, { verbsAdded: [verb] });
|
|
3658
3927
|
}
|
|
3659
3928
|
}
|
|
@@ -3811,7 +4080,7 @@ var Board = class _Board {
|
|
|
3811
4080
|
}
|
|
3812
4081
|
}
|
|
3813
4082
|
for (const file of await listFiles(this.paths.tasks(slug))) {
|
|
3814
|
-
const raw = await readMarkdown(
|
|
4083
|
+
const raw = await readMarkdown(path7.join(this.paths.tasks(slug), file), z16.record(z16.string(), z16.unknown()));
|
|
3815
4084
|
const legacy = raw.data["thread"];
|
|
3816
4085
|
if (legacy === void 0) {
|
|
3817
4086
|
continue;
|
|
@@ -3819,7 +4088,7 @@ var Board = class _Board {
|
|
|
3819
4088
|
const task2 = { ...TaskFrontmatterSchema.parse(raw.data), body: raw.body };
|
|
3820
4089
|
if ((legacy === "open" || legacy === "closed") && !await exists(this.paths.threadFile(threads, task2.id))) {
|
|
3821
4090
|
const ended = task2.status === "done" || task2.status === "abandoned";
|
|
3822
|
-
const
|
|
4091
|
+
const open4 = legacy === "open" && !ended;
|
|
3823
4092
|
const openedBy = opened.get(task2.id);
|
|
3824
4093
|
const closedBy = closed.get(task2.id);
|
|
3825
4094
|
await this.writeThread(threads, {
|
|
@@ -3827,10 +4096,10 @@ var Board = class _Board {
|
|
|
3827
4096
|
channel: channelRef(slug, "general"),
|
|
3828
4097
|
title: task2.title,
|
|
3829
4098
|
subject: { kind: "task", id: task2.id },
|
|
3830
|
-
state:
|
|
4099
|
+
state: open4 ? "open" : "closed",
|
|
3831
4100
|
openedBy: openedBy?.actor ?? task2.createdBy,
|
|
3832
4101
|
openedAt: openedBy?.ts ?? task2.createdAt,
|
|
3833
|
-
...
|
|
4102
|
+
...open4 ? {} : {
|
|
3834
4103
|
closedBy: closedBy?.actor ?? SYSTEM_ACTOR.name,
|
|
3835
4104
|
closedAt: closedBy?.ts ?? this.now().toISOString()
|
|
3836
4105
|
},
|
|
@@ -3862,6 +4131,24 @@ var Board = class _Board {
|
|
|
3862
4131
|
await writeJson(file, { ...cursors, channelsFrom });
|
|
3863
4132
|
}
|
|
3864
4133
|
}
|
|
4134
|
+
/**
|
|
4135
|
+
* A project's channels were once read, from the society scope, in the society's home
|
|
4136
|
+
* conversation, so a member had read each up to that cursor. Each project's conversation in the
|
|
4137
|
+
* society scope starts from it now, as each takes a conversation of its own.
|
|
4138
|
+
*/
|
|
4139
|
+
async startPlaceCursors() {
|
|
4140
|
+
for (const agent2 of await this.listAgents()) {
|
|
4141
|
+
const file = this.paths.agentCursors(agent2.name);
|
|
4142
|
+
if (!await exists(file)) {
|
|
4143
|
+
continue;
|
|
4144
|
+
}
|
|
4145
|
+
const cursors = await readJson(file, CursorsSchema);
|
|
4146
|
+
await writeJson(file, {
|
|
4147
|
+
...cursors,
|
|
4148
|
+
placesFrom: { [SOCIETY_SCOPE]: cursorOf(cursors, SOCIETY_SCOPE) }
|
|
4149
|
+
});
|
|
4150
|
+
}
|
|
4151
|
+
}
|
|
3865
4152
|
/**
|
|
3866
4153
|
* Threads were once read in the scope's single conversation, so a member had read every thread
|
|
3867
4154
|
* up to its scope's cursor. Each scope's threads start from that cursor now, as each thread's
|
|
@@ -3889,7 +4176,7 @@ var Board = class _Board {
|
|
|
3889
4176
|
* turns, so it follows nothing now.
|
|
3890
4177
|
*/
|
|
3891
4178
|
async renameInboxCursors() {
|
|
3892
|
-
const Legacy =
|
|
4179
|
+
const Legacy = z16.object({ inbox: z16.string().nullable() });
|
|
3893
4180
|
for (const agent2 of await this.listAgents()) {
|
|
3894
4181
|
const file = this.paths.agentCursors(agent2.name);
|
|
3895
4182
|
if (await exists(file)) {
|
|
@@ -3935,7 +4222,7 @@ ${charter.purpose}
|
|
|
3935
4222
|
*/
|
|
3936
4223
|
async alignSeedRole(seed) {
|
|
3937
4224
|
const file = this.paths.role(seed.name);
|
|
3938
|
-
const raw = await readMarkdown(file,
|
|
4225
|
+
const raw = await readMarkdown(file, z16.record(z16.string(), z16.unknown()));
|
|
3939
4226
|
const current = RoleCharterSchema.parse(raw.data);
|
|
3940
4227
|
const verbsAdded = seed.verbs.filter((verb) => !current.verbs.includes(verb));
|
|
3941
4228
|
const fieldsAligned = RoleCharterSchema.keyof().options.filter((key) => key === "verbs" || seed[key] === void 0 ? false : seed.name === USER_ROLE ? JSON.stringify(seed[key]) !== JSON.stringify(current[key]) : !(key in raw.data));
|
|
@@ -4002,13 +4289,15 @@ ${charter.purpose}
|
|
|
4002
4289
|
const token = mintToken();
|
|
4003
4290
|
const thread2 = threadOfPart(part);
|
|
4004
4291
|
const channel = channelOfPart(part);
|
|
4292
|
+
const place = placeOfPart(part);
|
|
4005
4293
|
this.turnTokens.set(hashToken(token), {
|
|
4006
4294
|
actor: {
|
|
4007
4295
|
name: agent2,
|
|
4008
4296
|
role: role2,
|
|
4009
4297
|
...scope === void 0 ? {} : { scope },
|
|
4010
4298
|
...thread2 === void 0 ? {} : { thread: thread2 },
|
|
4011
|
-
...channel === void 0 ? {} : { channel }
|
|
4299
|
+
...channel === void 0 ? {} : { channel },
|
|
4300
|
+
...place === void 0 ? {} : { place }
|
|
4012
4301
|
},
|
|
4013
4302
|
expiresAt: this.now().getTime() + ttlMs
|
|
4014
4303
|
});
|
|
@@ -4097,7 +4386,7 @@ ${charter.purpose}
|
|
|
4097
4386
|
});
|
|
4098
4387
|
}
|
|
4099
4388
|
signInsFile() {
|
|
4100
|
-
return
|
|
4389
|
+
return path7.join(this.paths.state(), "sign-ins.json");
|
|
4101
4390
|
}
|
|
4102
4391
|
async readSignIns() {
|
|
4103
4392
|
const file = this.signInsFile();
|
|
@@ -4194,7 +4483,7 @@ ${charter.purpose}
|
|
|
4194
4483
|
const files = await listFiles(this.paths.tasks(project2));
|
|
4195
4484
|
const tasks = [];
|
|
4196
4485
|
for (const file of files) {
|
|
4197
|
-
const doc = await readMarkdown(
|
|
4486
|
+
const doc = await readMarkdown(path7.join(this.paths.tasks(project2), file), TaskFrontmatterSchema);
|
|
4198
4487
|
tasks.push({ ...doc.data, body: doc.body });
|
|
4199
4488
|
}
|
|
4200
4489
|
return tasks;
|
|
@@ -4213,7 +4502,7 @@ ${charter.purpose}
|
|
|
4213
4502
|
const files = await listFiles(this.paths.proposals());
|
|
4214
4503
|
const proposals = [];
|
|
4215
4504
|
for (const file of files) {
|
|
4216
|
-
const doc = await readMarkdown(
|
|
4505
|
+
const doc = await readMarkdown(path7.join(this.paths.proposals(), file), ProposalFrontmatterSchema);
|
|
4217
4506
|
proposals.push({ ...doc.data, body: doc.body });
|
|
4218
4507
|
}
|
|
4219
4508
|
return proposals;
|
|
@@ -4240,7 +4529,7 @@ ${charter.purpose}
|
|
|
4240
4529
|
async listMembers() {
|
|
4241
4530
|
const members = [];
|
|
4242
4531
|
for (const file of await listFiles(this.paths.members())) {
|
|
4243
|
-
const doc = await readMarkdown(
|
|
4532
|
+
const doc = await readMarkdown(path7.join(this.paths.members(), file), MemberSchema);
|
|
4244
4533
|
members.push({ ...doc.data, profile: doc.body });
|
|
4245
4534
|
}
|
|
4246
4535
|
return members;
|
|
@@ -4252,14 +4541,14 @@ ${charter.purpose}
|
|
|
4252
4541
|
/** The society's norms, the one knowledge topic every turn loads. Empty until the steward writes it. */
|
|
4253
4542
|
async readSocietyNorms() {
|
|
4254
4543
|
const file = this.paths.societyKnowledgeFile("norms");
|
|
4255
|
-
return await exists(file) ? (await readMarkdown(file,
|
|
4544
|
+
return await exists(file) ? (await readMarkdown(file, z16.record(z16.string(), z16.unknown()))).body : "";
|
|
4256
4545
|
}
|
|
4257
4546
|
/** Knowledge topics of a project, or of the society when the project is null. */
|
|
4258
4547
|
async listKnowledge(project2) {
|
|
4259
4548
|
const dir = project2 === null ? this.paths.societyKnowledge() : this.paths.projectKnowledge(project2);
|
|
4260
4549
|
const topics = [];
|
|
4261
4550
|
for (const file of await listFiles(dir)) {
|
|
4262
|
-
topics.push(await this.knowledgeAt(
|
|
4551
|
+
topics.push(await this.knowledgeAt(path7.join(dir, file), project2, file.replace(/\.md$/, "")));
|
|
4263
4552
|
}
|
|
4264
4553
|
return topics;
|
|
4265
4554
|
}
|
|
@@ -4272,7 +4561,7 @@ ${charter.purpose}
|
|
|
4272
4561
|
*/
|
|
4273
4562
|
async knowledgeAt(file, project2, topic) {
|
|
4274
4563
|
const { data, body } = await readLooseMarkdown(file);
|
|
4275
|
-
const revision =
|
|
4564
|
+
const revision = z16.number().int().min(0).safeParse(data["revision"]);
|
|
4276
4565
|
const updatedBy = NameSchema.safeParse(data["updatedBy"]);
|
|
4277
4566
|
const updatedAt = IsoDateTimeSchema.safeParse(data["updatedAt"]);
|
|
4278
4567
|
return {
|
|
@@ -4289,7 +4578,7 @@ ${charter.purpose}
|
|
|
4289
4578
|
const aside = this.paths.removedKnowledge(project2 ?? SOCIETY_SCOPE);
|
|
4290
4579
|
let highest = 0;
|
|
4291
4580
|
for (const file of (await listFiles(aside)).filter((name) => name.startsWith(`${topic}.`))) {
|
|
4292
|
-
const copy = await this.knowledgeAt(
|
|
4581
|
+
const copy = await this.knowledgeAt(path7.join(aside, file), project2, topic);
|
|
4293
4582
|
highest = Math.max(highest, copy.revision);
|
|
4294
4583
|
}
|
|
4295
4584
|
return highest;
|
|
@@ -4305,7 +4594,7 @@ ${charter.purpose}
|
|
|
4305
4594
|
async listSkillsIn(dir, scope) {
|
|
4306
4595
|
const skills = [];
|
|
4307
4596
|
for (const entry of await listDirs(dir)) {
|
|
4308
|
-
const file =
|
|
4597
|
+
const file = path7.join(dir, entry, "SKILL.md");
|
|
4309
4598
|
if (!await exists(file)) {
|
|
4310
4599
|
continue;
|
|
4311
4600
|
}
|
|
@@ -4395,7 +4684,7 @@ ${charter.purpose}
|
|
|
4395
4684
|
if (!wanted.delete(file.slice(0, 26))) {
|
|
4396
4685
|
continue;
|
|
4397
4686
|
}
|
|
4398
|
-
const { data } = await readMarkdown(
|
|
4687
|
+
const { data } = await readMarkdown(path7.join(dir, file), MessageFrontmatterSchema);
|
|
4399
4688
|
found.push(MessageRefSchema.parse(data));
|
|
4400
4689
|
}
|
|
4401
4690
|
if (wanted.size === 0) {
|
|
@@ -4427,7 +4716,7 @@ ${charter.purpose}
|
|
|
4427
4716
|
if (!file.endsWith(".md")) {
|
|
4428
4717
|
continue;
|
|
4429
4718
|
}
|
|
4430
|
-
const doc = await readMarkdown(
|
|
4719
|
+
const doc = await readMarkdown(path7.join(threads, file), ThreadFrontmatterSchema);
|
|
4431
4720
|
if (doc.data.state !== "open") {
|
|
4432
4721
|
continue;
|
|
4433
4722
|
}
|
|
@@ -4454,7 +4743,7 @@ ${charter.purpose}
|
|
|
4454
4743
|
const all = [];
|
|
4455
4744
|
for (const threads of await this.threadScopes()) {
|
|
4456
4745
|
for (const file of await listFiles(threads)) {
|
|
4457
|
-
const doc = await readMarkdown(
|
|
4746
|
+
const doc = await readMarkdown(path7.join(threads, file), ThreadFrontmatterSchema);
|
|
4458
4747
|
const messages = await listFiles(this.paths.threadMessages(threads, doc.data.id));
|
|
4459
4748
|
const last = messages.at(-1);
|
|
4460
4749
|
all.push({
|
|
@@ -4821,16 +5110,20 @@ ${DASHBOARD_LINE}
|
|
|
4821
5110
|
}
|
|
4822
5111
|
/**
|
|
4823
5112
|
* How many unread digest messages a member has in each conversation: a scope's home, for its
|
|
4824
|
-
* general channel, one of its other channels,
|
|
5113
|
+
* general channel, one of its other channels, one of its threads, or, in the society scope, a
|
|
5114
|
+
* project's channels. The heartbeat asks, so that it wakes a member where its unread
|
|
4825
5115
|
* messages are and nowhere else. Society channels count only for a member woken in the society
|
|
4826
5116
|
* scope; a project member reads them at its next turn. Two kinds of post are in the digest but not
|
|
4827
5117
|
* counted, and are read when the member wakes for something else: a task step's, which wakes
|
|
4828
5118
|
* whoever it hands work to, and the board's own, operations signals and landing announcements,
|
|
4829
|
-
* whose signals that call for action wake their readers directly.
|
|
5119
|
+
* whose signals that call for action wake their readers directly. A closed thread counts for
|
|
5120
|
+
* nothing: its conversation has ended, and a turn there could only read its posts and be refused
|
|
5121
|
+
* a reply, while a wake queued before it closed, as for a mention, still runs there.
|
|
4830
5122
|
*/
|
|
4831
5123
|
async unreadByConversation(actor) {
|
|
4832
5124
|
const { cursors, upto } = await this.digestSnapshot(actor.name);
|
|
4833
5125
|
const counts = /* @__PURE__ */ new Map();
|
|
5126
|
+
const closed = /* @__PURE__ */ new Map();
|
|
4834
5127
|
const unscoped = { name: actor.name, role: actor.role };
|
|
4835
5128
|
for (const { scope, part, message, news } of await this.digestSince(unscoped, cursors, upto)) {
|
|
4836
5129
|
if (news === true || message.step !== void 0 || message.author === SYSTEM_ACTOR.name) {
|
|
@@ -4840,11 +5133,23 @@ ${DASHBOARD_LINE}
|
|
|
4840
5133
|
if (posted !== scope && scope !== SOCIETY_SCOPE) {
|
|
4841
5134
|
continue;
|
|
4842
5135
|
}
|
|
5136
|
+
const thread2 = threadOfPart(part);
|
|
5137
|
+
if (thread2 !== void 0) {
|
|
5138
|
+
let ended = closed.get(thread2);
|
|
5139
|
+
if (ended === void 0) {
|
|
5140
|
+
ended = (await this.tryFindThread(thread2))?.thread.state === "closed";
|
|
5141
|
+
closed.set(thread2, ended);
|
|
5142
|
+
}
|
|
5143
|
+
if (ended) {
|
|
5144
|
+
continue;
|
|
5145
|
+
}
|
|
5146
|
+
}
|
|
4843
5147
|
const key = conversationKey(scope, part);
|
|
4844
5148
|
const entry = counts.get(key) ?? {
|
|
4845
5149
|
scope,
|
|
4846
|
-
thread:
|
|
5150
|
+
thread: thread2 ?? null,
|
|
4847
5151
|
channel: channelOfPart(part) ?? null,
|
|
5152
|
+
place: placeOfPart(part) ?? null,
|
|
4848
5153
|
count: 0
|
|
4849
5154
|
};
|
|
4850
5155
|
counts.set(key, { ...entry, count: entry.count + 1 });
|
|
@@ -4902,8 +5207,9 @@ ${DASHBOARD_LINE}
|
|
|
4902
5207
|
* thread it takes part in, and for the front desk, which every post by the user wakes, every post
|
|
4903
5208
|
* by the user wherever it is. With a conversation, only what is filed there: for a thread, every
|
|
4904
5209
|
* message of the thread, since the conversation is about it; for a channel's, that channel's
|
|
4905
|
-
* top-level messages; for a
|
|
4906
|
-
*
|
|
5210
|
+
* top-level messages; for a project's in the society scope, the top-level messages of every
|
|
5211
|
+
* channel of a project the member is not in; for a scope's home, general's, since each thread,
|
|
5212
|
+
* each other channel, and each such project is a conversation of its own. Nothing newer than
|
|
4907
5213
|
* `upto` counts (see `digestSnapshot`).
|
|
4908
5214
|
*/
|
|
4909
5215
|
async digestSince(actor, cursors, upto, conversation) {
|
|
@@ -4923,11 +5229,17 @@ ${DASHBOARD_LINE}
|
|
|
4923
5229
|
const frontDesk = charter.wakeTriggers.includes(FRONT_DESK_TRIGGER);
|
|
4924
5230
|
const threadParticipation = /* @__PURE__ */ new Map();
|
|
4925
5231
|
const openChannels = /* @__PURE__ */ new Map();
|
|
5232
|
+
const liveProjects = /* @__PURE__ */ new Map();
|
|
4926
5233
|
const collected = [];
|
|
4927
5234
|
const newsFrom = charter.societyScope ? void 0 : newsStart(cursors);
|
|
4928
5235
|
const scopes = conversation !== void 0 ? [conversation.scope] : [...agent2.memberships, SOCIETY_SCOPE];
|
|
4929
5236
|
const starts = [
|
|
4930
|
-
...scopes.flatMap((each) => conversation === void 0 ? [
|
|
5237
|
+
...scopes.flatMap((each) => conversation === void 0 ? [
|
|
5238
|
+
cursorOf(cursors, each),
|
|
5239
|
+
threadStart(cursors, each),
|
|
5240
|
+
channelStart(cursors, each),
|
|
5241
|
+
placeStart(cursors, each)
|
|
5242
|
+
] : [cursorOf(cursors, each, conversation.part)]),
|
|
4931
5243
|
...newsFrom === void 0 ? [] : [newsFrom]
|
|
4932
5244
|
];
|
|
4933
5245
|
const known = starts.filter((start) => start !== null);
|
|
@@ -4937,7 +5249,7 @@ ${DASHBOARD_LINE}
|
|
|
4937
5249
|
const thread2 = message.thread ?? null;
|
|
4938
5250
|
const asked = message.mentions.includes(actor.name) || frontDesk && message.author === USER_NAME;
|
|
4939
5251
|
if (newsFrom !== void 0 && thread2 === null && filedIn === SOCIETY_SCOPE && !asked) {
|
|
4940
|
-
if ((newsFrom === null || message.id > newsFrom) && subscribed.has(message.channel) && message.author !== actor.name) {
|
|
5252
|
+
if ((conversation === void 0 || conversation.part === null) && (newsFrom === null || message.id > newsFrom) && subscribed.has(message.channel) && message.author !== actor.name) {
|
|
4941
5253
|
collected.push({
|
|
4942
5254
|
scope: conversation?.scope ?? SOCIETY_SCOPE,
|
|
4943
5255
|
part: null,
|
|
@@ -4948,12 +5260,18 @@ ${DASHBOARD_LINE}
|
|
|
4948
5260
|
continue;
|
|
4949
5261
|
}
|
|
4950
5262
|
const named = thread2 === null ? channelConversation(filedIn, message.channel) : void 0;
|
|
4951
|
-
let
|
|
4952
|
-
if (named !== void 0 &&
|
|
4953
|
-
|
|
4954
|
-
openChannels.set(message.channel,
|
|
4955
|
-
}
|
|
4956
|
-
const
|
|
5263
|
+
let open4 = named === void 0 ? void 0 : openChannels.get(message.channel);
|
|
5264
|
+
if (named !== void 0 && open4 === void 0) {
|
|
5265
|
+
open4 = await this.channelOpen(message.channel);
|
|
5266
|
+
openChannels.set(message.channel, open4);
|
|
5267
|
+
}
|
|
5268
|
+
const project2 = thread2 === null ? placeConversation(filedIn, message.channel) : void 0;
|
|
5269
|
+
let live = project2 === void 0 ? void 0 : liveProjects.get(project2);
|
|
5270
|
+
if (project2 !== void 0 && live === void 0) {
|
|
5271
|
+
live = await this.projectLive(project2);
|
|
5272
|
+
liveProjects.set(project2, live);
|
|
5273
|
+
}
|
|
5274
|
+
const part = thread2 ?? (open4 === true ? `#${named}` : live === true ? `~${project2}` : null);
|
|
4957
5275
|
if (conversation !== void 0 && (filedIn !== conversation.scope || part !== conversation.part)) {
|
|
4958
5276
|
continue;
|
|
4959
5277
|
}
|
|
@@ -5018,7 +5336,7 @@ ${task2.body}`;
|
|
|
5018
5336
|
}
|
|
5019
5337
|
}
|
|
5020
5338
|
for (const file of await listFiles(this.paths.projectKnowledge(project2))) {
|
|
5021
|
-
const content = await readFile4(
|
|
5339
|
+
const content = await readFile4(path7.join(this.paths.projectKnowledge(project2), file), "utf8");
|
|
5022
5340
|
if (content.toLowerCase().includes(needle)) {
|
|
5023
5341
|
hits.push({
|
|
5024
5342
|
kind: "knowledge",
|
|
@@ -5039,7 +5357,7 @@ ${task2.body}`;
|
|
|
5039
5357
|
shared.push({
|
|
5040
5358
|
kind: "knowledge",
|
|
5041
5359
|
ref: `society/knowledge/${file}`,
|
|
5042
|
-
file:
|
|
5360
|
+
file: path7.join(this.paths.societyKnowledge(), file)
|
|
5043
5361
|
});
|
|
5044
5362
|
}
|
|
5045
5363
|
for (const skill2 of await this.listSocietySkills()) {
|
|
@@ -5052,7 +5370,7 @@ ${task2.body}`;
|
|
|
5052
5370
|
shared.push({
|
|
5053
5371
|
kind: "memory",
|
|
5054
5372
|
ref: `memory/${file}`,
|
|
5055
|
-
file:
|
|
5373
|
+
file: path7.join(this.paths.agentMemory(actor.name), file)
|
|
5056
5374
|
});
|
|
5057
5375
|
}
|
|
5058
5376
|
for (const entry of shared) {
|
|
@@ -5142,7 +5460,7 @@ ${current.body}`);
|
|
|
5142
5460
|
});
|
|
5143
5461
|
const aside = this.paths.removedKnowledge(args.project ?? SOCIETY_SCOPE);
|
|
5144
5462
|
await mkdir4(aside, { recursive: true });
|
|
5145
|
-
await rename3(file,
|
|
5463
|
+
await rename3(file, path7.join(aside, `${args.topic}.${removed.removedAt.replaceAll(":", "-")}.md`));
|
|
5146
5464
|
await this.events.append("knowledge.removed", actor.name, {
|
|
5147
5465
|
topic: args.topic,
|
|
5148
5466
|
project: args.project
|
|
@@ -5190,7 +5508,7 @@ ${current.body}`);
|
|
|
5190
5508
|
});
|
|
5191
5509
|
const aside = this.paths.removedSkills();
|
|
5192
5510
|
await mkdir4(aside, { recursive: true });
|
|
5193
|
-
await rename3(this.paths.societySkill(args.name),
|
|
5511
|
+
await rename3(this.paths.societySkill(args.name), path7.join(aside, `${args.name}.${removed.removedAt.replaceAll(":", "-")}`));
|
|
5194
5512
|
await this.events.append("skill.removed", actor.name, {
|
|
5195
5513
|
name: args.name,
|
|
5196
5514
|
reason: args.reason
|
|
@@ -5235,11 +5553,65 @@ ${current.body}`);
|
|
|
5235
5553
|
return updated;
|
|
5236
5554
|
});
|
|
5237
5555
|
}
|
|
5556
|
+
/**
|
|
5557
|
+
* Keeps an image a citizen's turn names by its path, read on the runner the turn runs on, which
|
|
5558
|
+
* refuses a file outside the turn's worktree, the citizen's home, and the system temp folder.
|
|
5559
|
+
* Only the bytes of a PNG, JPEG, GIF, or WebP of at most `IMAGE_LIMIT_BYTES` are kept, checked
|
|
5560
|
+
* here again whatever the runner said, under a new id outside the projection, so no runner
|
|
5561
|
+
* mirrors them and no prompt carries them: a post shows the image by its `image:` address.
|
|
5562
|
+
*/
|
|
5563
|
+
async uploadImage(actor, input) {
|
|
5564
|
+
const args = VerbInputs.upload_image.parse(input);
|
|
5565
|
+
await this.authorize(actor, "upload_image");
|
|
5566
|
+
if (actor.scope === void 0) {
|
|
5567
|
+
throw new BoardError("INVALID_STATE", "upload_image reads the file on the machine a turn runs on, so only a turn can call it");
|
|
5568
|
+
}
|
|
5569
|
+
if (this.imageSource === null) {
|
|
5570
|
+
throw new BoardError("NOT_FOUND", "this board has no runners to read an image from");
|
|
5571
|
+
}
|
|
5572
|
+
const bytes = await this.imageSource(actor, args.path);
|
|
5573
|
+
const type = imageTypeOf(bytes);
|
|
5574
|
+
if (type === null) {
|
|
5575
|
+
throw new BoardError("VALIDATION", `${args.path} is not a PNG, JPEG, GIF, or WebP image by its first bytes`);
|
|
5576
|
+
}
|
|
5577
|
+
if (bytes.byteLength > IMAGE_LIMIT_BYTES) {
|
|
5578
|
+
throw new BoardError("VALIDATION", `${args.path} is ${bytes.byteLength} bytes, over the ${IMAGE_LIMIT_BYTES} an image may be`);
|
|
5579
|
+
}
|
|
5580
|
+
return this.mutex.run(async () => {
|
|
5581
|
+
const id = this.newId();
|
|
5582
|
+
await writeFileAtomic(this.paths.image(id), bytes);
|
|
5583
|
+
await this.events.append("image.uploaded", actor.name, {
|
|
5584
|
+
id,
|
|
5585
|
+
type,
|
|
5586
|
+
size: bytes.byteLength
|
|
5587
|
+
});
|
|
5588
|
+
const name = path7.basename(args.path).replaceAll(/[[\]\\\r\n]/g, "");
|
|
5589
|
+
return {
|
|
5590
|
+
id,
|
|
5591
|
+
type,
|
|
5592
|
+
size: bytes.byteLength,
|
|
5593
|
+
markdown: ``
|
|
5594
|
+
};
|
|
5595
|
+
});
|
|
5596
|
+
}
|
|
5597
|
+
/** An uploaded image's bytes and kind, which its first bytes tell. */
|
|
5598
|
+
async readImage(id) {
|
|
5599
|
+
const file = this.paths.image(UlidSchema.parse(id));
|
|
5600
|
+
if (!await exists(file)) {
|
|
5601
|
+
throw new BoardError("NOT_FOUND", `image ${id} not found`);
|
|
5602
|
+
}
|
|
5603
|
+
const bytes = await readFile4(file);
|
|
5604
|
+
const type = imageTypeOf(bytes);
|
|
5605
|
+
if (type === null) {
|
|
5606
|
+
throw new BoardError("NOT_FOUND", `image ${id} is not an image the board shows`);
|
|
5607
|
+
}
|
|
5608
|
+
return { type, bytes };
|
|
5609
|
+
}
|
|
5238
5610
|
/** Every cron, ended ones included, oldest first. */
|
|
5239
5611
|
async listCrons() {
|
|
5240
5612
|
const crons = [];
|
|
5241
5613
|
for (const file of await listFiles(this.paths.crons())) {
|
|
5242
|
-
crons.push(await this.readCronFile(
|
|
5614
|
+
crons.push(await this.readCronFile(path7.join(this.paths.crons(), file)));
|
|
5243
5615
|
}
|
|
5244
5616
|
return crons;
|
|
5245
5617
|
}
|
|
@@ -5361,11 +5733,16 @@ ${current.body}`);
|
|
|
5361
5733
|
}
|
|
5362
5734
|
/**
|
|
5363
5735
|
* Where a cron fires for its citizen: the thread, the channel, or the scope's home it names, else
|
|
5364
|
-
* the conversation of the turn that set it when it wakes its setter,
|
|
5736
|
+
* the conversation of the turn that set it when it wakes its setter, a project's channels' in
|
|
5737
|
+
* the society scope included, else its home outside projects.
|
|
5365
5738
|
*/
|
|
5366
5739
|
async cronConversation(actor, agent2, named) {
|
|
5367
5740
|
const own = actor.name === agent2.name;
|
|
5368
5741
|
if (named.project === void 0 && named.thread_id === void 0 && named.channel === void 0) {
|
|
5742
|
+
if (own && actor.scope === SOCIETY_SCOPE && actor.place !== void 0) {
|
|
5743
|
+
await this.readActiveProject(actor.place);
|
|
5744
|
+
return { scope: SOCIETY_SCOPE, place: actor.place };
|
|
5745
|
+
}
|
|
5369
5746
|
return own && actor.scope !== void 0 ? this.cronConversation(actor, agent2, {
|
|
5370
5747
|
project: actor.scope,
|
|
5371
5748
|
thread_id: actor.thread,
|
|
@@ -5441,7 +5818,8 @@ ${current.body}`);
|
|
|
5441
5818
|
agent: cron2.agent,
|
|
5442
5819
|
project: cron2.scope,
|
|
5443
5820
|
...cron2.thread === void 0 ? {} : { thread: cron2.thread },
|
|
5444
|
-
...cron2.channel === void 0 ? {} : { channel: cron2.channel }
|
|
5821
|
+
...cron2.channel === void 0 ? {} : { channel: cron2.channel },
|
|
5822
|
+
...cron2.place === void 0 ? {} : { place: cron2.place }
|
|
5445
5823
|
};
|
|
5446
5824
|
}
|
|
5447
5825
|
async readCronFile(file) {
|
|
@@ -5626,6 +6004,13 @@ ${current.body}`);
|
|
|
5626
6004
|
watchTurns(source) {
|
|
5627
6005
|
this.turnsInFlight = source;
|
|
5628
6006
|
}
|
|
6007
|
+
/**
|
|
6008
|
+
* Where `upload_image` reads a file from: the runner the calling turn runs on, through the server
|
|
6009
|
+
* that hosts the runners. Without one, as in the admin CLI, nothing can be uploaded.
|
|
6010
|
+
*/
|
|
6011
|
+
readImagesWith(source) {
|
|
6012
|
+
this.imageSource = source;
|
|
6013
|
+
}
|
|
5629
6014
|
/**
|
|
5630
6015
|
* Refuses to end conversations while another citizen's turn is in one of them: `cut` picks the
|
|
5631
6016
|
* turns that would lose theirs. The caller's own turn is exempt, since ending a conversation
|
|
@@ -5645,17 +6030,20 @@ ${current.body}`);
|
|
|
5645
6030
|
async openThreadIds(dir, keep) {
|
|
5646
6031
|
const ids = /* @__PURE__ */ new Set();
|
|
5647
6032
|
for (const file of await listFiles(dir)) {
|
|
5648
|
-
const doc = await readMarkdown(
|
|
6033
|
+
const doc = await readMarkdown(path7.join(dir, file), ThreadFrontmatterSchema);
|
|
5649
6034
|
if (doc.data.state === "open" && keep({ ...doc.data, body: doc.body })) {
|
|
5650
6035
|
ids.add(doc.data.id);
|
|
5651
6036
|
}
|
|
5652
6037
|
}
|
|
5653
6038
|
return ids;
|
|
5654
6039
|
}
|
|
5655
|
-
/**
|
|
6040
|
+
/**
|
|
6041
|
+
* Refuses to archive a project while a turn runs in it, in a citizen's conversation of its
|
|
6042
|
+
* channels in the society scope, or in a thread its archive closes.
|
|
6043
|
+
*/
|
|
5656
6044
|
async assertProjectQuiet(actor, slug) {
|
|
5657
6045
|
const threads = await this.openThreadIds(this.paths.threads(slug), () => true);
|
|
5658
|
-
this.assertNoTurnCut(actor, `project ${slug} cannot be archived`, (turn2) => turn2.scope === slug || turn2.thread !== void 0 && threads.has(turn2.thread));
|
|
6046
|
+
this.assertNoTurnCut(actor, `project ${slug} cannot be archived`, (turn2) => turn2.scope === slug || turn2.place === slug || turn2.thread !== void 0 && threads.has(turn2.thread));
|
|
5659
6047
|
}
|
|
5660
6048
|
async closeThread(actor, input) {
|
|
5661
6049
|
const args = VerbInputs.close_thread.parse(input);
|
|
@@ -6196,7 +6584,8 @@ ${args.summary}`);
|
|
|
6196
6584
|
filedFrom: {
|
|
6197
6585
|
scope: actor.scope,
|
|
6198
6586
|
...actor.thread === void 0 ? {} : { thread: actor.thread },
|
|
6199
|
-
...actor.channel === void 0 ? {} : { channel: actor.channel }
|
|
6587
|
+
...actor.channel === void 0 ? {} : { channel: actor.channel },
|
|
6588
|
+
...actor.place === void 0 ? {} : { place: actor.place }
|
|
6200
6589
|
}
|
|
6201
6590
|
},
|
|
6202
6591
|
charter: parsed.data
|
|
@@ -6455,6 +6844,8 @@ ${rationale}`}`);
|
|
|
6455
6844
|
return this.updateCron(actor, VerbInputs.update_cron.parse(input));
|
|
6456
6845
|
case "remove_cron":
|
|
6457
6846
|
return this.removeCron(actor, VerbInputs.remove_cron.parse(input));
|
|
6847
|
+
case "upload_image":
|
|
6848
|
+
return this.uploadImage(actor, VerbInputs.upload_image.parse(input));
|
|
6458
6849
|
default:
|
|
6459
6850
|
throw new BoardError("VALIDATION", `unknown verb ${String(verb)}`);
|
|
6460
6851
|
}
|
|
@@ -6501,7 +6892,7 @@ ${rationale}`}`);
|
|
|
6501
6892
|
}
|
|
6502
6893
|
async readAgentRoleBody(agent2) {
|
|
6503
6894
|
const file = this.paths.agentRole(agent2);
|
|
6504
|
-
return await exists(file) ? (await readMarkdown(file,
|
|
6895
|
+
return await exists(file) ? (await readMarkdown(file, z16.record(z16.string(), z16.unknown()))).body : "";
|
|
6505
6896
|
}
|
|
6506
6897
|
/** The steps of one finished turn, as the runner handed them over when it ended. */
|
|
6507
6898
|
/**
|
|
@@ -6606,19 +6997,20 @@ ${rationale}`}`);
|
|
|
6606
6997
|
project: input.project,
|
|
6607
6998
|
...input.thread === void 0 ? {} : { thread: input.thread },
|
|
6608
6999
|
...input.channel === void 0 ? {} : { channel: input.channel },
|
|
7000
|
+
...input.place === void 0 ? {} : { place: input.place },
|
|
6609
7001
|
messages: [...input.messages]
|
|
6610
7002
|
});
|
|
6611
7003
|
});
|
|
6612
7004
|
}
|
|
6613
7005
|
/**
|
|
6614
7006
|
* Where a conversation's session ids and last turn are kept: the scope's home, or beside it a
|
|
6615
|
-
* thread's or a
|
|
7007
|
+
* thread's, a channel's, or a project's, named by `part` as `conversationPart` gives it.
|
|
6616
7008
|
*/
|
|
6617
7009
|
sessionDir(agent2, scope, part) {
|
|
6618
7010
|
return part === void 0 ? this.paths.agentProject(agent2, scope) : this.paths.agentConversation(agent2, scope, part);
|
|
6619
7011
|
}
|
|
6620
7012
|
async readSessions(agent2, project2, part) {
|
|
6621
|
-
const file =
|
|
7013
|
+
const file = path7.join(this.sessionDir(agent2, project2, part), "sessions.json");
|
|
6622
7014
|
return await exists(file) ? readJson(file, SessionsFileSchema) : {};
|
|
6623
7015
|
}
|
|
6624
7016
|
/**
|
|
@@ -6629,15 +7021,34 @@ ${rationale}`}`);
|
|
|
6629
7021
|
await this.mutex.run(async () => {
|
|
6630
7022
|
const dir = this.sessionDir(agent2, project2, part);
|
|
6631
7023
|
await ensureDir(dir);
|
|
6632
|
-
const file =
|
|
7024
|
+
const file = path7.join(dir, "sessions.json");
|
|
6633
7025
|
const current = await exists(file) ? await readJson(file, SessionsFileSchema) : {};
|
|
6634
7026
|
const kept = current.runner === void 0 || current.runner === runner2 ? current : {};
|
|
6635
7027
|
await writeJson(file, { ...kept, [cli]: sessionId, runner: runner2 });
|
|
6636
7028
|
});
|
|
6637
7029
|
}
|
|
6638
|
-
/**
|
|
7030
|
+
/**
|
|
7031
|
+
* Drops a conversation's session for a CLI, so its next turn starts a new one, when the record
|
|
7032
|
+
* still names `sessionId`; returns whether it did.
|
|
7033
|
+
*/
|
|
7034
|
+
async dropSession(agent2, project2, cli, sessionId, part) {
|
|
7035
|
+
return this.mutex.run(async () => {
|
|
7036
|
+
const file = path7.join(this.sessionDir(agent2, project2, part), "sessions.json");
|
|
7037
|
+
if (!await exists(file)) {
|
|
7038
|
+
return false;
|
|
7039
|
+
}
|
|
7040
|
+
const current = await readJson(file, SessionsFileSchema);
|
|
7041
|
+
if (current[cli] !== sessionId) {
|
|
7042
|
+
return false;
|
|
7043
|
+
}
|
|
7044
|
+
const { [cli]: _dropped, ...kept } = current;
|
|
7045
|
+
await writeJson(file, kept);
|
|
7046
|
+
return true;
|
|
7047
|
+
});
|
|
7048
|
+
}
|
|
7049
|
+
/** The last turn of one conversation: the scope's home, a channel's, a project's, or a thread's. */
|
|
6639
7050
|
async readLastTurn(agent2, project2, part) {
|
|
6640
|
-
const file =
|
|
7051
|
+
const file = path7.join(this.sessionDir(agent2, project2, part), "last-turn.json");
|
|
6641
7052
|
return await exists(file) ? readJson(file, TurnRecordSchema) : null;
|
|
6642
7053
|
}
|
|
6643
7054
|
/** The latest turn a citizen took in a scope, in any of its conversations there. */
|
|
@@ -6645,8 +7056,9 @@ ${rationale}`}`);
|
|
|
6645
7056
|
let latest = await this.readLastTurn(agent2, project2);
|
|
6646
7057
|
const place = this.paths.agentProject(agent2, project2);
|
|
6647
7058
|
const parts = [
|
|
6648
|
-
...await listDirs(
|
|
6649
|
-
...(await listDirs(
|
|
7059
|
+
...await listDirs(path7.join(place, "threads")),
|
|
7060
|
+
...(await listDirs(path7.join(place, "channels"))).map((name) => `#${name}`),
|
|
7061
|
+
...(await listDirs(path7.join(place, "places"))).map((name) => `~${name}`)
|
|
6650
7062
|
];
|
|
6651
7063
|
for (const part of parts) {
|
|
6652
7064
|
const turn2 = await this.readLastTurn(agent2, project2, part);
|
|
@@ -6667,6 +7079,7 @@ ${rationale}`}`);
|
|
|
6667
7079
|
project: parsed.project,
|
|
6668
7080
|
...parsed.thread === void 0 ? {} : { thread: parsed.thread },
|
|
6669
7081
|
...parsed.channel === void 0 ? {} : { channel: parsed.channel },
|
|
7082
|
+
...parsed.place === void 0 ? {} : { place: parsed.place },
|
|
6670
7083
|
trigger: parsed.trigger.kind,
|
|
6671
7084
|
session: parsed.session,
|
|
6672
7085
|
runner: parsed.runner
|
|
@@ -6696,7 +7109,8 @@ ${rationale}`}`);
|
|
|
6696
7109
|
agent: parsed.agent,
|
|
6697
7110
|
project: parsed.project,
|
|
6698
7111
|
...parsed.thread === void 0 ? {} : { thread: parsed.thread },
|
|
6699
|
-
...parsed.channel === void 0 ? {} : { channel: parsed.channel }
|
|
7112
|
+
...parsed.channel === void 0 ? {} : { channel: parsed.channel },
|
|
7113
|
+
...parsed.place === void 0 ? {} : { place: parsed.place }
|
|
6700
7114
|
});
|
|
6701
7115
|
}
|
|
6702
7116
|
await this.events.append(failed ? "turn.failed" : "turn.completed", parsed.agent, {
|
|
@@ -6704,6 +7118,7 @@ ${rationale}`}`);
|
|
|
6704
7118
|
project: parsed.project,
|
|
6705
7119
|
...parsed.thread === void 0 ? {} : { thread: parsed.thread },
|
|
6706
7120
|
...parsed.channel === void 0 ? {} : { channel: parsed.channel },
|
|
7121
|
+
...parsed.place === void 0 ? {} : { place: parsed.place },
|
|
6707
7122
|
trigger: parsed.trigger.kind,
|
|
6708
7123
|
session: parsed.session,
|
|
6709
7124
|
exitReason: parsed.exitReason,
|
|
@@ -6713,7 +7128,9 @@ ${rationale}`}`);
|
|
|
6713
7128
|
model: parsed.model,
|
|
6714
7129
|
summary: parsed.status?.summary ?? null,
|
|
6715
7130
|
needsUserDecision: parsed.status?.needsUserDecision ?? false,
|
|
6716
|
-
error: parsed.error
|
|
7131
|
+
error: parsed.error,
|
|
7132
|
+
...parsed.resumeFailed === void 0 ? {} : { resumeFailed: parsed.resumeFailed },
|
|
7133
|
+
...parsed.sessionDropped === true ? { sessionDropped: true } : {}
|
|
6717
7134
|
});
|
|
6718
7135
|
});
|
|
6719
7136
|
}
|
|
@@ -6750,6 +7167,20 @@ ${rationale}`}`);
|
|
|
6750
7167
|
return task2;
|
|
6751
7168
|
});
|
|
6752
7169
|
}
|
|
7170
|
+
/**
|
|
7171
|
+
* Posts the board's word on a task's work into the task's thread, such as commits a runner
|
|
7172
|
+
* could not put on its branch: as the board, with every `@name` written as a plain name, so it
|
|
7173
|
+
* wakes nobody and counts toward no heartbeat. Null, posting nothing, once the thread is closed.
|
|
7174
|
+
*/
|
|
7175
|
+
async postTaskNotice(taskId, body) {
|
|
7176
|
+
return this.mutex.run(async () => {
|
|
7177
|
+
const found = await this.tryFindThread(taskId);
|
|
7178
|
+
if (found === null || found.thread.state !== "open") {
|
|
7179
|
+
return null;
|
|
7180
|
+
}
|
|
7181
|
+
return this.appendThreadMessage(SYSTEM_ACTOR.name, taskId, withoutMentions(body));
|
|
7182
|
+
});
|
|
7183
|
+
}
|
|
6753
7184
|
/**
|
|
6754
7185
|
* Records that a citizen's workspace in a conversation that has ended holds work on no branch, so
|
|
6755
7186
|
* the scheduler gives the citizen a closing turn there to decide about it. False, recording
|
|
@@ -6760,7 +7191,7 @@ ${rationale}`}`);
|
|
|
6760
7191
|
const payload = {
|
|
6761
7192
|
agent: input.agent,
|
|
6762
7193
|
project: input.scope,
|
|
6763
|
-
...conversation.kind === "thread" ? { thread: conversation.id } : { channel: conversation.name }
|
|
7194
|
+
...conversation.kind === "thread" ? { thread: conversation.id } : conversation.kind === "channel" ? { channel: conversation.name } : { place: conversation.project }
|
|
6764
7195
|
};
|
|
6765
7196
|
return this.mutex.run(async () => {
|
|
6766
7197
|
const earlier = (await this.events.readSince(null, Number.MAX_SAFE_INTEGER)).filter((event) => event.type === "workspace.leftovers" && Object.entries(payload).every(([key, value]) => event.payload[key] === value)).length;
|
|
@@ -6812,9 +7243,13 @@ ${rationale}`}`);
|
|
|
6812
7243
|
}
|
|
6813
7244
|
/**
|
|
6814
7245
|
* Whether a conversation with a workspace of its own has ended: a thread closed, a task ended
|
|
6815
|
-
* (for one from before tasks had threads), or a
|
|
7246
|
+
* (for one from before tasks had threads), a channel archived, or a project archived, which ends
|
|
7247
|
+
* every citizen's conversation of its channels in the society scope.
|
|
6816
7248
|
*/
|
|
6817
7249
|
async conversationEnded(conversation) {
|
|
7250
|
+
if (conversation.kind === "place") {
|
|
7251
|
+
return !await this.projectLive(conversation.project);
|
|
7252
|
+
}
|
|
6818
7253
|
if (conversation.kind === "channel") {
|
|
6819
7254
|
const place = conversation.scope === SOCIETY_SCOPE ? null : conversation.scope;
|
|
6820
7255
|
return !await this.channelOpen(channelRef(place, conversation.name));
|
|
@@ -6888,11 +7323,11 @@ ${rationale}`}`);
|
|
|
6888
7323
|
}
|
|
6889
7324
|
/** Small named state files under data/state, for cursors the scheduler must keep across restarts. */
|
|
6890
7325
|
async readState(name, schema, fallback) {
|
|
6891
|
-
const file =
|
|
7326
|
+
const file = path7.join(this.paths.state(), `${name}.json`);
|
|
6892
7327
|
return await exists(file) ? readJson(file, schema) : fallback;
|
|
6893
7328
|
}
|
|
6894
7329
|
async writeState(name, value) {
|
|
6895
|
-
await this.mutex.run(() => writeJson(
|
|
7330
|
+
await this.mutex.run(() => writeJson(path7.join(this.paths.state(), `${name}.json`), value));
|
|
6896
7331
|
}
|
|
6897
7332
|
/** The project's dashboard, empty at revision 0 when it has none. */
|
|
6898
7333
|
async readDashboard(slug) {
|
|
@@ -6909,7 +7344,7 @@ ${rationale}`}`);
|
|
|
6909
7344
|
return { project: slug, revision: 0, body: "" };
|
|
6910
7345
|
}
|
|
6911
7346
|
const { data, body } = await readLooseMarkdown(file);
|
|
6912
|
-
const revision =
|
|
7347
|
+
const revision = z16.number().int().min(0).safeParse(data["revision"]);
|
|
6913
7348
|
const updatedBy = NameSchema.safeParse(data["updatedBy"]);
|
|
6914
7349
|
const updatedAt = IsoDateTimeSchema.safeParse(data["updatedAt"]);
|
|
6915
7350
|
return {
|
|
@@ -6923,7 +7358,7 @@ ${rationale}`}`);
|
|
|
6923
7358
|
async listRunners() {
|
|
6924
7359
|
const runners = [];
|
|
6925
7360
|
for (const file of await listFiles(this.paths.runners())) {
|
|
6926
|
-
runners.push((await readMarkdown(
|
|
7361
|
+
runners.push((await readMarkdown(path7.join(this.paths.runners(), file), RunnerSchema)).data);
|
|
6927
7362
|
}
|
|
6928
7363
|
return runners;
|
|
6929
7364
|
}
|
|
@@ -6975,7 +7410,7 @@ ${rationale}`}`);
|
|
|
6975
7410
|
return this.runnerTokens.get(hashToken(token)) ?? null;
|
|
6976
7411
|
}
|
|
6977
7412
|
runnerTokensFile() {
|
|
6978
|
-
return
|
|
7413
|
+
return path7.join(this.paths.state(), "runners.json");
|
|
6979
7414
|
}
|
|
6980
7415
|
async readRunnerTokens() {
|
|
6981
7416
|
const file = this.runnerTokensFile();
|
|
@@ -7024,26 +7459,26 @@ ${rationale}`}`);
|
|
|
7024
7459
|
async listHomeConflicts(agent2) {
|
|
7025
7460
|
const home = this.paths.agent(agent2);
|
|
7026
7461
|
const found = [];
|
|
7027
|
-
const
|
|
7028
|
-
for (const entry of await
|
|
7462
|
+
const walk2 = async (dir, prefix) => {
|
|
7463
|
+
for (const entry of await readdir4(dir, { withFileTypes: true }).catch(() => [])) {
|
|
7029
7464
|
if (prefix === "" && SKIPPED_IN_HOME.has(entry.name)) {
|
|
7030
7465
|
continue;
|
|
7031
7466
|
}
|
|
7032
7467
|
const relative = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
7033
7468
|
if (entry.isDirectory()) {
|
|
7034
|
-
await
|
|
7469
|
+
await walk2(path7.join(dir, entry.name), relative);
|
|
7035
7470
|
continue;
|
|
7036
7471
|
}
|
|
7037
7472
|
const file = entry.isFile() ? conflictCopyOf(relative) : null;
|
|
7038
7473
|
if (file !== null) {
|
|
7039
|
-
const since = await this.homes.changedAt(home, relative) ?? (await stat3(
|
|
7474
|
+
const since = await this.homes.changedAt(home, relative) ?? (await stat3(path7.join(home, relative)).catch(() => null))?.mtime.toISOString();
|
|
7040
7475
|
if (since !== void 0) {
|
|
7041
7476
|
found.push({ path: relative, file, since });
|
|
7042
7477
|
}
|
|
7043
7478
|
}
|
|
7044
7479
|
}
|
|
7045
7480
|
};
|
|
7046
|
-
await
|
|
7481
|
+
await walk2(home, "");
|
|
7047
7482
|
return found.toSorted((a, b) => a.path.localeCompare(b.path));
|
|
7048
7483
|
}
|
|
7049
7484
|
/** The newest `limit` changes to an agent's home, newest first; empty while it is no repository. */
|
|
@@ -7120,7 +7555,7 @@ ${rationale}`}`);
|
|
|
7120
7555
|
async writeTurnRecord(record) {
|
|
7121
7556
|
const dir = this.sessionDir(record.agent, record.project, conversationPart(record));
|
|
7122
7557
|
await ensureDir(dir);
|
|
7123
|
-
await writeJson(
|
|
7558
|
+
await writeJson(path7.join(dir, "last-turn.json"), record);
|
|
7124
7559
|
}
|
|
7125
7560
|
// ---------------------------------------------------------------------------------------------
|
|
7126
7561
|
// Internals
|
|
@@ -7183,7 +7618,7 @@ ${charter.purpose}
|
|
|
7183
7618
|
|
|
7184
7619
|
${charter.purpose}
|
|
7185
7620
|
|
|
7186
|
-
Route every lesson with one question: about me, my craft, or the user, it goes in memory/core.md; about this codebase, it goes in the project's knowledge
|
|
7621
|
+
Route every lesson with one question: about me, my craft, or the user, it goes in memory/core.md; about how this codebase behaves, it goes in the repo's AGENTS.md or README; about this project as a place (runners, access, flow, where results live), it goes in the project's knowledge; something everyone should know, post it.
|
|
7187
7622
|
` + (seed.length === 0 ? "" : `
|
|
7188
7623
|
${SEED_INSTRUCTIONS_HEADING}
|
|
7189
7624
|
|
|
@@ -7200,7 +7635,7 @@ ${seed}
|
|
|
7200
7635
|
continue;
|
|
7201
7636
|
}
|
|
7202
7637
|
const file = this.paths.agentRole(agent2.name);
|
|
7203
|
-
const body = await exists(file) ? (await readMarkdown(file,
|
|
7638
|
+
const body = await exists(file) ? (await readMarkdown(file, z16.record(z16.string(), z16.unknown()))).body : "";
|
|
7204
7639
|
const at = body.indexOf(`
|
|
7205
7640
|
${SEED_INSTRUCTIONS_HEADING}
|
|
7206
7641
|
`);
|
|
@@ -7239,8 +7674,8 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7239
7674
|
async ensureAgentProject(name, slug) {
|
|
7240
7675
|
const dir = this.paths.agentProject(name, slug);
|
|
7241
7676
|
await ensureDir(dir);
|
|
7242
|
-
if (!await exists(
|
|
7243
|
-
await writeJson(
|
|
7677
|
+
if (!await exists(path7.join(dir, "sessions.json"))) {
|
|
7678
|
+
await writeJson(path7.join(dir, "sessions.json"), {});
|
|
7244
7679
|
}
|
|
7245
7680
|
}
|
|
7246
7681
|
/**
|
|
@@ -7272,7 +7707,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7272
7707
|
const at = turn2?.endedAt ?? turn2?.startedAt;
|
|
7273
7708
|
if (turn2 !== null && at !== void 0 && (lastTurnAt === void 0 || at > lastTurnAt)) {
|
|
7274
7709
|
lastTurnAt = at;
|
|
7275
|
-
lastTurnOutcome = `${turn2.trigger.kind} on ${scope}: ${turn2.exitReason ?? "running"}${turn2.status === null ? "" : `, ${turn2.status.summary.slice(0, 160)}`}`;
|
|
7710
|
+
lastTurnOutcome = `${turn2.trigger.kind} on ${scope}${turnConversation(turn2)}: ${turn2.exitReason ?? "running"}${turn2.status === null ? "" : `, ${turn2.status.summary.slice(0, 160)}`}`;
|
|
7276
7711
|
lastModel = turn2.model ?? void 0;
|
|
7277
7712
|
}
|
|
7278
7713
|
}
|
|
@@ -7492,7 +7927,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7492
7927
|
const file = this.paths.societySkillFile(skill2.name);
|
|
7493
7928
|
const replaced = await exists(file);
|
|
7494
7929
|
const taken = proposal2.skillFiles;
|
|
7495
|
-
const staged =
|
|
7930
|
+
const staged = path7.join(this.paths.proposalFiles(proposal2.id), "SKILL.md");
|
|
7496
7931
|
if (skill2.body === void 0 && (taken === void 0 || !await exists(staged))) {
|
|
7497
7932
|
throw new BoardError("INVALID_STATE", `proposal ${proposal2.id} holds no skill files`);
|
|
7498
7933
|
}
|
|
@@ -7555,8 +7990,8 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7555
7990
|
const dir = this.paths.proposalFiles(proposal2.id);
|
|
7556
7991
|
await rm2(dir, { recursive: true, force: true });
|
|
7557
7992
|
for (const entry of taken.files) {
|
|
7558
|
-
const target =
|
|
7559
|
-
await ensureDir(
|
|
7993
|
+
const target = path7.join(dir, ...entry.path.split("/"));
|
|
7994
|
+
await ensureDir(path7.dirname(target));
|
|
7560
7995
|
await writeFile4(target, contents.get(entry.path) ?? Buffer.alloc(0));
|
|
7561
7996
|
await chmod2(target, entry.executable ? 493 : 420);
|
|
7562
7997
|
}
|
|
@@ -7614,7 +8049,8 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7614
8049
|
const where = {
|
|
7615
8050
|
scope: turn2.project,
|
|
7616
8051
|
...turn2.thread === void 0 ? {} : { thread: turn2.thread },
|
|
7617
|
-
...turn2.channel === void 0 ? {} : { channel: turn2.channel }
|
|
8052
|
+
...turn2.channel === void 0 ? {} : { channel: turn2.channel },
|
|
8053
|
+
...turn2.place === void 0 ? {} : { place: turn2.place }
|
|
7618
8054
|
};
|
|
7619
8055
|
await this.mutex.run(async () => {
|
|
7620
8056
|
for (const proposal2 of await this.listProposals()) {
|
|
@@ -7646,16 +8082,25 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7646
8082
|
if (!isSafeRelativePath(entry.path)) {
|
|
7647
8083
|
continue;
|
|
7648
8084
|
}
|
|
7649
|
-
const bytes = await readFile4(
|
|
7650
|
-
const text = bytes === null || bytes.includes(0) ? null : bytes.toString("utf8");
|
|
8085
|
+
const bytes = await readFile4(path7.join(dir, ...entry.path.split("/"))).catch(() => null);
|
|
7651
8086
|
files.push({
|
|
7652
8087
|
...entry,
|
|
7653
|
-
|
|
7654
|
-
truncated: text !== null && text.length > SKILL_TEXT_LIMIT_CHARS
|
|
8088
|
+
...bytes === null ? { text: null, truncated: false } : skillFileText(bytes)
|
|
7655
8089
|
});
|
|
7656
8090
|
}
|
|
7657
8091
|
return files;
|
|
7658
8092
|
}
|
|
8093
|
+
/**
|
|
8094
|
+
* A society skill's folder as it is published, with each file's text, for the skill's view; an
|
|
8095
|
+
* unknown skill, or a folder without a SKILL.md, is not found.
|
|
8096
|
+
*/
|
|
8097
|
+
async readSocietySkillFiles(name) {
|
|
8098
|
+
const files = await readSkillFolder(this.paths.societySkill(NameSchema.parse(name)));
|
|
8099
|
+
if (files === null) {
|
|
8100
|
+
throw new BoardError("NOT_FOUND", `the society has no skill ${name}`);
|
|
8101
|
+
}
|
|
8102
|
+
return files;
|
|
8103
|
+
}
|
|
7659
8104
|
async validateAddAgent(input) {
|
|
7660
8105
|
if (await exists(this.paths.agentFile(input.name))) {
|
|
7661
8106
|
throw new BoardError("ALREADY_EXISTS", `agent ${input.name} already exists`);
|
|
@@ -7786,7 +8231,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7786
8231
|
this.assertNoTurnCut(actor, `${ref} cannot be archived`, (turn2) => turn2.channel === channel && turn2.scope === scope || turn2.thread !== void 0 && closing.has(turn2.thread));
|
|
7787
8232
|
const threadsClosed = [];
|
|
7788
8233
|
for (const file of await listFiles(threads)) {
|
|
7789
|
-
const doc = await readMarkdown(
|
|
8234
|
+
const doc = await readMarkdown(path7.join(threads, file), ThreadFrontmatterSchema);
|
|
7790
8235
|
if (doc.data.channel !== ref || doc.data.state !== "open") {
|
|
7791
8236
|
continue;
|
|
7792
8237
|
}
|
|
@@ -7892,7 +8337,8 @@ ${charter.purpose}
|
|
|
7892
8337
|
}
|
|
7893
8338
|
}
|
|
7894
8339
|
/**
|
|
7895
|
-
* Every member leaves, every open thread on its channels closes,
|
|
8340
|
+
* Every member leaves, every open thread on its channels closes, every citizen's conversation of
|
|
8341
|
+
* its channels in the society scope ends with the crons there, and the project is marked; its
|
|
7896
8342
|
* files, repository, and history stay. The notice is the board's, since the reason is free text
|
|
7897
8343
|
* a proposer wrote and a board-authored mention would wake whoever it names.
|
|
7898
8344
|
*/
|
|
@@ -7917,7 +8363,7 @@ ${charter.purpose}
|
|
|
7917
8363
|
const threads = this.paths.threads(slug);
|
|
7918
8364
|
const threadsClosed = [];
|
|
7919
8365
|
for (const file of await listFiles(threads)) {
|
|
7920
|
-
const doc = await readMarkdown(
|
|
8366
|
+
const doc = await readMarkdown(path7.join(threads, file), ThreadFrontmatterSchema);
|
|
7921
8367
|
if (doc.data.state !== "open") {
|
|
7922
8368
|
continue;
|
|
7923
8369
|
}
|
|
@@ -7935,7 +8381,7 @@ ${charter.purpose}
|
|
|
7935
8381
|
});
|
|
7936
8382
|
threadsClosed.push(doc.data.id);
|
|
7937
8383
|
}
|
|
7938
|
-
await this.endCronsUnlocked(by, `project ${slug} was archived`, (cron2) => cron2.scope === slug || cron2.thread !== void 0 && threadsClosed.includes(cron2.thread));
|
|
8384
|
+
await this.endCronsUnlocked(by, `project ${slug} was archived`, (cron2) => cron2.scope === slug || cron2.place === slug || cron2.thread !== void 0 && threadsClosed.includes(cron2.thread));
|
|
7939
8385
|
const project2 = await this.updateProject(slug, (p) => ({
|
|
7940
8386
|
...p,
|
|
7941
8387
|
members: [],
|
|
@@ -7992,10 +8438,10 @@ ${charter.purpose}
|
|
|
7992
8438
|
...project2,
|
|
7993
8439
|
members: project2.members.filter((member) => member !== name)
|
|
7994
8440
|
}));
|
|
7995
|
-
const sessions =
|
|
8441
|
+
const sessions = path7.join(this.paths.agentProject(name, slug), "sessions.json");
|
|
7996
8442
|
if (await exists(sessions)) {
|
|
7997
8443
|
const archived = `sessions.archived.${ts.replace(/[:.]/g, "-")}.json`;
|
|
7998
|
-
await rename3(sessions,
|
|
8444
|
+
await rename3(sessions, path7.join(this.paths.agentProject(name, slug), archived));
|
|
7999
8445
|
}
|
|
8000
8446
|
}
|
|
8001
8447
|
this.tokenIndex.delete(current.tokenHash);
|
|
@@ -8075,6 +8521,17 @@ ${charter.purpose}
|
|
|
8075
8521
|
return false;
|
|
8076
8522
|
}
|
|
8077
8523
|
}
|
|
8524
|
+
/**
|
|
8525
|
+
* Whether a project exists and is not archived, so citizens' conversations of its channels in
|
|
8526
|
+
* the society scope are still going.
|
|
8527
|
+
*/
|
|
8528
|
+
async projectLive(slug) {
|
|
8529
|
+
try {
|
|
8530
|
+
return (await this.readProject(slug)).archived === void 0;
|
|
8531
|
+
} catch {
|
|
8532
|
+
return false;
|
|
8533
|
+
}
|
|
8534
|
+
}
|
|
8078
8535
|
/**
|
|
8079
8536
|
* A channel that takes new posts, threads, tasks, and followers: it exists, it is not archived,
|
|
8080
8537
|
* and its project is not archived.
|
|
@@ -8325,7 +8782,7 @@ ${charter.purpose}
|
|
|
8325
8782
|
if (since !== null && id <= since || upto !== void 0 && id > upto) {
|
|
8326
8783
|
continue;
|
|
8327
8784
|
}
|
|
8328
|
-
const doc = await readMarkdown(
|
|
8785
|
+
const doc = await readMarkdown(path7.join(dir, file), MessageFrontmatterSchema);
|
|
8329
8786
|
messages.push({ ...doc.data, body: doc.body });
|
|
8330
8787
|
}
|
|
8331
8788
|
return messages;
|
|
@@ -8382,7 +8839,7 @@ function globals() {
|
|
|
8382
8839
|
function print(value, human) {
|
|
8383
8840
|
console.log(globals().json ? JSON.stringify(value, null, 2) : human());
|
|
8384
8841
|
}
|
|
8385
|
-
async function
|
|
8842
|
+
async function open3() {
|
|
8386
8843
|
return Board.open(globals().data);
|
|
8387
8844
|
}
|
|
8388
8845
|
async function actorFor(board, as) {
|
|
@@ -8428,7 +8885,7 @@ user.command("token").description("Issue the user a new token and print it once;
|
|
|
8428
8885
|
if (!opts.rotate) {
|
|
8429
8886
|
throw new BoardError("VALIDATION", "the board keeps only a hash of the user token, so it cannot show it again; `user token --rotate` issues a new one");
|
|
8430
8887
|
}
|
|
8431
|
-
const board = await
|
|
8888
|
+
const board = await open3();
|
|
8432
8889
|
const userToken = await board.rotateUserToken();
|
|
8433
8890
|
print({ userToken }, () => [
|
|
8434
8891
|
`User token (shown once): ${userToken}`,
|
|
@@ -8437,7 +8894,7 @@ user.command("token").description("Issue the user a new token and print it once;
|
|
|
8437
8894
|
});
|
|
8438
8895
|
var project = program.command("project").description("Manage projects");
|
|
8439
8896
|
project.command("add <slug>").description("Add a project with its default channels").option("--name <name>", "display name").option("--repo <url>", "git remote").option("--on-done <effect>", "what finishing a task does: none, or merge its branch").action(async (slug, opts) => {
|
|
8440
|
-
const board = await
|
|
8897
|
+
const board = await open3();
|
|
8441
8898
|
const added = await board.addProject(board.userActor(), {
|
|
8442
8899
|
slug,
|
|
8443
8900
|
name: opts.name,
|
|
@@ -8447,7 +8904,7 @@ project.command("add <slug>").description("Add a project with its default channe
|
|
|
8447
8904
|
print(added, () => `Project ${added.slug} added with channels ${added.channels.join(", ")}`);
|
|
8448
8905
|
});
|
|
8449
8906
|
project.command("configure <slug>").description("Set a project's completion effect").requiredOption("--on-done <effect>", "none, or merge").option("--as <agent>", "act as this agent instead of the user").action(async (slug, opts) => {
|
|
8450
|
-
const board = await
|
|
8907
|
+
const board = await open3();
|
|
8451
8908
|
const configured = await board.configureProject(await actorFor(board, opts.as), {
|
|
8452
8909
|
project: slug,
|
|
8453
8910
|
on_done: CompletionEffectSchema.parse(opts.onDone)
|
|
@@ -8455,7 +8912,7 @@ project.command("configure <slug>").description("Set a project's completion effe
|
|
|
8455
8912
|
print(configured, () => `Project ${configured.slug}: on done ${configured.onDone}`);
|
|
8456
8913
|
});
|
|
8457
8914
|
project.command("archive <slug>").description("Archive a project with no task in play: its members leave and its threads close").requiredOption("--reason <text>", "why the project is archived").action(async (slug, opts) => {
|
|
8458
|
-
const board = await
|
|
8915
|
+
const board = await open3();
|
|
8459
8916
|
const archived = await board.archiveProject(board.userActor(), {
|
|
8460
8917
|
project: slug,
|
|
8461
8918
|
reason: opts.reason
|
|
@@ -8463,13 +8920,13 @@ project.command("archive <slug>").description("Archive a project with no task in
|
|
|
8463
8920
|
print(archived, () => `Project ${archived.slug} archived`);
|
|
8464
8921
|
});
|
|
8465
8922
|
project.command("list").description("List projects").action(async () => {
|
|
8466
|
-
const board = await
|
|
8923
|
+
const board = await open3();
|
|
8467
8924
|
const projects = await board.listProjects();
|
|
8468
8925
|
print(projects, () => projects.map((p) => p.archived === void 0 ? `${p.slug} ${p.name} members: ${p.members.join(", ") || "none"}` : `${p.slug} ${p.name} archived ${p.archived.at} by ${p.archived.by}`).join("\n"));
|
|
8469
8926
|
});
|
|
8470
8927
|
var runner = program.command("runner").description("Manage runners");
|
|
8471
8928
|
runner.command("add <name>").description("Register a runner and print its token once").action(async (name) => {
|
|
8472
|
-
const board = await
|
|
8929
|
+
const board = await open3();
|
|
8473
8930
|
const { runner: added, token } = await board.addRunner(board.userActor(), name);
|
|
8474
8931
|
print({ runner: added, token }, () => [
|
|
8475
8932
|
`Runner ${added.name} added`,
|
|
@@ -8478,13 +8935,13 @@ runner.command("add <name>").description("Register a runner and print its token
|
|
|
8478
8935
|
].join("\n"));
|
|
8479
8936
|
});
|
|
8480
8937
|
runner.command("list").description("List runners and what they offer").action(async () => {
|
|
8481
|
-
const board = await
|
|
8938
|
+
const board = await open3();
|
|
8482
8939
|
const runners = await board.listRunners();
|
|
8483
8940
|
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"));
|
|
8484
8941
|
});
|
|
8485
8942
|
var agent = program.command("agent").description("Manage agents");
|
|
8486
8943
|
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) => {
|
|
8487
|
-
const board = await
|
|
8944
|
+
const board = await open3();
|
|
8488
8945
|
const cli = opts.cli === void 0 ? null : CliKindSchema.parse(opts.cli);
|
|
8489
8946
|
const { agent: added, token } = await board.addAgent(board.userActor(), {
|
|
8490
8947
|
name,
|
|
@@ -8497,7 +8954,7 @@ agent.command("add <name>").description("Add an agent and print its board token
|
|
|
8497
8954
|
print({ agent: added, token }, () => [`Agent ${added.name} added as ${added.role}`, `Token (shown once): ${token}`].join("\n"));
|
|
8498
8955
|
});
|
|
8499
8956
|
agent.command("runner <name> [runner]").description("Show or set the runner a citizen's work outside any project runs on; `none` pins it again on its next turn").action(async (name, chosen) => {
|
|
8500
|
-
const board = await
|
|
8957
|
+
const board = await open3();
|
|
8501
8958
|
if (chosen === void 0) {
|
|
8502
8959
|
const current = await board.readAgent(name);
|
|
8503
8960
|
print({ agent: name, homeRunner: current.homeRunner ?? null }, () => current.homeRunner === void 0 ? `${name} is not pinned; its next turn outside a project pins it` : `${name} runs on ${current.homeRunner}`);
|
|
@@ -8507,7 +8964,7 @@ agent.command("runner <name> [runner]").description("Show or set the runner a ci
|
|
|
8507
8964
|
print({ agent: name, homeRunner: updated.homeRunner ?? null }, () => updated.homeRunner === void 0 ? `${name} will be pinned on its next turn` : `${name} runs on ${updated.homeRunner} from its next turn`);
|
|
8508
8965
|
});
|
|
8509
8966
|
agent.command("list").description("List agents").action(async () => {
|
|
8510
|
-
const board = await
|
|
8967
|
+
const board = await open3();
|
|
8511
8968
|
const agents = await board.listAgents();
|
|
8512
8969
|
const members = await board.listMembers();
|
|
8513
8970
|
print(agents, () => agents.map((a) => {
|
|
@@ -8516,28 +8973,28 @@ agent.command("list").description("List agents").action(async () => {
|
|
|
8516
8973
|
}).join("\n"));
|
|
8517
8974
|
});
|
|
8518
8975
|
agent.command("model <name> [model]").description("Set the model a member's turns run with, from its next turn; no model means the CLI's default").action(async (name, model) => {
|
|
8519
|
-
const board = await
|
|
8976
|
+
const board = await open3();
|
|
8520
8977
|
const updated = await board.setAgentModel(board.userActor(), name, model ?? null);
|
|
8521
8978
|
print(updated, () => `${updated.name} runs with ${updated.model ?? "its CLI's default model"} from its next turn`);
|
|
8522
8979
|
});
|
|
8523
8980
|
agent.command("effort <name> [effort]").description("Set the reasoning effort a member's turns run with, from its next turn, such as high; no effort means the model's default").action(async (name, effort) => {
|
|
8524
|
-
const board = await
|
|
8981
|
+
const board = await open3();
|
|
8525
8982
|
const updated = await board.setAgentEffort(board.userActor(), name, effort ?? null);
|
|
8526
8983
|
print(updated, () => `${updated.name} runs with ${updated.effort === void 0 ? "its model's default effort" : `${updated.effort} effort`} from its next turn`);
|
|
8527
8984
|
});
|
|
8528
8985
|
agent.command("retire <name>").description("Retire a member: no more wakes, claims released, token revoked, sessions archived").requiredOption("--reason <text>", "why").action(async (name, opts) => {
|
|
8529
|
-
const board = await
|
|
8986
|
+
const board = await open3();
|
|
8530
8987
|
const retired = await board.retireAgent(board.userActor(), { name, reason: opts.reason });
|
|
8531
8988
|
print(retired, () => `Agent ${retired.name} retired: ${opts.reason}`);
|
|
8532
8989
|
});
|
|
8533
8990
|
var proposal = program.command("proposal").description("Governance proposals");
|
|
8534
8991
|
proposal.command("list").description("List proposals").action(async () => {
|
|
8535
|
-
const board = await
|
|
8992
|
+
const board = await open3();
|
|
8536
8993
|
const proposals = await board.listProposals();
|
|
8537
8994
|
print(proposals, () => proposals.length === 0 ? "No proposals." : proposals.map((p) => `${p.id} ${p.kind} ${p.status} by ${p.proposedBy}`).join("\n"));
|
|
8538
8995
|
});
|
|
8539
8996
|
proposal.command("show <id>").description("Show a proposal with its charter and rationale").action(async (id) => {
|
|
8540
|
-
const board = await
|
|
8997
|
+
const board = await open3();
|
|
8541
8998
|
const found = await board.readProposal(id);
|
|
8542
8999
|
print(found, () => [
|
|
8543
9000
|
`${found.id} ${found.kind} ${found.status} by ${found.proposedBy}`,
|
|
@@ -8547,7 +9004,7 @@ proposal.command("show <id>").description("Show a proposal with its charter and
|
|
|
8547
9004
|
].filter((line) => line.length > 0).join("\n"));
|
|
8548
9005
|
});
|
|
8549
9006
|
proposal.command("create").description("Propose a member, role, channel, reallocation, or retirement").requiredOption("--kind <kind>", "member, role, channel, reallocation, or retirement").requiredOption("--charter <json>", "the charter as a JSON object").option("--rationale <text>", "why", "").option("--as <agent>", "act as this agent instead of the user").action(async (opts) => {
|
|
8550
|
-
const board = await
|
|
9007
|
+
const board = await open3();
|
|
8551
9008
|
const created = await board.propose(await actorFor(board, opts.as), {
|
|
8552
9009
|
kind: ProposalKindSchema.parse(opts.kind),
|
|
8553
9010
|
charter: parseCharter(opts.charter),
|
|
@@ -8556,7 +9013,7 @@ proposal.command("create").description("Propose a member, role, channel, realloc
|
|
|
8556
9013
|
print(created, () => `Proposal ${created.id} (${created.kind}) is ${created.status}`);
|
|
8557
9014
|
});
|
|
8558
9015
|
proposal.command("approve <id>").description("Approve a proposal; the board provisions it").option("--reason <text>", "a note for the record").option("--as <agent>", "act as this agent instead of the user").action(async (id, opts) => {
|
|
8559
|
-
const board = await
|
|
9016
|
+
const board = await open3();
|
|
8560
9017
|
const decision = await board.approve(await actorFor(board, opts.as), {
|
|
8561
9018
|
proposal_id: id,
|
|
8562
9019
|
...opts.reason === void 0 ? {} : { reason: opts.reason }
|
|
@@ -8565,7 +9022,7 @@ proposal.command("approve <id>").description("Approve a proposal; the board prov
|
|
|
8565
9022
|
print({ decision, proposal: after }, () => `Proposal ${id} approved; it is now ${after.status}`);
|
|
8566
9023
|
});
|
|
8567
9024
|
proposal.command("reject <id>").description("Reject a proposal with a reason").requiredOption("--reason <text>", "why").option("--as <agent>", "act as this agent instead of the user").action(async (id, opts) => {
|
|
8568
|
-
const board = await
|
|
9025
|
+
const board = await open3();
|
|
8569
9026
|
const decision = await board.reject(await actorFor(board, opts.as), {
|
|
8570
9027
|
proposal_id: id,
|
|
8571
9028
|
reason: opts.reason
|
|
@@ -8574,14 +9031,14 @@ proposal.command("reject <id>").description("Reject a proposal with a reason").r
|
|
|
8574
9031
|
});
|
|
8575
9032
|
var role = program.command("role").description("Role charters");
|
|
8576
9033
|
role.command("list").description("List role charters").action(async () => {
|
|
8577
|
-
const board = await
|
|
9034
|
+
const board = await open3();
|
|
8578
9035
|
const roles = await board.listRoles();
|
|
8579
9036
|
print(roles, () => roles.map((r) => `${r.name} replicas up to ${r.maxReplicas} backlog threshold ${r.backlogThreshold} wakes on ${r.wakeTriggers.join(", ") || "direct wakes only"}`).join("\n"));
|
|
8580
9037
|
});
|
|
8581
9038
|
role.command("add <name>").description("Write a new role charter directly as the user").requiredOption("--purpose <text>", "what the role is for; its members read this every turn").option("--verbs <verbs...>", "board verbs granted; the member verbs by default").option("--triggers <triggers...>", "wakes it opts into: user_post, ops_event, heartbeat", [
|
|
8582
9039
|
"heartbeat"
|
|
8583
9040
|
]).action(async (name, opts) => {
|
|
8584
|
-
const board = await
|
|
9041
|
+
const board = await open3();
|
|
8585
9042
|
const added = await board.setRoleCharter(board.userActor(), {
|
|
8586
9043
|
name,
|
|
8587
9044
|
purpose: opts.purpose,
|
|
@@ -8591,7 +9048,7 @@ role.command("add <name>").description("Write a new role charter directly as the
|
|
|
8591
9048
|
print(added, () => `Role ${added.name} written with ${added.verbs.length} verbs`);
|
|
8592
9049
|
});
|
|
8593
9050
|
role.command("set <name>").description("Change a charter directly as the user: scaling cap, threshold, or purpose").option("--max-replicas <n>", "active members of the role per project the scheduler may reach").option("--backlog-threshold <n>", "load per member that adds a replica").option("--purpose <text>", "the charter's purpose").action(async (name, opts) => {
|
|
8594
|
-
const board = await
|
|
9051
|
+
const board = await open3();
|
|
8595
9052
|
const current = await board.readRole(name);
|
|
8596
9053
|
const updated = await board.setRoleCharter(board.userActor(), {
|
|
8597
9054
|
...current,
|
|
@@ -8603,7 +9060,7 @@ role.command("set <name>").description("Change a charter directly as the user: s
|
|
|
8603
9060
|
});
|
|
8604
9061
|
var channelCommand = program.command("channel").description("Channels");
|
|
8605
9062
|
channelCommand.command("add <ref>").description("Open a channel: <name> for the society or <project>/<name>").requiredOption("--purpose <text>", "what the channel is for").option("--as <agent>", "act as this agent instead of the user").action(async (ref, opts) => {
|
|
8606
|
-
const board = await
|
|
9063
|
+
const board = await open3();
|
|
8607
9064
|
const parsed = parseChannelRef(ref);
|
|
8608
9065
|
const added = await board.addChannel(await actorFor(board, opts.as), {
|
|
8609
9066
|
project: parsed.project,
|
|
@@ -8613,7 +9070,7 @@ channelCommand.command("add <ref>").description("Open a channel: <name> for the
|
|
|
8613
9070
|
print({ channel: added }, () => `Channel ${added} opened`);
|
|
8614
9071
|
});
|
|
8615
9072
|
channelCommand.command("archive <ref>").description("Archive a channel whose workstream is done, once no task filed there is in play").requiredOption("--reason <text>", "why its work is done").option("--as <agent>", "act as this agent instead of the user").action(async (ref, opts) => {
|
|
8616
|
-
const board = await
|
|
9073
|
+
const board = await open3();
|
|
8617
9074
|
const archived = await board.archiveChannel(await actorFor(board, opts.as), {
|
|
8618
9075
|
channel: ref,
|
|
8619
9076
|
reason: opts.reason
|
|
@@ -8621,12 +9078,12 @@ channelCommand.command("archive <ref>").description("Archive a channel whose wor
|
|
|
8621
9078
|
print(archived, () => `Channel ${ref} archived: ${archived.reason}`);
|
|
8622
9079
|
});
|
|
8623
9080
|
program.command("signals").description("Recent operations signals, oldest first").option("--limit <n>", "how many", "50").action(async (opts) => {
|
|
8624
|
-
const board = await
|
|
9081
|
+
const board = await open3();
|
|
8625
9082
|
const signals = await board.listSignals(Number(opts.limit));
|
|
8626
9083
|
print(signals, () => signals.length === 0 ? "No signals yet." : signals.map((s) => `[${s.ts}] ${s.signal.kind} ${s.signal.summary}`).join("\n"));
|
|
8627
9084
|
});
|
|
8628
9085
|
program.command("post <channel> <body>").description("Post a message to a channel, or into one of its threads").option("--as <agent>", "act as this agent instead of the user").option("--thread <threadId>", "post into the thread, which must hang off the channel").action(async (channel, body, opts) => {
|
|
8629
|
-
const board = await
|
|
9086
|
+
const board = await open3();
|
|
8630
9087
|
const who = await actorFor(board, opts.as);
|
|
8631
9088
|
const message = await board.postMessage(who, {
|
|
8632
9089
|
channel,
|
|
@@ -8636,13 +9093,13 @@ program.command("post <channel> <body>").description("Post a message to a channe
|
|
|
8636
9093
|
print(message, () => `Posted ${message.id} to ${message.channel}${message.thread === void 0 ? "" : ` (thread ${message.thread})`}`);
|
|
8637
9094
|
});
|
|
8638
9095
|
program.command("ask <text>").description("Post to the society's general channel as the user; the front desk routes it").action(async (text) => {
|
|
8639
|
-
const board = await
|
|
9096
|
+
const board = await open3();
|
|
8640
9097
|
const message = await board.postMessage(board.userActor(), { channel: "general", body: text });
|
|
8641
9098
|
print(message, () => `Posted ${message.id} to general; the concierge wakes on it`);
|
|
8642
9099
|
});
|
|
8643
9100
|
var task = program.command("task").description("Manage tasks");
|
|
8644
9101
|
task.command("create <project> <title>").description("Create a task with a plan, or with one stage anyone in the project may take").option("--body <text>", "task body", "").option("--parent <id>", "parent task id").option("--plan <json>", `stages, for example '[{"name":"analysis","role":"analyst"}]'`).option("--channel <name>", "the project's channel to file it in, such as a release's", "general").option("--as <agent>", "act as this agent instead of the user").action(async (projectSlug, title, opts) => {
|
|
8645
|
-
const board = await
|
|
9102
|
+
const board = await open3();
|
|
8646
9103
|
const who = await actorFor(board, opts.as);
|
|
8647
9104
|
const created = await board.createTask(who, {
|
|
8648
9105
|
project: projectSlug,
|
|
@@ -8658,17 +9115,17 @@ task.command("create <project> <title>").description("Create a task with a plan,
|
|
|
8658
9115
|
].join("\n"));
|
|
8659
9116
|
});
|
|
8660
9117
|
task.command("claim <id>").description("Hold the task's current stage").option("--as <agent>", "act as this agent instead of the user").action(async (id, opts) => {
|
|
8661
|
-
const board = await
|
|
9118
|
+
const board = await open3();
|
|
8662
9119
|
const claimed = await board.claimTask(await actorFor(board, opts.as), { task_id: id });
|
|
8663
9120
|
print(claimed, () => `Stage ${claimed.stage} of task ${claimed.id} held by ${claimed.claimedBy ?? "?"} until ${claimed.leaseExpiresAt ?? "?"}`);
|
|
8664
9121
|
});
|
|
8665
9122
|
task.command("release <id>").description("Let go of the stage held on a task").option("--as <agent>", "act as this agent instead of the user").action(async (id, opts) => {
|
|
8666
|
-
const board = await
|
|
9123
|
+
const board = await open3();
|
|
8667
9124
|
const released = await board.releaseTask(await actorFor(board, opts.as), { task_id: id });
|
|
8668
9125
|
print(released, () => `Stage ${released.stage} of task ${released.id} released`);
|
|
8669
9126
|
});
|
|
8670
9127
|
task.command("advance <id>").description("Finish the current stage; past the last one the task is done").option("--note <text>", "what was done, posted to the task's thread").option("--as <agent>", "act as this agent instead of the user").action(async (id, opts) => {
|
|
8671
|
-
const board = await
|
|
9128
|
+
const board = await open3();
|
|
8672
9129
|
const advanced = await board.advanceTask(await actorFor(board, opts.as), {
|
|
8673
9130
|
task_id: id,
|
|
8674
9131
|
...opts.note === void 0 ? {} : { note: opts.note }
|
|
@@ -8677,7 +9134,7 @@ task.command("advance <id>").description("Finish the current stage; past the las
|
|
|
8677
9134
|
${describePlan(advanced)}`);
|
|
8678
9135
|
});
|
|
8679
9136
|
task.command("plan <id> <stages>").description("Reshape the stages ahead: pass existing stages with their id to keep them, new ones without").option("--on-done <effect>", "override the completion effect for this task").option("--as <agent>", "act as this agent instead of the user").action(async (id, stages, opts) => {
|
|
8680
|
-
const board = await
|
|
9137
|
+
const board = await open3();
|
|
8681
9138
|
const planned = await board.planTask(await actorFor(board, opts.as), {
|
|
8682
9139
|
task_id: id,
|
|
8683
9140
|
stages: PlanEditSchema.parse(JSON.parse(stages)),
|
|
@@ -8686,7 +9143,7 @@ task.command("plan <id> <stages>").description("Reshape the stages ahead: pass e
|
|
|
8686
9143
|
print(planned, () => describePlan(planned));
|
|
8687
9144
|
});
|
|
8688
9145
|
task.command("update <id>").description("Send a task back to an earlier stage, abandon it, or set blockers, with a note for its thread").option("--stage <stageId>", "move back to this earlier stage").option("--abandon", "abandon the task", false).option("--note <text>", "a note, posted to the task's thread").option("--blocked-by <ids...>", "task ids this task waits on").option("--as <agent>", "act as this agent instead of the user").action(async (id, opts) => {
|
|
8689
|
-
const board = await
|
|
9146
|
+
const board = await open3();
|
|
8690
9147
|
const updated = await board.updateTask(await actorFor(board, opts.as), {
|
|
8691
9148
|
task_id: id,
|
|
8692
9149
|
...opts.stage === void 0 ? {} : { stage: opts.stage },
|
|
@@ -8697,7 +9154,7 @@ task.command("update <id>").description("Send a task back to an earlier stage, a
|
|
|
8697
9154
|
print(updated, () => `Task ${updated.id} is ${updated.status} at ${updated.stage}`);
|
|
8698
9155
|
});
|
|
8699
9156
|
task.command("show <id>").description("Show a task with its plan and its thread").action(async (id) => {
|
|
8700
|
-
const board = await
|
|
9157
|
+
const board = await open3();
|
|
8701
9158
|
const found = await board.getTask(board.userActor(), { task_id: id });
|
|
8702
9159
|
const threadState = await board.readThread(id).then((t) => t.state).catch(() => "none");
|
|
8703
9160
|
const said = found.messages.map((message) => {
|
|
@@ -8717,7 +9174,7 @@ ${message.body.trim()}
|
|
|
8717
9174
|
].join("\n"));
|
|
8718
9175
|
});
|
|
8719
9176
|
task.command("list <project>").description("List a project's tasks with their current stage").action(async (projectSlug) => {
|
|
8720
|
-
const board = await
|
|
9177
|
+
const board = await open3();
|
|
8721
9178
|
const tasks = await board.listTasks(projectSlug);
|
|
8722
9179
|
print(tasks, () => tasks.length === 0 ? "No tasks." : tasks.map((t) => {
|
|
8723
9180
|
const stage = t.stages[stageIndex(t, t.stage)];
|
|
@@ -8726,7 +9183,7 @@ task.command("list <project>").description("List a project's tasks with their cu
|
|
|
8726
9183
|
});
|
|
8727
9184
|
var thread = program.command("thread").description("Manage threads");
|
|
8728
9185
|
thread.command("open [taskId]").description("Open a thread on a task, on a proposal, or on a channel with a title").option("--proposal <id>", "open the proposal's thread").option("--channel <ref>", "the channel it hangs off; required for a topic").option("--title <text>", "the thread's title; required for a topic").option("--as <agent>", "act as this agent instead of the user").action(async (taskId, opts) => {
|
|
8729
|
-
const board = await
|
|
9186
|
+
const board = await open3();
|
|
8730
9187
|
const opened = await board.openThread(await actorFor(board, opts.as), {
|
|
8731
9188
|
...taskId === void 0 ? {} : { task_id: taskId },
|
|
8732
9189
|
...opts.proposal === void 0 ? {} : { proposal_id: opts.proposal },
|
|
@@ -8736,7 +9193,7 @@ thread.command("open [taskId]").description("Open a thread on a task, on a propo
|
|
|
8736
9193
|
print(opened, () => `Thread ${opened.id} opened on ${opened.channel}: ${opened.title}`);
|
|
8737
9194
|
});
|
|
8738
9195
|
thread.command("close <threadId>").description("Close a thread with a summary posted to its channel").requiredOption("--summary <text>", "closure summary").option("--as <agent>", "act as this agent instead of the user").action(async (threadId, opts) => {
|
|
8739
|
-
const board = await
|
|
9196
|
+
const board = await open3();
|
|
8740
9197
|
const summary = await board.closeThread(await actorFor(board, opts.as), {
|
|
8741
9198
|
thread_id: threadId,
|
|
8742
9199
|
summary: opts.summary
|
|
@@ -8744,7 +9201,7 @@ thread.command("close <threadId>").description("Close a thread with a summary po
|
|
|
8744
9201
|
print(summary, () => `Thread closed; summary posted as ${summary.id} to ${summary.channel}`);
|
|
8745
9202
|
});
|
|
8746
9203
|
thread.command("list").description("List threads, the society's first, then each project's").option("--open", "only open threads", false).action(async (opts) => {
|
|
8747
|
-
const board = await
|
|
9204
|
+
const board = await open3();
|
|
8748
9205
|
const threads = (await board.listThreads()).filter((t) => !opts.open || t.state === "open");
|
|
8749
9206
|
print(threads, () => threads.length === 0 ? "No threads." : threads.map((t) => {
|
|
8750
9207
|
const about = t.subject === void 0 ? "topic" : t.subject.kind;
|
|
@@ -8752,7 +9209,7 @@ thread.command("list").description("List threads, the society's first, then each
|
|
|
8752
9209
|
}).join("\n"));
|
|
8753
9210
|
});
|
|
8754
9211
|
thread.command("show <threadId>").description("Show a thread and its messages").action(async (threadId) => {
|
|
8755
|
-
const board = await
|
|
9212
|
+
const board = await open3();
|
|
8756
9213
|
const found = await board.readThread(threadId);
|
|
8757
9214
|
const messages = await board.listThread(threadId);
|
|
8758
9215
|
print({ thread: found, messages }, () => [
|
|
@@ -8763,17 +9220,17 @@ thread.command("show <threadId>").description("Show a thread and its messages").
|
|
|
8763
9220
|
].join("\n"));
|
|
8764
9221
|
});
|
|
8765
9222
|
program.command("pause").description("Stop all wakeups; turns in flight finish").action(async () => {
|
|
8766
|
-
const board = await
|
|
9223
|
+
const board = await open3();
|
|
8767
9224
|
await board.setPaused(board.userActor(), true);
|
|
8768
9225
|
print({ paused: true }, () => "Society paused");
|
|
8769
9226
|
});
|
|
8770
9227
|
program.command("resume").description("Allow wakeups again").action(async () => {
|
|
8771
|
-
const board = await
|
|
9228
|
+
const board = await open3();
|
|
8772
9229
|
await board.setPaused(board.userActor(), false);
|
|
8773
9230
|
print({ paused: false }, () => "Society resumed");
|
|
8774
9231
|
});
|
|
8775
9232
|
program.command("society").description("The society's settings").command("timezone [zone]").description("Show the society's time zone, or set it: crons that name none are read in it").action(async (zone) => {
|
|
8776
|
-
const board = await
|
|
9233
|
+
const board = await open3();
|
|
8777
9234
|
const society = zone === void 0 ? await board.society() : await board.setTimezone(board.userActor(), zone);
|
|
8778
9235
|
print({ timezone: society.timezone }, () => `Time zone: ${society.timezone}`);
|
|
8779
9236
|
});
|
|
@@ -8781,7 +9238,8 @@ function describeCron(cron2, timeZone) {
|
|
|
8781
9238
|
const where = [
|
|
8782
9239
|
cron2.scope,
|
|
8783
9240
|
cron2.thread,
|
|
8784
|
-
cron2.channel === void 0 ? void 0 : `#${cron2.channel}
|
|
9241
|
+
cron2.channel === void 0 ? void 0 : `#${cron2.channel}`,
|
|
9242
|
+
cron2.place === void 0 ? void 0 : `~${cron2.place}`
|
|
8785
9243
|
].filter((part) => part !== void 0).join("/");
|
|
8786
9244
|
const next = nextFire(cron2, timeZone);
|
|
8787
9245
|
const state = cron2.ended !== void 0 ? `ended (${cron2.ended.reason})` : cron2.paused !== void 0 ? "paused" : next === null ? "no time to come" : `next ${next.toISOString()}`;
|
|
@@ -8789,13 +9247,13 @@ function describeCron(cron2, timeZone) {
|
|
|
8789
9247
|
}
|
|
8790
9248
|
var cron = program.command("cron").description("Crons that wake citizens on a clock");
|
|
8791
9249
|
cron.command("list").description("List crons, ended ones too with --all").option("--all", "include ended crons", false).action(async (opts) => {
|
|
8792
|
-
const board = await
|
|
9250
|
+
const board = await open3();
|
|
8793
9251
|
const { timezone } = await board.society();
|
|
8794
9252
|
const crons = (await board.listCrons()).filter((each) => opts.all || each.ended === void 0);
|
|
8795
9253
|
print(crons, () => crons.length === 0 ? "No crons." : crons.map((each) => describeCron(each, timezone)).join("\n"));
|
|
8796
9254
|
});
|
|
8797
9255
|
cron.command("add <agent> <title>").description("Set a cron that wakes a citizen, with a note on what to do then").requiredOption("--note <text>", "what the citizen does when it fires").option("--cron <expression>", "five fields: minute hour day-of-month month day-of-week").option("--timezone <zone>", "the zone the expression is read in; the society's otherwise").option("--at <time>", "one time to fire, with its offset, instead of --cron").option("--project <slug>", "where it fires: a project the citizen is in, or society").option("--thread <id>", "an open thread there whose conversation it fires in").option("--channel <name>", "an open channel there, other than general, whose conversation it fires in").option("--as <agent>", "act as this agent instead of the user").action(async (agentName, title, opts) => {
|
|
8798
|
-
const board = await
|
|
9256
|
+
const board = await open3();
|
|
8799
9257
|
const created = await board.createCron(await actorFor(board, opts.as), {
|
|
8800
9258
|
title,
|
|
8801
9259
|
note: opts.note,
|
|
@@ -8815,7 +9273,7 @@ for (const [name, paused] of [
|
|
|
8815
9273
|
["resume", false]
|
|
8816
9274
|
]) {
|
|
8817
9275
|
cron.command(`${name} <id>`).description(paused ? "Pause a cron" : "Resume a paused cron from its next time").option("--as <agent>", "act as this agent instead of the user").action(async (id, opts) => {
|
|
8818
|
-
const board = await
|
|
9276
|
+
const board = await open3();
|
|
8819
9277
|
const updated = await board.updateCron(await actorFor(board, opts.as), {
|
|
8820
9278
|
cron_id: id,
|
|
8821
9279
|
paused
|
|
@@ -8825,7 +9283,7 @@ for (const [name, paused] of [
|
|
|
8825
9283
|
});
|
|
8826
9284
|
}
|
|
8827
9285
|
cron.command("remove <id>").description("End a cron for good").requiredOption("--reason <text>", "why it ends").option("--as <agent>", "act as this agent instead of the user").action(async (id, opts) => {
|
|
8828
|
-
const board = await
|
|
9286
|
+
const board = await open3();
|
|
8829
9287
|
const removed = await board.removeCron(await actorFor(board, opts.as), {
|
|
8830
9288
|
cron_id: id,
|
|
8831
9289
|
reason: opts.reason
|
|
@@ -8834,12 +9292,12 @@ cron.command("remove <id>").description("End a cron for good").requiredOption("-
|
|
|
8834
9292
|
});
|
|
8835
9293
|
var knowledge = program.command("knowledge").description("Shared knowledge, by project or society");
|
|
8836
9294
|
knowledge.command("list [project]").description("List knowledge topics of a project, or of the society when no project is given").action(async (projectSlug) => {
|
|
8837
|
-
const board = await
|
|
9295
|
+
const board = await open3();
|
|
8838
9296
|
const topics = await board.listKnowledge(projectSlug ?? null);
|
|
8839
9297
|
print(topics, () => topics.length === 0 ? "No knowledge yet." : topics.map((t) => `${t.topic} revision ${t.revision}, updated by ${t.updatedBy} at ${t.updatedAt}`).join("\n"));
|
|
8840
9298
|
});
|
|
8841
9299
|
knowledge.command("show <topic>").description("Print a knowledge topic").option("--project <slug>", "the project's topic instead of the society's").action(async (topic, opts) => {
|
|
8842
|
-
const board = await
|
|
9300
|
+
const board = await open3();
|
|
8843
9301
|
const found = (await board.listKnowledge(opts.project ?? null)).find((t) => t.topic === topic);
|
|
8844
9302
|
if (found === void 0) {
|
|
8845
9303
|
throw new BoardError("NOT_FOUND", `no knowledge topic ${topic}`);
|
|
@@ -8847,8 +9305,8 @@ knowledge.command("show <topic>").description("Print a knowledge topic").option(
|
|
|
8847
9305
|
print(found, () => found.body);
|
|
8848
9306
|
});
|
|
8849
9307
|
knowledge.command("write <topic>").description("Write a knowledge topic as the user, from a file or standard input").requiredOption("--revision <n>", "the revision it replaces, as knowledge list shows it; 0 for a new topic").option("--project <slug>", "the project's topic instead of the society's").option("--file <path>", "read the body from this file instead of standard input").option("--as <agent>", "act as this agent instead of the user").action(async (topic, opts) => {
|
|
8850
|
-
const board = await
|
|
8851
|
-
const body = opts.file === void 0 ? await readStdin() : await readFile5(
|
|
9308
|
+
const board = await open3();
|
|
9309
|
+
const body = opts.file === void 0 ? await readStdin() : await readFile5(path8.resolve(opts.file), "utf8");
|
|
8852
9310
|
const written = await board.writeKnowledge(await actorFor(board, opts.as), {
|
|
8853
9311
|
project: opts.project ?? null,
|
|
8854
9312
|
topic,
|
|
@@ -8859,7 +9317,7 @@ knowledge.command("write <topic>").description("Write a knowledge topic as the u
|
|
|
8859
9317
|
});
|
|
8860
9318
|
var skill = program.command("skill").description("Skills promoted to the society");
|
|
8861
9319
|
skill.command("list").description("List the society's skills, and each member's own").action(async () => {
|
|
8862
|
-
const board = await
|
|
9320
|
+
const board = await open3();
|
|
8863
9321
|
const society = await board.listSocietySkills();
|
|
8864
9322
|
const own = [];
|
|
8865
9323
|
for (const member of await board.listAgents()) {
|
|
@@ -8874,7 +9332,7 @@ skill.command("list").description("List the society's skills, and each member's
|
|
|
8874
9332
|
].join("\n") || "No skills yet.");
|
|
8875
9333
|
});
|
|
8876
9334
|
skill.command("remove <name>").description("Remove a skill from the society's skills; its text is set aside, not destroyed").requiredOption("--reason <text>", "why it is wrong, superseded, or no longer used").option("--as <agent>", "act as this agent instead of the user").action(async (name, opts) => {
|
|
8877
|
-
const board = await
|
|
9335
|
+
const board = await open3();
|
|
8878
9336
|
const removed = await board.removeSkill(await actorFor(board, opts.as), {
|
|
8879
9337
|
name,
|
|
8880
9338
|
reason: opts.reason
|
|
@@ -8883,13 +9341,13 @@ skill.command("remove <name>").description("Remove a skill from the society's sk
|
|
|
8883
9341
|
});
|
|
8884
9342
|
var turn = program.command("turn").description("Turn operations");
|
|
8885
9343
|
turn.command("digest <agent>").description("Show the unread messages the agent's next turn opens with, in one scope or all; the cursors stay put").option("--limit <n>", "maximum messages", "50").option("--project <slug>", "the scope whose turn to show: a project, or society").action(async (agentName, opts) => {
|
|
8886
|
-
const board = await
|
|
9344
|
+
const board = await open3();
|
|
8887
9345
|
const actor = await board.actorFor(agentName);
|
|
8888
9346
|
const digest = await board.readDigest(opts.project === void 0 ? actor : { ...actor, scope: opts.project }, { advance: false, limit: Number(opts.limit) });
|
|
8889
9347
|
print(digest, () => digest.messages.length === 0 ? "No unread messages." : digest.messages.map((m) => `[${m.ts}] ${m.channel}${m.thread === void 0 ? "" : ` (thread ${m.thread})`} @${m.author}: ${m.body.trim()}`).join("\n"));
|
|
8890
9348
|
});
|
|
8891
9349
|
turn.command("run <agent>").description("Enqueue a manual wake for an agent on a project, or a reflection turn").requiredOption("--project <slug>", "project to act on, or society for a society-scope turn").option("--reason <text>", "why", "manual wake from the admin CLI").option("--reflect", "a reflection turn instead of a working turn", false).action(async (agentName, opts) => {
|
|
8892
|
-
const board = await
|
|
9350
|
+
const board = await open3();
|
|
8893
9351
|
const event = await board.requestWake(board.userActor(), {
|
|
8894
9352
|
agent: agentName,
|
|
8895
9353
|
project: opts.project,
|