@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/server/index.js
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
// apps/server/dist/index.js
|
|
4
4
|
import { access as access2 } from "node:fs/promises";
|
|
5
|
-
import
|
|
5
|
+
import path10 from "node:path";
|
|
6
6
|
import { serve } from "@hono/node-server";
|
|
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",
|
|
@@ -233,6 +235,7 @@ var CronFrontmatterSchema = z3.object({
|
|
|
233
235
|
scope: NameSchema,
|
|
234
236
|
thread: UlidSchema.optional(),
|
|
235
237
|
channel: NameSchema.optional(),
|
|
238
|
+
place: NameSchema.optional(),
|
|
236
239
|
schedule: CronScheduleSchema,
|
|
237
240
|
createdBy: NameSchema,
|
|
238
241
|
createdAt: IsoDateTimeSchema,
|
|
@@ -382,15 +385,16 @@ function quotaLevel(quota, now) {
|
|
|
382
385
|
var WINDOW_NAMES = {
|
|
383
386
|
five_hour: "5h",
|
|
384
387
|
seven_day: "7d",
|
|
385
|
-
seven_day_opus: "7d
|
|
386
|
-
seven_day_sonnet: "7d
|
|
388
|
+
seven_day_opus: "7d opus",
|
|
389
|
+
seven_day_sonnet: "7d sonnet",
|
|
387
390
|
seven_day_oauth_apps: "7d apps",
|
|
388
391
|
seven_day_overage_included: "7d extra",
|
|
389
392
|
overage: "extra usage"
|
|
390
393
|
};
|
|
391
394
|
function windowName(window) {
|
|
392
395
|
if (window.label !== void 0) {
|
|
393
|
-
|
|
396
|
+
const label = window.label.toLowerCase();
|
|
397
|
+
return window.minutes === 10080 ? `7d ${label}` : label;
|
|
394
398
|
}
|
|
395
399
|
const named = WINDOW_NAMES[window.key];
|
|
396
400
|
if (named !== void 0) {
|
|
@@ -989,7 +993,8 @@ var DashboardSchema = z5.object({
|
|
|
989
993
|
var ProposalOriginSchema = z5.object({
|
|
990
994
|
scope: NameSchema,
|
|
991
995
|
thread: UlidSchema.optional(),
|
|
992
|
-
channel: NameSchema.optional()
|
|
996
|
+
channel: NameSchema.optional(),
|
|
997
|
+
place: NameSchema.optional()
|
|
993
998
|
});
|
|
994
999
|
var SKILL_FILES_LIMIT = 100;
|
|
995
1000
|
var SKILL_BYTES_LIMIT = 10 * 1024 * 1024;
|
|
@@ -1037,130 +1042,175 @@ var DecisionSchema = z5.object({
|
|
|
1037
1042
|
});
|
|
1038
1043
|
|
|
1039
1044
|
// packages/shared/dist/verbs.js
|
|
1045
|
+
import { z as z7 } from "zod";
|
|
1046
|
+
|
|
1047
|
+
// packages/shared/dist/images.js
|
|
1040
1048
|
import { z as z6 } from "zod";
|
|
1041
|
-
var
|
|
1042
|
-
var
|
|
1043
|
-
var
|
|
1049
|
+
var IMAGE_LIMIT_BYTES = 8 * 1024 * 1024;
|
|
1050
|
+
var ImageTypeSchema = z6.enum(["png", "jpeg", "gif", "webp"]);
|
|
1051
|
+
var IMAGE_MEDIA_TYPES = {
|
|
1052
|
+
png: "image/png",
|
|
1053
|
+
jpeg: "image/jpeg",
|
|
1054
|
+
gif: "image/gif",
|
|
1055
|
+
webp: "image/webp"
|
|
1056
|
+
};
|
|
1057
|
+
function startsWith(bytes, prefix, at = 0) {
|
|
1058
|
+
for (let index = 0; index < prefix.length; index += 1) {
|
|
1059
|
+
if (bytes[at + index] !== prefix[index]) {
|
|
1060
|
+
return false;
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
return true;
|
|
1064
|
+
}
|
|
1065
|
+
var ascii = (text) => new TextEncoder().encode(text);
|
|
1066
|
+
function imageTypeOf(bytes) {
|
|
1067
|
+
if (startsWith(bytes, [137, 80, 78, 71, 13, 10, 26, 10])) {
|
|
1068
|
+
return "png";
|
|
1069
|
+
}
|
|
1070
|
+
if (startsWith(bytes, [255, 216, 255])) {
|
|
1071
|
+
return "jpeg";
|
|
1072
|
+
}
|
|
1073
|
+
if (startsWith(bytes, ascii("GIF87a")) || startsWith(bytes, ascii("GIF89a"))) {
|
|
1074
|
+
return "gif";
|
|
1075
|
+
}
|
|
1076
|
+
if (startsWith(bytes, ascii("RIFF")) && startsWith(bytes, ascii("WEBP"), 8)) {
|
|
1077
|
+
return "webp";
|
|
1078
|
+
}
|
|
1079
|
+
return null;
|
|
1080
|
+
}
|
|
1081
|
+
var ImagePathSchema = z6.string().min(1).max(4096).refine((value) => !value.includes("\0"), { message: "a path with no NUL character" });
|
|
1082
|
+
var IMAGE_SCHEME = "image:";
|
|
1083
|
+
var UploadedImageSchema = z6.object({
|
|
1084
|
+
id: UlidSchema,
|
|
1085
|
+
type: ImageTypeSchema,
|
|
1086
|
+
size: z6.number().int().positive(),
|
|
1087
|
+
markdown: z6.string()
|
|
1088
|
+
});
|
|
1089
|
+
|
|
1090
|
+
// packages/shared/dist/verbs.js
|
|
1091
|
+
var MomentSchema = z7.iso.datetime({ offset: true });
|
|
1092
|
+
var CronTitleSchema = z7.string().trim().min(1).max(120);
|
|
1093
|
+
var CronNoteSchema = z7.string().trim().min(1).max(4e3);
|
|
1044
1094
|
var VerbInputs = {
|
|
1045
|
-
post_message:
|
|
1095
|
+
post_message: z7.object({
|
|
1046
1096
|
/** Required outside a thread; in one it may be left out, and must otherwise be the thread's. */
|
|
1047
1097
|
channel: ChannelRefSchema.optional(),
|
|
1048
|
-
body:
|
|
1098
|
+
body: z7.string().min(1),
|
|
1049
1099
|
thread_id: UlidSchema.optional()
|
|
1050
1100
|
}),
|
|
1051
|
-
read_inbox:
|
|
1101
|
+
read_inbox: z7.object({
|
|
1052
1102
|
since_cursor: UlidSchema.nullable().optional(),
|
|
1053
|
-
limit:
|
|
1054
|
-
advance:
|
|
1103
|
+
limit: z7.number().int().positive().max(200).default(50),
|
|
1104
|
+
advance: z7.boolean().default(true)
|
|
1055
1105
|
}),
|
|
1056
|
-
search:
|
|
1057
|
-
query:
|
|
1106
|
+
search: z7.object({
|
|
1107
|
+
query: z7.string().min(1),
|
|
1058
1108
|
project: NameSchema.optional(),
|
|
1059
1109
|
channel: ChannelRefSchema.optional(),
|
|
1060
|
-
limit:
|
|
1110
|
+
limit: z7.number().int().positive().max(100).default(20)
|
|
1061
1111
|
}),
|
|
1062
1112
|
/** On a task or a proposal, whose id the thread takes, or on a channel with a title. */
|
|
1063
|
-
open_thread:
|
|
1113
|
+
open_thread: z7.object({
|
|
1064
1114
|
task_id: UlidSchema.optional(),
|
|
1065
1115
|
proposal_id: UlidSchema.optional(),
|
|
1066
1116
|
channel: ChannelRefSchema.optional(),
|
|
1067
|
-
title:
|
|
1117
|
+
title: z7.string().min(1).max(200).optional()
|
|
1068
1118
|
}),
|
|
1069
|
-
close_thread:
|
|
1070
|
-
create_task:
|
|
1119
|
+
close_thread: z7.object({ thread_id: UlidSchema, summary: z7.string().min(1) }),
|
|
1120
|
+
create_task: z7.object({
|
|
1071
1121
|
project: NameSchema,
|
|
1072
|
-
title:
|
|
1073
|
-
body:
|
|
1122
|
+
title: z7.string().min(1).max(200),
|
|
1123
|
+
body: z7.string().default(""),
|
|
1074
1124
|
parent_id: UlidSchema.optional(),
|
|
1075
|
-
required_capabilities:
|
|
1125
|
+
required_capabilities: z7.array(z7.string()).default([]),
|
|
1076
1126
|
/** The plan; without it the task gets one stage, work, that anyone in the project may take. */
|
|
1077
|
-
stages:
|
|
1127
|
+
stages: z7.array(PlanStageSchema).min(1).optional(),
|
|
1078
1128
|
/** A channel of the project the task belongs to, such as a release's; its thread opens there. */
|
|
1079
1129
|
channel: NameSchema.default("general")
|
|
1080
1130
|
}),
|
|
1081
|
-
claim_task:
|
|
1082
|
-
release_task:
|
|
1083
|
-
update_task:
|
|
1131
|
+
claim_task: z7.object({ task_id: UlidSchema }),
|
|
1132
|
+
release_task: z7.object({ task_id: UlidSchema }),
|
|
1133
|
+
update_task: z7.object({
|
|
1084
1134
|
task_id: UlidSchema,
|
|
1085
1135
|
/** Move the task back to this earlier stage. */
|
|
1086
1136
|
stage: StageIdSchema.optional(),
|
|
1087
|
-
status:
|
|
1137
|
+
status: z7.enum(["abandoned"]).optional(),
|
|
1088
1138
|
/** Posted to the task's thread as the caller, marked with the step when the task moved. */
|
|
1089
|
-
note:
|
|
1090
|
-
blocked_by:
|
|
1139
|
+
note: z7.string().min(1).optional(),
|
|
1140
|
+
blocked_by: z7.array(UlidSchema).optional(),
|
|
1091
1141
|
/**
|
|
1092
1142
|
* The pull request a `ghpr` task lands through, on its project's repository, with the merge
|
|
1093
1143
|
* commit's subject and body; null unlinks it.
|
|
1094
1144
|
*/
|
|
1095
|
-
pull_request:
|
|
1145
|
+
pull_request: z7.object({
|
|
1096
1146
|
url: PullRequestUrlSchema,
|
|
1097
1147
|
merge_subject: MergeSubjectSchema.optional(),
|
|
1098
|
-
merge_body:
|
|
1148
|
+
merge_body: z7.string().max(2e4).optional()
|
|
1099
1149
|
}).nullable().optional()
|
|
1100
1150
|
}),
|
|
1101
|
-
get_task:
|
|
1102
|
-
subscribe:
|
|
1103
|
-
unsubscribe:
|
|
1104
|
-
propose:
|
|
1151
|
+
get_task: z7.object({ task_id: UlidSchema }),
|
|
1152
|
+
subscribe: z7.object({ channel: ChannelRefSchema }),
|
|
1153
|
+
unsubscribe: z7.object({ channel: ChannelRefSchema }),
|
|
1154
|
+
propose: z7.object({
|
|
1105
1155
|
kind: ProposalKindSchema,
|
|
1106
|
-
charter:
|
|
1107
|
-
rationale:
|
|
1156
|
+
charter: z7.record(z7.string(), z7.unknown()),
|
|
1157
|
+
rationale: z7.string().default("")
|
|
1108
1158
|
}),
|
|
1109
|
-
approve:
|
|
1110
|
-
reject:
|
|
1111
|
-
create_project:
|
|
1159
|
+
approve: z7.object({ proposal_id: UlidSchema, reason: z7.string().optional() }),
|
|
1160
|
+
reject: z7.object({ proposal_id: UlidSchema, reason: z7.string().min(1) }),
|
|
1161
|
+
create_project: z7.object({
|
|
1112
1162
|
slug: NameSchema,
|
|
1113
|
-
name:
|
|
1114
|
-
repo:
|
|
1163
|
+
name: z7.string().min(1).optional(),
|
|
1164
|
+
repo: z7.string().min(1).nullable().default(null),
|
|
1115
1165
|
default_branch: DefaultBranchSchema.default("main"),
|
|
1116
1166
|
on_done: CompletionEffectSchema.optional()
|
|
1117
1167
|
}),
|
|
1118
|
-
join_project:
|
|
1119
|
-
leave_project:
|
|
1120
|
-
write_knowledge:
|
|
1168
|
+
join_project: z7.object({ project: NameSchema, agent: NameSchema.optional() }),
|
|
1169
|
+
leave_project: z7.object({ project: NameSchema, agent: NameSchema.optional() }),
|
|
1170
|
+
write_knowledge: z7.object({
|
|
1121
1171
|
/** A project you belong to, or null for society knowledge, which the steward and the user curate. */
|
|
1122
1172
|
project: NameSchema.nullable().default(null),
|
|
1123
1173
|
topic: NameSchema,
|
|
1124
|
-
body:
|
|
1174
|
+
body: z7.string().min(1),
|
|
1125
1175
|
/** The `revision` in the frontmatter of the topic you read, 0 when it names none or for a new topic. */
|
|
1126
|
-
revision:
|
|
1176
|
+
revision: z7.number().int().min(0)
|
|
1127
1177
|
}),
|
|
1128
|
-
remove_knowledge:
|
|
1178
|
+
remove_knowledge: z7.object({
|
|
1129
1179
|
/** As for write_knowledge: a project you belong to, or null for society knowledge. */
|
|
1130
1180
|
project: NameSchema.nullable().default(null),
|
|
1131
1181
|
topic: NameSchema
|
|
1132
1182
|
}),
|
|
1133
|
-
plan_task:
|
|
1183
|
+
plan_task: z7.object({
|
|
1134
1184
|
task_id: UlidSchema,
|
|
1135
|
-
stages:
|
|
1185
|
+
stages: z7.array(PlanEditStageSchema),
|
|
1136
1186
|
on_done: CompletionEffectSchema.optional()
|
|
1137
1187
|
}),
|
|
1138
1188
|
/** The note is posted to the task's thread as the caller, marked with the step. */
|
|
1139
|
-
advance_task:
|
|
1140
|
-
configure_project:
|
|
1189
|
+
advance_task: z7.object({ task_id: UlidSchema, note: z7.string().min(1).optional() }),
|
|
1190
|
+
configure_project: z7.object({
|
|
1141
1191
|
project: NameSchema,
|
|
1142
1192
|
on_done: CompletionEffectSchema.optional(),
|
|
1143
1193
|
/** A new display name; the slug, which everything refers to the project by, never changes. */
|
|
1144
|
-
name:
|
|
1194
|
+
name: z7.string().trim().min(1).max(120).optional(),
|
|
1145
1195
|
/** The branch tasks start from and land on from now; tasks in play keep their branches. */
|
|
1146
1196
|
default_branch: DefaultBranchSchema.optional()
|
|
1147
1197
|
}),
|
|
1148
|
-
archive_project:
|
|
1149
|
-
create_channel:
|
|
1198
|
+
archive_project: z7.object({ project: NameSchema, reason: z7.string().min(1) }),
|
|
1199
|
+
create_channel: z7.object({
|
|
1150
1200
|
/** A project, or null for a channel of the society. */
|
|
1151
1201
|
project: NameSchema.nullable().default(null),
|
|
1152
1202
|
name: NameSchema,
|
|
1153
|
-
purpose:
|
|
1203
|
+
purpose: z7.string().min(1)
|
|
1154
1204
|
}),
|
|
1155
|
-
archive_channel:
|
|
1156
|
-
update_dashboard:
|
|
1205
|
+
archive_channel: z7.object({ channel: ChannelRefSchema, reason: z7.string().min(1) }),
|
|
1206
|
+
update_dashboard: z7.object({
|
|
1157
1207
|
project: NameSchema,
|
|
1158
1208
|
/** The whole dashboard as markdown; the board keeps its frontmatter. */
|
|
1159
|
-
body:
|
|
1209
|
+
body: z7.string().min(1),
|
|
1160
1210
|
/** The `revision` in the frontmatter of the dashboard you read, 0 when it names none. */
|
|
1161
|
-
revision:
|
|
1211
|
+
revision: z7.number().int().min(0)
|
|
1162
1212
|
}),
|
|
1163
|
-
create_cron:
|
|
1213
|
+
create_cron: z7.object({
|
|
1164
1214
|
title: CronTitleSchema,
|
|
1165
1215
|
/** What to do when it fires; quoted in the prompt of every turn it starts. */
|
|
1166
1216
|
note: CronNoteSchema,
|
|
@@ -1179,10 +1229,10 @@ var VerbInputs = {
|
|
|
1179
1229
|
/** An open channel of that place other than general, whose conversation it fires in. */
|
|
1180
1230
|
channel: NameSchema.optional()
|
|
1181
1231
|
}),
|
|
1182
|
-
update_cron:
|
|
1232
|
+
update_cron: z7.object({
|
|
1183
1233
|
cron_id: UlidSchema,
|
|
1184
1234
|
/** Pause it, or resume it, which starts its schedule afresh from now. */
|
|
1185
|
-
paused:
|
|
1235
|
+
paused: z7.boolean().optional(),
|
|
1186
1236
|
title: CronTitleSchema.optional(),
|
|
1187
1237
|
note: CronNoteSchema.optional(),
|
|
1188
1238
|
/** A new expression, or `at` for one time; either starts the schedule afresh. */
|
|
@@ -1191,8 +1241,12 @@ var VerbInputs = {
|
|
|
1191
1241
|
timezone: TimeZoneSchema.nullable().optional(),
|
|
1192
1242
|
at: MomentSchema.optional()
|
|
1193
1243
|
}),
|
|
1194
|
-
remove_cron:
|
|
1195
|
-
remove_skill:
|
|
1244
|
+
remove_cron: z7.object({ cron_id: UlidSchema, reason: z7.string().trim().min(1) }),
|
|
1245
|
+
remove_skill: z7.object({ name: NameSchema, reason: z7.string().trim().min(1) }),
|
|
1246
|
+
upload_image: z7.object({
|
|
1247
|
+
/** The image file on the machine your turn runs on: absolute, or relative to your working directory. */
|
|
1248
|
+
path: ImagePathSchema
|
|
1249
|
+
})
|
|
1196
1250
|
};
|
|
1197
1251
|
var VERB_DESCRIPTIONS = {
|
|
1198
1252
|
post_message: "Post a markdown message to a channel, or into a thread when thread_id is given; a thread's message goes to the thread's channel.",
|
|
@@ -1203,7 +1257,7 @@ var VERB_DESCRIPTIONS = {
|
|
|
1203
1257
|
create_task: "Create a task in a project with its plan: stages of {name, role or agent, gate}. Without stages it gets one stage, work, that anyone in the project may take. File it in a channel of the project, such as a release's, or general by default; its thread opens there with it, under the task's id, for everything said about the work.",
|
|
1204
1258
|
claim_task: "Hold the task's current stage. Claims are leases renewed by every turn that touches the task.",
|
|
1205
1259
|
release_task: "Let go of the stage you hold so someone else can take it.",
|
|
1206
|
-
update_task: "Move a task back to an earlier stage, abandon it, set the tasks it is blocked by (blocked_by: its stage wakes nobody until they have all ended), or link its pull request (pull_request: url, with the merge commit's merge_subject and merge_body), which a ghpr task needs before its last stage completes. A note is posted to the task's thread.",
|
|
1260
|
+
update_task: "Move a task back to an earlier stage (the current stage's holder, the user, the steward, or the concierge; anyone else asks the concierge in the task's thread), abandon it, set the tasks it is blocked by (blocked_by: its stage wakes nobody until they have all ended), or link its pull request (pull_request: url, with the merge commit's merge_subject and merge_body), which a ghpr task needs before its last stage completes. A note is posted to the task's thread.",
|
|
1207
1261
|
get_task: "Read a task with its plan, its brief, and the messages of its thread.",
|
|
1208
1262
|
subscribe: "Subscribe to a channel. Subscriptions feed your digest; they never wake you.",
|
|
1209
1263
|
unsubscribe: "Unsubscribe from a channel.",
|
|
@@ -1225,11 +1279,12 @@ var VERB_DESCRIPTIONS = {
|
|
|
1225
1279
|
create_cron: "Set a cron that wakes you later: a title, a note on what to do then, and either cron, a five-field expression (minute hour day-of-month month day-of-week) read in timezone or the society's, or at, one time. It fires in this turn's conversation unless you name a project, thread_id, or channel, at most every 15 minutes, and every fire is a turn. Only the user, the steward, and the concierge set one that wakes another citizen (agent).",
|
|
1226
1280
|
update_cron: "Pause or resume a cron, or change its title, note, or schedule; resuming or rescheduling starts it afresh from now. Yours, or anyone's for the user, the steward, and the concierge.",
|
|
1227
1281
|
remove_cron: "End a cron for good, with a reason. Yours, or anyone's for the user, the steward, and the concierge. A cron also ends with its conversation, and a one-time cron once it fires.",
|
|
1228
|
-
remove_skill: "Remove a skill from the society's skills, with a reason, when it is wrong, superseded, or no longer used: no skills index lists it from the next turn, and a notice in general says so with the reason. Its proposer's own copy stays in its home. Steward and user only; the board keeps its text aside for the user to restore."
|
|
1282
|
+
remove_skill: "Remove a skill from the society's skills, with a reason, when it is wrong, superseded, or no longer used: no skills index lists it from the next turn, and a notice in general says so with the reason. Its proposer's own copy stays in its home. Steward and user only; the board keeps its text aside for the user to restore.",
|
|
1283
|
+
upload_image: "Upload an image file, such as a screenshot or a chart, from the machine your turn runs on: a PNG, JPEG, GIF, or WebP of at most 8 MB, in your worktree, your home, or the system temp folder. Returns its id and the markdown that shows it in a post, ``; write what it shows as the alt text."
|
|
1229
1284
|
};
|
|
1230
1285
|
|
|
1231
1286
|
// packages/shared/dist/events.js
|
|
1232
|
-
import { z as
|
|
1287
|
+
import { z as z8 } from "zod";
|
|
1233
1288
|
var BOARD_EVENT_TYPES = [
|
|
1234
1289
|
"society.initialized",
|
|
1235
1290
|
"project.added",
|
|
@@ -1293,21 +1348,22 @@ var BOARD_EVENT_TYPES = [
|
|
|
1293
1348
|
"cron.updated",
|
|
1294
1349
|
"cron.fired",
|
|
1295
1350
|
"cron.skipped",
|
|
1296
|
-
"cron.ended"
|
|
1351
|
+
"cron.ended",
|
|
1352
|
+
"image.uploaded"
|
|
1297
1353
|
];
|
|
1298
|
-
var BoardEventTypeSchema =
|
|
1299
|
-
var BoardEventSchema =
|
|
1354
|
+
var BoardEventTypeSchema = z8.enum(BOARD_EVENT_TYPES);
|
|
1355
|
+
var BoardEventSchema = z8.object({
|
|
1300
1356
|
id: UlidSchema,
|
|
1301
1357
|
ts: IsoDateTimeSchema,
|
|
1302
1358
|
type: BoardEventTypeSchema,
|
|
1303
1359
|
actor: NameSchema,
|
|
1304
|
-
payload:
|
|
1360
|
+
payload: z8.record(z8.string(), z8.unknown())
|
|
1305
1361
|
});
|
|
1306
|
-
var UsageSchema =
|
|
1307
|
-
inputTokens:
|
|
1308
|
-
outputTokens:
|
|
1309
|
-
cacheReadTokens:
|
|
1310
|
-
cacheWriteTokens:
|
|
1362
|
+
var UsageSchema = z8.object({
|
|
1363
|
+
inputTokens: z8.number().int().nonnegative(),
|
|
1364
|
+
outputTokens: z8.number().int().nonnegative(),
|
|
1365
|
+
cacheReadTokens: z8.number().int().nonnegative().default(0),
|
|
1366
|
+
cacheWriteTokens: z8.number().int().nonnegative().default(0)
|
|
1311
1367
|
});
|
|
1312
1368
|
function addUsage(a, b) {
|
|
1313
1369
|
return {
|
|
@@ -1317,14 +1373,14 @@ function addUsage(a, b) {
|
|
|
1317
1373
|
cacheWriteTokens: a.cacheWriteTokens + b.cacheWriteTokens
|
|
1318
1374
|
};
|
|
1319
1375
|
}
|
|
1320
|
-
var TurnStatusSchema =
|
|
1321
|
-
summary:
|
|
1322
|
-
claimsHeld:
|
|
1323
|
-
blockedOn:
|
|
1324
|
-
needsUserDecision:
|
|
1325
|
-
memoryUpdated:
|
|
1376
|
+
var TurnStatusSchema = z8.object({
|
|
1377
|
+
summary: z8.string().min(1),
|
|
1378
|
+
claimsHeld: z8.array(UlidSchema).default([]),
|
|
1379
|
+
blockedOn: z8.array(z8.string()).default([]),
|
|
1380
|
+
needsUserDecision: z8.boolean().default(false),
|
|
1381
|
+
memoryUpdated: z8.boolean().default(false)
|
|
1326
1382
|
});
|
|
1327
|
-
var TurnExitReasonSchema =
|
|
1383
|
+
var TurnExitReasonSchema = z8.enum([
|
|
1328
1384
|
"completed",
|
|
1329
1385
|
"timeout",
|
|
1330
1386
|
"error",
|
|
@@ -1332,50 +1388,51 @@ var TurnExitReasonSchema = z7.enum([
|
|
|
1332
1388
|
"blocked",
|
|
1333
1389
|
"stopped"
|
|
1334
1390
|
]);
|
|
1335
|
-
var
|
|
1336
|
-
|
|
1337
|
-
|
|
1391
|
+
var ResumeFailureSchema = z8.enum(["lost", "retry"]);
|
|
1392
|
+
var AgentEventSchema = z8.discriminatedUnion("type", [
|
|
1393
|
+
z8.object({
|
|
1394
|
+
type: z8.literal("turn_started"),
|
|
1338
1395
|
agent: NameSchema,
|
|
1339
|
-
session:
|
|
1396
|
+
session: z8.string(),
|
|
1340
1397
|
runner: NameSchema,
|
|
1341
1398
|
/** The model the CLI reports running, when it reports one. */
|
|
1342
|
-
model:
|
|
1399
|
+
model: z8.string().optional()
|
|
1343
1400
|
}),
|
|
1344
1401
|
/**
|
|
1345
1402
|
* The CLI took a steer into the running turn. Adapters name the steer; the server adds the
|
|
1346
1403
|
* messages it delivered and the text the CLI was given.
|
|
1347
1404
|
*/
|
|
1348
|
-
|
|
1349
|
-
type:
|
|
1350
|
-
steer:
|
|
1351
|
-
messages:
|
|
1352
|
-
text:
|
|
1405
|
+
z8.object({
|
|
1406
|
+
type: z8.literal("steered"),
|
|
1407
|
+
steer: z8.string().min(1),
|
|
1408
|
+
messages: z8.array(UlidSchema).optional(),
|
|
1409
|
+
text: z8.string().optional()
|
|
1353
1410
|
}),
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
type:
|
|
1358
|
-
name:
|
|
1359
|
-
ok:
|
|
1411
|
+
z8.object({ type: z8.literal("text"), delta: z8.string() }),
|
|
1412
|
+
z8.object({ type: z8.literal("tool_call"), name: z8.string(), input: z8.unknown() }),
|
|
1413
|
+
z8.object({
|
|
1414
|
+
type: z8.literal("tool_result"),
|
|
1415
|
+
name: z8.string(),
|
|
1416
|
+
ok: z8.boolean(),
|
|
1360
1417
|
/** What the tool returned, cut to `OUTPUT_LIMIT` characters by `capOutput`. */
|
|
1361
|
-
output:
|
|
1418
|
+
output: z8.string().optional()
|
|
1362
1419
|
}),
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
type:
|
|
1420
|
+
z8.object({ type: z8.literal("approval_requested"), kind: z8.string(), detail: z8.unknown() }),
|
|
1421
|
+
z8.object({
|
|
1422
|
+
type: z8.literal("turn_completed"),
|
|
1366
1423
|
usage: UsageSchema,
|
|
1367
|
-
costUsd:
|
|
1424
|
+
costUsd: z8.number().nonnegative(),
|
|
1368
1425
|
status: TurnStatusSchema.nullable(),
|
|
1369
1426
|
exitReason: TurnExitReasonSchema
|
|
1370
1427
|
}),
|
|
1371
|
-
|
|
1428
|
+
z8.object({ type: z8.literal("error"), message: z8.string() })
|
|
1372
1429
|
]);
|
|
1373
|
-
var TranscriptEntrySchema =
|
|
1430
|
+
var TranscriptEntrySchema = z8.object({
|
|
1374
1431
|
ts: IsoDateTimeSchema,
|
|
1375
1432
|
event: AgentEventSchema
|
|
1376
1433
|
});
|
|
1377
|
-
var LiveTurnEventSchema =
|
|
1378
|
-
seq:
|
|
1434
|
+
var LiveTurnEventSchema = z8.object({
|
|
1435
|
+
seq: z8.number().int().positive(),
|
|
1379
1436
|
ts: IsoDateTimeSchema,
|
|
1380
1437
|
agent: NameSchema,
|
|
1381
1438
|
project: NameSchema,
|
|
@@ -1383,14 +1440,16 @@ var LiveTurnEventSchema = z7.object({
|
|
|
1383
1440
|
thread: UlidSchema.optional(),
|
|
1384
1441
|
/** The channel, never general, whose conversation the turn is in. */
|
|
1385
1442
|
channel: NameSchema.optional(),
|
|
1443
|
+
/** The project whose channels' conversation the turn is in, for a turn in the society scope. */
|
|
1444
|
+
place: NameSchema.optional(),
|
|
1386
1445
|
event: AgentEventSchema
|
|
1387
1446
|
});
|
|
1388
1447
|
|
|
1389
1448
|
// packages/shared/dist/turns.js
|
|
1390
|
-
import { z as
|
|
1449
|
+
import { z as z10 } from "zod";
|
|
1391
1450
|
|
|
1392
1451
|
// packages/shared/dist/runner.js
|
|
1393
|
-
import { z as
|
|
1452
|
+
import { z as z9 } from "zod";
|
|
1394
1453
|
var RUNNER_PROTOCOL = 1;
|
|
1395
1454
|
var PATH_TOKENS = {
|
|
1396
1455
|
home: "{{stellaris:home}}",
|
|
@@ -1401,75 +1460,77 @@ var SERVER_TOKEN = "{{stellaris:server}}";
|
|
|
1401
1460
|
var WORKSPACE_TOKEN = "{{stellaris:workspace}}";
|
|
1402
1461
|
var HOME_SCRATCH = "scratch";
|
|
1403
1462
|
var HOME_FILE_LIMIT_BYTES = 8 * 1024 * 1024;
|
|
1404
|
-
var RunnerHelloSchema =
|
|
1405
|
-
protocol:
|
|
1406
|
-
version:
|
|
1463
|
+
var RunnerHelloSchema = z9.object({
|
|
1464
|
+
protocol: z9.number().int().positive(),
|
|
1465
|
+
version: z9.string().min(1),
|
|
1407
1466
|
os: RunnerOsSchema,
|
|
1408
|
-
clis:
|
|
1467
|
+
clis: z9.array(CliKindSchema),
|
|
1409
1468
|
/** The CLIs whose adapter can keep a session warm between turns. */
|
|
1410
|
-
residentClis:
|
|
1469
|
+
residentClis: z9.array(CliKindSchema).default([]),
|
|
1411
1470
|
/** The CLIs whose adapter can take input into a running turn; the server steers only these. */
|
|
1412
|
-
steerableClis:
|
|
1471
|
+
steerableClis: z9.array(CliKindSchema).default([]),
|
|
1413
1472
|
/** The CLIs whose adapter can stop a running turn; the server stops only these. */
|
|
1414
|
-
stoppableClis:
|
|
1473
|
+
stoppableClis: z9.array(CliKindSchema).default([]),
|
|
1415
1474
|
/** Whether the runner answers file reads on its projects' branches; one from before never does. */
|
|
1416
|
-
branchReads:
|
|
1475
|
+
branchReads: z9.boolean().default(false),
|
|
1417
1476
|
/** Whether the runner lists what a branch changed; one from before never does. */
|
|
1418
|
-
branchChanges:
|
|
1477
|
+
branchChanges: z9.boolean().default(false),
|
|
1419
1478
|
/** Whether the runner answers a read of a CLI's quota; one from before never does. */
|
|
1420
|
-
quotaReads:
|
|
1421
|
-
|
|
1479
|
+
quotaReads: z9.boolean().default(false),
|
|
1480
|
+
/** Whether the runner reads an image a turn running there uploads; one from before never does. */
|
|
1481
|
+
imageReads: z9.boolean().default(false),
|
|
1482
|
+
capabilities: z9.array(z9.string()).default([]),
|
|
1422
1483
|
/**
|
|
1423
1484
|
* What the runner's account lacks of the git identity citizens' own commits carry, checked
|
|
1424
1485
|
* before each hello outside any repository; empty when it has both. Absent from a runner from
|
|
1425
1486
|
* before the check, which no default fills in, so the board marks only what a runner reported.
|
|
1426
1487
|
*/
|
|
1427
|
-
gitIdentityMissing:
|
|
1488
|
+
gitIdentityMissing: z9.array(GitIdentityPartSchema).optional(),
|
|
1428
1489
|
/** Turns the runner runs at once, or null for no limit. */
|
|
1429
|
-
slots:
|
|
1490
|
+
slots: z9.number().int().positive().nullable(),
|
|
1430
1491
|
/** Turns the runner is still running, when it registers again; the server fails the rest of its turns. */
|
|
1431
|
-
turns:
|
|
1492
|
+
turns: z9.array(UlidSchema).default([])
|
|
1432
1493
|
});
|
|
1433
|
-
var RunnerWelcomeSchema =
|
|
1434
|
-
var RunnerEnrollRequestSchema =
|
|
1435
|
-
protocol:
|
|
1436
|
-
version:
|
|
1437
|
-
hostname:
|
|
1494
|
+
var RunnerWelcomeSchema = z9.object({ name: NameSchema, version: z9.string() });
|
|
1495
|
+
var RunnerEnrollRequestSchema = z9.object({
|
|
1496
|
+
protocol: z9.number().int().positive(),
|
|
1497
|
+
version: z9.string().min(1).max(64),
|
|
1498
|
+
hostname: z9.string().min(1).max(253),
|
|
1438
1499
|
os: RunnerOsSchema,
|
|
1439
|
-
clis:
|
|
1440
|
-
capabilities:
|
|
1500
|
+
clis: z9.array(CliKindSchema).max(8),
|
|
1501
|
+
capabilities: z9.array(z9.string().min(1).max(64)).max(32).default([])
|
|
1441
1502
|
});
|
|
1442
|
-
var RunnerEnrollmentSchema =
|
|
1443
|
-
deviceCode:
|
|
1444
|
-
userCode:
|
|
1503
|
+
var RunnerEnrollmentSchema = z9.object({
|
|
1504
|
+
deviceCode: z9.string().min(1),
|
|
1505
|
+
userCode: z9.string().min(1),
|
|
1445
1506
|
expiresAt: IsoDateTimeSchema,
|
|
1446
|
-
intervalMs:
|
|
1507
|
+
intervalMs: z9.number().int().positive()
|
|
1447
1508
|
});
|
|
1448
|
-
var RunnerEnrollPollSchema =
|
|
1449
|
-
var RunnerEnrollStatusSchema =
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1509
|
+
var RunnerEnrollPollSchema = z9.object({ deviceCode: z9.string().min(1) });
|
|
1510
|
+
var RunnerEnrollStatusSchema = z9.discriminatedUnion("status", [
|
|
1511
|
+
z9.object({ status: z9.literal("pending") }),
|
|
1512
|
+
z9.object({ status: z9.literal("approved"), name: NameSchema, token: z9.string().min(1) }),
|
|
1513
|
+
z9.object({ status: z9.literal("denied") })
|
|
1453
1514
|
]);
|
|
1454
|
-
var PendingEnrollmentSchema =
|
|
1455
|
-
userCode:
|
|
1456
|
-
hostname:
|
|
1515
|
+
var PendingEnrollmentSchema = z9.object({
|
|
1516
|
+
userCode: z9.string().min(1),
|
|
1517
|
+
hostname: z9.string(),
|
|
1457
1518
|
os: RunnerOsSchema,
|
|
1458
|
-
clis:
|
|
1459
|
-
capabilities:
|
|
1460
|
-
version:
|
|
1519
|
+
clis: z9.array(CliKindSchema),
|
|
1520
|
+
capabilities: z9.array(z9.string()),
|
|
1521
|
+
version: z9.string(),
|
|
1461
1522
|
requestedAt: IsoDateTimeSchema,
|
|
1462
1523
|
expiresAt: IsoDateTimeSchema
|
|
1463
1524
|
});
|
|
1464
|
-
var ProjectRepoSchema =
|
|
1525
|
+
var ProjectRepoSchema = z9.object({
|
|
1465
1526
|
slug: NameSchema,
|
|
1466
|
-
origin:
|
|
1467
|
-
defaultBranch:
|
|
1527
|
+
origin: z9.string().nullable(),
|
|
1528
|
+
defaultBranch: z9.string().min(1),
|
|
1468
1529
|
/**
|
|
1469
1530
|
* The board lands finished tasks on the default branch, itself or through their pull requests,
|
|
1470
1531
|
* so the runner lets nothing else move it.
|
|
1471
1532
|
*/
|
|
1472
|
-
boardLands:
|
|
1533
|
+
boardLands: z9.boolean()
|
|
1473
1534
|
});
|
|
1474
1535
|
function projectRepo(project) {
|
|
1475
1536
|
return {
|
|
@@ -1479,163 +1540,196 @@ function projectRepo(project) {
|
|
|
1479
1540
|
boardLands: project.onDone !== "none"
|
|
1480
1541
|
};
|
|
1481
1542
|
}
|
|
1482
|
-
var TurnWorkspaceSchema =
|
|
1483
|
-
|
|
1484
|
-
kind:
|
|
1543
|
+
var TurnWorkspaceSchema = z9.discriminatedUnion("kind", [
|
|
1544
|
+
z9.object({
|
|
1545
|
+
kind: z9.literal("home"),
|
|
1485
1546
|
thread: UlidSchema.optional(),
|
|
1486
|
-
channel: NameSchema.optional()
|
|
1547
|
+
channel: NameSchema.optional(),
|
|
1548
|
+
place: NameSchema.optional()
|
|
1487
1549
|
}),
|
|
1488
|
-
|
|
1489
|
-
kind:
|
|
1550
|
+
z9.object({
|
|
1551
|
+
kind: z9.literal("project"),
|
|
1490
1552
|
repo: ProjectRepoSchema,
|
|
1491
|
-
branches:
|
|
1553
|
+
branches: z9.array(z9.string().min(1)),
|
|
1492
1554
|
thread: UlidSchema.optional(),
|
|
1493
1555
|
channel: NameSchema.optional()
|
|
1494
1556
|
}),
|
|
1495
|
-
|
|
1557
|
+
z9.object({
|
|
1558
|
+
kind: z9.literal("task"),
|
|
1559
|
+
repo: ProjectRepoSchema,
|
|
1560
|
+
taskId: UlidSchema,
|
|
1561
|
+
/**
|
|
1562
|
+
* The citizen holding the task's current stage, when someone does: a turn of anyone else
|
|
1563
|
+
* works detached at the branch's tip, so the holder's turns find the branch free.
|
|
1564
|
+
*/
|
|
1565
|
+
holder: NameSchema.optional()
|
|
1566
|
+
})
|
|
1496
1567
|
]);
|
|
1497
|
-
var TurnJobSchema =
|
|
1568
|
+
var TurnJobSchema = z9.object({
|
|
1498
1569
|
turnId: UlidSchema,
|
|
1499
1570
|
agent: NameSchema,
|
|
1500
1571
|
role: NameSchema,
|
|
1501
1572
|
cli: CliKindSchema,
|
|
1502
|
-
model:
|
|
1573
|
+
model: z9.string().optional(),
|
|
1503
1574
|
/** The reasoning effort set on the agent; absent for the model's own default. */
|
|
1504
|
-
effort:
|
|
1575
|
+
effort: z9.string().optional(),
|
|
1505
1576
|
/** A project slug, or the society scope. */
|
|
1506
1577
|
scope: NameSchema,
|
|
1507
1578
|
/** The thread whose conversation the turn is in; absent for the home conversation. */
|
|
1508
1579
|
thread: UlidSchema.optional(),
|
|
1509
1580
|
/** The channel, never general, whose conversation the turn is in. */
|
|
1510
1581
|
channel: NameSchema.optional(),
|
|
1582
|
+
/** The project whose channels' conversation the turn is in, for a turn in the society scope. */
|
|
1583
|
+
place: NameSchema.optional(),
|
|
1511
1584
|
/**
|
|
1512
1585
|
* The session to resume, or null to start one: the CLI's adapter picks a fresh session's id, and
|
|
1513
1586
|
* the outcome reports it.
|
|
1514
1587
|
*/
|
|
1515
|
-
session:
|
|
1588
|
+
session: z9.string().min(1).nullable(),
|
|
1516
1589
|
/** The running total the session reported after its last turn, which a resumed session starts from. */
|
|
1517
|
-
costSoFarUsd:
|
|
1590
|
+
costSoFarUsd: z9.number().nonnegative(),
|
|
1518
1591
|
/** The rendered charter, norms, memory core, and skills index, with `PATH_TOKENS` for places. */
|
|
1519
|
-
instructions:
|
|
1520
|
-
prompt:
|
|
1521
|
-
mcp:
|
|
1522
|
-
limits:
|
|
1523
|
-
timeoutMs:
|
|
1524
|
-
maxTurns:
|
|
1592
|
+
instructions: z9.string(),
|
|
1593
|
+
prompt: z9.string(),
|
|
1594
|
+
mcp: z9.object({ url: z9.string().min(1), token: z9.string().min(1) }),
|
|
1595
|
+
limits: z9.object({
|
|
1596
|
+
timeoutMs: z9.number().int().positive().nullable(),
|
|
1597
|
+
maxTurns: z9.number().int().positive().nullable()
|
|
1525
1598
|
}),
|
|
1526
1599
|
/** Present when the runner keeps the conversation's session warm between turns, under `key`. */
|
|
1527
|
-
resident:
|
|
1600
|
+
resident: z9.object({ key: z9.string().min(1), idleMs: z9.number().int().positive() }).optional(),
|
|
1528
1601
|
workspace: TurnWorkspaceSchema
|
|
1529
1602
|
});
|
|
1530
|
-
var TurnWorkSchema =
|
|
1531
|
-
var
|
|
1532
|
-
|
|
1603
|
+
var TurnWorkSchema = z9.object({ branch: z9.string().min(1), head: z9.string().min(1) });
|
|
1604
|
+
var StuckWorkSchema = z9.object({
|
|
1605
|
+
branch: z9.string().min(1),
|
|
1606
|
+
agent: NameSchema,
|
|
1607
|
+
commits: z9.array(z9.string()),
|
|
1608
|
+
total: z9.number().int().positive()
|
|
1609
|
+
});
|
|
1610
|
+
var TurnEventsSchema = z9.object({ entries: z9.array(TranscriptEntrySchema) });
|
|
1611
|
+
var TurnOutcomeSchema = z9.object({
|
|
1533
1612
|
exitReason: TurnExitReasonSchema,
|
|
1534
1613
|
status: TurnStatusSchema.nullable(),
|
|
1535
|
-
error:
|
|
1614
|
+
error: z9.string().nullable(),
|
|
1536
1615
|
usage: UsageSchema,
|
|
1537
|
-
costUsd:
|
|
1616
|
+
costUsd: z9.number().nonnegative(),
|
|
1538
1617
|
/** The session's running total after this turn, for CLIs that report one. */
|
|
1539
|
-
sessionCostUsd:
|
|
1540
|
-
/**
|
|
1541
|
-
|
|
1618
|
+
sessionCostUsd: z9.number().nonnegative().optional(),
|
|
1619
|
+
/**
|
|
1620
|
+
* The session the CLI used, which differs from the job's for CLIs that assign their own ids. A
|
|
1621
|
+
* turn whose CLI never started, because it failed or was stopped first, reports the job's own,
|
|
1622
|
+
* null in a conversation's first turn, so a session never started is not recorded.
|
|
1623
|
+
*/
|
|
1624
|
+
session: z9.string().min(1).nullable(),
|
|
1625
|
+
/**
|
|
1626
|
+
* Set when the CLI could not resume the job's session, which failed the turn before any model
|
|
1627
|
+
* call; the server then drops a `lost` session, and one that failed `retry` too often in a row. A
|
|
1628
|
+
* runner from before never sends it.
|
|
1629
|
+
*/
|
|
1630
|
+
resumeFailed: ResumeFailureSchema.optional(),
|
|
1542
1631
|
/** The model the CLI reported, when it reported one. */
|
|
1543
|
-
model:
|
|
1632
|
+
model: z9.string().nullable(),
|
|
1544
1633
|
work: TurnWorkSchema.nullable(),
|
|
1545
1634
|
/**
|
|
1546
1635
|
* Whether the turn's own workspace, a thread's, a channel's, or a task's, holds work on no
|
|
1547
1636
|
* branch after the hand-back: uncommitted changes, or commits no branch contains.
|
|
1548
1637
|
*/
|
|
1549
|
-
leftovers:
|
|
1638
|
+
leftovers: z9.boolean().default(false),
|
|
1639
|
+
/**
|
|
1640
|
+
* Commits the hand-back found its task's worktrees holding that no fast-forward of the task's
|
|
1641
|
+
* branch can take, each told once; a runner from before never sends it.
|
|
1642
|
+
*/
|
|
1643
|
+
stuck: z9.array(StuckWorkSchema).optional(),
|
|
1550
1644
|
/**
|
|
1551
1645
|
* The CLI's plan quota as the turn's stream last reported it, when it reported any; a runner from
|
|
1552
1646
|
* before never sends it.
|
|
1553
1647
|
*/
|
|
1554
1648
|
quota: CliQuotaSchema.optional()
|
|
1555
1649
|
});
|
|
1556
|
-
var TurnAckSchema =
|
|
1557
|
-
var LandRequestSchema =
|
|
1650
|
+
var TurnAckSchema = z9.object({ dropWorkspace: z9.boolean() });
|
|
1651
|
+
var LandRequestSchema = z9.object({
|
|
1558
1652
|
repo: ProjectRepoSchema,
|
|
1559
|
-
branch:
|
|
1653
|
+
branch: z9.string().min(1),
|
|
1560
1654
|
/**
|
|
1561
1655
|
* For a `ghpr` task, the pull request to merge in place of the branch, which must hold the
|
|
1562
1656
|
* branch's files, and the merge commit's subject and body, the pull request's title and an
|
|
1563
1657
|
* empty body when left out.
|
|
1564
1658
|
*/
|
|
1565
|
-
pullRequest:
|
|
1566
|
-
url:
|
|
1567
|
-
subject:
|
|
1568
|
-
body:
|
|
1659
|
+
pullRequest: z9.object({
|
|
1660
|
+
url: z9.string().min(1),
|
|
1661
|
+
subject: z9.string().min(1).optional(),
|
|
1662
|
+
body: z9.string().optional()
|
|
1569
1663
|
}).optional()
|
|
1570
1664
|
});
|
|
1571
|
-
var CloseRequestSchema =
|
|
1665
|
+
var CloseRequestSchema = z9.object({
|
|
1572
1666
|
repo: ProjectRepoSchema,
|
|
1573
|
-
url:
|
|
1574
|
-
comment:
|
|
1667
|
+
url: z9.string().min(1),
|
|
1668
|
+
comment: z9.string().min(1)
|
|
1575
1669
|
});
|
|
1576
|
-
var MergeOutcomeSchema =
|
|
1577
|
-
var ModelListSchema =
|
|
1670
|
+
var MergeOutcomeSchema = z9.object({ ok: z9.boolean(), detail: z9.string() });
|
|
1671
|
+
var ModelListSchema = z9.array(ModelOptionSchema);
|
|
1578
1672
|
var BRANCH_FILE_LIMIT_BYTES = 8 * 1024 * 1024;
|
|
1579
|
-
var BranchPathSchema =
|
|
1580
|
-
var BranchNameSchema =
|
|
1581
|
-
var BranchReadSchema =
|
|
1673
|
+
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" });
|
|
1674
|
+
var BranchNameSchema = z9.string().regex(/^[A-Za-z0-9][A-Za-z0-9._/-]*$/);
|
|
1675
|
+
var BranchReadSchema = z9.object({
|
|
1582
1676
|
repo: ProjectRepoSchema,
|
|
1583
1677
|
branch: BranchNameSchema,
|
|
1584
1678
|
path: BranchPathSchema
|
|
1585
1679
|
});
|
|
1586
|
-
var BranchCommitSchema =
|
|
1587
|
-
id:
|
|
1680
|
+
var BranchCommitSchema = z9.object({
|
|
1681
|
+
id: z9.string().min(1),
|
|
1588
1682
|
at: IsoDateTimeSchema,
|
|
1589
|
-
author:
|
|
1683
|
+
author: z9.string()
|
|
1590
1684
|
});
|
|
1591
|
-
var BranchFileSchema =
|
|
1592
|
-
|
|
1593
|
-
kind:
|
|
1685
|
+
var BranchFileSchema = z9.discriminatedUnion("kind", [
|
|
1686
|
+
z9.object({
|
|
1687
|
+
kind: z9.literal("file"),
|
|
1594
1688
|
path: BranchPathSchema,
|
|
1595
1689
|
commit: BranchCommitSchema,
|
|
1596
|
-
size:
|
|
1597
|
-
content:
|
|
1690
|
+
size: z9.number().int().nonnegative(),
|
|
1691
|
+
content: z9.string().nullable()
|
|
1598
1692
|
}),
|
|
1599
|
-
|
|
1600
|
-
kind:
|
|
1693
|
+
z9.object({
|
|
1694
|
+
kind: z9.literal("dir"),
|
|
1601
1695
|
path: BranchPathSchema,
|
|
1602
1696
|
commit: BranchCommitSchema,
|
|
1603
|
-
entries:
|
|
1604
|
-
name:
|
|
1605
|
-
kind:
|
|
1606
|
-
size:
|
|
1697
|
+
entries: z9.array(z9.object({
|
|
1698
|
+
name: z9.string().min(1),
|
|
1699
|
+
kind: z9.enum(["file", "dir"]),
|
|
1700
|
+
size: z9.number().int().nonnegative().nullable()
|
|
1607
1701
|
}))
|
|
1608
1702
|
})
|
|
1609
1703
|
]);
|
|
1610
|
-
var BranchChangesReadSchema =
|
|
1704
|
+
var BranchChangesReadSchema = z9.object({
|
|
1611
1705
|
repo: ProjectRepoSchema,
|
|
1612
1706
|
branch: BranchNameSchema
|
|
1613
1707
|
});
|
|
1614
|
-
var BranchFileChangeSchema =
|
|
1615
|
-
path:
|
|
1616
|
-
status:
|
|
1708
|
+
var BranchFileChangeSchema = z9.object({
|
|
1709
|
+
path: z9.string().min(1),
|
|
1710
|
+
status: z9.enum(["added", "modified", "deleted"]),
|
|
1617
1711
|
/** Lines added and removed, or null for a binary file. */
|
|
1618
|
-
added:
|
|
1619
|
-
removed:
|
|
1712
|
+
added: z9.number().int().nonnegative().nullable(),
|
|
1713
|
+
removed: z9.number().int().nonnegative().nullable(),
|
|
1620
1714
|
/** Absent when the file changed only through a merge of another branch. */
|
|
1621
|
-
lastChange:
|
|
1715
|
+
lastChange: z9.object({ author: z9.string(), at: IsoDateTimeSchema }).optional()
|
|
1622
1716
|
});
|
|
1623
|
-
var BranchChangesSchema =
|
|
1717
|
+
var BranchChangesSchema = z9.object({
|
|
1624
1718
|
head: BranchCommitSchema,
|
|
1625
|
-
files:
|
|
1626
|
-
total:
|
|
1719
|
+
files: z9.array(BranchFileChangeSchema),
|
|
1720
|
+
total: z9.number().int().nonnegative()
|
|
1627
1721
|
});
|
|
1628
|
-
var BranchDiffReadSchema =
|
|
1722
|
+
var BranchDiffReadSchema = z9.object({
|
|
1629
1723
|
repo: ProjectRepoSchema,
|
|
1630
1724
|
branch: BranchNameSchema,
|
|
1631
1725
|
path: BranchPathSchema.min(1)
|
|
1632
1726
|
});
|
|
1633
|
-
var BranchFileDiffSchema =
|
|
1727
|
+
var BranchFileDiffSchema = z9.object({
|
|
1634
1728
|
head: BranchCommitSchema,
|
|
1635
1729
|
path: BranchPathSchema,
|
|
1636
1730
|
status: BranchFileChangeSchema.shape.status,
|
|
1637
|
-
patch:
|
|
1638
|
-
truncated:
|
|
1731
|
+
patch: z9.string().nullable(),
|
|
1732
|
+
truncated: z9.boolean()
|
|
1639
1733
|
});
|
|
1640
1734
|
function patchHunks(patch, limit2) {
|
|
1641
1735
|
if (/^Binary files .* differ$/m.test(patch)) {
|
|
@@ -1649,75 +1743,98 @@ function patchHunks(patch, limit2) {
|
|
|
1649
1743
|
const cut = hunks.lastIndexOf("\n", limit2);
|
|
1650
1744
|
return { patch: hunks.slice(0, cut === -1 ? limit2 : cut), truncated: true };
|
|
1651
1745
|
}
|
|
1652
|
-
var
|
|
1653
|
-
|
|
1654
|
-
|
|
1746
|
+
var TurnImageReadSchema = z9.object({
|
|
1747
|
+
turnId: UlidSchema,
|
|
1748
|
+
path: ImagePathSchema
|
|
1749
|
+
});
|
|
1750
|
+
var ImageRefusalSchema = z9.enum(["missing", "outside", "not_image", "too_big"]);
|
|
1751
|
+
var TurnImageSchema = z9.discriminatedUnion("kind", [
|
|
1752
|
+
z9.object({
|
|
1753
|
+
kind: z9.literal("image"),
|
|
1754
|
+
type: ImageTypeSchema,
|
|
1755
|
+
size: z9.number().int().positive(),
|
|
1756
|
+
content: z9.string()
|
|
1757
|
+
}),
|
|
1758
|
+
z9.object({ kind: z9.literal("refused"), why: ImageRefusalSchema, detail: z9.string() })
|
|
1759
|
+
]);
|
|
1760
|
+
var WorkspaceConversationSchema = z9.discriminatedUnion("kind", [
|
|
1761
|
+
z9.object({ kind: z9.literal("thread"), id: UlidSchema }),
|
|
1762
|
+
z9.object({ kind: z9.literal("channel"), scope: NameSchema, name: NameSchema }),
|
|
1763
|
+
z9.object({ kind: z9.literal("place"), project: NameSchema })
|
|
1655
1764
|
]);
|
|
1656
1765
|
function workspaceConversationOf(turn) {
|
|
1657
1766
|
if (turn.thread !== void 0) {
|
|
1658
1767
|
return { kind: "thread", id: turn.thread };
|
|
1659
1768
|
}
|
|
1660
|
-
|
|
1769
|
+
if (turn.channel !== void 0) {
|
|
1770
|
+
return { kind: "channel", scope: turn.scope, name: turn.channel };
|
|
1771
|
+
}
|
|
1772
|
+
return turn.place === void 0 ? null : { kind: "place", project: turn.place };
|
|
1661
1773
|
}
|
|
1662
|
-
var HeldWorkspaceSchema =
|
|
1774
|
+
var HeldWorkspaceSchema = z9.object({
|
|
1663
1775
|
agent: NameSchema,
|
|
1664
1776
|
scope: NameSchema,
|
|
1665
1777
|
conversation: WorkspaceConversationSchema
|
|
1666
1778
|
});
|
|
1667
|
-
var HeldWorkspacesSchema =
|
|
1668
|
-
var SweepResultSchema =
|
|
1669
|
-
var TurnSteerSchema =
|
|
1670
|
-
var RunnerMessageSchema =
|
|
1671
|
-
|
|
1672
|
-
|
|
1779
|
+
var HeldWorkspacesSchema = z9.object({ workspaces: z9.array(HeldWorkspaceSchema) });
|
|
1780
|
+
var SweepResultSchema = z9.object({ kept: z9.array(HeldWorkspaceSchema) });
|
|
1781
|
+
var TurnSteerSchema = z9.object({ id: z9.uuid(), text: z9.string().min(1) });
|
|
1782
|
+
var RunnerMessageSchema = z9.discriminatedUnion("type", [
|
|
1783
|
+
z9.object({ type: z9.literal("turn"), job: TurnJobSchema }),
|
|
1784
|
+
z9.object({ type: z9.literal("land"), request: z9.string().min(1), land: LandRequestSchema }),
|
|
1673
1785
|
/** Answered with a `MergeOutcome`. */
|
|
1674
|
-
|
|
1675
|
-
|
|
1786
|
+
z9.object({ type: z9.literal("close"), request: z9.string().min(1), close: CloseRequestSchema }),
|
|
1787
|
+
z9.object({ type: z9.literal("models"), request: z9.string().min(1), cli: CliKindSchema }),
|
|
1676
1788
|
/**
|
|
1677
1789
|
* Answered with a `CliQuota` read from the CLI without a turn, or null when its adapter cannot
|
|
1678
1790
|
* read one. Sent only to runners whose hello says `quotaReads`.
|
|
1679
1791
|
*/
|
|
1680
|
-
|
|
1792
|
+
z9.object({ type: z9.literal("quota"), request: z9.string().min(1), cli: CliKindSchema }),
|
|
1681
1793
|
/** Answered with a `BranchFile`, or null when the branch or the path does not exist. */
|
|
1682
|
-
|
|
1794
|
+
z9.object({ type: z9.literal("file"), request: z9.string().min(1), read: BranchReadSchema }),
|
|
1683
1795
|
/** Answered with `BranchChanges`, or null when the repository or the branch does not exist. */
|
|
1684
|
-
|
|
1685
|
-
type:
|
|
1686
|
-
request:
|
|
1796
|
+
z9.object({
|
|
1797
|
+
type: z9.literal("changes"),
|
|
1798
|
+
request: z9.string().min(1),
|
|
1687
1799
|
read: BranchChangesReadSchema
|
|
1688
1800
|
}),
|
|
1689
1801
|
/** Answered with a `BranchFileDiff`, or null when the branch does not exist or left the path alone. */
|
|
1690
|
-
|
|
1802
|
+
z9.object({ type: z9.literal("diff"), request: z9.string().min(1), read: BranchDiffReadSchema }),
|
|
1803
|
+
/**
|
|
1804
|
+
* Answered with a `TurnImage`, or null when the turn is not running there. Sent only to runners
|
|
1805
|
+
* whose hello says `imageReads`.
|
|
1806
|
+
*/
|
|
1807
|
+
z9.object({ type: z9.literal("image"), request: z9.string().min(1), read: TurnImageReadSchema }),
|
|
1691
1808
|
/** Answered with whether the CLI accepted it; that it took it comes as a `steered` event. */
|
|
1692
|
-
|
|
1693
|
-
type:
|
|
1694
|
-
request:
|
|
1809
|
+
z9.object({
|
|
1810
|
+
type: z9.literal("steer"),
|
|
1811
|
+
request: z9.string().min(1),
|
|
1695
1812
|
turnId: UlidSchema,
|
|
1696
1813
|
steer: TurnSteerSchema
|
|
1697
1814
|
}),
|
|
1698
1815
|
/** Answered with whether the turn was there to stop; it ends `stopped`. */
|
|
1699
|
-
|
|
1816
|
+
z9.object({ type: z9.literal("stop"), request: z9.string().min(1), turnId: UlidSchema }),
|
|
1700
1817
|
/**
|
|
1701
1818
|
* A conversation ended: the runner removes every citizen's workspace for it but those a turn
|
|
1702
1819
|
* still uses, and answers with a `SweepResult` naming those it kept for holding work on no branch.
|
|
1703
1820
|
*/
|
|
1704
|
-
|
|
1705
|
-
type:
|
|
1706
|
-
request:
|
|
1821
|
+
z9.object({
|
|
1822
|
+
type: z9.literal("sweep"),
|
|
1823
|
+
request: z9.string().min(1),
|
|
1707
1824
|
conversation: WorkspaceConversationSchema
|
|
1708
1825
|
})
|
|
1709
1826
|
]);
|
|
1710
|
-
var RunnerAnswerSchema =
|
|
1711
|
-
|
|
1712
|
-
|
|
1827
|
+
var RunnerAnswerSchema = z9.discriminatedUnion("ok", [
|
|
1828
|
+
z9.object({ ok: z9.literal(true), value: z9.unknown() }),
|
|
1829
|
+
z9.object({ ok: z9.literal(false), error: z9.string() })
|
|
1713
1830
|
]);
|
|
1714
|
-
var WarmSessionsSchema =
|
|
1715
|
-
var FileManifestSchema =
|
|
1831
|
+
var WarmSessionsSchema = z9.object({ warm: z9.array(z9.string()) });
|
|
1832
|
+
var FileManifestSchema = z9.object({ files: z9.record(z9.string(), z9.string()) });
|
|
1716
1833
|
function fileVersion(sha, executable) {
|
|
1717
1834
|
return executable ? `${sha}+x` : sha;
|
|
1718
1835
|
}
|
|
1719
|
-
var FileReadSchema =
|
|
1720
|
-
var FileContentsSchema =
|
|
1836
|
+
var FileReadSchema = z9.object({ paths: z9.array(z9.string()) });
|
|
1837
|
+
var FileContentsSchema = z9.object({ files: z9.record(z9.string(), z9.string()) });
|
|
1721
1838
|
function isSafeRelativePath(file) {
|
|
1722
1839
|
if (file.length === 0 || file.startsWith("/") || file.includes("\\") || file.includes("\0")) {
|
|
1723
1840
|
return false;
|
|
@@ -1726,7 +1843,7 @@ function isSafeRelativePath(file) {
|
|
|
1726
1843
|
}
|
|
1727
1844
|
|
|
1728
1845
|
// packages/shared/dist/turns.js
|
|
1729
|
-
var TriggerKindSchema =
|
|
1846
|
+
var TriggerKindSchema = z10.enum([
|
|
1730
1847
|
"mention",
|
|
1731
1848
|
"stage",
|
|
1732
1849
|
"task_done",
|
|
@@ -1740,11 +1857,11 @@ var TriggerKindSchema = z9.enum([
|
|
|
1740
1857
|
"closing",
|
|
1741
1858
|
"cron"
|
|
1742
1859
|
]);
|
|
1743
|
-
var TriggerSchema =
|
|
1860
|
+
var TriggerSchema = z10.object({
|
|
1744
1861
|
kind: TriggerKindSchema,
|
|
1745
1862
|
from: NameSchema.optional(),
|
|
1746
|
-
fromUser:
|
|
1747
|
-
reason:
|
|
1863
|
+
fromUser: z10.boolean().default(false),
|
|
1864
|
+
reason: z10.string().default(""),
|
|
1748
1865
|
taskId: UlidSchema.optional(),
|
|
1749
1866
|
messageId: UlidSchema.optional(),
|
|
1750
1867
|
/** The channel the waking message was posted in. */
|
|
@@ -1752,20 +1869,27 @@ var TriggerSchema = z9.object({
|
|
|
1752
1869
|
/** The cron that came due, for a `cron` wake. */
|
|
1753
1870
|
cronId: UlidSchema.optional()
|
|
1754
1871
|
});
|
|
1755
|
-
var TurnThreadSchema =
|
|
1756
|
-
var TurnDispatchSchema =
|
|
1872
|
+
var TurnThreadSchema = z10.object({ id: UlidSchema, task: z10.boolean() });
|
|
1873
|
+
var TurnDispatchSchema = z10.object({
|
|
1757
1874
|
agent: NameSchema,
|
|
1758
1875
|
project: NameSchema,
|
|
1759
1876
|
thread: TurnThreadSchema.optional(),
|
|
1760
1877
|
channel: NameSchema.optional(),
|
|
1878
|
+
place: NameSchema.optional(),
|
|
1761
1879
|
trigger: TriggerSchema,
|
|
1762
|
-
priority:
|
|
1763
|
-
onboarding:
|
|
1880
|
+
priority: z10.number().int().min(0).max(2),
|
|
1881
|
+
onboarding: z10.boolean().default(false),
|
|
1764
1882
|
/** Every cron that came due for this turn, whatever wake it merged into; its prompt quotes each. */
|
|
1765
|
-
crons:
|
|
1883
|
+
crons: z10.array(UlidSchema).optional()
|
|
1766
1884
|
});
|
|
1767
1885
|
function conversationPart(conversation) {
|
|
1768
|
-
|
|
1886
|
+
if (conversation.thread !== void 0) {
|
|
1887
|
+
return conversation.thread;
|
|
1888
|
+
}
|
|
1889
|
+
if (conversation.channel !== void 0) {
|
|
1890
|
+
return `#${conversation.channel}`;
|
|
1891
|
+
}
|
|
1892
|
+
return conversation.place === void 0 ? void 0 : `~${conversation.place}`;
|
|
1769
1893
|
}
|
|
1770
1894
|
function sessionKey(agent, scope, part) {
|
|
1771
1895
|
return part === void 0 ? `${agent}/${scope}` : `${agent}/${scope}/${part}`;
|
|
@@ -1778,7 +1902,10 @@ function parseSessionKey(key) {
|
|
|
1778
1902
|
if (part === void 0) {
|
|
1779
1903
|
return { agent, scope };
|
|
1780
1904
|
}
|
|
1781
|
-
|
|
1905
|
+
if (part.startsWith("#")) {
|
|
1906
|
+
return { agent, scope, channel: part.slice(1) };
|
|
1907
|
+
}
|
|
1908
|
+
return part.startsWith("~") ? { agent, scope, place: part.slice(1) } : { agent, scope, thread: part };
|
|
1782
1909
|
}
|
|
1783
1910
|
function channelConversation(scope, channel) {
|
|
1784
1911
|
const at = channel.indexOf("/");
|
|
@@ -1786,7 +1913,11 @@ function channelConversation(scope, channel) {
|
|
|
1786
1913
|
const name = at < 0 ? channel : channel.slice(at + 1);
|
|
1787
1914
|
return place === scope && name !== "general" ? name : void 0;
|
|
1788
1915
|
}
|
|
1789
|
-
|
|
1916
|
+
function placeConversation(scope, channel) {
|
|
1917
|
+
const at = channel.indexOf("/");
|
|
1918
|
+
return scope === SOCIETY_SCOPE && at >= 0 ? channel.slice(0, at) : void 0;
|
|
1919
|
+
}
|
|
1920
|
+
var TurnRecordSchema = z10.object({
|
|
1790
1921
|
/** Assigned by the board when the turn begins; the turn's transcript is filed under it. */
|
|
1791
1922
|
id: UlidSchema.optional(),
|
|
1792
1923
|
agent: NameSchema,
|
|
@@ -1795,83 +1926,94 @@ var TurnRecordSchema = z9.object({
|
|
|
1795
1926
|
thread: UlidSchema.optional(),
|
|
1796
1927
|
/** The channel, never general, whose conversation the turn was in. */
|
|
1797
1928
|
channel: NameSchema.optional(),
|
|
1929
|
+
/** The project whose channels' conversation the turn was in, for a turn in the society scope. */
|
|
1930
|
+
place: NameSchema.optional(),
|
|
1798
1931
|
runner: NameSchema,
|
|
1799
1932
|
cli: CliKindSchema.nullable(),
|
|
1800
|
-
session:
|
|
1933
|
+
session: z10.string().nullable(),
|
|
1801
1934
|
trigger: TriggerSchema,
|
|
1802
1935
|
startedAt: IsoDateTimeSchema,
|
|
1803
1936
|
endedAt: IsoDateTimeSchema.nullable(),
|
|
1804
1937
|
exitReason: TurnExitReasonSchema.nullable(),
|
|
1805
1938
|
status: TurnStatusSchema.nullable(),
|
|
1806
|
-
error:
|
|
1939
|
+
error: z10.string().nullable(),
|
|
1807
1940
|
usage: UsageSchema.nullable(),
|
|
1808
|
-
costUsd:
|
|
1941
|
+
costUsd: z10.number().nonnegative().default(0),
|
|
1809
1942
|
/**
|
|
1810
1943
|
* The running total the CLI reported for the session after this turn, when it reports one. A
|
|
1811
1944
|
* resumed session starts from it, so the next turn's own cost is measured from here.
|
|
1812
1945
|
*/
|
|
1813
|
-
sessionCostUsd:
|
|
1814
|
-
toolCalls:
|
|
1946
|
+
sessionCostUsd: z10.number().nonnegative().optional(),
|
|
1947
|
+
toolCalls: z10.number().int().nonnegative().default(0),
|
|
1815
1948
|
/** The model the CLI reported for this turn, else the model configured for the agent, else null. */
|
|
1816
|
-
model:
|
|
1949
|
+
model: z10.string().nullable().default(null),
|
|
1817
1950
|
/** The task branch the turn left work on, with its commit, as the runner reported it. */
|
|
1818
1951
|
work: TurnWorkSchema.optional(),
|
|
1819
1952
|
/** Who stopped the turn, for one that ended `stopped`. */
|
|
1820
1953
|
stoppedBy: NameSchema.optional(),
|
|
1954
|
+
/** Why the CLI could not resume the turn's session, for a turn that failed so. */
|
|
1955
|
+
resumeFailed: ResumeFailureSchema.optional(),
|
|
1956
|
+
/** How many turns in a row, this one included, could not resume the session for a `retry`. */
|
|
1957
|
+
resumeRetries: z10.number().int().positive().optional(),
|
|
1958
|
+
/** Whether the server dropped the conversation's session after this turn, which the next starts anew. */
|
|
1959
|
+
sessionDropped: z10.boolean().optional(),
|
|
1821
1960
|
/** The crons whose fire started the turn, which record how it ended. */
|
|
1822
|
-
crons:
|
|
1961
|
+
crons: z10.array(UlidSchema).optional()
|
|
1823
1962
|
});
|
|
1824
|
-
var RunningTurnSchema =
|
|
1963
|
+
var RunningTurnSchema = z10.object({
|
|
1825
1964
|
turnId: UlidSchema,
|
|
1826
1965
|
agent: NameSchema,
|
|
1827
1966
|
scope: NameSchema,
|
|
1828
1967
|
thread: UlidSchema.optional(),
|
|
1829
1968
|
channel: NameSchema.optional(),
|
|
1969
|
+
place: NameSchema.optional(),
|
|
1830
1970
|
cli: CliKindSchema,
|
|
1831
1971
|
askedIn: ChannelRefSchema.optional(),
|
|
1832
|
-
steerable:
|
|
1833
|
-
stoppable:
|
|
1972
|
+
steerable: z10.boolean(),
|
|
1973
|
+
stoppable: z10.boolean()
|
|
1834
1974
|
});
|
|
1835
|
-
var SessionsFileSchema =
|
|
1836
|
-
claude:
|
|
1837
|
-
codex:
|
|
1975
|
+
var SessionsFileSchema = z10.object({
|
|
1976
|
+
claude: z10.string().optional(),
|
|
1977
|
+
codex: z10.string().optional(),
|
|
1838
1978
|
runner: NameSchema.optional()
|
|
1839
1979
|
});
|
|
1840
|
-
var WakeRequestSchema =
|
|
1980
|
+
var WakeRequestSchema = z10.object({
|
|
1841
1981
|
agent: NameSchema,
|
|
1842
1982
|
project: NameSchema,
|
|
1843
|
-
reason:
|
|
1983
|
+
reason: z10.string().default("manual wake"),
|
|
1844
1984
|
/** A manual wake, or a reflection turn requested ahead of the cadence. */
|
|
1845
|
-
kind:
|
|
1985
|
+
kind: z10.enum(["manual", "reflection"]).default("manual")
|
|
1846
1986
|
});
|
|
1847
|
-
var TurnHistoryEntrySchema =
|
|
1987
|
+
var TurnHistoryEntrySchema = z10.object({
|
|
1848
1988
|
/** The id of the event that ended the turn. */
|
|
1849
1989
|
id: UlidSchema,
|
|
1850
1990
|
/** The turn's own id, when it has a transcript to open; turns before transcripts have none. */
|
|
1851
1991
|
turnId: UlidSchema.optional(),
|
|
1852
1992
|
ts: IsoDateTimeSchema,
|
|
1853
|
-
outcome:
|
|
1993
|
+
outcome: z10.enum(["completed", "failed"]),
|
|
1854
1994
|
/** A project slug, or the society scope. */
|
|
1855
1995
|
project: NameSchema,
|
|
1856
1996
|
/** The thread whose conversation the turn was in; absent for the home conversation. */
|
|
1857
1997
|
thread: UlidSchema.optional(),
|
|
1858
1998
|
/** The channel, never general, whose conversation the turn was in. */
|
|
1859
1999
|
channel: NameSchema.optional(),
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
2000
|
+
/** The project whose channels' conversation the turn was in, for a turn in the society scope. */
|
|
2001
|
+
place: NameSchema.optional(),
|
|
2002
|
+
trigger: z10.string(),
|
|
2003
|
+
exitReason: z10.string().nullable(),
|
|
2004
|
+
costUsd: z10.number(),
|
|
1863
2005
|
/** The tokens the turn used; absent for turns logged before turns recorded them. */
|
|
1864
2006
|
usage: UsageSchema.optional(),
|
|
1865
|
-
model:
|
|
1866
|
-
summary:
|
|
1867
|
-
error:
|
|
2007
|
+
model: z10.string().nullable(),
|
|
2008
|
+
summary: z10.string().nullable(),
|
|
2009
|
+
error: z10.string().nullable(),
|
|
1868
2010
|
/** When the log recorded the turn's start; absent when the start is missing from the log. */
|
|
1869
2011
|
startedAt: IsoDateTimeSchema.optional(),
|
|
1870
|
-
toolCalls:
|
|
2012
|
+
toolCalls: z10.number().int().nonnegative().optional()
|
|
1871
2013
|
});
|
|
1872
2014
|
|
|
1873
2015
|
// packages/shared/dist/ops.js
|
|
1874
|
-
import { z as
|
|
2016
|
+
import { z as z11 } from "zod";
|
|
1875
2017
|
var OPS_SIGNAL_KINDS = [
|
|
1876
2018
|
"waiting_stage",
|
|
1877
2019
|
"backlog",
|
|
@@ -1887,7 +2029,7 @@ var OPS_SIGNAL_KINDS = [
|
|
|
1887
2029
|
"stuck_workspace",
|
|
1888
2030
|
"cron_failing"
|
|
1889
2031
|
];
|
|
1890
|
-
var OpsSignalKindSchema =
|
|
2032
|
+
var OpsSignalKindSchema = z11.enum(OPS_SIGNAL_KINDS);
|
|
1891
2033
|
var WAKING_SIGNAL_KINDS = [
|
|
1892
2034
|
"backlog",
|
|
1893
2035
|
"role_gap",
|
|
@@ -1897,15 +2039,15 @@ var WAKING_SIGNAL_KINDS = [
|
|
|
1897
2039
|
"blocked_capability",
|
|
1898
2040
|
"scaled"
|
|
1899
2041
|
];
|
|
1900
|
-
var OpsSignalSchema =
|
|
2042
|
+
var OpsSignalSchema = z11.object({
|
|
1901
2043
|
kind: OpsSignalKindSchema,
|
|
1902
2044
|
/** Identifies the condition, for example `backlog:demo:engineer`, so a persisting one is not logged again every pass. */
|
|
1903
|
-
key:
|
|
2045
|
+
key: z11.string().min(1),
|
|
1904
2046
|
/** One line a reader can act on. */
|
|
1905
|
-
summary:
|
|
2047
|
+
summary: z11.string().min(1),
|
|
1906
2048
|
/** The counter or timer value behind the signal. */
|
|
1907
|
-
value:
|
|
1908
|
-
threshold:
|
|
2049
|
+
value: z11.number(),
|
|
2050
|
+
threshold: z11.number().optional(),
|
|
1909
2051
|
project: NameSchema.optional(),
|
|
1910
2052
|
agent: NameSchema.optional(),
|
|
1911
2053
|
role: NameSchema.optional(),
|
|
@@ -1913,8 +2055,8 @@ var OpsSignalSchema = z10.object({
|
|
|
1913
2055
|
});
|
|
1914
2056
|
|
|
1915
2057
|
// packages/shared/dist/metrics.js
|
|
1916
|
-
import { z as
|
|
1917
|
-
var MetricsWindowSchema =
|
|
2058
|
+
import { z as z12 } from "zod";
|
|
2059
|
+
var MetricsWindowSchema = z12.enum(["24h", "7d", "all"]);
|
|
1918
2060
|
var METRICS_WINDOW_MS = {
|
|
1919
2061
|
"24h": 24 * 36e5,
|
|
1920
2062
|
"7d": 7 * 24 * 36e5,
|
|
@@ -1925,9 +2067,9 @@ function isBoardAction(tool) {
|
|
|
1925
2067
|
const verb = /^mcp__board__(.+)$/.exec(tool)?.[1];
|
|
1926
2068
|
return verb !== void 0 && !READ_VERBS.some((read2) => read2 === verb);
|
|
1927
2069
|
}
|
|
1928
|
-
var CountSchema =
|
|
1929
|
-
var MsSchema =
|
|
1930
|
-
var MetricsSchema =
|
|
2070
|
+
var CountSchema = z12.number().int().nonnegative();
|
|
2071
|
+
var MsSchema = z12.number().nonnegative();
|
|
2072
|
+
var MetricsSchema = z12.object({
|
|
1931
2073
|
window: MetricsWindowSchema,
|
|
1932
2074
|
since: IsoDateTimeSchema.nullable(),
|
|
1933
2075
|
until: IsoDateTimeSchema,
|
|
@@ -1935,12 +2077,12 @@ var MetricsSchema = z11.object({
|
|
|
1935
2077
|
* Finished turns that changed nothing on the board. Reflections are left out, since they write
|
|
1936
2078
|
* memory, not the board; turns without a transcript are counted apart, their actions unknown.
|
|
1937
2079
|
*/
|
|
1938
|
-
idle:
|
|
2080
|
+
idle: z12.object({
|
|
1939
2081
|
turns: CountSchema,
|
|
1940
2082
|
idle: CountSchema,
|
|
1941
2083
|
unknown: CountSchema,
|
|
1942
|
-
byTrigger:
|
|
1943
|
-
byAgent:
|
|
2084
|
+
byTrigger: z12.array(z12.object({ trigger: z12.string(), turns: CountSchema, idle: CountSchema })),
|
|
2085
|
+
byAgent: z12.array(z12.object({
|
|
1944
2086
|
agent: NameSchema,
|
|
1945
2087
|
role: NameSchema.nullable(),
|
|
1946
2088
|
turns: CountSchema,
|
|
@@ -1948,33 +2090,33 @@ var MetricsSchema = z11.object({
|
|
|
1948
2090
|
}))
|
|
1949
2091
|
}),
|
|
1950
2092
|
/** Work sent back to an earlier stage: over the tasks finished in the window, and by where. */
|
|
1951
|
-
sentBack:
|
|
2093
|
+
sentBack: z12.object({
|
|
1952
2094
|
finished: CountSchema,
|
|
1953
2095
|
sendBacks: CountSchema,
|
|
1954
2096
|
tasksSentBack: CountSchema,
|
|
1955
|
-
byStage:
|
|
1956
|
-
bySender:
|
|
2097
|
+
byStage: z12.array(z12.object({ project: NameSchema, stage: z12.string(), count: CountSchema })),
|
|
2098
|
+
bySender: z12.array(z12.object({ agent: NameSchema, count: CountSchema }))
|
|
1957
2099
|
}),
|
|
1958
2100
|
/** Posts in the threads of the tasks finished in the window, the board's own notices left out. */
|
|
1959
|
-
messages:
|
|
2101
|
+
messages: z12.object({
|
|
1960
2102
|
finished: CountSchema,
|
|
1961
2103
|
messages: CountSchema,
|
|
1962
|
-
byProject:
|
|
1963
|
-
busiest:
|
|
2104
|
+
byProject: z12.array(z12.object({ project: NameSchema, finished: CountSchema, messages: CountSchema })),
|
|
2105
|
+
busiest: z12.array(z12.object({
|
|
1964
2106
|
taskId: UlidSchema,
|
|
1965
|
-
title:
|
|
2107
|
+
title: z12.string(),
|
|
1966
2108
|
project: NameSchema,
|
|
1967
2109
|
messages: CountSchema
|
|
1968
2110
|
}))
|
|
1969
2111
|
}),
|
|
1970
2112
|
/** Time from a mention to the start of the turn it woke, in the conversation it was made in. */
|
|
1971
|
-
latency:
|
|
2113
|
+
latency: z12.object({
|
|
1972
2114
|
mentions: CountSchema,
|
|
1973
2115
|
/** Mentions no turn has followed yet. */
|
|
1974
2116
|
unanswered: CountSchema,
|
|
1975
2117
|
medianMs: MsSchema.nullable(),
|
|
1976
2118
|
slowestMs: MsSchema.nullable(),
|
|
1977
|
-
byAgent:
|
|
2119
|
+
byAgent: z12.array(z12.object({
|
|
1978
2120
|
agent: NameSchema,
|
|
1979
2121
|
mentions: CountSchema,
|
|
1980
2122
|
medianMs: MsSchema,
|
|
@@ -1982,73 +2124,73 @@ var MetricsSchema = z11.object({
|
|
|
1982
2124
|
}))
|
|
1983
2125
|
}),
|
|
1984
2126
|
/** The user's decisions: proposals decided, questions answered, and stages passed or sent back. */
|
|
1985
|
-
decisions:
|
|
2127
|
+
decisions: z12.object({
|
|
1986
2128
|
total: CountSchema,
|
|
1987
|
-
byDay:
|
|
1988
|
-
day:
|
|
2129
|
+
byDay: z12.array(z12.object({
|
|
2130
|
+
day: z12.string(),
|
|
1989
2131
|
proposals: CountSchema,
|
|
1990
2132
|
answers: CountSchema,
|
|
1991
2133
|
stages: CountSchema
|
|
1992
2134
|
}))
|
|
1993
2135
|
}),
|
|
1994
2136
|
/** Tasks that needed a capability no connected runner offered, and whether they still do. */
|
|
1995
|
-
blocked:
|
|
2137
|
+
blocked: z12.array(z12.object({
|
|
1996
2138
|
taskId: UlidSchema,
|
|
1997
|
-
title:
|
|
2139
|
+
title: z12.string().nullable(),
|
|
1998
2140
|
project: NameSchema.nullable(),
|
|
1999
|
-
summary:
|
|
2141
|
+
summary: z12.string(),
|
|
2000
2142
|
firstAt: IsoDateTimeSchema,
|
|
2001
2143
|
lastAt: IsoDateTimeSchema,
|
|
2002
|
-
holds:
|
|
2144
|
+
holds: z12.boolean()
|
|
2003
2145
|
}))
|
|
2004
2146
|
});
|
|
2005
|
-
var CitizenTokensSchema =
|
|
2147
|
+
var CitizenTokensSchema = z12.object({
|
|
2006
2148
|
agent: NameSchema,
|
|
2007
2149
|
turns: CountSchema,
|
|
2008
2150
|
withUsage: CountSchema,
|
|
2009
2151
|
usage: UsageSchema.nullable()
|
|
2010
2152
|
});
|
|
2011
|
-
var TokenUseSchema =
|
|
2153
|
+
var TokenUseSchema = z12.object({
|
|
2012
2154
|
window: MetricsWindowSchema,
|
|
2013
2155
|
since: IsoDateTimeSchema.nullable(),
|
|
2014
2156
|
until: IsoDateTimeSchema,
|
|
2015
|
-
citizens:
|
|
2157
|
+
citizens: z12.array(CitizenTokensSchema)
|
|
2016
2158
|
});
|
|
2017
2159
|
|
|
2018
2160
|
// packages/shared/dist/config.js
|
|
2019
|
-
import { z as
|
|
2020
|
-
var LogLevelSchema =
|
|
2021
|
-
var GithubSignInSchema =
|
|
2022
|
-
clientId:
|
|
2023
|
-
clientSecret:
|
|
2161
|
+
import { z as z13 } from "zod";
|
|
2162
|
+
var LogLevelSchema = z13.enum(["trace", "debug", "info", "warn", "error", "fatal"]);
|
|
2163
|
+
var GithubSignInSchema = z13.object({
|
|
2164
|
+
clientId: z13.string().min(1),
|
|
2165
|
+
clientSecret: z13.string().min(1),
|
|
2024
2166
|
/** Logins, lowercased; GitHub logins are case-insensitive. */
|
|
2025
|
-
users:
|
|
2167
|
+
users: z13.array(z13.string().min(1)).min(1)
|
|
2026
2168
|
});
|
|
2027
|
-
var ServerConfigSchema =
|
|
2028
|
-
dataDir:
|
|
2029
|
-
host:
|
|
2030
|
-
port:
|
|
2169
|
+
var ServerConfigSchema = z13.object({
|
|
2170
|
+
dataDir: z13.string().min(1),
|
|
2171
|
+
host: z13.string().min(1),
|
|
2172
|
+
port: z13.number().int().positive().max(65535),
|
|
2031
2173
|
/**
|
|
2032
2174
|
* An address every runner's agents reach this server at, for example https://board.example, or
|
|
2033
2175
|
* null for each runner to use the address it reaches the server at itself.
|
|
2034
2176
|
*/
|
|
2035
|
-
publicUrl:
|
|
2177
|
+
publicUrl: z13.string().min(1).nullable(),
|
|
2036
2178
|
logLevel: LogLevelSchema,
|
|
2037
2179
|
/** Turns running at once across every runner, or null for no limit beyond each runner's own. */
|
|
2038
|
-
concurrency:
|
|
2180
|
+
concurrency: z13.number().int().positive().nullable(),
|
|
2039
2181
|
/** How long one turn may run before it is stopped, or null for no limit. */
|
|
2040
|
-
turnTimeoutMs:
|
|
2182
|
+
turnTimeoutMs: z13.number().int().positive().nullable(),
|
|
2041
2183
|
/** Rounds of tool calls one Claude turn may take, or null for no limit. */
|
|
2042
|
-
toolRounds:
|
|
2184
|
+
toolRounds: z13.number().int().positive().nullable(),
|
|
2043
2185
|
/** How long a resident session stays warm after its last turn. */
|
|
2044
|
-
residentIdleMs:
|
|
2186
|
+
residentIdleMs: z13.number().int().positive(),
|
|
2045
2187
|
/** Signing in to the board with GitHub, or null when only a token lets anyone in. */
|
|
2046
2188
|
github: GithubSignInSchema.nullable(),
|
|
2047
2189
|
/**
|
|
2048
2190
|
* Reverse proxies in front of the server whose X-Forwarded-For entries name the client, for the
|
|
2049
2191
|
* rate limits on what anyone may ask without a token: 0 when clients connect directly.
|
|
2050
2192
|
*/
|
|
2051
|
-
trustedProxies:
|
|
2193
|
+
trustedProxies: z13.number().int().min(0).max(16)
|
|
2052
2194
|
});
|
|
2053
2195
|
function limit(value, fallback) {
|
|
2054
2196
|
if (value === void 0 || value.trim() === "") {
|
|
@@ -2086,25 +2228,25 @@ function loadServerConfig(env) {
|
|
|
2086
2228
|
trustedProxies: Number(env["STELLARIS_TRUSTED_PROXIES"] ?? "0")
|
|
2087
2229
|
});
|
|
2088
2230
|
}
|
|
2089
|
-
var RunnerConfigSchema =
|
|
2231
|
+
var RunnerConfigSchema = z13.object({
|
|
2090
2232
|
/** The board server, for example http://127.0.0.1:4700. */
|
|
2091
|
-
serverUrl:
|
|
2233
|
+
serverUrl: z13.string().url(),
|
|
2092
2234
|
/**
|
|
2093
2235
|
* The runner's token, from `runner add`, or null for the one it saved when it was enrolled, or
|
|
2094
2236
|
* to enroll it.
|
|
2095
2237
|
*/
|
|
2096
|
-
token:
|
|
2238
|
+
token: z13.string().min(1).nullable(),
|
|
2097
2239
|
/** The runner's own data directory: the board mirror, agent homes, repositories, worktrees. */
|
|
2098
|
-
dataDir:
|
|
2240
|
+
dataDir: z13.string().min(1),
|
|
2099
2241
|
logLevel: LogLevelSchema,
|
|
2100
2242
|
/** Turns this machine runs at once, or null for no limit. */
|
|
2101
|
-
slots:
|
|
2102
|
-
clis:
|
|
2103
|
-
capabilities:
|
|
2243
|
+
slots: z13.number().int().positive().nullable(),
|
|
2244
|
+
clis: z13.array(CliKindSchema).min(1),
|
|
2245
|
+
capabilities: z13.array(z13.string())
|
|
2104
2246
|
});
|
|
2105
2247
|
|
|
2106
2248
|
// packages/shared/dist/version.js
|
|
2107
|
-
var STELLARIS_VERSION = "0.3.
|
|
2249
|
+
var STELLARIS_VERSION = "0.3.24";
|
|
2108
2250
|
|
|
2109
2251
|
// packages/shared/dist/mentions.js
|
|
2110
2252
|
import { parse, postprocess, preprocess } from "micromark";
|
|
@@ -2488,6 +2630,7 @@ var HOME_GITIGNORE = [
|
|
|
2488
2630
|
"/projects/*/last-turn.json",
|
|
2489
2631
|
"/projects/*/threads/",
|
|
2490
2632
|
"/projects/*/channels/",
|
|
2633
|
+
"/projects/*/places/",
|
|
2491
2634
|
`/${HOME_SCRATCH}/`,
|
|
2492
2635
|
"*.tmp",
|
|
2493
2636
|
"node_modules/",
|
|
@@ -2505,7 +2648,7 @@ var HOME_GITIGNORE = [
|
|
|
2505
2648
|
var IGNORE_FILE = `${HOME_GITIGNORE.join("\n")}
|
|
2506
2649
|
`;
|
|
2507
2650
|
var PRE_RECEIVE = `#!/bin/sh
|
|
2508
|
-
forbidden='^(agent\\.json|cursors\\.json|role\\.md|turns/|\\.claude/|\\.codex/|projects/[^/]+/(sessions[^/]*\\.json|last-turn\\.json|threads/|channels/))'
|
|
2651
|
+
forbidden='^(agent\\.json|cursors\\.json|role\\.md|turns/|\\.claude/|\\.codex/|projects/[^/]+/(sessions[^/]*\\.json|last-turn\\.json|threads/|channels/|places/))'
|
|
2509
2652
|
while read old new ref; do
|
|
2510
2653
|
if [ "$ref" != "refs/heads/main" ]; then
|
|
2511
2654
|
echo "a home takes pushes to main only" >&2
|
|
@@ -2825,32 +2968,33 @@ function postMentions(body) {
|
|
|
2825
2968
|
}
|
|
2826
2969
|
|
|
2827
2970
|
// packages/board-core/dist/metrics.js
|
|
2828
|
-
import { z as
|
|
2829
|
-
var TurnPayload =
|
|
2830
|
-
turnId:
|
|
2831
|
-
project:
|
|
2832
|
-
thread:
|
|
2833
|
-
channel:
|
|
2834
|
-
|
|
2971
|
+
import { z as z14 } from "zod";
|
|
2972
|
+
var TurnPayload = z14.object({
|
|
2973
|
+
turnId: z14.string().optional(),
|
|
2974
|
+
project: z14.string(),
|
|
2975
|
+
thread: z14.string().optional(),
|
|
2976
|
+
channel: z14.string().optional(),
|
|
2977
|
+
place: z14.string().optional(),
|
|
2978
|
+
trigger: z14.string()
|
|
2835
2979
|
});
|
|
2836
|
-
var TaskPayload =
|
|
2837
|
-
taskId:
|
|
2838
|
-
project:
|
|
2839
|
-
from:
|
|
2980
|
+
var TaskPayload = z14.object({
|
|
2981
|
+
taskId: z14.string(),
|
|
2982
|
+
project: z14.string(),
|
|
2983
|
+
from: z14.string().optional()
|
|
2840
2984
|
});
|
|
2841
|
-
var MessagePayload =
|
|
2842
|
-
id:
|
|
2843
|
-
channel:
|
|
2844
|
-
thread:
|
|
2845
|
-
mentions:
|
|
2985
|
+
var MessagePayload = z14.object({
|
|
2986
|
+
id: z14.string().optional(),
|
|
2987
|
+
channel: z14.string(),
|
|
2988
|
+
thread: z14.string().nullable().optional(),
|
|
2989
|
+
mentions: z14.array(z14.string()).default([])
|
|
2846
2990
|
});
|
|
2847
|
-
var SteeredPayload =
|
|
2848
|
-
var SignalPayload =
|
|
2849
|
-
kind:
|
|
2850
|
-
key:
|
|
2851
|
-
summary:
|
|
2852
|
-
taskId:
|
|
2853
|
-
project:
|
|
2991
|
+
var SteeredPayload = z14.object({ messages: z14.array(z14.string()) });
|
|
2992
|
+
var SignalPayload = z14.object({
|
|
2993
|
+
kind: z14.string(),
|
|
2994
|
+
key: z14.string(),
|
|
2995
|
+
summary: z14.string(),
|
|
2996
|
+
taskId: z14.string().optional(),
|
|
2997
|
+
project: z14.string().optional()
|
|
2854
2998
|
});
|
|
2855
2999
|
function median(values) {
|
|
2856
3000
|
const sorted = values.toSorted((a, b) => a - b);
|
|
@@ -2876,11 +3020,16 @@ function answers(mention, turn) {
|
|
|
2876
3020
|
if (!mention.scopes.includes(turn.project)) {
|
|
2877
3021
|
return false;
|
|
2878
3022
|
}
|
|
2879
|
-
const
|
|
3023
|
+
const topLevel = mention.thread === null;
|
|
3024
|
+
const own = topLevel ? channelConversation(turn.project, mention.channel) : void 0;
|
|
3025
|
+
const place = topLevel ? placeConversation(turn.project, mention.channel) : void 0;
|
|
2880
3026
|
if (turn.channel !== null) {
|
|
2881
|
-
return
|
|
3027
|
+
return topLevel && own === turn.channel;
|
|
3028
|
+
}
|
|
3029
|
+
if (turn.place !== null) {
|
|
3030
|
+
return topLevel && place === turn.place;
|
|
2882
3031
|
}
|
|
2883
|
-
return
|
|
3032
|
+
return topLevel && own === void 0 && place === void 0 || turn.trigger === "mention";
|
|
2884
3033
|
}
|
|
2885
3034
|
function computeMetrics(input) {
|
|
2886
3035
|
const { events, now, window } = input;
|
|
@@ -2946,6 +3095,7 @@ function computeMetrics(input) {
|
|
|
2946
3095
|
at,
|
|
2947
3096
|
thread: turn.data.thread ?? null,
|
|
2948
3097
|
channel: turn.data.channel ?? null,
|
|
3098
|
+
place: turn.data.place ?? null,
|
|
2949
3099
|
project: turn.data.project,
|
|
2950
3100
|
trigger: turn.data.trigger
|
|
2951
3101
|
};
|
|
@@ -3140,19 +3290,20 @@ function computeMetrics(input) {
|
|
|
3140
3290
|
}
|
|
3141
3291
|
|
|
3142
3292
|
// packages/board-core/dist/turn-history.js
|
|
3143
|
-
import { z as
|
|
3144
|
-
var TurnHistoryPayloadSchema =
|
|
3293
|
+
import { z as z15 } from "zod";
|
|
3294
|
+
var TurnHistoryPayloadSchema = z15.object({
|
|
3145
3295
|
project: NameSchema,
|
|
3146
3296
|
thread: UlidSchema.optional(),
|
|
3147
3297
|
channel: NameSchema.optional(),
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3298
|
+
place: NameSchema.optional(),
|
|
3299
|
+
trigger: z15.string().default("manual"),
|
|
3300
|
+
exitReason: z15.string().nullable().default(null),
|
|
3301
|
+
costUsd: z15.number().default(0),
|
|
3151
3302
|
usage: UsageSchema.optional(),
|
|
3152
|
-
model:
|
|
3153
|
-
summary:
|
|
3154
|
-
error:
|
|
3155
|
-
toolCalls:
|
|
3303
|
+
model: z15.string().nullable().default(null),
|
|
3304
|
+
summary: z15.string().nullable().default(null),
|
|
3305
|
+
error: z15.string().nullable().default(null),
|
|
3306
|
+
toolCalls: z15.number().int().nonnegative().optional(),
|
|
3156
3307
|
turnId: UlidSchema.optional()
|
|
3157
3308
|
});
|
|
3158
3309
|
function tokenUse(events, window, now) {
|
|
@@ -3383,11 +3534,14 @@ var BoardPaths = class {
|
|
|
3383
3534
|
return path5.join(this.agentProjects(name), slug);
|
|
3384
3535
|
}
|
|
3385
3536
|
/**
|
|
3386
|
-
* A thread's
|
|
3387
|
-
* conversation; `part` is a thread's id or
|
|
3537
|
+
* A thread's, a channel's, or, in the society scope, a project's conversation's session and last
|
|
3538
|
+
* turn, beside the scope's home conversation; `part` is a thread's id, `#channel`, or `~project`.
|
|
3388
3539
|
*/
|
|
3389
3540
|
agentConversation(name, slug, part) {
|
|
3390
|
-
|
|
3541
|
+
if (part.startsWith("#")) {
|
|
3542
|
+
return path5.join(this.agentProject(name, slug), "channels", part.slice(1));
|
|
3543
|
+
}
|
|
3544
|
+
return part.startsWith("~") ? path5.join(this.agentProject(name, slug), "places", part.slice(1)) : path5.join(this.agentProject(name, slug), "threads", part);
|
|
3391
3545
|
}
|
|
3392
3546
|
agentCursors(name) {
|
|
3393
3547
|
return path5.join(this.agent(name), "cursors.json");
|
|
@@ -3398,6 +3552,13 @@ var BoardPaths = class {
|
|
|
3398
3552
|
eventLog() {
|
|
3399
3553
|
return path5.join(this.events(), "log.jsonl");
|
|
3400
3554
|
}
|
|
3555
|
+
/** Images citizens uploaded, by id: outside the projection, which every runner mirrors. */
|
|
3556
|
+
images() {
|
|
3557
|
+
return path5.join(this.dataDir, "images");
|
|
3558
|
+
}
|
|
3559
|
+
image(id) {
|
|
3560
|
+
return path5.join(this.images(), id);
|
|
3561
|
+
}
|
|
3401
3562
|
state() {
|
|
3402
3563
|
return path5.join(this.dataDir, "state");
|
|
3403
3564
|
}
|
|
@@ -3414,6 +3575,101 @@ var BoardPaths = class {
|
|
|
3414
3575
|
}
|
|
3415
3576
|
};
|
|
3416
3577
|
|
|
3578
|
+
// packages/board-core/dist/skill-folder.js
|
|
3579
|
+
import { constants } from "node:fs";
|
|
3580
|
+
import { lstat, open as open2, readdir as readdir3, realpath } from "node:fs/promises";
|
|
3581
|
+
import path6 from "node:path";
|
|
3582
|
+
var SKILL_TEXT_LIMIT_CHARS = 1e5;
|
|
3583
|
+
var SKILL_TEXT_LIMIT_BYTES = SKILL_TEXT_LIMIT_CHARS * 4;
|
|
3584
|
+
function skillFileText(bytes) {
|
|
3585
|
+
const text = bytes.includes(0) ? null : bytes.toString("utf8");
|
|
3586
|
+
return {
|
|
3587
|
+
text: text === null ? null : text.slice(0, SKILL_TEXT_LIMIT_CHARS),
|
|
3588
|
+
truncated: text !== null && text.length > SKILL_TEXT_LIMIT_CHARS
|
|
3589
|
+
};
|
|
3590
|
+
}
|
|
3591
|
+
function inside(root, file) {
|
|
3592
|
+
const relative = path6.relative(root, file);
|
|
3593
|
+
return relative !== ".." && !relative.startsWith(`..${path6.sep}`) && !path6.isAbsolute(relative);
|
|
3594
|
+
}
|
|
3595
|
+
async function walk(root, prefix, found) {
|
|
3596
|
+
const entries = await readdir3(path6.join(root, ...prefix === "" ? [] : prefix.split("/")), {
|
|
3597
|
+
withFileTypes: true
|
|
3598
|
+
}).catch(() => []);
|
|
3599
|
+
for (const entry of entries.toSorted((a, b) => a.name < b.name ? -1 : 1)) {
|
|
3600
|
+
if (found.length >= SKILL_FILES_LIMIT) {
|
|
3601
|
+
return;
|
|
3602
|
+
}
|
|
3603
|
+
const relative = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
3604
|
+
if (!isSafeRelativePath(relative)) {
|
|
3605
|
+
continue;
|
|
3606
|
+
}
|
|
3607
|
+
if (entry.isDirectory()) {
|
|
3608
|
+
await walk(root, relative, found);
|
|
3609
|
+
} else if (entry.isFile() && !entry.name.endsWith(".tmp")) {
|
|
3610
|
+
found.push(relative);
|
|
3611
|
+
}
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
async function readEntry(root, relative) {
|
|
3615
|
+
let real;
|
|
3616
|
+
try {
|
|
3617
|
+
real = await realpath(path6.join(root, ...relative.split("/")));
|
|
3618
|
+
} catch {
|
|
3619
|
+
return null;
|
|
3620
|
+
}
|
|
3621
|
+
if (!inside(root, real)) {
|
|
3622
|
+
return null;
|
|
3623
|
+
}
|
|
3624
|
+
const handle = await open2(real, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK).catch(() => null);
|
|
3625
|
+
if (handle === null) {
|
|
3626
|
+
return null;
|
|
3627
|
+
}
|
|
3628
|
+
try {
|
|
3629
|
+
const stats = await handle.stat();
|
|
3630
|
+
if (!stats.isFile()) {
|
|
3631
|
+
return null;
|
|
3632
|
+
}
|
|
3633
|
+
const buffer = Buffer.alloc(Math.min(stats.size, SKILL_TEXT_LIMIT_BYTES));
|
|
3634
|
+
let size = 0;
|
|
3635
|
+
while (size < buffer.length) {
|
|
3636
|
+
const { bytesRead } = await handle.read(buffer, size, buffer.length - size, size);
|
|
3637
|
+
if (bytesRead === 0) {
|
|
3638
|
+
break;
|
|
3639
|
+
}
|
|
3640
|
+
size += bytesRead;
|
|
3641
|
+
}
|
|
3642
|
+
return {
|
|
3643
|
+
path: relative,
|
|
3644
|
+
size: stats.size,
|
|
3645
|
+
executable: (stats.mode & 73) !== 0,
|
|
3646
|
+
...skillFileText(buffer.subarray(0, size))
|
|
3647
|
+
};
|
|
3648
|
+
} finally {
|
|
3649
|
+
await handle.close();
|
|
3650
|
+
}
|
|
3651
|
+
}
|
|
3652
|
+
async function readSkillFolder(dir) {
|
|
3653
|
+
const info = await lstat(dir).catch(() => null);
|
|
3654
|
+
if (info === null || !info.isDirectory()) {
|
|
3655
|
+
return null;
|
|
3656
|
+
}
|
|
3657
|
+
const root = await realpath(dir).catch(() => null);
|
|
3658
|
+
if (root === null) {
|
|
3659
|
+
return null;
|
|
3660
|
+
}
|
|
3661
|
+
const found = [];
|
|
3662
|
+
await walk(root, "", found);
|
|
3663
|
+
const files = [];
|
|
3664
|
+
for (const relative of found) {
|
|
3665
|
+
const file = await readEntry(root, relative);
|
|
3666
|
+
if (file !== null) {
|
|
3667
|
+
files.push(file);
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
return files.some((file) => file.path === "SKILL.md") ? files : null;
|
|
3671
|
+
}
|
|
3672
|
+
|
|
3417
3673
|
// packages/board-core/dist/tokens.js
|
|
3418
3674
|
import { createHash as createHash2, randomBytes } from "node:crypto";
|
|
3419
3675
|
function mintToken() {
|
|
@@ -3427,48 +3683,62 @@ function hashToken(token) {
|
|
|
3427
3683
|
var SYSTEM_ACTOR = { name: "board", role: USER_ROLE };
|
|
3428
3684
|
var CLOSING_ATTEMPTS = 3;
|
|
3429
3685
|
function ownTurn(actor, turn) {
|
|
3430
|
-
return turn.agent === actor.name && turn.scope === actor.scope && turn.thread === actor.thread && turn.channel === actor.channel;
|
|
3686
|
+
return turn.agent === actor.name && turn.scope === actor.scope && turn.thread === actor.thread && turn.channel === actor.channel && turn.place === actor.place;
|
|
3431
3687
|
}
|
|
3432
3688
|
function namesList(names) {
|
|
3433
3689
|
return names.length <= 1 ? names[0] ?? "" : `${names.slice(0, -1).join(", ")} and ${names.at(-1) ?? ""}`;
|
|
3434
3690
|
}
|
|
3435
3691
|
var SKIPPED_IN_HOME = /* @__PURE__ */ new Set([".git", "turns", ".claude", ".codex"]);
|
|
3436
|
-
var RunnerTokensSchema =
|
|
3437
|
-
runners:
|
|
3692
|
+
var RunnerTokensSchema = z16.object({
|
|
3693
|
+
runners: z16.record(z16.string(), z16.object({ tokenHash: z16.string().min(1) }))
|
|
3438
3694
|
});
|
|
3439
|
-
var SignInsSchema =
|
|
3440
|
-
signIns:
|
|
3441
|
-
tokenHash:
|
|
3442
|
-
login:
|
|
3695
|
+
var SignInsSchema = z16.object({
|
|
3696
|
+
signIns: z16.array(z16.object({
|
|
3697
|
+
tokenHash: z16.string().min(1),
|
|
3698
|
+
login: z16.string().min(1),
|
|
3443
3699
|
/** The account's picture at the identity provider; sign-ins from before it have none. */
|
|
3444
|
-
avatarUrl:
|
|
3700
|
+
avatarUrl: z16.url().optional(),
|
|
3445
3701
|
createdAt: IsoDateTimeSchema,
|
|
3446
3702
|
expiresAt: IsoDateTimeSchema
|
|
3447
3703
|
})).default([])
|
|
3448
3704
|
});
|
|
3449
|
-
var TurnStartPayloadSchema =
|
|
3450
|
-
var CursorsSchema =
|
|
3451
|
-
digest:
|
|
3452
|
-
scopes:
|
|
3453
|
-
threadsFrom:
|
|
3454
|
-
channelsFrom:
|
|
3705
|
+
var TurnStartPayloadSchema = z16.object({ project: NameSchema, turnId: UlidSchema.optional() });
|
|
3706
|
+
var CursorsSchema = z16.object({
|
|
3707
|
+
digest: z16.string().nullable(),
|
|
3708
|
+
scopes: z16.record(z16.string(), z16.string()).default({}),
|
|
3709
|
+
threadsFrom: z16.record(z16.string(), z16.string().nullable()).default({}),
|
|
3710
|
+
channelsFrom: z16.record(z16.string(), z16.string().nullable()).default({}),
|
|
3711
|
+
placesFrom: z16.record(z16.string(), z16.string().nullable()).default({}),
|
|
3455
3712
|
/** How far a work role has read news, which goes to whichever of its general conversations reads first. */
|
|
3456
|
-
news:
|
|
3713
|
+
news: z16.string().optional()
|
|
3457
3714
|
});
|
|
3458
|
-
var NO_CURSORS = {
|
|
3715
|
+
var NO_CURSORS = {
|
|
3716
|
+
digest: null,
|
|
3717
|
+
scopes: {},
|
|
3718
|
+
threadsFrom: {},
|
|
3719
|
+
channelsFrom: {},
|
|
3720
|
+
placesFrom: {}
|
|
3721
|
+
};
|
|
3459
3722
|
function conversationKey(scope, part) {
|
|
3460
3723
|
return part === null ? scope : `${scope}/${part}`;
|
|
3461
3724
|
}
|
|
3462
3725
|
function channelOfPart(part) {
|
|
3463
3726
|
return part?.startsWith("#") === true ? part.slice(1) : void 0;
|
|
3464
3727
|
}
|
|
3728
|
+
function placeOfPart(part) {
|
|
3729
|
+
return part?.startsWith("~") === true ? part.slice(1) : void 0;
|
|
3730
|
+
}
|
|
3465
3731
|
function threadOfPart(part) {
|
|
3466
|
-
return part === null || part === void 0 || part.startsWith("#") ? void 0 : part;
|
|
3732
|
+
return part === null || part === void 0 || part.startsWith("#") || part.startsWith("~") ? void 0 : part;
|
|
3467
3733
|
}
|
|
3468
3734
|
function channelStart(cursors, scope) {
|
|
3469
3735
|
const from = cursors.channelsFrom[scope];
|
|
3470
3736
|
return from === void 0 ? cursors.digest : from;
|
|
3471
3737
|
}
|
|
3738
|
+
function placeStart(cursors, scope) {
|
|
3739
|
+
const from = cursors.placesFrom[scope];
|
|
3740
|
+
return from === void 0 ? cursors.digest : from;
|
|
3741
|
+
}
|
|
3472
3742
|
function threadStart(cursors, scope) {
|
|
3473
3743
|
const from = cursors.threadsFrom[scope];
|
|
3474
3744
|
return from === void 0 ? cursors.digest : from;
|
|
@@ -3484,7 +3754,7 @@ function cursorOf(cursors, scope, part = null) {
|
|
|
3484
3754
|
if (part === null) {
|
|
3485
3755
|
return cursors.scopes[scope] ?? cursors.digest;
|
|
3486
3756
|
}
|
|
3487
|
-
return cursors.scopes[conversationKey(scope, part)] ?? (part.startsWith("#") ? channelStart(cursors, scope) : threadStart(cursors, scope));
|
|
3757
|
+
return cursors.scopes[conversationKey(scope, part)] ?? (part.startsWith("#") ? channelStart(cursors, scope) : part.startsWith("~") ? placeStart(cursors, scope) : threadStart(cursors, scope));
|
|
3488
3758
|
}
|
|
3489
3759
|
function byId(a, b) {
|
|
3490
3760
|
return a.message.id < b.message.id ? -1 : a.message.id > b.message.id ? 1 : 0;
|
|
@@ -3492,12 +3762,14 @@ function byId(a, b) {
|
|
|
3492
3762
|
function turnTime(record) {
|
|
3493
3763
|
return record.endedAt ?? record.startedAt;
|
|
3494
3764
|
}
|
|
3495
|
-
|
|
3765
|
+
function turnConversation(record) {
|
|
3766
|
+
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` : "";
|
|
3767
|
+
}
|
|
3768
|
+
var PausedSchema = z16.object({ paused: z16.boolean() });
|
|
3496
3769
|
var DEFAULT_LEASE_MS = 30 * 60 * 1e3;
|
|
3497
3770
|
function withoutMentions(text) {
|
|
3498
3771
|
return text.replaceAll(MENTION_PATTERN, "$1$2");
|
|
3499
3772
|
}
|
|
3500
|
-
var SKILL_TEXT_LIMIT_CHARS = 1e5;
|
|
3501
3773
|
function skillFolderOf(proposal) {
|
|
3502
3774
|
if (proposal.kind !== "skill") {
|
|
3503
3775
|
return null;
|
|
@@ -3506,7 +3778,7 @@ function skillFolderOf(proposal) {
|
|
|
3506
3778
|
return parsed.success && parsed.data.body === void 0 ? parsed.data.name : null;
|
|
3507
3779
|
}
|
|
3508
3780
|
function proposedFrom(origin, where) {
|
|
3509
|
-
return origin !== void 0 && origin.scope === where.scope && origin.thread === where.thread && origin.channel === where.channel;
|
|
3781
|
+
return origin !== void 0 && origin.scope === where.scope && origin.thread === where.thread && origin.channel === where.channel && origin.place === where.place;
|
|
3510
3782
|
}
|
|
3511
3783
|
function megabytes(bytes) {
|
|
3512
3784
|
return (bytes / (1024 * 1024)).toFixed(1).replace(/\.0$/, "");
|
|
@@ -3517,7 +3789,7 @@ var OPS_WAKE_TRIGGER = "ops_event";
|
|
|
3517
3789
|
var FRONT_DESK_TRIGGER = "user_post";
|
|
3518
3790
|
var OPS_CHANNELS = ["governance"];
|
|
3519
3791
|
var FRONT_DESK_CHANNELS = [ASK_CHANNEL];
|
|
3520
|
-
var AlignmentsSchema =
|
|
3792
|
+
var AlignmentsSchema = z16.object({ applied: z16.array(z16.string()).default([]) });
|
|
3521
3793
|
var FOLLOW_DECISIONS = "ops-readers-follow-decisions";
|
|
3522
3794
|
var THREAD_RECORDS = "threads-as-records";
|
|
3523
3795
|
var DIGEST_CURSORS = "digest-replaces-inbox";
|
|
@@ -3528,13 +3800,15 @@ var THREAD_CONVERSATIONS = "thread-conversations";
|
|
|
3528
3800
|
var ASKS_CHANNEL_OPENED = "asks-channel-opened";
|
|
3529
3801
|
var KNOWLEDGE_REMOVAL = "knowledge-removal-granted";
|
|
3530
3802
|
var CHANNEL_CONVERSATIONS = "channel-conversations";
|
|
3803
|
+
var PLACE_CONVERSATIONS = "place-conversations";
|
|
3531
3804
|
var DASHBOARD_UPDATES = "dashboard-updates-granted";
|
|
3532
3805
|
var CRON_VERBS = "cron-verbs-granted";
|
|
3533
3806
|
var KNOWLEDGE_NOTICES = "knowledge-notices";
|
|
3534
|
-
var
|
|
3807
|
+
var IMAGE_UPLOADS = "image-uploads-granted";
|
|
3808
|
+
var KnowledgeEventSchema = z16.object({
|
|
3535
3809
|
topic: NameSchema,
|
|
3536
3810
|
project: NameSchema.nullable(),
|
|
3537
|
-
replaced:
|
|
3811
|
+
replaced: z16.boolean().optional()
|
|
3538
3812
|
});
|
|
3539
3813
|
var CRON_VERB_NAMES = ["create_cron", "update_cron", "remove_cron"];
|
|
3540
3814
|
var OLD_DASHBOARD_LINE = "Agents may edit this file. It is rendered by the board UI.";
|
|
@@ -3601,8 +3875,8 @@ function restage(existing, edit) {
|
|
|
3601
3875
|
};
|
|
3602
3876
|
}
|
|
3603
3877
|
function reopenStage(stage) {
|
|
3604
|
-
const { completedBy: _by, completedAt: _at, ...
|
|
3605
|
-
return
|
|
3878
|
+
const { completedBy: _by, completedAt: _at, ...open3 } = stage;
|
|
3879
|
+
return open3;
|
|
3606
3880
|
}
|
|
3607
3881
|
function gateChanges(before, after) {
|
|
3608
3882
|
const changes = [];
|
|
@@ -3666,6 +3940,7 @@ var Board = class _Board {
|
|
|
3666
3940
|
mutex = new Mutex();
|
|
3667
3941
|
/** The turns in flight, which the server hosting them supplies; none until it does. */
|
|
3668
3942
|
turnsInFlight = () => [];
|
|
3943
|
+
imageSource = null;
|
|
3669
3944
|
newId = monotonicFactory();
|
|
3670
3945
|
events;
|
|
3671
3946
|
actionCounts = /* @__PURE__ */ new Map();
|
|
@@ -3682,7 +3957,7 @@ var Board = class _Board {
|
|
|
3682
3957
|
homes;
|
|
3683
3958
|
constructor(dataDir, options) {
|
|
3684
3959
|
this.paths = new BoardPaths(dataDir);
|
|
3685
|
-
this.homes = new HomeRepos(
|
|
3960
|
+
this.homes = new HomeRepos(path7.join(this.paths.state(), "home-hooks"));
|
|
3686
3961
|
this.leaseMs = options.leaseMs ?? DEFAULT_LEASE_MS;
|
|
3687
3962
|
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
3688
3963
|
this.events = new EventLog(this.paths.eventLog(), () => this.newId(), this.now);
|
|
@@ -3715,7 +3990,7 @@ var Board = class _Board {
|
|
|
3715
3990
|
* repeated, so a member that later chooses otherwise keeps its choice.
|
|
3716
3991
|
*/
|
|
3717
3992
|
async applyAlignments() {
|
|
3718
|
-
const file =
|
|
3993
|
+
const file = path7.join(this.paths.state(), "alignments.json");
|
|
3719
3994
|
const { applied } = await exists(file) ? await readJson(file, AlignmentsSchema) : { applied: [] };
|
|
3720
3995
|
const pending = [
|
|
3721
3996
|
FOLLOW_DECISIONS,
|
|
@@ -3730,7 +4005,9 @@ var Board = class _Board {
|
|
|
3730
4005
|
CHANNEL_CONVERSATIONS,
|
|
3731
4006
|
DASHBOARD_UPDATES,
|
|
3732
4007
|
CRON_VERBS,
|
|
3733
|
-
KNOWLEDGE_NOTICES
|
|
4008
|
+
KNOWLEDGE_NOTICES,
|
|
4009
|
+
IMAGE_UPLOADS,
|
|
4010
|
+
PLACE_CONVERSATIONS
|
|
3734
4011
|
].filter((name) => !applied.includes(name));
|
|
3735
4012
|
if (pending.length === 0) {
|
|
3736
4013
|
return;
|
|
@@ -3760,13 +4037,13 @@ var Board = class _Board {
|
|
|
3760
4037
|
await this.openAsksChannel();
|
|
3761
4038
|
}
|
|
3762
4039
|
if (pending.includes(KNOWLEDGE_REMOVAL)) {
|
|
3763
|
-
await this.
|
|
4040
|
+
await this.grantBeside("write_knowledge", "remove_knowledge");
|
|
3764
4041
|
}
|
|
3765
4042
|
if (pending.includes(CHANNEL_CONVERSATIONS)) {
|
|
3766
4043
|
await this.startChannelCursors();
|
|
3767
4044
|
}
|
|
3768
4045
|
if (pending.includes(DASHBOARD_UPDATES)) {
|
|
3769
|
-
await this.
|
|
4046
|
+
await this.grantBeside("write_knowledge", "update_dashboard");
|
|
3770
4047
|
await this.rewordDashboards();
|
|
3771
4048
|
}
|
|
3772
4049
|
if (pending.includes(CRON_VERBS)) {
|
|
@@ -3775,6 +4052,12 @@ var Board = class _Board {
|
|
|
3775
4052
|
if (pending.includes(KNOWLEDGE_NOTICES)) {
|
|
3776
4053
|
await this.noticeKnowledgeNotes();
|
|
3777
4054
|
}
|
|
4055
|
+
if (pending.includes(IMAGE_UPLOADS)) {
|
|
4056
|
+
await this.grantBeside("post_message", "upload_image");
|
|
4057
|
+
}
|
|
4058
|
+
if (pending.includes(PLACE_CONVERSATIONS)) {
|
|
4059
|
+
await this.startPlaceCursors();
|
|
4060
|
+
}
|
|
3778
4061
|
await writeJson(file, { applied: [...applied, ...pending] });
|
|
3779
4062
|
}
|
|
3780
4063
|
/**
|
|
@@ -3816,12 +4099,12 @@ var Board = class _Board {
|
|
|
3816
4099
|
}
|
|
3817
4100
|
}
|
|
3818
4101
|
/**
|
|
3819
|
-
* Grants `verb` once to every charter granting `
|
|
3820
|
-
*
|
|
4102
|
+
* Grants `verb` once to every charter granting `beside`, the work roles' charters in practice;
|
|
4103
|
+
* seed roles have it from their seed.
|
|
3821
4104
|
*/
|
|
3822
|
-
async
|
|
4105
|
+
async grantBeside(beside, verb) {
|
|
3823
4106
|
for (const charter of await this.listRoles()) {
|
|
3824
|
-
if (charter.verbs.includes(
|
|
4107
|
+
if (charter.verbs.includes(beside) && !charter.verbs.includes(verb)) {
|
|
3825
4108
|
await this.writeRoleUnlocked(USER_NAME, { ...charter, verbs: [...charter.verbs, verb] }, { verbsAdded: [verb] });
|
|
3826
4109
|
}
|
|
3827
4110
|
}
|
|
@@ -3979,7 +4262,7 @@ var Board = class _Board {
|
|
|
3979
4262
|
}
|
|
3980
4263
|
}
|
|
3981
4264
|
for (const file of await listFiles(this.paths.tasks(slug))) {
|
|
3982
|
-
const raw = await readMarkdown(
|
|
4265
|
+
const raw = await readMarkdown(path7.join(this.paths.tasks(slug), file), z16.record(z16.string(), z16.unknown()));
|
|
3983
4266
|
const legacy = raw.data["thread"];
|
|
3984
4267
|
if (legacy === void 0) {
|
|
3985
4268
|
continue;
|
|
@@ -3987,7 +4270,7 @@ var Board = class _Board {
|
|
|
3987
4270
|
const task = { ...TaskFrontmatterSchema.parse(raw.data), body: raw.body };
|
|
3988
4271
|
if ((legacy === "open" || legacy === "closed") && !await exists(this.paths.threadFile(threads, task.id))) {
|
|
3989
4272
|
const ended = task.status === "done" || task.status === "abandoned";
|
|
3990
|
-
const
|
|
4273
|
+
const open3 = legacy === "open" && !ended;
|
|
3991
4274
|
const openedBy = opened.get(task.id);
|
|
3992
4275
|
const closedBy = closed.get(task.id);
|
|
3993
4276
|
await this.writeThread(threads, {
|
|
@@ -3995,10 +4278,10 @@ var Board = class _Board {
|
|
|
3995
4278
|
channel: channelRef(slug, "general"),
|
|
3996
4279
|
title: task.title,
|
|
3997
4280
|
subject: { kind: "task", id: task.id },
|
|
3998
|
-
state:
|
|
4281
|
+
state: open3 ? "open" : "closed",
|
|
3999
4282
|
openedBy: openedBy?.actor ?? task.createdBy,
|
|
4000
4283
|
openedAt: openedBy?.ts ?? task.createdAt,
|
|
4001
|
-
...
|
|
4284
|
+
...open3 ? {} : {
|
|
4002
4285
|
closedBy: closedBy?.actor ?? SYSTEM_ACTOR.name,
|
|
4003
4286
|
closedAt: closedBy?.ts ?? this.now().toISOString()
|
|
4004
4287
|
},
|
|
@@ -4030,6 +4313,24 @@ var Board = class _Board {
|
|
|
4030
4313
|
await writeJson(file, { ...cursors, channelsFrom });
|
|
4031
4314
|
}
|
|
4032
4315
|
}
|
|
4316
|
+
/**
|
|
4317
|
+
* A project's channels were once read, from the society scope, in the society's home
|
|
4318
|
+
* conversation, so a member had read each up to that cursor. Each project's conversation in the
|
|
4319
|
+
* society scope starts from it now, as each takes a conversation of its own.
|
|
4320
|
+
*/
|
|
4321
|
+
async startPlaceCursors() {
|
|
4322
|
+
for (const agent of await this.listAgents()) {
|
|
4323
|
+
const file = this.paths.agentCursors(agent.name);
|
|
4324
|
+
if (!await exists(file)) {
|
|
4325
|
+
continue;
|
|
4326
|
+
}
|
|
4327
|
+
const cursors = await readJson(file, CursorsSchema);
|
|
4328
|
+
await writeJson(file, {
|
|
4329
|
+
...cursors,
|
|
4330
|
+
placesFrom: { [SOCIETY_SCOPE]: cursorOf(cursors, SOCIETY_SCOPE) }
|
|
4331
|
+
});
|
|
4332
|
+
}
|
|
4333
|
+
}
|
|
4033
4334
|
/**
|
|
4034
4335
|
* Threads were once read in the scope's single conversation, so a member had read every thread
|
|
4035
4336
|
* up to its scope's cursor. Each scope's threads start from that cursor now, as each thread's
|
|
@@ -4057,7 +4358,7 @@ var Board = class _Board {
|
|
|
4057
4358
|
* turns, so it follows nothing now.
|
|
4058
4359
|
*/
|
|
4059
4360
|
async renameInboxCursors() {
|
|
4060
|
-
const Legacy =
|
|
4361
|
+
const Legacy = z16.object({ inbox: z16.string().nullable() });
|
|
4061
4362
|
for (const agent of await this.listAgents()) {
|
|
4062
4363
|
const file = this.paths.agentCursors(agent.name);
|
|
4063
4364
|
if (await exists(file)) {
|
|
@@ -4103,7 +4404,7 @@ ${charter.purpose}
|
|
|
4103
4404
|
*/
|
|
4104
4405
|
async alignSeedRole(seed) {
|
|
4105
4406
|
const file = this.paths.role(seed.name);
|
|
4106
|
-
const raw = await readMarkdown(file,
|
|
4407
|
+
const raw = await readMarkdown(file, z16.record(z16.string(), z16.unknown()));
|
|
4107
4408
|
const current = RoleCharterSchema.parse(raw.data);
|
|
4108
4409
|
const verbsAdded = seed.verbs.filter((verb) => !current.verbs.includes(verb));
|
|
4109
4410
|
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));
|
|
@@ -4170,13 +4471,15 @@ ${charter.purpose}
|
|
|
4170
4471
|
const token = mintToken();
|
|
4171
4472
|
const thread = threadOfPart(part);
|
|
4172
4473
|
const channel = channelOfPart(part);
|
|
4474
|
+
const place = placeOfPart(part);
|
|
4173
4475
|
this.turnTokens.set(hashToken(token), {
|
|
4174
4476
|
actor: {
|
|
4175
4477
|
name: agent,
|
|
4176
4478
|
role,
|
|
4177
4479
|
...scope === void 0 ? {} : { scope },
|
|
4178
4480
|
...thread === void 0 ? {} : { thread },
|
|
4179
|
-
...channel === void 0 ? {} : { channel }
|
|
4481
|
+
...channel === void 0 ? {} : { channel },
|
|
4482
|
+
...place === void 0 ? {} : { place }
|
|
4180
4483
|
},
|
|
4181
4484
|
expiresAt: this.now().getTime() + ttlMs
|
|
4182
4485
|
});
|
|
@@ -4265,7 +4568,7 @@ ${charter.purpose}
|
|
|
4265
4568
|
});
|
|
4266
4569
|
}
|
|
4267
4570
|
signInsFile() {
|
|
4268
|
-
return
|
|
4571
|
+
return path7.join(this.paths.state(), "sign-ins.json");
|
|
4269
4572
|
}
|
|
4270
4573
|
async readSignIns() {
|
|
4271
4574
|
const file = this.signInsFile();
|
|
@@ -4362,7 +4665,7 @@ ${charter.purpose}
|
|
|
4362
4665
|
const files = await listFiles(this.paths.tasks(project));
|
|
4363
4666
|
const tasks = [];
|
|
4364
4667
|
for (const file of files) {
|
|
4365
|
-
const doc = await readMarkdown(
|
|
4668
|
+
const doc = await readMarkdown(path7.join(this.paths.tasks(project), file), TaskFrontmatterSchema);
|
|
4366
4669
|
tasks.push({ ...doc.data, body: doc.body });
|
|
4367
4670
|
}
|
|
4368
4671
|
return tasks;
|
|
@@ -4381,7 +4684,7 @@ ${charter.purpose}
|
|
|
4381
4684
|
const files = await listFiles(this.paths.proposals());
|
|
4382
4685
|
const proposals = [];
|
|
4383
4686
|
for (const file of files) {
|
|
4384
|
-
const doc = await readMarkdown(
|
|
4687
|
+
const doc = await readMarkdown(path7.join(this.paths.proposals(), file), ProposalFrontmatterSchema);
|
|
4385
4688
|
proposals.push({ ...doc.data, body: doc.body });
|
|
4386
4689
|
}
|
|
4387
4690
|
return proposals;
|
|
@@ -4408,7 +4711,7 @@ ${charter.purpose}
|
|
|
4408
4711
|
async listMembers() {
|
|
4409
4712
|
const members = [];
|
|
4410
4713
|
for (const file of await listFiles(this.paths.members())) {
|
|
4411
|
-
const doc = await readMarkdown(
|
|
4714
|
+
const doc = await readMarkdown(path7.join(this.paths.members(), file), MemberSchema);
|
|
4412
4715
|
members.push({ ...doc.data, profile: doc.body });
|
|
4413
4716
|
}
|
|
4414
4717
|
return members;
|
|
@@ -4420,14 +4723,14 @@ ${charter.purpose}
|
|
|
4420
4723
|
/** The society's norms, the one knowledge topic every turn loads. Empty until the steward writes it. */
|
|
4421
4724
|
async readSocietyNorms() {
|
|
4422
4725
|
const file = this.paths.societyKnowledgeFile("norms");
|
|
4423
|
-
return await exists(file) ? (await readMarkdown(file,
|
|
4726
|
+
return await exists(file) ? (await readMarkdown(file, z16.record(z16.string(), z16.unknown()))).body : "";
|
|
4424
4727
|
}
|
|
4425
4728
|
/** Knowledge topics of a project, or of the society when the project is null. */
|
|
4426
4729
|
async listKnowledge(project) {
|
|
4427
4730
|
const dir = project === null ? this.paths.societyKnowledge() : this.paths.projectKnowledge(project);
|
|
4428
4731
|
const topics = [];
|
|
4429
4732
|
for (const file of await listFiles(dir)) {
|
|
4430
|
-
topics.push(await this.knowledgeAt(
|
|
4733
|
+
topics.push(await this.knowledgeAt(path7.join(dir, file), project, file.replace(/\.md$/, "")));
|
|
4431
4734
|
}
|
|
4432
4735
|
return topics;
|
|
4433
4736
|
}
|
|
@@ -4440,7 +4743,7 @@ ${charter.purpose}
|
|
|
4440
4743
|
*/
|
|
4441
4744
|
async knowledgeAt(file, project, topic) {
|
|
4442
4745
|
const { data, body } = await readLooseMarkdown(file);
|
|
4443
|
-
const revision =
|
|
4746
|
+
const revision = z16.number().int().min(0).safeParse(data["revision"]);
|
|
4444
4747
|
const updatedBy = NameSchema.safeParse(data["updatedBy"]);
|
|
4445
4748
|
const updatedAt = IsoDateTimeSchema.safeParse(data["updatedAt"]);
|
|
4446
4749
|
return {
|
|
@@ -4457,7 +4760,7 @@ ${charter.purpose}
|
|
|
4457
4760
|
const aside = this.paths.removedKnowledge(project ?? SOCIETY_SCOPE);
|
|
4458
4761
|
let highest = 0;
|
|
4459
4762
|
for (const file of (await listFiles(aside)).filter((name) => name.startsWith(`${topic}.`))) {
|
|
4460
|
-
const copy = await this.knowledgeAt(
|
|
4763
|
+
const copy = await this.knowledgeAt(path7.join(aside, file), project, topic);
|
|
4461
4764
|
highest = Math.max(highest, copy.revision);
|
|
4462
4765
|
}
|
|
4463
4766
|
return highest;
|
|
@@ -4473,7 +4776,7 @@ ${charter.purpose}
|
|
|
4473
4776
|
async listSkillsIn(dir, scope) {
|
|
4474
4777
|
const skills = [];
|
|
4475
4778
|
for (const entry of await listDirs(dir)) {
|
|
4476
|
-
const file =
|
|
4779
|
+
const file = path7.join(dir, entry, "SKILL.md");
|
|
4477
4780
|
if (!await exists(file)) {
|
|
4478
4781
|
continue;
|
|
4479
4782
|
}
|
|
@@ -4563,7 +4866,7 @@ ${charter.purpose}
|
|
|
4563
4866
|
if (!wanted.delete(file.slice(0, 26))) {
|
|
4564
4867
|
continue;
|
|
4565
4868
|
}
|
|
4566
|
-
const { data } = await readMarkdown(
|
|
4869
|
+
const { data } = await readMarkdown(path7.join(dir, file), MessageFrontmatterSchema);
|
|
4567
4870
|
found.push(MessageRefSchema.parse(data));
|
|
4568
4871
|
}
|
|
4569
4872
|
if (wanted.size === 0) {
|
|
@@ -4595,7 +4898,7 @@ ${charter.purpose}
|
|
|
4595
4898
|
if (!file.endsWith(".md")) {
|
|
4596
4899
|
continue;
|
|
4597
4900
|
}
|
|
4598
|
-
const doc = await readMarkdown(
|
|
4901
|
+
const doc = await readMarkdown(path7.join(threads, file), ThreadFrontmatterSchema);
|
|
4599
4902
|
if (doc.data.state !== "open") {
|
|
4600
4903
|
continue;
|
|
4601
4904
|
}
|
|
@@ -4622,7 +4925,7 @@ ${charter.purpose}
|
|
|
4622
4925
|
const all = [];
|
|
4623
4926
|
for (const threads of await this.threadScopes()) {
|
|
4624
4927
|
for (const file of await listFiles(threads)) {
|
|
4625
|
-
const doc = await readMarkdown(
|
|
4928
|
+
const doc = await readMarkdown(path7.join(threads, file), ThreadFrontmatterSchema);
|
|
4626
4929
|
const messages = await listFiles(this.paths.threadMessages(threads, doc.data.id));
|
|
4627
4930
|
const last = messages.at(-1);
|
|
4628
4931
|
all.push({
|
|
@@ -4989,16 +5292,20 @@ ${DASHBOARD_LINE}
|
|
|
4989
5292
|
}
|
|
4990
5293
|
/**
|
|
4991
5294
|
* How many unread digest messages a member has in each conversation: a scope's home, for its
|
|
4992
|
-
* general channel, one of its other channels,
|
|
5295
|
+
* general channel, one of its other channels, one of its threads, or, in the society scope, a
|
|
5296
|
+
* project's channels. The heartbeat asks, so that it wakes a member where its unread
|
|
4993
5297
|
* messages are and nowhere else. Society channels count only for a member woken in the society
|
|
4994
5298
|
* scope; a project member reads them at its next turn. Two kinds of post are in the digest but not
|
|
4995
5299
|
* counted, and are read when the member wakes for something else: a task step's, which wakes
|
|
4996
5300
|
* whoever it hands work to, and the board's own, operations signals and landing announcements,
|
|
4997
|
-
* whose signals that call for action wake their readers directly.
|
|
5301
|
+
* whose signals that call for action wake their readers directly. A closed thread counts for
|
|
5302
|
+
* nothing: its conversation has ended, and a turn there could only read its posts and be refused
|
|
5303
|
+
* a reply, while a wake queued before it closed, as for a mention, still runs there.
|
|
4998
5304
|
*/
|
|
4999
5305
|
async unreadByConversation(actor) {
|
|
5000
5306
|
const { cursors, upto } = await this.digestSnapshot(actor.name);
|
|
5001
5307
|
const counts = /* @__PURE__ */ new Map();
|
|
5308
|
+
const closed = /* @__PURE__ */ new Map();
|
|
5002
5309
|
const unscoped = { name: actor.name, role: actor.role };
|
|
5003
5310
|
for (const { scope, part, message, news } of await this.digestSince(unscoped, cursors, upto)) {
|
|
5004
5311
|
if (news === true || message.step !== void 0 || message.author === SYSTEM_ACTOR.name) {
|
|
@@ -5008,11 +5315,23 @@ ${DASHBOARD_LINE}
|
|
|
5008
5315
|
if (posted !== scope && scope !== SOCIETY_SCOPE) {
|
|
5009
5316
|
continue;
|
|
5010
5317
|
}
|
|
5318
|
+
const thread = threadOfPart(part);
|
|
5319
|
+
if (thread !== void 0) {
|
|
5320
|
+
let ended = closed.get(thread);
|
|
5321
|
+
if (ended === void 0) {
|
|
5322
|
+
ended = (await this.tryFindThread(thread))?.thread.state === "closed";
|
|
5323
|
+
closed.set(thread, ended);
|
|
5324
|
+
}
|
|
5325
|
+
if (ended) {
|
|
5326
|
+
continue;
|
|
5327
|
+
}
|
|
5328
|
+
}
|
|
5011
5329
|
const key = conversationKey(scope, part);
|
|
5012
5330
|
const entry = counts.get(key) ?? {
|
|
5013
5331
|
scope,
|
|
5014
|
-
thread:
|
|
5332
|
+
thread: thread ?? null,
|
|
5015
5333
|
channel: channelOfPart(part) ?? null,
|
|
5334
|
+
place: placeOfPart(part) ?? null,
|
|
5016
5335
|
count: 0
|
|
5017
5336
|
};
|
|
5018
5337
|
counts.set(key, { ...entry, count: entry.count + 1 });
|
|
@@ -5070,8 +5389,9 @@ ${DASHBOARD_LINE}
|
|
|
5070
5389
|
* thread it takes part in, and for the front desk, which every post by the user wakes, every post
|
|
5071
5390
|
* by the user wherever it is. With a conversation, only what is filed there: for a thread, every
|
|
5072
5391
|
* message of the thread, since the conversation is about it; for a channel's, that channel's
|
|
5073
|
-
* top-level messages; for a
|
|
5074
|
-
*
|
|
5392
|
+
* top-level messages; for a project's in the society scope, the top-level messages of every
|
|
5393
|
+
* channel of a project the member is not in; for a scope's home, general's, since each thread,
|
|
5394
|
+
* each other channel, and each such project is a conversation of its own. Nothing newer than
|
|
5075
5395
|
* `upto` counts (see `digestSnapshot`).
|
|
5076
5396
|
*/
|
|
5077
5397
|
async digestSince(actor, cursors, upto, conversation) {
|
|
@@ -5091,11 +5411,17 @@ ${DASHBOARD_LINE}
|
|
|
5091
5411
|
const frontDesk = charter.wakeTriggers.includes(FRONT_DESK_TRIGGER);
|
|
5092
5412
|
const threadParticipation = /* @__PURE__ */ new Map();
|
|
5093
5413
|
const openChannels = /* @__PURE__ */ new Map();
|
|
5414
|
+
const liveProjects = /* @__PURE__ */ new Map();
|
|
5094
5415
|
const collected = [];
|
|
5095
5416
|
const newsFrom = charter.societyScope ? void 0 : newsStart(cursors);
|
|
5096
5417
|
const scopes = conversation !== void 0 ? [conversation.scope] : [...agent.memberships, SOCIETY_SCOPE];
|
|
5097
5418
|
const starts = [
|
|
5098
|
-
...scopes.flatMap((each) => conversation === void 0 ? [
|
|
5419
|
+
...scopes.flatMap((each) => conversation === void 0 ? [
|
|
5420
|
+
cursorOf(cursors, each),
|
|
5421
|
+
threadStart(cursors, each),
|
|
5422
|
+
channelStart(cursors, each),
|
|
5423
|
+
placeStart(cursors, each)
|
|
5424
|
+
] : [cursorOf(cursors, each, conversation.part)]),
|
|
5099
5425
|
...newsFrom === void 0 ? [] : [newsFrom]
|
|
5100
5426
|
];
|
|
5101
5427
|
const known = starts.filter((start) => start !== null);
|
|
@@ -5105,7 +5431,7 @@ ${DASHBOARD_LINE}
|
|
|
5105
5431
|
const thread = message.thread ?? null;
|
|
5106
5432
|
const asked = message.mentions.includes(actor.name) || frontDesk && message.author === USER_NAME;
|
|
5107
5433
|
if (newsFrom !== void 0 && thread === null && filedIn === SOCIETY_SCOPE && !asked) {
|
|
5108
|
-
if ((newsFrom === null || message.id > newsFrom) && subscribed.has(message.channel) && message.author !== actor.name) {
|
|
5434
|
+
if ((conversation === void 0 || conversation.part === null) && (newsFrom === null || message.id > newsFrom) && subscribed.has(message.channel) && message.author !== actor.name) {
|
|
5109
5435
|
collected.push({
|
|
5110
5436
|
scope: conversation?.scope ?? SOCIETY_SCOPE,
|
|
5111
5437
|
part: null,
|
|
@@ -5116,12 +5442,18 @@ ${DASHBOARD_LINE}
|
|
|
5116
5442
|
continue;
|
|
5117
5443
|
}
|
|
5118
5444
|
const named = thread === null ? channelConversation(filedIn, message.channel) : void 0;
|
|
5119
|
-
let
|
|
5120
|
-
if (named !== void 0 &&
|
|
5121
|
-
|
|
5122
|
-
openChannels.set(message.channel,
|
|
5123
|
-
}
|
|
5124
|
-
const
|
|
5445
|
+
let open3 = named === void 0 ? void 0 : openChannels.get(message.channel);
|
|
5446
|
+
if (named !== void 0 && open3 === void 0) {
|
|
5447
|
+
open3 = await this.channelOpen(message.channel);
|
|
5448
|
+
openChannels.set(message.channel, open3);
|
|
5449
|
+
}
|
|
5450
|
+
const project = thread === null ? placeConversation(filedIn, message.channel) : void 0;
|
|
5451
|
+
let live = project === void 0 ? void 0 : liveProjects.get(project);
|
|
5452
|
+
if (project !== void 0 && live === void 0) {
|
|
5453
|
+
live = await this.projectLive(project);
|
|
5454
|
+
liveProjects.set(project, live);
|
|
5455
|
+
}
|
|
5456
|
+
const part = thread ?? (open3 === true ? `#${named}` : live === true ? `~${project}` : null);
|
|
5125
5457
|
if (conversation !== void 0 && (filedIn !== conversation.scope || part !== conversation.part)) {
|
|
5126
5458
|
continue;
|
|
5127
5459
|
}
|
|
@@ -5186,7 +5518,7 @@ ${task.body}`;
|
|
|
5186
5518
|
}
|
|
5187
5519
|
}
|
|
5188
5520
|
for (const file of await listFiles(this.paths.projectKnowledge(project))) {
|
|
5189
|
-
const content = await readFile4(
|
|
5521
|
+
const content = await readFile4(path7.join(this.paths.projectKnowledge(project), file), "utf8");
|
|
5190
5522
|
if (content.toLowerCase().includes(needle)) {
|
|
5191
5523
|
hits.push({
|
|
5192
5524
|
kind: "knowledge",
|
|
@@ -5207,7 +5539,7 @@ ${task.body}`;
|
|
|
5207
5539
|
shared.push({
|
|
5208
5540
|
kind: "knowledge",
|
|
5209
5541
|
ref: `society/knowledge/${file}`,
|
|
5210
|
-
file:
|
|
5542
|
+
file: path7.join(this.paths.societyKnowledge(), file)
|
|
5211
5543
|
});
|
|
5212
5544
|
}
|
|
5213
5545
|
for (const skill of await this.listSocietySkills()) {
|
|
@@ -5220,7 +5552,7 @@ ${task.body}`;
|
|
|
5220
5552
|
shared.push({
|
|
5221
5553
|
kind: "memory",
|
|
5222
5554
|
ref: `memory/${file}`,
|
|
5223
|
-
file:
|
|
5555
|
+
file: path7.join(this.paths.agentMemory(actor.name), file)
|
|
5224
5556
|
});
|
|
5225
5557
|
}
|
|
5226
5558
|
for (const entry of shared) {
|
|
@@ -5310,7 +5642,7 @@ ${current.body}`);
|
|
|
5310
5642
|
});
|
|
5311
5643
|
const aside = this.paths.removedKnowledge(args.project ?? SOCIETY_SCOPE);
|
|
5312
5644
|
await mkdir4(aside, { recursive: true });
|
|
5313
|
-
await rename3(file,
|
|
5645
|
+
await rename3(file, path7.join(aside, `${args.topic}.${removed.removedAt.replaceAll(":", "-")}.md`));
|
|
5314
5646
|
await this.events.append("knowledge.removed", actor.name, {
|
|
5315
5647
|
topic: args.topic,
|
|
5316
5648
|
project: args.project
|
|
@@ -5358,7 +5690,7 @@ ${current.body}`);
|
|
|
5358
5690
|
});
|
|
5359
5691
|
const aside = this.paths.removedSkills();
|
|
5360
5692
|
await mkdir4(aside, { recursive: true });
|
|
5361
|
-
await rename3(this.paths.societySkill(args.name),
|
|
5693
|
+
await rename3(this.paths.societySkill(args.name), path7.join(aside, `${args.name}.${removed.removedAt.replaceAll(":", "-")}`));
|
|
5362
5694
|
await this.events.append("skill.removed", actor.name, {
|
|
5363
5695
|
name: args.name,
|
|
5364
5696
|
reason: args.reason
|
|
@@ -5403,11 +5735,65 @@ ${current.body}`);
|
|
|
5403
5735
|
return updated;
|
|
5404
5736
|
});
|
|
5405
5737
|
}
|
|
5738
|
+
/**
|
|
5739
|
+
* Keeps an image a citizen's turn names by its path, read on the runner the turn runs on, which
|
|
5740
|
+
* refuses a file outside the turn's worktree, the citizen's home, and the system temp folder.
|
|
5741
|
+
* Only the bytes of a PNG, JPEG, GIF, or WebP of at most `IMAGE_LIMIT_BYTES` are kept, checked
|
|
5742
|
+
* here again whatever the runner said, under a new id outside the projection, so no runner
|
|
5743
|
+
* mirrors them and no prompt carries them: a post shows the image by its `image:` address.
|
|
5744
|
+
*/
|
|
5745
|
+
async uploadImage(actor, input) {
|
|
5746
|
+
const args = VerbInputs.upload_image.parse(input);
|
|
5747
|
+
await this.authorize(actor, "upload_image");
|
|
5748
|
+
if (actor.scope === void 0) {
|
|
5749
|
+
throw new BoardError("INVALID_STATE", "upload_image reads the file on the machine a turn runs on, so only a turn can call it");
|
|
5750
|
+
}
|
|
5751
|
+
if (this.imageSource === null) {
|
|
5752
|
+
throw new BoardError("NOT_FOUND", "this board has no runners to read an image from");
|
|
5753
|
+
}
|
|
5754
|
+
const bytes = await this.imageSource(actor, args.path);
|
|
5755
|
+
const type = imageTypeOf(bytes);
|
|
5756
|
+
if (type === null) {
|
|
5757
|
+
throw new BoardError("VALIDATION", `${args.path} is not a PNG, JPEG, GIF, or WebP image by its first bytes`);
|
|
5758
|
+
}
|
|
5759
|
+
if (bytes.byteLength > IMAGE_LIMIT_BYTES) {
|
|
5760
|
+
throw new BoardError("VALIDATION", `${args.path} is ${bytes.byteLength} bytes, over the ${IMAGE_LIMIT_BYTES} an image may be`);
|
|
5761
|
+
}
|
|
5762
|
+
return this.mutex.run(async () => {
|
|
5763
|
+
const id = this.newId();
|
|
5764
|
+
await writeFileAtomic(this.paths.image(id), bytes);
|
|
5765
|
+
await this.events.append("image.uploaded", actor.name, {
|
|
5766
|
+
id,
|
|
5767
|
+
type,
|
|
5768
|
+
size: bytes.byteLength
|
|
5769
|
+
});
|
|
5770
|
+
const name = path7.basename(args.path).replaceAll(/[[\]\\\r\n]/g, "");
|
|
5771
|
+
return {
|
|
5772
|
+
id,
|
|
5773
|
+
type,
|
|
5774
|
+
size: bytes.byteLength,
|
|
5775
|
+
markdown: ``
|
|
5776
|
+
};
|
|
5777
|
+
});
|
|
5778
|
+
}
|
|
5779
|
+
/** An uploaded image's bytes and kind, which its first bytes tell. */
|
|
5780
|
+
async readImage(id) {
|
|
5781
|
+
const file = this.paths.image(UlidSchema.parse(id));
|
|
5782
|
+
if (!await exists(file)) {
|
|
5783
|
+
throw new BoardError("NOT_FOUND", `image ${id} not found`);
|
|
5784
|
+
}
|
|
5785
|
+
const bytes = await readFile4(file);
|
|
5786
|
+
const type = imageTypeOf(bytes);
|
|
5787
|
+
if (type === null) {
|
|
5788
|
+
throw new BoardError("NOT_FOUND", `image ${id} is not an image the board shows`);
|
|
5789
|
+
}
|
|
5790
|
+
return { type, bytes };
|
|
5791
|
+
}
|
|
5406
5792
|
/** Every cron, ended ones included, oldest first. */
|
|
5407
5793
|
async listCrons() {
|
|
5408
5794
|
const crons = [];
|
|
5409
5795
|
for (const file of await listFiles(this.paths.crons())) {
|
|
5410
|
-
crons.push(await this.readCronFile(
|
|
5796
|
+
crons.push(await this.readCronFile(path7.join(this.paths.crons(), file)));
|
|
5411
5797
|
}
|
|
5412
5798
|
return crons;
|
|
5413
5799
|
}
|
|
@@ -5529,11 +5915,16 @@ ${current.body}`);
|
|
|
5529
5915
|
}
|
|
5530
5916
|
/**
|
|
5531
5917
|
* Where a cron fires for its citizen: the thread, the channel, or the scope's home it names, else
|
|
5532
|
-
* the conversation of the turn that set it when it wakes its setter,
|
|
5918
|
+
* the conversation of the turn that set it when it wakes its setter, a project's channels' in
|
|
5919
|
+
* the society scope included, else its home outside projects.
|
|
5533
5920
|
*/
|
|
5534
5921
|
async cronConversation(actor, agent, named) {
|
|
5535
5922
|
const own = actor.name === agent.name;
|
|
5536
5923
|
if (named.project === void 0 && named.thread_id === void 0 && named.channel === void 0) {
|
|
5924
|
+
if (own && actor.scope === SOCIETY_SCOPE && actor.place !== void 0) {
|
|
5925
|
+
await this.readActiveProject(actor.place);
|
|
5926
|
+
return { scope: SOCIETY_SCOPE, place: actor.place };
|
|
5927
|
+
}
|
|
5537
5928
|
return own && actor.scope !== void 0 ? this.cronConversation(actor, agent, {
|
|
5538
5929
|
project: actor.scope,
|
|
5539
5930
|
thread_id: actor.thread,
|
|
@@ -5609,7 +6000,8 @@ ${current.body}`);
|
|
|
5609
6000
|
agent: cron.agent,
|
|
5610
6001
|
project: cron.scope,
|
|
5611
6002
|
...cron.thread === void 0 ? {} : { thread: cron.thread },
|
|
5612
|
-
...cron.channel === void 0 ? {} : { channel: cron.channel }
|
|
6003
|
+
...cron.channel === void 0 ? {} : { channel: cron.channel },
|
|
6004
|
+
...cron.place === void 0 ? {} : { place: cron.place }
|
|
5613
6005
|
};
|
|
5614
6006
|
}
|
|
5615
6007
|
async readCronFile(file) {
|
|
@@ -5794,6 +6186,13 @@ ${current.body}`);
|
|
|
5794
6186
|
watchTurns(source) {
|
|
5795
6187
|
this.turnsInFlight = source;
|
|
5796
6188
|
}
|
|
6189
|
+
/**
|
|
6190
|
+
* Where `upload_image` reads a file from: the runner the calling turn runs on, through the server
|
|
6191
|
+
* that hosts the runners. Without one, as in the admin CLI, nothing can be uploaded.
|
|
6192
|
+
*/
|
|
6193
|
+
readImagesWith(source) {
|
|
6194
|
+
this.imageSource = source;
|
|
6195
|
+
}
|
|
5797
6196
|
/**
|
|
5798
6197
|
* Refuses to end conversations while another citizen's turn is in one of them: `cut` picks the
|
|
5799
6198
|
* turns that would lose theirs. The caller's own turn is exempt, since ending a conversation
|
|
@@ -5813,17 +6212,20 @@ ${current.body}`);
|
|
|
5813
6212
|
async openThreadIds(dir, keep) {
|
|
5814
6213
|
const ids = /* @__PURE__ */ new Set();
|
|
5815
6214
|
for (const file of await listFiles(dir)) {
|
|
5816
|
-
const doc = await readMarkdown(
|
|
6215
|
+
const doc = await readMarkdown(path7.join(dir, file), ThreadFrontmatterSchema);
|
|
5817
6216
|
if (doc.data.state === "open" && keep({ ...doc.data, body: doc.body })) {
|
|
5818
6217
|
ids.add(doc.data.id);
|
|
5819
6218
|
}
|
|
5820
6219
|
}
|
|
5821
6220
|
return ids;
|
|
5822
6221
|
}
|
|
5823
|
-
/**
|
|
6222
|
+
/**
|
|
6223
|
+
* Refuses to archive a project while a turn runs in it, in a citizen's conversation of its
|
|
6224
|
+
* channels in the society scope, or in a thread its archive closes.
|
|
6225
|
+
*/
|
|
5824
6226
|
async assertProjectQuiet(actor, slug) {
|
|
5825
6227
|
const threads = await this.openThreadIds(this.paths.threads(slug), () => true);
|
|
5826
|
-
this.assertNoTurnCut(actor, `project ${slug} cannot be archived`, (turn) => turn.scope === slug || turn.thread !== void 0 && threads.has(turn.thread));
|
|
6228
|
+
this.assertNoTurnCut(actor, `project ${slug} cannot be archived`, (turn) => turn.scope === slug || turn.place === slug || turn.thread !== void 0 && threads.has(turn.thread));
|
|
5827
6229
|
}
|
|
5828
6230
|
async closeThread(actor, input) {
|
|
5829
6231
|
const args = VerbInputs.close_thread.parse(input);
|
|
@@ -6364,7 +6766,8 @@ ${args.summary}`);
|
|
|
6364
6766
|
filedFrom: {
|
|
6365
6767
|
scope: actor.scope,
|
|
6366
6768
|
...actor.thread === void 0 ? {} : { thread: actor.thread },
|
|
6367
|
-
...actor.channel === void 0 ? {} : { channel: actor.channel }
|
|
6769
|
+
...actor.channel === void 0 ? {} : { channel: actor.channel },
|
|
6770
|
+
...actor.place === void 0 ? {} : { place: actor.place }
|
|
6368
6771
|
}
|
|
6369
6772
|
},
|
|
6370
6773
|
charter: parsed.data
|
|
@@ -6623,6 +7026,8 @@ ${rationale}`}`);
|
|
|
6623
7026
|
return this.updateCron(actor, VerbInputs.update_cron.parse(input));
|
|
6624
7027
|
case "remove_cron":
|
|
6625
7028
|
return this.removeCron(actor, VerbInputs.remove_cron.parse(input));
|
|
7029
|
+
case "upload_image":
|
|
7030
|
+
return this.uploadImage(actor, VerbInputs.upload_image.parse(input));
|
|
6626
7031
|
default:
|
|
6627
7032
|
throw new BoardError("VALIDATION", `unknown verb ${String(verb)}`);
|
|
6628
7033
|
}
|
|
@@ -6669,7 +7074,7 @@ ${rationale}`}`);
|
|
|
6669
7074
|
}
|
|
6670
7075
|
async readAgentRoleBody(agent) {
|
|
6671
7076
|
const file = this.paths.agentRole(agent);
|
|
6672
|
-
return await exists(file) ? (await readMarkdown(file,
|
|
7077
|
+
return await exists(file) ? (await readMarkdown(file, z16.record(z16.string(), z16.unknown()))).body : "";
|
|
6673
7078
|
}
|
|
6674
7079
|
/** The steps of one finished turn, as the runner handed them over when it ended. */
|
|
6675
7080
|
/**
|
|
@@ -6774,19 +7179,20 @@ ${rationale}`}`);
|
|
|
6774
7179
|
project: input.project,
|
|
6775
7180
|
...input.thread === void 0 ? {} : { thread: input.thread },
|
|
6776
7181
|
...input.channel === void 0 ? {} : { channel: input.channel },
|
|
7182
|
+
...input.place === void 0 ? {} : { place: input.place },
|
|
6777
7183
|
messages: [...input.messages]
|
|
6778
7184
|
});
|
|
6779
7185
|
});
|
|
6780
7186
|
}
|
|
6781
7187
|
/**
|
|
6782
7188
|
* Where a conversation's session ids and last turn are kept: the scope's home, or beside it a
|
|
6783
|
-
* thread's or a
|
|
7189
|
+
* thread's, a channel's, or a project's, named by `part` as `conversationPart` gives it.
|
|
6784
7190
|
*/
|
|
6785
7191
|
sessionDir(agent, scope, part) {
|
|
6786
7192
|
return part === void 0 ? this.paths.agentProject(agent, scope) : this.paths.agentConversation(agent, scope, part);
|
|
6787
7193
|
}
|
|
6788
7194
|
async readSessions(agent, project, part) {
|
|
6789
|
-
const file =
|
|
7195
|
+
const file = path7.join(this.sessionDir(agent, project, part), "sessions.json");
|
|
6790
7196
|
return await exists(file) ? readJson(file, SessionsFileSchema) : {};
|
|
6791
7197
|
}
|
|
6792
7198
|
/**
|
|
@@ -6797,15 +7203,34 @@ ${rationale}`}`);
|
|
|
6797
7203
|
await this.mutex.run(async () => {
|
|
6798
7204
|
const dir = this.sessionDir(agent, project, part);
|
|
6799
7205
|
await ensureDir(dir);
|
|
6800
|
-
const file =
|
|
7206
|
+
const file = path7.join(dir, "sessions.json");
|
|
6801
7207
|
const current = await exists(file) ? await readJson(file, SessionsFileSchema) : {};
|
|
6802
7208
|
const kept = current.runner === void 0 || current.runner === runner ? current : {};
|
|
6803
7209
|
await writeJson(file, { ...kept, [cli]: sessionId, runner });
|
|
6804
7210
|
});
|
|
6805
7211
|
}
|
|
6806
|
-
/**
|
|
7212
|
+
/**
|
|
7213
|
+
* Drops a conversation's session for a CLI, so its next turn starts a new one, when the record
|
|
7214
|
+
* still names `sessionId`; returns whether it did.
|
|
7215
|
+
*/
|
|
7216
|
+
async dropSession(agent, project, cli, sessionId, part) {
|
|
7217
|
+
return this.mutex.run(async () => {
|
|
7218
|
+
const file = path7.join(this.sessionDir(agent, project, part), "sessions.json");
|
|
7219
|
+
if (!await exists(file)) {
|
|
7220
|
+
return false;
|
|
7221
|
+
}
|
|
7222
|
+
const current = await readJson(file, SessionsFileSchema);
|
|
7223
|
+
if (current[cli] !== sessionId) {
|
|
7224
|
+
return false;
|
|
7225
|
+
}
|
|
7226
|
+
const { [cli]: _dropped, ...kept } = current;
|
|
7227
|
+
await writeJson(file, kept);
|
|
7228
|
+
return true;
|
|
7229
|
+
});
|
|
7230
|
+
}
|
|
7231
|
+
/** The last turn of one conversation: the scope's home, a channel's, a project's, or a thread's. */
|
|
6807
7232
|
async readLastTurn(agent, project, part) {
|
|
6808
|
-
const file =
|
|
7233
|
+
const file = path7.join(this.sessionDir(agent, project, part), "last-turn.json");
|
|
6809
7234
|
return await exists(file) ? readJson(file, TurnRecordSchema) : null;
|
|
6810
7235
|
}
|
|
6811
7236
|
/** The latest turn a citizen took in a scope, in any of its conversations there. */
|
|
@@ -6813,8 +7238,9 @@ ${rationale}`}`);
|
|
|
6813
7238
|
let latest = await this.readLastTurn(agent, project);
|
|
6814
7239
|
const place = this.paths.agentProject(agent, project);
|
|
6815
7240
|
const parts = [
|
|
6816
|
-
...await listDirs(
|
|
6817
|
-
...(await listDirs(
|
|
7241
|
+
...await listDirs(path7.join(place, "threads")),
|
|
7242
|
+
...(await listDirs(path7.join(place, "channels"))).map((name) => `#${name}`),
|
|
7243
|
+
...(await listDirs(path7.join(place, "places"))).map((name) => `~${name}`)
|
|
6818
7244
|
];
|
|
6819
7245
|
for (const part of parts) {
|
|
6820
7246
|
const turn = await this.readLastTurn(agent, project, part);
|
|
@@ -6835,6 +7261,7 @@ ${rationale}`}`);
|
|
|
6835
7261
|
project: parsed.project,
|
|
6836
7262
|
...parsed.thread === void 0 ? {} : { thread: parsed.thread },
|
|
6837
7263
|
...parsed.channel === void 0 ? {} : { channel: parsed.channel },
|
|
7264
|
+
...parsed.place === void 0 ? {} : { place: parsed.place },
|
|
6838
7265
|
trigger: parsed.trigger.kind,
|
|
6839
7266
|
session: parsed.session,
|
|
6840
7267
|
runner: parsed.runner
|
|
@@ -6864,7 +7291,8 @@ ${rationale}`}`);
|
|
|
6864
7291
|
agent: parsed.agent,
|
|
6865
7292
|
project: parsed.project,
|
|
6866
7293
|
...parsed.thread === void 0 ? {} : { thread: parsed.thread },
|
|
6867
|
-
...parsed.channel === void 0 ? {} : { channel: parsed.channel }
|
|
7294
|
+
...parsed.channel === void 0 ? {} : { channel: parsed.channel },
|
|
7295
|
+
...parsed.place === void 0 ? {} : { place: parsed.place }
|
|
6868
7296
|
});
|
|
6869
7297
|
}
|
|
6870
7298
|
await this.events.append(failed ? "turn.failed" : "turn.completed", parsed.agent, {
|
|
@@ -6872,6 +7300,7 @@ ${rationale}`}`);
|
|
|
6872
7300
|
project: parsed.project,
|
|
6873
7301
|
...parsed.thread === void 0 ? {} : { thread: parsed.thread },
|
|
6874
7302
|
...parsed.channel === void 0 ? {} : { channel: parsed.channel },
|
|
7303
|
+
...parsed.place === void 0 ? {} : { place: parsed.place },
|
|
6875
7304
|
trigger: parsed.trigger.kind,
|
|
6876
7305
|
session: parsed.session,
|
|
6877
7306
|
exitReason: parsed.exitReason,
|
|
@@ -6881,7 +7310,9 @@ ${rationale}`}`);
|
|
|
6881
7310
|
model: parsed.model,
|
|
6882
7311
|
summary: parsed.status?.summary ?? null,
|
|
6883
7312
|
needsUserDecision: parsed.status?.needsUserDecision ?? false,
|
|
6884
|
-
error: parsed.error
|
|
7313
|
+
error: parsed.error,
|
|
7314
|
+
...parsed.resumeFailed === void 0 ? {} : { resumeFailed: parsed.resumeFailed },
|
|
7315
|
+
...parsed.sessionDropped === true ? { sessionDropped: true } : {}
|
|
6885
7316
|
});
|
|
6886
7317
|
});
|
|
6887
7318
|
}
|
|
@@ -6918,6 +7349,20 @@ ${rationale}`}`);
|
|
|
6918
7349
|
return task;
|
|
6919
7350
|
});
|
|
6920
7351
|
}
|
|
7352
|
+
/**
|
|
7353
|
+
* Posts the board's word on a task's work into the task's thread, such as commits a runner
|
|
7354
|
+
* could not put on its branch: as the board, with every `@name` written as a plain name, so it
|
|
7355
|
+
* wakes nobody and counts toward no heartbeat. Null, posting nothing, once the thread is closed.
|
|
7356
|
+
*/
|
|
7357
|
+
async postTaskNotice(taskId, body) {
|
|
7358
|
+
return this.mutex.run(async () => {
|
|
7359
|
+
const found = await this.tryFindThread(taskId);
|
|
7360
|
+
if (found === null || found.thread.state !== "open") {
|
|
7361
|
+
return null;
|
|
7362
|
+
}
|
|
7363
|
+
return this.appendThreadMessage(SYSTEM_ACTOR.name, taskId, withoutMentions(body));
|
|
7364
|
+
});
|
|
7365
|
+
}
|
|
6921
7366
|
/**
|
|
6922
7367
|
* Records that a citizen's workspace in a conversation that has ended holds work on no branch, so
|
|
6923
7368
|
* the scheduler gives the citizen a closing turn there to decide about it. False, recording
|
|
@@ -6928,7 +7373,7 @@ ${rationale}`}`);
|
|
|
6928
7373
|
const payload = {
|
|
6929
7374
|
agent: input.agent,
|
|
6930
7375
|
project: input.scope,
|
|
6931
|
-
...conversation.kind === "thread" ? { thread: conversation.id } : { channel: conversation.name }
|
|
7376
|
+
...conversation.kind === "thread" ? { thread: conversation.id } : conversation.kind === "channel" ? { channel: conversation.name } : { place: conversation.project }
|
|
6932
7377
|
};
|
|
6933
7378
|
return this.mutex.run(async () => {
|
|
6934
7379
|
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;
|
|
@@ -6980,9 +7425,13 @@ ${rationale}`}`);
|
|
|
6980
7425
|
}
|
|
6981
7426
|
/**
|
|
6982
7427
|
* Whether a conversation with a workspace of its own has ended: a thread closed, a task ended
|
|
6983
|
-
* (for one from before tasks had threads), or a
|
|
7428
|
+
* (for one from before tasks had threads), a channel archived, or a project archived, which ends
|
|
7429
|
+
* every citizen's conversation of its channels in the society scope.
|
|
6984
7430
|
*/
|
|
6985
7431
|
async conversationEnded(conversation) {
|
|
7432
|
+
if (conversation.kind === "place") {
|
|
7433
|
+
return !await this.projectLive(conversation.project);
|
|
7434
|
+
}
|
|
6986
7435
|
if (conversation.kind === "channel") {
|
|
6987
7436
|
const place = conversation.scope === SOCIETY_SCOPE ? null : conversation.scope;
|
|
6988
7437
|
return !await this.channelOpen(channelRef(place, conversation.name));
|
|
@@ -7056,11 +7505,11 @@ ${rationale}`}`);
|
|
|
7056
7505
|
}
|
|
7057
7506
|
/** Small named state files under data/state, for cursors the scheduler must keep across restarts. */
|
|
7058
7507
|
async readState(name, schema, fallback) {
|
|
7059
|
-
const file =
|
|
7508
|
+
const file = path7.join(this.paths.state(), `${name}.json`);
|
|
7060
7509
|
return await exists(file) ? readJson(file, schema) : fallback;
|
|
7061
7510
|
}
|
|
7062
7511
|
async writeState(name, value) {
|
|
7063
|
-
await this.mutex.run(() => writeJson(
|
|
7512
|
+
await this.mutex.run(() => writeJson(path7.join(this.paths.state(), `${name}.json`), value));
|
|
7064
7513
|
}
|
|
7065
7514
|
/** The project's dashboard, empty at revision 0 when it has none. */
|
|
7066
7515
|
async readDashboard(slug) {
|
|
@@ -7077,7 +7526,7 @@ ${rationale}`}`);
|
|
|
7077
7526
|
return { project: slug, revision: 0, body: "" };
|
|
7078
7527
|
}
|
|
7079
7528
|
const { data, body } = await readLooseMarkdown(file);
|
|
7080
|
-
const revision =
|
|
7529
|
+
const revision = z16.number().int().min(0).safeParse(data["revision"]);
|
|
7081
7530
|
const updatedBy = NameSchema.safeParse(data["updatedBy"]);
|
|
7082
7531
|
const updatedAt = IsoDateTimeSchema.safeParse(data["updatedAt"]);
|
|
7083
7532
|
return {
|
|
@@ -7091,7 +7540,7 @@ ${rationale}`}`);
|
|
|
7091
7540
|
async listRunners() {
|
|
7092
7541
|
const runners2 = [];
|
|
7093
7542
|
for (const file of await listFiles(this.paths.runners())) {
|
|
7094
|
-
runners2.push((await readMarkdown(
|
|
7543
|
+
runners2.push((await readMarkdown(path7.join(this.paths.runners(), file), RunnerSchema)).data);
|
|
7095
7544
|
}
|
|
7096
7545
|
return runners2;
|
|
7097
7546
|
}
|
|
@@ -7143,7 +7592,7 @@ ${rationale}`}`);
|
|
|
7143
7592
|
return this.runnerTokens.get(hashToken(token)) ?? null;
|
|
7144
7593
|
}
|
|
7145
7594
|
runnerTokensFile() {
|
|
7146
|
-
return
|
|
7595
|
+
return path7.join(this.paths.state(), "runners.json");
|
|
7147
7596
|
}
|
|
7148
7597
|
async readRunnerTokens() {
|
|
7149
7598
|
const file = this.runnerTokensFile();
|
|
@@ -7192,26 +7641,26 @@ ${rationale}`}`);
|
|
|
7192
7641
|
async listHomeConflicts(agent) {
|
|
7193
7642
|
const home = this.paths.agent(agent);
|
|
7194
7643
|
const found = [];
|
|
7195
|
-
const
|
|
7196
|
-
for (const entry of await
|
|
7644
|
+
const walk2 = async (dir, prefix) => {
|
|
7645
|
+
for (const entry of await readdir4(dir, { withFileTypes: true }).catch(() => [])) {
|
|
7197
7646
|
if (prefix === "" && SKIPPED_IN_HOME.has(entry.name)) {
|
|
7198
7647
|
continue;
|
|
7199
7648
|
}
|
|
7200
7649
|
const relative = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
7201
7650
|
if (entry.isDirectory()) {
|
|
7202
|
-
await
|
|
7651
|
+
await walk2(path7.join(dir, entry.name), relative);
|
|
7203
7652
|
continue;
|
|
7204
7653
|
}
|
|
7205
7654
|
const file = entry.isFile() ? conflictCopyOf(relative) : null;
|
|
7206
7655
|
if (file !== null) {
|
|
7207
|
-
const since = await this.homes.changedAt(home, relative) ?? (await stat3(
|
|
7656
|
+
const since = await this.homes.changedAt(home, relative) ?? (await stat3(path7.join(home, relative)).catch(() => null))?.mtime.toISOString();
|
|
7208
7657
|
if (since !== void 0) {
|
|
7209
7658
|
found.push({ path: relative, file, since });
|
|
7210
7659
|
}
|
|
7211
7660
|
}
|
|
7212
7661
|
}
|
|
7213
7662
|
};
|
|
7214
|
-
await
|
|
7663
|
+
await walk2(home, "");
|
|
7215
7664
|
return found.toSorted((a, b) => a.path.localeCompare(b.path));
|
|
7216
7665
|
}
|
|
7217
7666
|
/** The newest `limit` changes to an agent's home, newest first; empty while it is no repository. */
|
|
@@ -7288,7 +7737,7 @@ ${rationale}`}`);
|
|
|
7288
7737
|
async writeTurnRecord(record) {
|
|
7289
7738
|
const dir = this.sessionDir(record.agent, record.project, conversationPart(record));
|
|
7290
7739
|
await ensureDir(dir);
|
|
7291
|
-
await writeJson(
|
|
7740
|
+
await writeJson(path7.join(dir, "last-turn.json"), record);
|
|
7292
7741
|
}
|
|
7293
7742
|
// ---------------------------------------------------------------------------------------------
|
|
7294
7743
|
// Internals
|
|
@@ -7351,7 +7800,7 @@ ${charter.purpose}
|
|
|
7351
7800
|
|
|
7352
7801
|
${charter.purpose}
|
|
7353
7802
|
|
|
7354
|
-
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
|
|
7803
|
+
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.
|
|
7355
7804
|
` + (seed.length === 0 ? "" : `
|
|
7356
7805
|
${SEED_INSTRUCTIONS_HEADING}
|
|
7357
7806
|
|
|
@@ -7368,7 +7817,7 @@ ${seed}
|
|
|
7368
7817
|
continue;
|
|
7369
7818
|
}
|
|
7370
7819
|
const file = this.paths.agentRole(agent.name);
|
|
7371
|
-
const body = await exists(file) ? (await readMarkdown(file,
|
|
7820
|
+
const body = await exists(file) ? (await readMarkdown(file, z16.record(z16.string(), z16.unknown()))).body : "";
|
|
7372
7821
|
const at = body.indexOf(`
|
|
7373
7822
|
${SEED_INSTRUCTIONS_HEADING}
|
|
7374
7823
|
`);
|
|
@@ -7407,8 +7856,8 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7407
7856
|
async ensureAgentProject(name, slug) {
|
|
7408
7857
|
const dir = this.paths.agentProject(name, slug);
|
|
7409
7858
|
await ensureDir(dir);
|
|
7410
|
-
if (!await exists(
|
|
7411
|
-
await writeJson(
|
|
7859
|
+
if (!await exists(path7.join(dir, "sessions.json"))) {
|
|
7860
|
+
await writeJson(path7.join(dir, "sessions.json"), {});
|
|
7412
7861
|
}
|
|
7413
7862
|
}
|
|
7414
7863
|
/**
|
|
@@ -7440,7 +7889,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7440
7889
|
const at = turn?.endedAt ?? turn?.startedAt;
|
|
7441
7890
|
if (turn !== null && at !== void 0 && (lastTurnAt === void 0 || at > lastTurnAt)) {
|
|
7442
7891
|
lastTurnAt = at;
|
|
7443
|
-
lastTurnOutcome = `${turn.trigger.kind} on ${scope}: ${turn.exitReason ?? "running"}${turn.status === null ? "" : `, ${turn.status.summary.slice(0, 160)}`}`;
|
|
7892
|
+
lastTurnOutcome = `${turn.trigger.kind} on ${scope}${turnConversation(turn)}: ${turn.exitReason ?? "running"}${turn.status === null ? "" : `, ${turn.status.summary.slice(0, 160)}`}`;
|
|
7444
7893
|
lastModel = turn.model ?? void 0;
|
|
7445
7894
|
}
|
|
7446
7895
|
}
|
|
@@ -7660,7 +8109,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7660
8109
|
const file = this.paths.societySkillFile(skill.name);
|
|
7661
8110
|
const replaced = await exists(file);
|
|
7662
8111
|
const taken = proposal.skillFiles;
|
|
7663
|
-
const staged =
|
|
8112
|
+
const staged = path7.join(this.paths.proposalFiles(proposal.id), "SKILL.md");
|
|
7664
8113
|
if (skill.body === void 0 && (taken === void 0 || !await exists(staged))) {
|
|
7665
8114
|
throw new BoardError("INVALID_STATE", `proposal ${proposal.id} holds no skill files`);
|
|
7666
8115
|
}
|
|
@@ -7723,8 +8172,8 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7723
8172
|
const dir = this.paths.proposalFiles(proposal.id);
|
|
7724
8173
|
await rm2(dir, { recursive: true, force: true });
|
|
7725
8174
|
for (const entry of taken.files) {
|
|
7726
|
-
const target =
|
|
7727
|
-
await ensureDir(
|
|
8175
|
+
const target = path7.join(dir, ...entry.path.split("/"));
|
|
8176
|
+
await ensureDir(path7.dirname(target));
|
|
7728
8177
|
await writeFile4(target, contents.get(entry.path) ?? Buffer.alloc(0));
|
|
7729
8178
|
await chmod2(target, entry.executable ? 493 : 420);
|
|
7730
8179
|
}
|
|
@@ -7782,7 +8231,8 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7782
8231
|
const where = {
|
|
7783
8232
|
scope: turn.project,
|
|
7784
8233
|
...turn.thread === void 0 ? {} : { thread: turn.thread },
|
|
7785
|
-
...turn.channel === void 0 ? {} : { channel: turn.channel }
|
|
8234
|
+
...turn.channel === void 0 ? {} : { channel: turn.channel },
|
|
8235
|
+
...turn.place === void 0 ? {} : { place: turn.place }
|
|
7786
8236
|
};
|
|
7787
8237
|
await this.mutex.run(async () => {
|
|
7788
8238
|
for (const proposal of await this.listProposals()) {
|
|
@@ -7814,16 +8264,25 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7814
8264
|
if (!isSafeRelativePath(entry.path)) {
|
|
7815
8265
|
continue;
|
|
7816
8266
|
}
|
|
7817
|
-
const bytes = await readFile4(
|
|
7818
|
-
const text = bytes === null || bytes.includes(0) ? null : bytes.toString("utf8");
|
|
8267
|
+
const bytes = await readFile4(path7.join(dir, ...entry.path.split("/"))).catch(() => null);
|
|
7819
8268
|
files.push({
|
|
7820
8269
|
...entry,
|
|
7821
|
-
|
|
7822
|
-
truncated: text !== null && text.length > SKILL_TEXT_LIMIT_CHARS
|
|
8270
|
+
...bytes === null ? { text: null, truncated: false } : skillFileText(bytes)
|
|
7823
8271
|
});
|
|
7824
8272
|
}
|
|
7825
8273
|
return files;
|
|
7826
8274
|
}
|
|
8275
|
+
/**
|
|
8276
|
+
* A society skill's folder as it is published, with each file's text, for the skill's view; an
|
|
8277
|
+
* unknown skill, or a folder without a SKILL.md, is not found.
|
|
8278
|
+
*/
|
|
8279
|
+
async readSocietySkillFiles(name) {
|
|
8280
|
+
const files = await readSkillFolder(this.paths.societySkill(NameSchema.parse(name)));
|
|
8281
|
+
if (files === null) {
|
|
8282
|
+
throw new BoardError("NOT_FOUND", `the society has no skill ${name}`);
|
|
8283
|
+
}
|
|
8284
|
+
return files;
|
|
8285
|
+
}
|
|
7827
8286
|
async validateAddAgent(input) {
|
|
7828
8287
|
if (await exists(this.paths.agentFile(input.name))) {
|
|
7829
8288
|
throw new BoardError("ALREADY_EXISTS", `agent ${input.name} already exists`);
|
|
@@ -7954,7 +8413,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7954
8413
|
this.assertNoTurnCut(actor, `${ref} cannot be archived`, (turn) => turn.channel === channel && turn.scope === scope || turn.thread !== void 0 && closing.has(turn.thread));
|
|
7955
8414
|
const threadsClosed = [];
|
|
7956
8415
|
for (const file of await listFiles(threads)) {
|
|
7957
|
-
const doc = await readMarkdown(
|
|
8416
|
+
const doc = await readMarkdown(path7.join(threads, file), ThreadFrontmatterSchema);
|
|
7958
8417
|
if (doc.data.channel !== ref || doc.data.state !== "open") {
|
|
7959
8418
|
continue;
|
|
7960
8419
|
}
|
|
@@ -8060,7 +8519,8 @@ ${charter.purpose}
|
|
|
8060
8519
|
}
|
|
8061
8520
|
}
|
|
8062
8521
|
/**
|
|
8063
|
-
* Every member leaves, every open thread on its channels closes,
|
|
8522
|
+
* Every member leaves, every open thread on its channels closes, every citizen's conversation of
|
|
8523
|
+
* its channels in the society scope ends with the crons there, and the project is marked; its
|
|
8064
8524
|
* files, repository, and history stay. The notice is the board's, since the reason is free text
|
|
8065
8525
|
* a proposer wrote and a board-authored mention would wake whoever it names.
|
|
8066
8526
|
*/
|
|
@@ -8085,7 +8545,7 @@ ${charter.purpose}
|
|
|
8085
8545
|
const threads = this.paths.threads(slug);
|
|
8086
8546
|
const threadsClosed = [];
|
|
8087
8547
|
for (const file of await listFiles(threads)) {
|
|
8088
|
-
const doc = await readMarkdown(
|
|
8548
|
+
const doc = await readMarkdown(path7.join(threads, file), ThreadFrontmatterSchema);
|
|
8089
8549
|
if (doc.data.state !== "open") {
|
|
8090
8550
|
continue;
|
|
8091
8551
|
}
|
|
@@ -8103,7 +8563,7 @@ ${charter.purpose}
|
|
|
8103
8563
|
});
|
|
8104
8564
|
threadsClosed.push(doc.data.id);
|
|
8105
8565
|
}
|
|
8106
|
-
await this.endCronsUnlocked(by, `project ${slug} was archived`, (cron) => cron.scope === slug || cron.thread !== void 0 && threadsClosed.includes(cron.thread));
|
|
8566
|
+
await this.endCronsUnlocked(by, `project ${slug} was archived`, (cron) => cron.scope === slug || cron.place === slug || cron.thread !== void 0 && threadsClosed.includes(cron.thread));
|
|
8107
8567
|
const project = await this.updateProject(slug, (p) => ({
|
|
8108
8568
|
...p,
|
|
8109
8569
|
members: [],
|
|
@@ -8160,10 +8620,10 @@ ${charter.purpose}
|
|
|
8160
8620
|
...project,
|
|
8161
8621
|
members: project.members.filter((member) => member !== name)
|
|
8162
8622
|
}));
|
|
8163
|
-
const sessions =
|
|
8623
|
+
const sessions = path7.join(this.paths.agentProject(name, slug), "sessions.json");
|
|
8164
8624
|
if (await exists(sessions)) {
|
|
8165
8625
|
const archived = `sessions.archived.${ts.replace(/[:.]/g, "-")}.json`;
|
|
8166
|
-
await rename3(sessions,
|
|
8626
|
+
await rename3(sessions, path7.join(this.paths.agentProject(name, slug), archived));
|
|
8167
8627
|
}
|
|
8168
8628
|
}
|
|
8169
8629
|
this.tokenIndex.delete(current.tokenHash);
|
|
@@ -8243,6 +8703,17 @@ ${charter.purpose}
|
|
|
8243
8703
|
return false;
|
|
8244
8704
|
}
|
|
8245
8705
|
}
|
|
8706
|
+
/**
|
|
8707
|
+
* Whether a project exists and is not archived, so citizens' conversations of its channels in
|
|
8708
|
+
* the society scope are still going.
|
|
8709
|
+
*/
|
|
8710
|
+
async projectLive(slug) {
|
|
8711
|
+
try {
|
|
8712
|
+
return (await this.readProject(slug)).archived === void 0;
|
|
8713
|
+
} catch {
|
|
8714
|
+
return false;
|
|
8715
|
+
}
|
|
8716
|
+
}
|
|
8246
8717
|
/**
|
|
8247
8718
|
* A channel that takes new posts, threads, tasks, and followers: it exists, it is not archived,
|
|
8248
8719
|
* and its project is not archived.
|
|
@@ -8493,7 +8964,7 @@ ${charter.purpose}
|
|
|
8493
8964
|
if (since !== null && id <= since || upto !== void 0 && id > upto) {
|
|
8494
8965
|
continue;
|
|
8495
8966
|
}
|
|
8496
|
-
const doc = await readMarkdown(
|
|
8967
|
+
const doc = await readMarkdown(path7.join(dir, file), MessageFrontmatterSchema);
|
|
8497
8968
|
messages.push({ ...doc.data, body: doc.body });
|
|
8498
8969
|
}
|
|
8499
8970
|
return messages;
|
|
@@ -8584,16 +9055,26 @@ function decideWake(input) {
|
|
|
8584
9055
|
}
|
|
8585
9056
|
|
|
8586
9057
|
// packages/scheduler/dist/scheduler.js
|
|
8587
|
-
import { z as
|
|
9058
|
+
import { z as z17 } from "zod";
|
|
8588
9059
|
function besideFields(beside) {
|
|
8589
9060
|
if (beside === void 0) {
|
|
8590
9061
|
return {};
|
|
8591
9062
|
}
|
|
8592
|
-
|
|
9063
|
+
if ("thread" in beside) {
|
|
9064
|
+
return { thread: beside.thread };
|
|
9065
|
+
}
|
|
9066
|
+
return "channel" in beside ? { channel: beside.channel } : { place: beside.place };
|
|
9067
|
+
}
|
|
9068
|
+
function namedIn(dispatch) {
|
|
9069
|
+
return {
|
|
9070
|
+
...dispatch.thread === void 0 ? {} : { thread: dispatch.thread.id },
|
|
9071
|
+
...dispatch.channel === void 0 ? {} : { channel: dispatch.channel },
|
|
9072
|
+
...dispatch.place === void 0 ? {} : { place: dispatch.place }
|
|
9073
|
+
};
|
|
8593
9074
|
}
|
|
8594
9075
|
function conversationKey2(agent, scope, beside) {
|
|
8595
9076
|
const fields = besideFields(beside);
|
|
8596
|
-
return sessionKey(agent, scope, conversationPart({ thread: fields.thread?.id, channel: fields.channel }));
|
|
9077
|
+
return sessionKey(agent, scope, conversationPart({ thread: fields.thread?.id, channel: fields.channel, place: fields.place }));
|
|
8597
9078
|
}
|
|
8598
9079
|
var DEFAULT_TIMINGS = Object.freeze({
|
|
8599
9080
|
pollMs: 1e3,
|
|
@@ -8611,21 +9092,21 @@ var DEFAULT_TIMINGS = Object.freeze({
|
|
|
8611
9092
|
scaleCooldownMs: 36e5,
|
|
8612
9093
|
reflectionMs: 24 * 36e5
|
|
8613
9094
|
});
|
|
8614
|
-
var SchedulerTimingsSchema =
|
|
8615
|
-
pollMs:
|
|
8616
|
-
debounceMs:
|
|
8617
|
-
userDebounceMs:
|
|
8618
|
-
heartbeatMs:
|
|
8619
|
-
waitingStageMs:
|
|
8620
|
-
leaseSweepMs:
|
|
8621
|
-
opsIntervalMs:
|
|
8622
|
-
signalRepeatMs:
|
|
8623
|
-
costReportMs:
|
|
8624
|
-
staleThreadMs:
|
|
8625
|
-
idleMemberMs:
|
|
8626
|
-
homeConflictMs:
|
|
8627
|
-
scaleCooldownMs:
|
|
8628
|
-
reflectionMs:
|
|
9095
|
+
var SchedulerTimingsSchema = z17.object({
|
|
9096
|
+
pollMs: z17.number().positive(),
|
|
9097
|
+
debounceMs: z17.number().nonnegative(),
|
|
9098
|
+
userDebounceMs: z17.number().nonnegative(),
|
|
9099
|
+
heartbeatMs: z17.number().positive(),
|
|
9100
|
+
waitingStageMs: z17.number().positive(),
|
|
9101
|
+
leaseSweepMs: z17.number().positive(),
|
|
9102
|
+
opsIntervalMs: z17.number().positive(),
|
|
9103
|
+
signalRepeatMs: z17.number().positive(),
|
|
9104
|
+
costReportMs: z17.number().positive(),
|
|
9105
|
+
staleThreadMs: z17.number().positive(),
|
|
9106
|
+
idleMemberMs: z17.number().positive(),
|
|
9107
|
+
homeConflictMs: z17.number().positive(),
|
|
9108
|
+
scaleCooldownMs: z17.number().positive(),
|
|
9109
|
+
reflectionMs: z17.number().positive()
|
|
8629
9110
|
}).partial();
|
|
8630
9111
|
function parseTimings(input) {
|
|
8631
9112
|
const parsed = SchedulerTimingsSchema.parse(input);
|
|
@@ -8637,32 +9118,32 @@ function parseTimings(input) {
|
|
|
8637
9118
|
}
|
|
8638
9119
|
return overrides;
|
|
8639
9120
|
}
|
|
8640
|
-
var PendingSchema =
|
|
9121
|
+
var PendingSchema = z17.object({
|
|
8641
9122
|
dispatch: TurnDispatchSchema,
|
|
8642
|
-
readyAt:
|
|
8643
|
-
stageWakes:
|
|
8644
|
-
holder:
|
|
9123
|
+
readyAt: z17.number(),
|
|
9124
|
+
stageWakes: z17.array(TriggerSchema).optional(),
|
|
9125
|
+
holder: z17.boolean().optional(),
|
|
8645
9126
|
newestMessage: UlidSchema.optional(),
|
|
8646
|
-
onlyMessages:
|
|
9127
|
+
onlyMessages: z17.boolean().optional(),
|
|
8647
9128
|
steeredThrough: UlidSchema.optional(),
|
|
8648
|
-
onlyCrons:
|
|
9129
|
+
onlyCrons: z17.boolean().optional()
|
|
8649
9130
|
});
|
|
8650
|
-
var StateSchema =
|
|
8651
|
-
cursor:
|
|
8652
|
-
lastHeartbeat:
|
|
9131
|
+
var StateSchema = z17.object({
|
|
9132
|
+
cursor: z17.string().nullable(),
|
|
9133
|
+
lastHeartbeat: z17.record(z17.string(), z17.string()),
|
|
8653
9134
|
/** Waiting stages already signalled, as `task:stage`, so each is logged once while it waits. */
|
|
8654
|
-
waitingSeen:
|
|
9135
|
+
waitingSeen: z17.record(z17.string(), z17.string()).default({}),
|
|
8655
9136
|
/** Queued dispatches survive a restart; the cursor has already moved past the events that made them. */
|
|
8656
|
-
pending:
|
|
8657
|
-
opsReported:
|
|
8658
|
-
releaseCounts:
|
|
8659
|
-
lastOps:
|
|
8660
|
-
lastScaled:
|
|
8661
|
-
lastCostReport:
|
|
8662
|
-
costSinceReport:
|
|
8663
|
-
turnsSinceReport:
|
|
9137
|
+
pending: z17.record(z17.string(), PendingSchema).default({}),
|
|
9138
|
+
opsReported: z17.record(z17.string(), z17.string()).default({}),
|
|
9139
|
+
releaseCounts: z17.record(z17.string(), z17.number().int().nonnegative()).default({}),
|
|
9140
|
+
lastOps: z17.string().nullable().default(null),
|
|
9141
|
+
lastScaled: z17.record(z17.string(), z17.string()).default({}),
|
|
9142
|
+
lastCostReport: z17.string().nullable().default(null),
|
|
9143
|
+
costSinceReport: z17.number().nonnegative().default(0),
|
|
9144
|
+
turnsSinceReport: z17.number().int().nonnegative().default(0),
|
|
8664
9145
|
/** When each member last reflected, or was first seen; the cadence counts from here. */
|
|
8665
|
-
lastReflection:
|
|
9146
|
+
lastReflection: z17.record(z17.string(), z17.string()).default({})
|
|
8666
9147
|
});
|
|
8667
9148
|
function messageOf(trigger) {
|
|
8668
9149
|
return trigger.kind === "mention" || trigger.kind === "user_post" ? trigger.messageId : void 0;
|
|
@@ -9065,6 +9546,21 @@ var Scheduler = class {
|
|
|
9065
9546
|
}
|
|
9066
9547
|
return;
|
|
9067
9548
|
}
|
|
9549
|
+
case "project.archived": {
|
|
9550
|
+
const slug = stringOf(payload["slug"]);
|
|
9551
|
+
if (slug === null) {
|
|
9552
|
+
return;
|
|
9553
|
+
}
|
|
9554
|
+
this.sweep({ kind: "place", project: slug });
|
|
9555
|
+
for (const [key, item] of this.pending) {
|
|
9556
|
+
const { dispatch } = item;
|
|
9557
|
+
if (dispatch.place === slug) {
|
|
9558
|
+
this.pending.delete(key);
|
|
9559
|
+
this.log.info({ agent: dispatch.agent, project: dispatch.project, place: slug }, "dropping project wake, the project is archived");
|
|
9560
|
+
}
|
|
9561
|
+
}
|
|
9562
|
+
return;
|
|
9563
|
+
}
|
|
9068
9564
|
case "agent.left": {
|
|
9069
9565
|
const name = stringOf(payload["name"]);
|
|
9070
9566
|
const project = stringOf(payload["project"]);
|
|
@@ -9366,11 +9862,12 @@ var Scheduler = class {
|
|
|
9366
9862
|
const scope = stringOf(payload["project"]);
|
|
9367
9863
|
const thread = stringOf(payload["thread"]);
|
|
9368
9864
|
const channel = stringOf(payload["channel"]);
|
|
9865
|
+
const place = stringOf(payload["place"]);
|
|
9369
9866
|
const agent = name === null ? null : await this.tryReadAgent(name);
|
|
9370
9867
|
if (agent === null || scope === null) {
|
|
9371
9868
|
return;
|
|
9372
9869
|
}
|
|
9373
|
-
const beside = thread !== null ? { thread: await this.conversationOf(thread, scope) } : channel !== null ? { channel } : null;
|
|
9870
|
+
const beside = thread !== null ? { thread: await this.conversationOf(thread, scope) } : channel !== null ? { channel } : place !== null ? { place } : null;
|
|
9374
9871
|
if (beside === null) {
|
|
9375
9872
|
return;
|
|
9376
9873
|
}
|
|
@@ -9415,7 +9912,7 @@ var Scheduler = class {
|
|
|
9415
9912
|
}
|
|
9416
9913
|
return {
|
|
9417
9914
|
scope,
|
|
9418
|
-
beside: conversation.kind === "thread" ? { thread: await this.conversationOf(conversation.id, scope) } : { channel: conversation.name }
|
|
9915
|
+
beside: conversation.kind === "thread" ? { thread: await this.conversationOf(conversation.id, scope) } : conversation.kind === "channel" ? { channel: conversation.name } : { place: conversation.project }
|
|
9419
9916
|
};
|
|
9420
9917
|
}
|
|
9421
9918
|
/**
|
|
@@ -9433,14 +9930,23 @@ var Scheduler = class {
|
|
|
9433
9930
|
}
|
|
9434
9931
|
/**
|
|
9435
9932
|
* The conversation a post wakes a citizen in, for a turn in `scope`: its thread's, else its
|
|
9436
|
-
* channel's when that is a channel of the scope other than general, else
|
|
9933
|
+
* channel's when that is a channel of the scope other than general, else, for a turn in the
|
|
9934
|
+
* society scope, its project's when it is a project's channel, general included, else the
|
|
9935
|
+
* scope's home.
|
|
9437
9936
|
*/
|
|
9438
9937
|
async postedIn(scope, channel, threadId) {
|
|
9439
9938
|
if (threadId !== void 0) {
|
|
9440
9939
|
return { thread: await this.conversationOf(threadId, scope) };
|
|
9441
9940
|
}
|
|
9442
|
-
|
|
9443
|
-
|
|
9941
|
+
if (channel === null) {
|
|
9942
|
+
return void 0;
|
|
9943
|
+
}
|
|
9944
|
+
const name = channelConversation(scope, channel);
|
|
9945
|
+
if (name !== void 0) {
|
|
9946
|
+
return { channel: name };
|
|
9947
|
+
}
|
|
9948
|
+
const place = placeConversation(scope, channel);
|
|
9949
|
+
return place === void 0 ? void 0 : { place };
|
|
9444
9950
|
}
|
|
9445
9951
|
async tryReadAgent(name) {
|
|
9446
9952
|
try {
|
|
@@ -9582,7 +10088,7 @@ var Scheduler = class {
|
|
|
9582
10088
|
};
|
|
9583
10089
|
for (const entry of unread) {
|
|
9584
10090
|
if (entry.scope === project) {
|
|
9585
|
-
const beside = entry.thread !== null ? { thread: await this.conversationOf(entry.thread, project) } : entry.channel !== null ? { channel: entry.channel } : void 0;
|
|
10091
|
+
const beside = entry.thread !== null ? { thread: await this.conversationOf(entry.thread, project) } : entry.channel !== null ? { channel: entry.channel } : entry.place !== null ? { place: entry.place } : void 0;
|
|
9586
10092
|
reasonsIn(beside).digestSize += entry.count;
|
|
9587
10093
|
}
|
|
9588
10094
|
}
|
|
@@ -9669,7 +10175,10 @@ var Scheduler = class {
|
|
|
9669
10175
|
if (cron.thread !== void 0) {
|
|
9670
10176
|
return { thread: await this.conversationOf(cron.thread, cron.scope) };
|
|
9671
10177
|
}
|
|
9672
|
-
|
|
10178
|
+
if (cron.channel !== void 0) {
|
|
10179
|
+
return { channel: cron.channel };
|
|
10180
|
+
}
|
|
10181
|
+
return cron.place === void 0 ? void 0 : { place: cron.place };
|
|
9673
10182
|
}
|
|
9674
10183
|
/** Whether a cron's wake waits in the queue or its turn is running. */
|
|
9675
10184
|
cronQueuedOrRunning(id) {
|
|
@@ -10054,8 +10563,7 @@ var Scheduler = class {
|
|
|
10054
10563
|
const attempt = steer({
|
|
10055
10564
|
agent: dispatch.agent,
|
|
10056
10565
|
project: dispatch.project,
|
|
10057
|
-
...dispatch
|
|
10058
|
-
...dispatch.channel === void 0 ? {} : { channel: dispatch.channel }
|
|
10566
|
+
...namedIn(dispatch)
|
|
10059
10567
|
}).then((outcome) => {
|
|
10060
10568
|
const current = this.pending.get(key);
|
|
10061
10569
|
if (outcome !== "busy" && current !== void 0) {
|
|
@@ -10076,7 +10584,7 @@ var Scheduler = class {
|
|
|
10076
10584
|
return false;
|
|
10077
10585
|
}
|
|
10078
10586
|
const { dispatch } = item;
|
|
10079
|
-
const cursor = await this.board.digestCursor(dispatch.agent, dispatch.project, conversationPart(
|
|
10587
|
+
const cursor = await this.board.digestCursor(dispatch.agent, dispatch.project, conversationPart(namedIn(dispatch)));
|
|
10080
10588
|
return cursor !== null && cursor >= item.newestMessage;
|
|
10081
10589
|
}
|
|
10082
10590
|
async dispatchReady(now) {
|
|
@@ -10113,6 +10621,7 @@ var Scheduler = class {
|
|
|
10113
10621
|
project: dispatch.project,
|
|
10114
10622
|
thread: dispatch.thread?.id,
|
|
10115
10623
|
channel: dispatch.channel,
|
|
10624
|
+
place: dispatch.place,
|
|
10116
10625
|
trigger: dispatch.trigger.kind
|
|
10117
10626
|
}, "dispatching turn");
|
|
10118
10627
|
const crons = dispatch.crons ?? [];
|
|
@@ -10229,17 +10738,17 @@ import { randomUUID } from "node:crypto";
|
|
|
10229
10738
|
|
|
10230
10739
|
// packages/turn-host/dist/render.js
|
|
10231
10740
|
var TURN_CONTRACT = [
|
|
10232
|
-
"- Every turn belongs to one conversation, named at the top of your prompt: your home conversation in a project or the society, for its general channel and for work tied to no other channel and no thread; the conversation of one other channel of the place, a workstream such as a release; or the conversation of one thread you take part in, a task's, a proposal's, or a topic's. Each has its own session and its own turns, which may run while this one does. The prompt carries this conversation only: in a thread, its new messages, or the whole thread when the conversation is new, and in a task's thread the task and its stage; in a channel's or at home, that channel's or general's messages that concern you. Read anything else you need with get_task, search, and the board's files, and leave other channels', other threads', and other projects' work to their own turns.",
|
|
10741
|
+
"- Every turn belongs to one conversation, named at the top of your prompt: your home conversation in a project or the society, for its general channel and for work tied to no other channel and no thread; the conversation of one other channel of the place, a workstream such as a release; outside projects, the conversation of one project's channels, general and its workstreams together, for posts there that ask something of you while you are not a member; or the conversation of one thread you take part in, a task's, a proposal's, or a topic's. Each has its own session and its own turns, which may run while this one does. The prompt carries this conversation only: in a thread, its new messages, or the whole thread when the conversation is new, and in a task's thread the task and its stage; in a channel's, a project's, or at home, that channel's, that project's channels', or general's messages that concern you. Read anything else you need with get_task, search, and the board's files, and leave other channels', other threads', and other projects' work to their own turns.",
|
|
10233
10742
|
"- Act through the board tools (the `board` MCP server). Holding a stage is a lease; every turn that touches the task renews it.",
|
|
10234
|
-
"- Work only inside your worktree. In a task's conversation it is a worktree of its own on the task's branch `task/<id>`: commit your work there. After each turn the runner commits what you left on the branch and lets it go for the next holder. Your home worktree is on your own branch, `agent/<name>`, for work tied to no task; nothing lands from it by itself. In a proposal's, a topic's, or a channel's conversation it is a worktree of its own, detached at the project's default branch, which goes when the thread closes or the channel is archived, so work that should last goes on a task's branch. Before each turn the runner fetches the project's remote and brings what holds no work of yours up to the default branch: your own branch when it has no commits of its own, a task's branch nobody has worked on, and a detached worktree with nothing uncommitted; what it leaves where it is, and why, is under Your workspace in the prompt. It never merges or commits for you.",
|
|
10743
|
+
"- Work only inside your worktree. In a task's conversation it is a worktree of its own on the task's branch `task/<id>`: commit your work there. The branch is checked out in one worktree at a time and kept for the turns of whoever holds the task's stage, so a turn of anyone else there, or one that starts while another turn has the branch, works detached at its tip, which Your workspace then says; its commits go on the branch by a fast-forward when a turn in the task ends with the branch free, and the task's thread is told of commits that no longer build on its tip. After each turn the runner commits what you left on the branch and lets it go for the next holder. Your home worktree is on your own branch, `agent/<name>`, for work tied to no task; nothing lands from it by itself. In a proposal's, a topic's, or a channel's conversation it is a worktree of its own, detached at the project's default branch, which goes when the thread closes or the channel is archived, so work that should last goes on a task's branch. Before each turn the runner fetches the project's remote and brings what holds no work of yours up to the default branch: your own branch when it has no commits of its own, a task's branch nobody has worked on, and a detached worktree with nothing uncommitted; what it leaves where it is, and why, is under Your workspace in the prompt. It never merges or commits for you.",
|
|
10235
10744
|
"- Posts that would wake this conversation may arrive while you work, delivered into the turn under a heading that says so and formatted like the digest. They are part of this turn: act on them now, after the step in hand, or not at all, as with the digest. The user may also stop a turn; the next turn is told.",
|
|
10236
10745
|
"- Silence is allowed. If the digest needs no reply, post nothing.",
|
|
10237
10746
|
"- To come back to something later, set a cron with create_cron: a note on what to do then, and a five-field cron expression or one time with at. It wakes you in this conversation unless you name another, at most every fifteen minutes, and every fire is a paid turn, so end a cron with remove_cron once it has done its job. Never wait by holding a stage, sleeping, or leaving a process running: a turn's board token ends with the turn, so nothing it leaves behind can act on the board. The society's crons are under society/crons/ in the board projection.",
|
|
10238
10747
|
"- An @mention wakes the citizen named, and every wake costs a turn. Address someone with @ only when you need them to act; when you merely refer to citizens, write their names plainly. A name in a code span or a code block is no mention, so code keeps its @ as written. @user wakes nobody, since the user takes no turns; it is what puts a question before the user, so use it only when you need the user to answer or decide.",
|
|
10239
10748
|
"- A thread is a conversation off a channel that reaches only its participants and whoever is mentioned in it. Every task has one, opened with it under the task's id on the channel of its project it was filed in, general unless it was filed in another: talk about a task there, with post_message and its thread_id, and the notes you give advance_task and update_task are posted there as you. Its participants are the task's creator, its stage holders and assignees, whoever may take the stage that waits, and anyone who posted; it closes when the task ends. Every proposal has one too, in governance, opened with its pitch and closed by its decision. Open other threads with open_thread on a channel with a title, for any other topic; close_thread posts your summary to its channel.",
|
|
10240
|
-
"- To show an image in a post, such as a screenshot or a chart,
|
|
10241
|
-
"- Route every lesson: about you, your craft, or the user, write it to memory/core.md in your home directory, and keep that file short by moving detail to memory/<topic>.md, your archive; a durable fact about a project
|
|
10242
|
-
'- A procedure you have followed twice is a skill: write it to skills/<name>/SKILL.md in your home, frontmatter with `name` and a one-line `description` and then the steps, and your skills index lists it from the next turn. Files the steps use, such as scripts/ and references/, go in the same folder, named in SKILL.md by paths relative to it. Propose it with kind "skill" when
|
|
10749
|
+
"- To show an image in a post, such as a screenshot or a chart, save it anywhere you can read it, such as a folder from `mktemp -d`, call upload_image with its path, and put the markdown it returns in the post, ``, with what the image shows as its alt text: the board keeps the image, so it shows in any conversation after the file is gone. It takes a PNG, JPEG, GIF, or WebP of at most 8 MB from your worktree, your home, or the system temp folder. An image committed on a task's branch, linked by its path in the task's worktree, still shows in place, and so does one at an https address. Never put an image or any other file into a message as base64 or a data: URL: the board does not show it, and every reader of the conversation gets it in full in their prompt.",
|
|
10750
|
+
"- Route every lesson: about you, your craft, or the user, write it to memory/core.md in your home directory, and keep that file short by moving detail to memory/<topic>.md, your archive; how a codebase behaves (architecture, conventions, gotchas, how to build, test, and verify it) goes in its AGENTS.md or README, committed on a task's branch like any change to the code, or proposed in the task's thread or the project channel when it is outside the task at hand; a durable fact about a project as a place (runners, access, flow, where results live) goes through `write_knowledge` on that project; something the whole project should know now, post it to the project channel, and anything about one task, in the task's thread.",
|
|
10751
|
+
'- A procedure you have followed twice is a candidate for a skill, which it is when it makes a citizen better at a kind of work on any project: write it to skills/<name>/SKILL.md in your home, frontmatter with `name` and a one-line `description` and then the steps, and your skills index lists it from the next turn. Files the steps use, such as scripts/ and references/, go in the same folder, named in SKILL.md by paths relative to it. Propose it with kind "skill" when more than one citizen would use it and nothing in it is tied to one place; a skill only you use stays yours, however many projects you use it on.',
|
|
10243
10752
|
"- To take a published skill for yourself, install it in a temporary folder and copy it into your home: in a folder from `mktemp -d`, `npx -y skills add <source> --skill <name> -a universal --copy -y` writes `.agents/skills/<name>/` and a lock file there, and you copy that skill folder to skills/<name>/ in your home, named in lowercase letters, digits, and hyphens. Never install one inside a worktree, where it would be committed into the project, or with -g, which writes to the runner's own home, shared by every citizen on it and listed in no skills index.",
|
|
10244
10753
|
"- Report memoryUpdated: true in the status object whenever you changed memory/core.md or a skill, so a warm session restarts with the new instructions.",
|
|
10245
10754
|
"- Other turns of yours may be running at the same time and share your home. Change a file there with your edit tool, which refuses a file changed since you read it, and never rewrite one whole from an earlier read, with a shell redirect or a script, or you erase what another of your turns wrote.",
|
|
@@ -10250,7 +10759,7 @@ var PLANNING = [
|
|
|
10250
10759
|
"- A task is a plan of stages between open and done. Hold the current stage with claim_task and finish it with advance_task; the next stage becomes current, and past the last one the task is done.",
|
|
10251
10760
|
"- Name stages by what gets done. Plan the next few steps rather than everything, and reshape with plan_task as the work teaches you.",
|
|
10252
10761
|
"- Assign a stage to a role when anyone in it could do it, to a citizen only when it must be them, and to nobody when anyone in the project could. A citizen named on a stage must be a member of the task's project; add them first. A stage wakes everyone it is open to when it comes up, so the narrower the assignee, the fewer turns it costs.",
|
|
10253
|
-
"- Reshape rather than force: another round is an inserted stage, and a wait on something outside the society is a stage named for what it waits on. When a check finds work unfinished,
|
|
10762
|
+
"- Reshape rather than force: another round is an inserted stage, and a wait on something outside the society is a stage named for what it waits on. When a check finds work unfinished, the holder of the current stage sends the task back to an earlier stage with update_task, and so may the user, the steward, and the concierge; anyone else asks the concierge in the task's thread.",
|
|
10254
10763
|
"- A task that cannot start until other tasks finish names them with update_task blocked_by. While any of them is in play, its stage wakes nobody, neither when it comes up nor on a heartbeat; once the last has ended, done or abandoned, the stage wakes as if it had just come up, or wakes its holder. Nobody needs to clear the block or mention anyone when they end; change blocked_by only when the plan changes.",
|
|
10255
10764
|
"- A gated stage is an independent check: nobody who held an earlier stage of the task may hold it, and only the user, the steward, and the concierge may add, remove, move, reassign, or ungate it. Ask them in the task's thread when a gate should change.",
|
|
10256
10765
|
"- Commit your work on the task's branch before you advance, and say what you did in the advance note; it is the handover the next holder reads in the task's thread."
|
|
@@ -10288,7 +10797,7 @@ function renderOnboardingPreamble(context2) {
|
|
|
10288
10797
|
function renderSkillsIndex(skills) {
|
|
10289
10798
|
if (skills.length === 0) {
|
|
10290
10799
|
return [
|
|
10291
|
-
"None yet.
|
|
10800
|
+
"None yet. Something you have done the same way twice is a candidate for the first; the turn contract says when it is one."
|
|
10292
10801
|
];
|
|
10293
10802
|
}
|
|
10294
10803
|
return [
|
|
@@ -10304,7 +10813,7 @@ function renderInstructions(input) {
|
|
|
10304
10813
|
if (norms.length > 0) {
|
|
10305
10814
|
sections.push("", "## Society norms", "", norms);
|
|
10306
10815
|
}
|
|
10307
|
-
sections.push("", "## Core memory", "", memory.length === 0 ? "(empty)" : memory, "", "## Skills", "", ...renderSkillsIndex(input.skills ?? []), "", "## Where things are", "", `- Your home directory: ${input.homeDir}. memory/core.md is loaded every turn; memory/<topic>.md is your archive; skills/<name>/SKILL.md are your skills; projects/<slug>/notes.md are your notes. You may read and write all of it, and the board's search covers your archive and skills for you alone. The runner keeps it in git and shares it with the board after every turn, so whatever machine your next turn runs on has it; leave git there to the runner. Files over ${HOME_FILE_LIMIT_BYTES / (1024 * 1024)} MB are not kept.`, `- ${HOME_SCRATCH}/ in your home is your working folder in turns outside any project, and a thread's or a
|
|
10816
|
+
sections.push("", "## Core memory", "", memory.length === 0 ? "(empty)" : memory, "", "## Skills", "", ...renderSkillsIndex(input.skills ?? []), "", "## Where things are", "", `- Your home directory: ${input.homeDir}. memory/core.md is loaded every turn; memory/<topic>.md is your archive; skills/<name>/SKILL.md are your skills; projects/<slug>/notes.md are your notes. You may read and write all of it, and the board's search covers your archive and skills for you alone. The runner keeps it in git and shares it with the board after every turn, so whatever machine your next turn runs on has it; leave git there to the runner. Files over ${HOME_FILE_LIMIT_BYTES / (1024 * 1024)} MB are not kept.`, `- ${HOME_SCRATCH}/ in your home is your working folder in turns outside any project, and a thread's, a channel's, or a project's channels' conversation there works in a folder of its own inside it, which goes when the thread closes or the channel or the project is archived: none of it is kept or shared, so put downloads, environments, and other working files there, and keep the rest of your home for what you mean to remember.`, `- The board projection: ${input.boardDir} (read-only markdown: society and project channels, tasks, threads; shared knowledge under projects/<slug>/knowledge/ and society/knowledge/; the society's skills under society/skills/; each project's dashboard, the status page the user reads on its view, at projects/<slug>/dashboard.md, which its members change with update_dashboard). Search it with your file tools; act through the board tools.`, "", "## Turn contract", "", TURN_CONTRACT, "", "## Planning", "", PLANNING, "", "## Governance", "", GOVERNANCE);
|
|
10308
10817
|
if (input.onboarding !== void 0) {
|
|
10309
10818
|
sections.push("", "## First turn", "", renderOnboardingPreamble(input.onboarding));
|
|
10310
10819
|
}
|
|
@@ -10317,9 +10826,9 @@ var REFLECTION_INTRO = "This turn is for your memory; take no new work, and answ
|
|
|
10317
10826
|
var RECONCILE = "- Reconcile first: merge each copy listed under Edits to reconcile in your home into the file beside it, then delete the copy.";
|
|
10318
10827
|
var REFLECTION_STEPS = [
|
|
10319
10828
|
"- Consolidate memory/core.md: keep it to lessons that still hold, about sixty lines at most, and move detail worth keeping to memory/<topic>.md in your home, which the board's search covers for you alone.",
|
|
10320
|
-
'- Extract a skill: a procedure you have followed twice goes to skills/<name>/SKILL.md, frontmatter with `name` and a one-line `description` and then the steps, with any script or reference it uses in the same folder. Propose it with kind "skill" when
|
|
10829
|
+
'- Extract a skill: a procedure you have followed twice is a candidate, and one that makes a citizen better at a kind of work on any project goes to skills/<name>/SKILL.md, frontmatter with `name` and a one-line `description` and then the steps, with any script or reference it uses in the same folder. Propose it with kind "skill" when more than one citizen would use it and nothing in it is tied to one place; one only you use stays yours.',
|
|
10321
10830
|
"- Refresh profile.md: one paragraph on what you do well and what to send your way; the roster the front desk routes with is built from it.",
|
|
10322
|
-
"- Share: a durable fact about this scope
|
|
10831
|
+
"- Share: how a codebase behaves goes in its AGENTS.md or README through a task like any change to the code, so propose it in that task's thread or the project channel; a durable fact about this scope as a place (runners, access, flow, where results live) goes through write_knowledge; a norm the whole society should follow goes to the steward as a plain post in general.",
|
|
10323
10832
|
"- Report memoryUpdated: true in the status object when core.md or a skill changed."
|
|
10324
10833
|
];
|
|
10325
10834
|
var MAX_BODY_CHARS = 1500;
|
|
@@ -10458,7 +10967,7 @@ function buildTurnPrompt(input) {
|
|
|
10458
10967
|
lines.push("Operations signals arrived; they are listed under Operations signals below. Decide whether a proposal is warranted, and stay silent if not.");
|
|
10459
10968
|
}
|
|
10460
10969
|
if (dispatch.trigger.kind === "stage") {
|
|
10461
|
-
lines.push("A stage is waiting for you: claim it with claim_task unless you hold it already, do the work in your worktree, which is on the task's branch, commit there, then advance_task. If the plan no longer fits, reshape it with plan_task.");
|
|
10970
|
+
lines.push("A stage is waiting for you: claim it with claim_task unless you hold it already, do the work in your worktree, which is on the task's branch unless Your workspace says otherwise, commit there, then advance_task. If the plan no longer fits, reshape it with plan_task.");
|
|
10462
10971
|
}
|
|
10463
10972
|
if (dispatch.trigger.kind === "task_done") {
|
|
10464
10973
|
lines.push("A task you created is done. Tell whoever asked for it, if anyone did.");
|
|
@@ -10477,8 +10986,11 @@ function buildTurnPrompt(input) {
|
|
|
10477
10986
|
if (conversation === null && dispatch.channel !== void 0) {
|
|
10478
10987
|
const ref = channelRef(dispatch.project === SOCIETY_SCOPE ? null : dispatch.project, dispatch.channel);
|
|
10479
10988
|
lines.push(`This turn is in your conversation of ${ref}, a workstream with turns of its own: its posts, and the work it asks for. Your home conversation for ${dispatch.project} carries its general channel, and each other channel and each thread you take part in is a conversation of its own; read one when you need it, and leave its work to its turn. Post here with post_message and channel ${ref}.`);
|
|
10989
|
+
} else if (conversation === null && dispatch.place !== void 0) {
|
|
10990
|
+
lines.push(`This turn is in your conversation of project ${dispatch.place}'s channels, from outside the project: the posts in its general and its other channels that concern you, and the work they ask for. Your home conversation for ${dispatch.project} carries its general channel, and each other project's channels, each other channel, and each thread you take part in is a conversation of its own with turns of its own; read one when you need it, and leave its work to its turn. Answer in the channel a post came from with post_message.`);
|
|
10480
10991
|
} else if (conversation === null) {
|
|
10481
|
-
|
|
10992
|
+
const besides = dispatch.project === SOCIETY_SCOPE ? "Each other channel, each project's channels you are asked in from outside the project, and each thread you take part in, a task's, a proposal's, or a topic's," : "Each other channel, a workstream, and each thread you take part in, a task's, a proposal's, or a topic's,";
|
|
10993
|
+
lines.push(`This turn is in your home conversation for ${dispatch.project}: its general channel, and work tied to no other channel and no thread. ${besides} is a conversation of its own with turns of its own; read one when you need it, and leave its work to its turn.`);
|
|
10482
10994
|
} else {
|
|
10483
10995
|
const { thread, task } = conversation;
|
|
10484
10996
|
const about = thread.subject === void 0 ? "a topic" : `${thread.subject.kind} ${thread.subject.id}`;
|
|
@@ -10513,7 +11025,9 @@ function buildTurnPrompt(input) {
|
|
|
10513
11025
|
lines.push("", "## The user stopped your previous turn", "", `It was ended on purpose at ${last.endedAt ?? "its end"}, not by a failure. Do not pick its work up again unless this turn asks for it. The worktree may hold uncommitted changes; run git status before doing anything else.`);
|
|
10514
11026
|
}
|
|
10515
11027
|
if (last !== null && (last.exitReason === "error" || last.exitReason === "timeout")) {
|
|
10516
|
-
lines.push("", "## Your previous turn did not finish", "", `It ended with ${last.exitReason}${last.error === null ? "" : `: ${last.error}`}.`,
|
|
11028
|
+
lines.push("", "## Your previous turn did not finish", "", `It ended with ${last.exitReason}${last.error === null ? "" : `: ${last.error}`}.`, ...last.sessionDropped === true ? [
|
|
11029
|
+
"Its CLI could not resume this conversation's session, so the session was dropped and this turn starts a new one: nothing of your earlier turns here carries over but what is on the board and in your home."
|
|
11030
|
+
] : [], "The worktree may hold uncommitted changes. Run git status before doing anything else.");
|
|
10517
11031
|
}
|
|
10518
11032
|
const society = input.societyView;
|
|
10519
11033
|
if (society !== void 0 && society !== null) {
|
|
@@ -10543,7 +11057,7 @@ function buildTurnPrompt(input) {
|
|
|
10543
11057
|
if (knowledge !== void 0 && knowledge !== null) {
|
|
10544
11058
|
lines.push("", `## Knowledge of ${dispatch.project}`, "");
|
|
10545
11059
|
if (knowledge.topics.length === 0) {
|
|
10546
|
-
lines.push("None yet. A durable fact about this
|
|
11060
|
+
lines.push("None yet. A durable fact about this place, such as its runners, access, flow, or where results live, is worth a write_knowledge call.");
|
|
10547
11061
|
} else {
|
|
10548
11062
|
lines.push(`Topics under ${knowledge.dir}; open one with your file tools when it is relevant.`, "");
|
|
10549
11063
|
for (const topic of knowledge.topics) {
|
|
@@ -10609,6 +11123,13 @@ function capitalized(text) {
|
|
|
10609
11123
|
}
|
|
10610
11124
|
|
|
10611
11125
|
// packages/turn-host/dist/host.js
|
|
11126
|
+
function conversationOfJob(job) {
|
|
11127
|
+
return {
|
|
11128
|
+
...job.thread === void 0 ? {} : { thread: job.thread },
|
|
11129
|
+
...job.channel === void 0 ? {} : { channel: job.channel },
|
|
11130
|
+
...job.place === void 0 ? {} : { place: job.place }
|
|
11131
|
+
};
|
|
11132
|
+
}
|
|
10612
11133
|
var SILENT = { info() {
|
|
10613
11134
|
}, warn() {
|
|
10614
11135
|
}, error() {
|
|
@@ -10619,6 +11140,12 @@ var DEFAULT_RESIDENT_IDLE_MS = 10 * 6e4;
|
|
|
10619
11140
|
var LEASE_RENEW_MS = 10 * 6e4;
|
|
10620
11141
|
var USER_POST_TRIGGER2 = "user_post";
|
|
10621
11142
|
var OPS_TRIGGER2 = "ops_event";
|
|
11143
|
+
var RESUME_RETRIES = 3;
|
|
11144
|
+
function stuckNotice(stuck) {
|
|
11145
|
+
const more = stuck.total - stuck.commits.length;
|
|
11146
|
+
const commits = `${stuck.commits.map((commit) => `\`${commit}\``).join(", ")}${more > 0 ? `, and ${more} more` : ""}`;
|
|
11147
|
+
return `${stuck.agent}'s worktree for this task holds commits that \`${stuck.branch}\` lacks and that the runner cannot fast-forward it to, because they no longer build on its tip or another worktree's commits build on it too: ${commits}. Until someone puts them on the branch, it lacks them, and so does what lands from it; any worktree of the project's clone can cherry-pick or merge them by id.`;
|
|
11148
|
+
}
|
|
10622
11149
|
var MAX_SIGNALS = 30;
|
|
10623
11150
|
var TurnHost = class {
|
|
10624
11151
|
board;
|
|
@@ -10663,13 +11190,15 @@ var TurnHost = class {
|
|
|
10663
11190
|
const actor = { name: agent.name, role: agent.role };
|
|
10664
11191
|
const thread = dispatch.thread;
|
|
10665
11192
|
const channel = thread === void 0 ? dispatch.channel : void 0;
|
|
10666
|
-
const
|
|
11193
|
+
const place = thread === void 0 && channel === void 0 && dispatch.project === SOCIETY_SCOPE ? dispatch.place : void 0;
|
|
11194
|
+
const part = conversationPart({ thread: thread?.id, channel, place });
|
|
10667
11195
|
const startedAt = this.now().toISOString();
|
|
10668
11196
|
const base = {
|
|
10669
11197
|
agent: agent.name,
|
|
10670
11198
|
project: dispatch.project,
|
|
10671
11199
|
...thread === void 0 ? {} : { thread: thread.id },
|
|
10672
11200
|
...channel === void 0 ? {} : { channel },
|
|
11201
|
+
...place === void 0 ? {} : { place },
|
|
10673
11202
|
runner: runner.name,
|
|
10674
11203
|
cli: agent.cli,
|
|
10675
11204
|
session: null,
|
|
@@ -10694,23 +11223,25 @@ var TurnHost = class {
|
|
|
10694
11223
|
const charter = await this.board.readRole(agent.role);
|
|
10695
11224
|
const societyScope = dispatch.project === SOCIETY_SCOPE;
|
|
10696
11225
|
const taskId = thread?.task === true ? thread.id : void 0;
|
|
11226
|
+
const task = taskId === void 0 ? null : await this.board.findTask(taskId).then((found) => found.task).catch(() => null);
|
|
10697
11227
|
const project = societyScope ? null : await this.board.readProject(dispatch.project);
|
|
10698
|
-
const workspace = await this.workspaceOf(project, thread, channel);
|
|
11228
|
+
const workspace = await this.workspaceOf(project, thread, channel, place, task);
|
|
10699
11229
|
const sessions = await this.board.readSessions(agent.name, dispatch.project, part);
|
|
10700
11230
|
const recorded = sessions.runner === runner.name ? sessions[agent.cli] : void 0;
|
|
10701
11231
|
const newSession = recorded === void 0;
|
|
10702
11232
|
const lastTurn = await this.board.readLastTurn(agent.name, dispatch.project, part);
|
|
10703
11233
|
const costSoFarUsd = !newSession && lastTurn?.session === recorded ? lastTurn.sessionCostUsd ?? lastTurn.costUsd : 0;
|
|
11234
|
+
const resumeRetries = !newSession && lastTurn?.session === recorded && lastTurn.resumeFailed === "retry" ? lastTurn.resumeRetries ?? 1 : 0;
|
|
10704
11235
|
const digest = await this.board.readDigest({
|
|
10705
11236
|
...actor,
|
|
10706
11237
|
scope: dispatch.project,
|
|
10707
11238
|
...thread === void 0 ? {} : { thread: thread.id },
|
|
10708
|
-
...channel === void 0 ? {} : { channel }
|
|
11239
|
+
...channel === void 0 ? {} : { channel },
|
|
11240
|
+
...place === void 0 ? {} : { place }
|
|
10709
11241
|
}, { advance: false, limit: thread === void 0 ? 50 : 100 });
|
|
10710
11242
|
const threadSoFar = thread !== void 0 && newSession ? (await this.board.listThread(thread.id).catch(() => [])).slice(-100) : null;
|
|
10711
11243
|
const messages = threadSoFar ?? digest.messages;
|
|
10712
11244
|
const cursorAfter = threadSoFar?.at(-1)?.id ?? digest.cursor;
|
|
10713
|
-
const task = taskId === void 0 ? null : await this.board.findTask(taskId).then((found) => found.task).catch(() => null);
|
|
10714
11245
|
const held = task === null ? [] : (await this.board.heldClaims(agent.name)).filter((each) => each.id === task.id);
|
|
10715
11246
|
const onboarding = dispatch.onboarding || newSession && part === void 0 ? {
|
|
10716
11247
|
agentName: agent.name,
|
|
@@ -10769,7 +11300,7 @@ var TurnHost = class {
|
|
|
10769
11300
|
fresh: threadSoFar !== null,
|
|
10770
11301
|
...threadRecord.state === "closed" ? { replyIn: await this.replyIn(threadRecord) } : {}
|
|
10771
11302
|
};
|
|
10772
|
-
const ending = dispatch.trigger.kind === "closing" ? await this.endingOf(dispatch.project, threadRecord, task, channel) : null;
|
|
11303
|
+
const ending = dispatch.trigger.kind === "closing" ? await this.endingOf(dispatch.project, threadRecord, task, channel, place) : null;
|
|
10773
11304
|
const society = await this.board.society();
|
|
10774
11305
|
const crons = [];
|
|
10775
11306
|
for (const id of dispatch.crons ?? []) {
|
|
@@ -10818,6 +11349,7 @@ var TurnHost = class {
|
|
|
10818
11349
|
scope: dispatch.project,
|
|
10819
11350
|
...thread === void 0 ? {} : { thread: thread.id },
|
|
10820
11351
|
...channel === void 0 ? {} : { channel },
|
|
11352
|
+
...place === void 0 ? {} : { place },
|
|
10821
11353
|
session: recorded ?? null,
|
|
10822
11354
|
costSoFarUsd,
|
|
10823
11355
|
instructions,
|
|
@@ -10844,6 +11376,7 @@ var TurnHost = class {
|
|
|
10844
11376
|
stopRequestedBy: null,
|
|
10845
11377
|
held: held.map((each) => each.id),
|
|
10846
11378
|
costSoFarUsd,
|
|
11379
|
+
resumeRetries,
|
|
10847
11380
|
configuredModel: agent.model ?? null,
|
|
10848
11381
|
transcript: [],
|
|
10849
11382
|
coldToken: residentKey === null ? token : null,
|
|
@@ -10857,6 +11390,7 @@ var TurnHost = class {
|
|
|
10857
11390
|
project: dispatch.project,
|
|
10858
11391
|
thread: thread?.id,
|
|
10859
11392
|
channel,
|
|
11393
|
+
place,
|
|
10860
11394
|
runner: runner.name,
|
|
10861
11395
|
session: recorded,
|
|
10862
11396
|
newSession,
|
|
@@ -10893,8 +11427,7 @@ var TurnHost = class {
|
|
|
10893
11427
|
turnId,
|
|
10894
11428
|
agent: turn.job.agent,
|
|
10895
11429
|
project: turn.job.scope,
|
|
10896
|
-
|
|
10897
|
-
channel: turn.job.channel,
|
|
11430
|
+
...conversationOfJob(turn.job),
|
|
10898
11431
|
messages: sent.messages
|
|
10899
11432
|
}).catch((error) => {
|
|
10900
11433
|
this.log.warn({ turnId, error: String(error) }, "could not record a steer");
|
|
@@ -10902,17 +11435,14 @@ var TurnHost = class {
|
|
|
10902
11435
|
}
|
|
10903
11436
|
}
|
|
10904
11437
|
turn.transcript.push(entry);
|
|
10905
|
-
this.onEvent?.(turn.job.agent, turn.job.scope, entry.event,
|
|
10906
|
-
thread: turn.job.thread,
|
|
10907
|
-
channel: turn.job.channel
|
|
10908
|
-
});
|
|
11438
|
+
this.onEvent?.(turn.job.agent, turn.job.scope, entry.event, conversationOfJob(turn.job));
|
|
10909
11439
|
}
|
|
10910
11440
|
}
|
|
10911
11441
|
/** The turn in flight in a conversation, if any. */
|
|
10912
11442
|
turnIn(agent, scope, conversation = {}) {
|
|
10913
11443
|
for (const turn of this.open.values()) {
|
|
10914
11444
|
const { job } = turn;
|
|
10915
|
-
if (job.agent === agent && job.scope === scope && job.thread === conversation.thread && job.channel === conversation.channel) {
|
|
11445
|
+
if (job.agent === agent && job.scope === scope && job.thread === conversation.thread && job.channel === conversation.channel && job.place === conversation.place) {
|
|
10916
11446
|
return { turnId: job.turnId, runner: turn.runner, cli: job.cli };
|
|
10917
11447
|
}
|
|
10918
11448
|
}
|
|
@@ -10923,8 +11453,7 @@ var TurnHost = class {
|
|
|
10923
11453
|
return [...this.open.values()].map(({ job }) => ({
|
|
10924
11454
|
agent: job.agent,
|
|
10925
11455
|
scope: job.scope,
|
|
10926
|
-
...job
|
|
10927
|
-
...job.channel === void 0 ? {} : { channel: job.channel }
|
|
11456
|
+
...conversationOfJob(job)
|
|
10928
11457
|
}));
|
|
10929
11458
|
}
|
|
10930
11459
|
/** The turns in flight, for the interface: their conversations and where a mention reaches them. */
|
|
@@ -10933,8 +11462,7 @@ var TurnHost = class {
|
|
|
10933
11462
|
turnId: turn.job.turnId,
|
|
10934
11463
|
agent: turn.job.agent,
|
|
10935
11464
|
scope: turn.job.scope,
|
|
10936
|
-
...turn.job
|
|
10937
|
-
...turn.job.channel === void 0 ? {} : { channel: turn.job.channel },
|
|
11465
|
+
...conversationOfJob(turn.job),
|
|
10938
11466
|
cli: turn.job.cli,
|
|
10939
11467
|
...turn.record.trigger.channel === void 0 ? {} : { askedIn: turn.record.trigger.channel },
|
|
10940
11468
|
runner: turn.runner
|
|
@@ -10954,12 +11482,7 @@ var TurnHost = class {
|
|
|
10954
11482
|
return "busy";
|
|
10955
11483
|
}
|
|
10956
11484
|
const { job } = turn;
|
|
10957
|
-
const digest = await this.board.readDigest({
|
|
10958
|
-
...turn.actor,
|
|
10959
|
-
scope: job.scope,
|
|
10960
|
-
...job.thread === void 0 ? {} : { thread: job.thread },
|
|
10961
|
-
...job.channel === void 0 ? {} : { channel: job.channel }
|
|
10962
|
-
}, {
|
|
11485
|
+
const digest = await this.board.readDigest({ ...turn.actor, scope: job.scope, ...conversationOfJob(job) }, {
|
|
10963
11486
|
...turn.delivered === null ? {} : { since_cursor: turn.delivered },
|
|
10964
11487
|
advance: false,
|
|
10965
11488
|
limit: 50
|
|
@@ -11019,6 +11542,8 @@ var TurnHost = class {
|
|
|
11019
11542
|
}
|
|
11020
11543
|
const { job } = turn;
|
|
11021
11544
|
const part = conversationPart(job);
|
|
11545
|
+
const resumeRetries = outcome.resumeFailed === "retry" ? turn.resumeRetries + 1 : void 0;
|
|
11546
|
+
const dropSession = job.session !== null && (outcome.resumeFailed === "lost" || (resumeRetries ?? 0) >= RESUME_RETRIES);
|
|
11022
11547
|
const finished = {
|
|
11023
11548
|
...turn.record,
|
|
11024
11549
|
session: outcome.session,
|
|
@@ -11032,20 +11557,35 @@ var TurnHost = class {
|
|
|
11032
11557
|
toolCalls: turn.transcript.filter((entry) => entry.event.type === "tool_call").length,
|
|
11033
11558
|
model: outcome.model ?? turn.configuredModel,
|
|
11034
11559
|
...outcome.work === null ? {} : { work: outcome.work },
|
|
11035
|
-
...outcome.exitReason === "stopped" ? { stoppedBy: turn.stopRequestedBy ?? USER_NAME } : {}
|
|
11560
|
+
...outcome.exitReason === "stopped" ? { stoppedBy: turn.stopRequestedBy ?? USER_NAME } : {},
|
|
11561
|
+
...outcome.resumeFailed === void 0 ? {} : { resumeFailed: outcome.resumeFailed },
|
|
11562
|
+
...resumeRetries === void 0 ? {} : { resumeRetries },
|
|
11563
|
+
...dropSession ? { sessionDropped: true } : {}
|
|
11036
11564
|
};
|
|
11037
11565
|
const ended = await this.conversationEnded(job);
|
|
11038
11566
|
const closing = turn.record.trigger.kind === "closing";
|
|
11039
11567
|
const decided = closing && outcome.exitReason !== "error" && outcome.exitReason !== "timeout";
|
|
11040
11568
|
const recorded = await this.recorded(turn, async () => {
|
|
11041
|
-
if (
|
|
11569
|
+
if (dropSession && job.session !== null) {
|
|
11570
|
+
await this.board.dropSession(job.agent, job.scope, job.cli, job.session, part);
|
|
11571
|
+
this.log.warn({
|
|
11572
|
+
agent: job.agent,
|
|
11573
|
+
project: job.scope,
|
|
11574
|
+
thread: job.thread,
|
|
11575
|
+
channel: job.channel,
|
|
11576
|
+
place: job.place,
|
|
11577
|
+
session: job.session,
|
|
11578
|
+
resumeFailed: outcome.resumeFailed,
|
|
11579
|
+
resumeRetries
|
|
11580
|
+
}, "dropped a session its CLI could not resume");
|
|
11581
|
+
} else if (outcome.session !== null && outcome.session !== job.session) {
|
|
11042
11582
|
await this.board.writeSession(job.agent, job.scope, job.cli, outcome.session, turn.runner, part);
|
|
11043
11583
|
}
|
|
11044
11584
|
if (outcome.exitReason === "completed" || outcome.exitReason === "blocked" || outcome.exitReason === "stopped") {
|
|
11045
11585
|
await this.board.setDigestCursor(job.agent, job.scope, turn.delivered, part);
|
|
11046
11586
|
await this.renewLeases(turn.actor, turn.held);
|
|
11047
11587
|
if (outcome.status?.needsUserDecision === true) {
|
|
11048
|
-
await this.askUser(turn.actor, job, turn.record.startedAt, outcome.status.summary);
|
|
11588
|
+
await this.askUser(turn.actor, job, turn.record.trigger.channel, turn.record.startedAt, outcome.status.summary);
|
|
11049
11589
|
}
|
|
11050
11590
|
}
|
|
11051
11591
|
await this.board.finishTurn(finished, turn.transcript);
|
|
@@ -11057,7 +11597,7 @@ var TurnHost = class {
|
|
|
11057
11597
|
conversation
|
|
11058
11598
|
});
|
|
11059
11599
|
if (!again) {
|
|
11060
|
-
const where = conversation.kind === "thread" ? `thread ${conversation.id}` : `channel ${conversation.name}`;
|
|
11600
|
+
const where = conversation.kind === "thread" ? `thread ${conversation.id}` : conversation.kind === "channel" ? `channel ${conversation.name}` : `project ${conversation.project}'s channels`;
|
|
11061
11601
|
await this.board.publishSignal({
|
|
11062
11602
|
kind: "stuck_workspace",
|
|
11063
11603
|
key: `stuck_workspace:${job.agent}:${job.scope}:${conversationPart(job) ?? ""}`,
|
|
@@ -11074,12 +11614,18 @@ var TurnHost = class {
|
|
|
11074
11614
|
this.log.warn({ runner: turn.runner, cli: job.cli, error: String(error) }, "could not record a quota");
|
|
11075
11615
|
});
|
|
11076
11616
|
}
|
|
11617
|
+
for (const stuck of outcome.stuck ?? []) {
|
|
11618
|
+
await this.board.postTaskNotice(stuck.branch.slice("task/".length), stuckNotice(stuck)).catch((error) => {
|
|
11619
|
+
this.log.warn({ branch: stuck.branch, agent: stuck.agent, error: String(error) }, "could not tell a task's thread of commits its branch cannot take");
|
|
11620
|
+
});
|
|
11621
|
+
}
|
|
11077
11622
|
if (recorded) {
|
|
11078
11623
|
this.log.info({
|
|
11079
11624
|
agent: job.agent,
|
|
11080
11625
|
project: job.scope,
|
|
11081
11626
|
thread: job.thread,
|
|
11082
11627
|
channel: job.channel,
|
|
11628
|
+
place: job.place,
|
|
11083
11629
|
exitReason: finished.exitReason
|
|
11084
11630
|
}, "turn finished");
|
|
11085
11631
|
await this.takeSkillFiles(finished);
|
|
@@ -11146,6 +11692,7 @@ var TurnHost = class {
|
|
|
11146
11692
|
project: dispatch.project,
|
|
11147
11693
|
...dispatch.thread === void 0 ? {} : { thread: dispatch.thread.id },
|
|
11148
11694
|
...dispatch.thread !== void 0 || dispatch.channel === void 0 ? {} : { channel: dispatch.channel },
|
|
11695
|
+
...dispatch.thread !== void 0 || dispatch.channel !== void 0 || dispatch.project !== SOCIETY_SCOPE || dispatch.place === void 0 ? {} : { place: dispatch.place },
|
|
11149
11696
|
runner,
|
|
11150
11697
|
cli: agent?.cli ?? null,
|
|
11151
11698
|
session: null,
|
|
@@ -11192,24 +11739,26 @@ var TurnHost = class {
|
|
|
11192
11739
|
return token;
|
|
11193
11740
|
}
|
|
11194
11741
|
/**
|
|
11195
|
-
* Where a conversation works: a task's on its branch,
|
|
11742
|
+
* Where a conversation works: a task's on its branch, which its stage's holder has to itself,
|
|
11743
|
+
* any other thread's, a channel's, and a project's channels' seen from the society scope in a
|
|
11196
11744
|
* place of its own, and the home in the citizen's own.
|
|
11197
11745
|
*/
|
|
11198
|
-
async workspaceOf(project, thread, channel) {
|
|
11746
|
+
async workspaceOf(project, thread, channel, place, task) {
|
|
11199
11747
|
const own = thread !== void 0 ? thread.task ? {} : { thread: thread.id } : channel === void 0 ? {} : { channel };
|
|
11200
11748
|
if (project === null) {
|
|
11201
|
-
return { kind: "home", ...own };
|
|
11749
|
+
return { kind: "home", ...own, ...place === void 0 ? {} : { place } };
|
|
11202
11750
|
}
|
|
11203
11751
|
const repo = projectRepo(project);
|
|
11204
11752
|
if (thread?.task === true) {
|
|
11205
|
-
|
|
11753
|
+
const holder = task?.status === "claimed" && task.claimedBy !== void 0 && task.claimedBy !== USER_NAME && (task.leaseExpiresAt === void 0 || new Date(task.leaseExpiresAt).getTime() > this.now().getTime()) ? task.claimedBy : void 0;
|
|
11754
|
+
return { kind: "task", repo, taskId: thread.id, ...holder === void 0 ? {} : { holder } };
|
|
11206
11755
|
}
|
|
11207
|
-
const branches = (await this.board.listTasks(project.slug)).filter((
|
|
11756
|
+
const branches = (await this.board.listTasks(project.slug)).filter((each) => each.status === "open" || each.status === "claimed").map((each) => `task/${each.id}`);
|
|
11208
11757
|
return { kind: "project", repo, branches, ...own };
|
|
11209
11758
|
}
|
|
11210
11759
|
/**
|
|
11211
|
-
* Whether a thread conversation's task or thread has ended,
|
|
11212
|
-
*
|
|
11760
|
+
* Whether a thread conversation's task or thread has ended, a channel conversation's channel was
|
|
11761
|
+
* archived, or a project conversation's project was, which frees its workspace.
|
|
11213
11762
|
*/
|
|
11214
11763
|
async conversationEnded(job) {
|
|
11215
11764
|
const conversation = workspaceConversationOf(job);
|
|
@@ -11223,14 +11772,14 @@ var TurnHost = class {
|
|
|
11223
11772
|
* How a closing turn's conversation ended, and where its citizen asks someone else to take over
|
|
11224
11773
|
* work that is not its own: the thread's channel while it is open, else the place's general.
|
|
11225
11774
|
*/
|
|
11226
|
-
async endingOf(scope, thread, task, channel) {
|
|
11775
|
+
async endingOf(scope, thread, task, channel, project) {
|
|
11227
11776
|
const place = scope === SOCIETY_SCOPE ? null : scope;
|
|
11228
11777
|
if (thread !== void 0) {
|
|
11229
11778
|
const closed = `the thread "${thread.title}" on ${thread.channel} was closed${thread.closedBy === void 0 ? "" : ` by ${thread.closedBy}`}${task === null ? "" : `, its task ${task.status}`}`;
|
|
11230
11779
|
return { how: closed, askIn: await this.replyIn(thread) };
|
|
11231
11780
|
}
|
|
11232
11781
|
if (channel === void 0) {
|
|
11233
|
-
return null;
|
|
11782
|
+
return project === void 0 ? null : { how: `project ${project} was archived`, askIn: channelRef(place, "general") };
|
|
11234
11783
|
}
|
|
11235
11784
|
const ref = channelRef(place, channel);
|
|
11236
11785
|
const archived = (await this.board.listChannels()).find((each) => each.ref === ref)?.archived;
|
|
@@ -11244,13 +11793,15 @@ var TurnHost = class {
|
|
|
11244
11793
|
* the user, which is what puts it before them. One that mentioned the user nowhere asks in its
|
|
11245
11794
|
* own thread when it was in one that is still open, so the answer comes back to the same
|
|
11246
11795
|
* conversation, and otherwise in a thread of its own on its conversation's channel, or its scope's
|
|
11247
|
-
* general for the home, opened as the citizen with its summary
|
|
11248
|
-
*
|
|
11249
|
-
*
|
|
11796
|
+
* general for the home, opened as the citizen with its summary; a project's conversation in the
|
|
11797
|
+
* society scope asks on the channel of the project its wake came from, else the project's general.
|
|
11798
|
+
* One that proposed something the user decides has put its decision before the user already,
|
|
11799
|
+
* where deciding it clears it; a mention would stay until the user posted beside it.
|
|
11250
11800
|
*/
|
|
11251
|
-
async askUser(actor, job, since, summary) {
|
|
11801
|
+
async askUser(actor, job, askedIn, since, summary) {
|
|
11252
11802
|
const threadId = job.thread;
|
|
11253
11803
|
const place = job.scope === SOCIETY_SCOPE ? null : job.scope;
|
|
11804
|
+
const channel = job.place === void 0 ? channelRef(place, job.channel ?? "general") : askedIn !== void 0 && parseChannelRef(askedIn).project === job.place ? askedIn : channelRef(job.place, "general");
|
|
11254
11805
|
try {
|
|
11255
11806
|
if (await this.board.hasMentioned(actor.name, USER_NAME, since)) {
|
|
11256
11807
|
return;
|
|
@@ -11268,7 +11819,7 @@ var TurnHost = class {
|
|
|
11268
11819
|
return;
|
|
11269
11820
|
}
|
|
11270
11821
|
const thread = await this.board.openThread(actor, {
|
|
11271
|
-
channel
|
|
11822
|
+
channel,
|
|
11272
11823
|
title: `${actor.name} asks for a decision: ${summary}`.slice(0, 200)
|
|
11273
11824
|
});
|
|
11274
11825
|
await this.board.postMessage(actor, {
|
|
@@ -11319,6 +11870,12 @@ var DEFAULT_GRACE_MS = 2 * 6e4;
|
|
|
11319
11870
|
var DEFAULT_REQUEST_TIMEOUT_MS = 10 * 6e4;
|
|
11320
11871
|
var CONTROL_TIMEOUT_MS = 15e3;
|
|
11321
11872
|
var FILE_TIMEOUT_MS = 6e4;
|
|
11873
|
+
var IMAGE_REFUSALS = {
|
|
11874
|
+
missing: "NOT_FOUND",
|
|
11875
|
+
outside: "FORBIDDEN",
|
|
11876
|
+
not_image: "VALIDATION",
|
|
11877
|
+
too_big: "VALIDATION"
|
|
11878
|
+
};
|
|
11322
11879
|
var SWEEP_TIMEOUT_MS = 2 * 6e4;
|
|
11323
11880
|
var DEFAULT_ACK_TIMEOUT_MS = 3e4;
|
|
11324
11881
|
var RunnerHub = class {
|
|
@@ -11578,7 +12135,8 @@ var RunnerHub = class {
|
|
|
11578
12135
|
async steer(conversation) {
|
|
11579
12136
|
const turn = this.host.turnIn(conversation.agent, conversation.project, {
|
|
11580
12137
|
thread: conversation.thread,
|
|
11581
|
-
channel: conversation.channel
|
|
12138
|
+
channel: conversation.channel,
|
|
12139
|
+
place: conversation.place
|
|
11582
12140
|
});
|
|
11583
12141
|
const seat = turn === null ? void 0 : this.seats.get(turn.runner);
|
|
11584
12142
|
if (turn === null || seat === void 0 || seat.send === null) {
|
|
@@ -11795,11 +12353,11 @@ var RunnerHub = class {
|
|
|
11795
12353
|
* NOT_FOUND when the project has no runner yet or the branch or the path does not exist, and
|
|
11796
12354
|
* `RunnerAwayError` while its runner is away.
|
|
11797
12355
|
*/
|
|
11798
|
-
async readBranch(project, branch,
|
|
12356
|
+
async readBranch(project, branch, path11) {
|
|
11799
12357
|
const { seat, repo } = await this.projectSeat(project, "branchReads", "read files");
|
|
11800
|
-
const file = BranchFileSchema.nullable().parse(await this.request(seat, (request) => ({ type: "file", request, read: { repo, branch, path:
|
|
12358
|
+
const file = BranchFileSchema.nullable().parse(await this.request(seat, (request) => ({ type: "file", request, read: { repo, branch, path: path11 } }), FILE_TIMEOUT_MS));
|
|
11801
12359
|
if (file === null) {
|
|
11802
|
-
throw new BoardError("NOT_FOUND", `${branch} has no ${
|
|
12360
|
+
throw new BoardError("NOT_FOUND", `${branch} has no ${path11 === "" ? "commit" : path11}`);
|
|
11803
12361
|
}
|
|
11804
12362
|
return file;
|
|
11805
12363
|
}
|
|
@@ -11819,14 +12377,41 @@ var RunnerHub = class {
|
|
|
11819
12377
|
* What a branch of a project's repository did to one file since it left the default branch, read
|
|
11820
12378
|
* on the runner the project lives on, with the same refusals as `branchChanges`.
|
|
11821
12379
|
*/
|
|
11822
|
-
async branchDiff(project, branch,
|
|
12380
|
+
async branchDiff(project, branch, path11) {
|
|
11823
12381
|
const { seat, repo } = await this.projectSeat(project, "branchChanges", "list changes");
|
|
11824
|
-
const diff = BranchFileDiffSchema.nullable().parse(await this.request(seat, (request) => ({ type: "diff", request, read: { repo, branch, path:
|
|
12382
|
+
const diff = BranchFileDiffSchema.nullable().parse(await this.request(seat, (request) => ({ type: "diff", request, read: { repo, branch, path: path11 } }), FILE_TIMEOUT_MS));
|
|
11825
12383
|
if (diff === null) {
|
|
11826
|
-
throw new BoardError("NOT_FOUND", `${branch} did not change ${
|
|
12384
|
+
throw new BoardError("NOT_FOUND", `${branch} did not change ${path11}`);
|
|
11827
12385
|
}
|
|
11828
12386
|
return diff;
|
|
11829
12387
|
}
|
|
12388
|
+
/**
|
|
12389
|
+
* The image file a citizen's turn names for `upload_image`, read on the runner that turn runs on,
|
|
12390
|
+
* which refuses a file outside the turn's worktree, its citizen's home, and the system temp
|
|
12391
|
+
* folder, and anything but a PNG, JPEG, GIF, or WebP within the size limit; each refusal comes
|
|
12392
|
+
* back as a `BoardError` in the runner's words.
|
|
12393
|
+
*/
|
|
12394
|
+
async readTurnImage(actor, file) {
|
|
12395
|
+
const turn = this.host.runningTurns().find((each) => each.agent === actor.name && each.scope === actor.scope && each.thread === actor.thread && each.channel === actor.channel && each.place === actor.place);
|
|
12396
|
+
if (turn === void 0) {
|
|
12397
|
+
throw new BoardError("INVALID_STATE", `${actor.name} has no turn running in this conversation to read ${file} from`);
|
|
12398
|
+
}
|
|
12399
|
+
const seat = this.seats.get(turn.runner);
|
|
12400
|
+
if (seat === void 0 || seat.send === null) {
|
|
12401
|
+
throw new RunnerAwayError(`runner ${turn.runner}, where the turn runs, is not connected`);
|
|
12402
|
+
}
|
|
12403
|
+
if (!seat.hello.imageReads) {
|
|
12404
|
+
throw new BoardError("INVALID_STATE", `runner ${seat.name}, where the turn runs, cannot read images yet; upgrade it`);
|
|
12405
|
+
}
|
|
12406
|
+
const image = TurnImageSchema.nullable().parse(await this.request(seat, (request) => ({ type: "image", request, read: { turnId: turn.turnId, path: file } }), FILE_TIMEOUT_MS));
|
|
12407
|
+
if (image === null) {
|
|
12408
|
+
throw new BoardError("INVALID_STATE", `the turn is no longer running on runner ${seat.name}`);
|
|
12409
|
+
}
|
|
12410
|
+
if (image.kind === "refused") {
|
|
12411
|
+
throw new BoardError(IMAGE_REFUSALS[image.why], image.detail);
|
|
12412
|
+
}
|
|
12413
|
+
return Buffer.from(image.content, "base64");
|
|
12414
|
+
}
|
|
11830
12415
|
/** The models a CLI offers, asked of a connected runner that has it; none when no runner has it. */
|
|
11831
12416
|
/**
|
|
11832
12417
|
* The connected runner a model list for `cli` comes from: the first of `prefer` that has the CLI,
|
|
@@ -12098,20 +12683,20 @@ async function handleMcpRequest(board2, actor, request, version) {
|
|
|
12098
12683
|
import { Hono as Hono3 } from "hono";
|
|
12099
12684
|
import { compress } from "hono/compress";
|
|
12100
12685
|
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
12101
|
-
import { z as
|
|
12686
|
+
import { z as z19, ZodError } from "zod";
|
|
12102
12687
|
|
|
12103
12688
|
// apps/server/dist/github-auth.js
|
|
12104
12689
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
12105
12690
|
import { Hono } from "hono";
|
|
12106
|
-
import { z as
|
|
12691
|
+
import { z as z18 } from "zod";
|
|
12107
12692
|
var SIGN_IN_TTL_MS = 30 * 24 * 36e5;
|
|
12108
12693
|
var STATE_TTL_MS = 10 * 6e4;
|
|
12109
12694
|
var MAX_STATES = 1e3;
|
|
12110
|
-
var TokenAnswerSchema =
|
|
12111
|
-
var GithubUserSchema =
|
|
12112
|
-
login:
|
|
12113
|
-
id:
|
|
12114
|
-
avatar_url:
|
|
12695
|
+
var TokenAnswerSchema = z18.object({ access_token: z18.string().min(1) });
|
|
12696
|
+
var GithubUserSchema = z18.object({
|
|
12697
|
+
login: z18.string().min(1),
|
|
12698
|
+
id: z18.number().int(),
|
|
12699
|
+
avatar_url: z18.string().optional()
|
|
12115
12700
|
});
|
|
12116
12701
|
function githubAvatar(url) {
|
|
12117
12702
|
try {
|
|
@@ -12307,7 +12892,7 @@ function rateLimit(options) {
|
|
|
12307
12892
|
}
|
|
12308
12893
|
|
|
12309
12894
|
// apps/server/dist/runner-routes.js
|
|
12310
|
-
import
|
|
12895
|
+
import path8 from "node:path";
|
|
12311
12896
|
import { Hono as Hono2 } from "hono";
|
|
12312
12897
|
import { streamSSE } from "hono/streaming";
|
|
12313
12898
|
|
|
@@ -12500,7 +13085,7 @@ function runnerRoutes(board2, hub, enrollments, enrollLimit) {
|
|
|
12500
13085
|
const marker = `/homes/${agent}/git`;
|
|
12501
13086
|
const at = c.req.path.indexOf(marker);
|
|
12502
13087
|
return homeGit.serve({
|
|
12503
|
-
root:
|
|
13088
|
+
root: path8.dirname(repository),
|
|
12504
13089
|
agent,
|
|
12505
13090
|
rest: at === -1 ? "" : c.req.path.slice(at + marker.length),
|
|
12506
13091
|
request: c.req.raw,
|
|
@@ -12513,7 +13098,7 @@ function runnerRoutes(board2, hub, enrollments, enrollLimit) {
|
|
|
12513
13098
|
|
|
12514
13099
|
// apps/server/dist/static.js
|
|
12515
13100
|
import { readFile as readFile5, stat as stat4 } from "node:fs/promises";
|
|
12516
|
-
import
|
|
13101
|
+
import path9 from "node:path";
|
|
12517
13102
|
var CONTENT_TYPES = {
|
|
12518
13103
|
".html": "text/html; charset=utf-8",
|
|
12519
13104
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -12541,11 +13126,11 @@ function within2(root, pathname) {
|
|
|
12541
13126
|
} catch {
|
|
12542
13127
|
return null;
|
|
12543
13128
|
}
|
|
12544
|
-
const candidate =
|
|
12545
|
-
return candidate === root || candidate.startsWith(root +
|
|
13129
|
+
const candidate = path9.resolve(root, `.${decoded}`);
|
|
13130
|
+
return candidate === root || candidate.startsWith(root + path9.sep) ? candidate : null;
|
|
12546
13131
|
}
|
|
12547
13132
|
function spaHandler(staticDir) {
|
|
12548
|
-
const root =
|
|
13133
|
+
const root = path9.resolve(staticDir);
|
|
12549
13134
|
return async (c) => {
|
|
12550
13135
|
const { pathname } = new URL(c.req.url);
|
|
12551
13136
|
if (pathname.startsWith("/api/")) {
|
|
@@ -12556,14 +13141,14 @@ function spaHandler(staticDir) {
|
|
|
12556
13141
|
if (file !== null && await isFile(file)) {
|
|
12557
13142
|
return c.body(await readFile5(file), 200, {
|
|
12558
13143
|
...headers,
|
|
12559
|
-
"content-type": CONTENT_TYPES[
|
|
13144
|
+
"content-type": CONTENT_TYPES[path9.extname(file)] ?? "application/octet-stream",
|
|
12560
13145
|
"cache-control": pathname.startsWith("/assets/") ? "public, max-age=31536000, immutable" : "no-cache"
|
|
12561
13146
|
});
|
|
12562
13147
|
}
|
|
12563
13148
|
if (pathname.startsWith("/assets/")) {
|
|
12564
13149
|
return c.text("not found", 404, headers);
|
|
12565
13150
|
}
|
|
12566
|
-
const index =
|
|
13151
|
+
const index = path9.join(root, "index.html");
|
|
12567
13152
|
if (!await isFile(index)) {
|
|
12568
13153
|
return c.text("The interface is not built: run pnpm build:web.", 404);
|
|
12569
13154
|
}
|
|
@@ -12586,19 +13171,19 @@ var ERROR_STATUS = {
|
|
|
12586
13171
|
INVALID_STATE: 409
|
|
12587
13172
|
};
|
|
12588
13173
|
var EVENT_PAGE = 100;
|
|
12589
|
-
var RetireBodySchema =
|
|
12590
|
-
var RunnerBodySchema =
|
|
12591
|
-
var EnrollmentApprovalSchema =
|
|
12592
|
-
var HomeRunnerBodySchema =
|
|
12593
|
-
var ModelBodySchema =
|
|
12594
|
-
var EffortBodySchema =
|
|
12595
|
-
var HistoryLimitSchema =
|
|
12596
|
-
var MessageIdsSchema =
|
|
12597
|
-
var TimezoneBodySchema =
|
|
12598
|
-
var ChannelBodySchema =
|
|
13174
|
+
var RetireBodySchema = z19.object({ reason: z19.string().min(1) });
|
|
13175
|
+
var RunnerBodySchema = z19.object({ name: NameSchema });
|
|
13176
|
+
var EnrollmentApprovalSchema = z19.object({ name: NameSchema });
|
|
13177
|
+
var HomeRunnerBodySchema = z19.object({ runner: NameSchema.nullable() });
|
|
13178
|
+
var ModelBodySchema = z19.object({ model: ModelNameSchema.nullable() });
|
|
13179
|
+
var EffortBodySchema = z19.object({ effort: EffortSchema.nullable() });
|
|
13180
|
+
var HistoryLimitSchema = z19.coerce.number().int().min(1).max(500);
|
|
13181
|
+
var MessageIdsSchema = z19.string().transform((ids) => ids.split(",").filter((id) => id !== "")).pipe(z19.array(UlidSchema).min(1).max(MESSAGE_LOOKUP_LIMIT));
|
|
13182
|
+
var TimezoneBodySchema = z19.object({ timezone: TimeZoneSchema });
|
|
13183
|
+
var ChannelBodySchema = z19.object({
|
|
12599
13184
|
project: NameSchema.nullable().default(null),
|
|
12600
13185
|
name: NameSchema,
|
|
12601
|
-
purpose:
|
|
13186
|
+
purpose: z19.string().min(1)
|
|
12602
13187
|
});
|
|
12603
13188
|
function bearer2(c) {
|
|
12604
13189
|
const header = c.req.header("authorization") ?? "";
|
|
@@ -12729,6 +13314,7 @@ function createApp(deps) {
|
|
|
12729
13314
|
});
|
|
12730
13315
|
api.get("/society/knowledge", async (c) => c.json(await board2.listKnowledge(null)));
|
|
12731
13316
|
api.get("/skills", async (c) => c.json(await board2.listSocietySkills()));
|
|
13317
|
+
api.get("/skills/:name/files", async (c) => c.json(await board2.readSocietySkillFiles(NameSchema.parse(c.req.param("name")))));
|
|
12732
13318
|
api.post("/agents/:name/retire", async (c) => {
|
|
12733
13319
|
const body = RetireBodySchema.parse(await c.req.json());
|
|
12734
13320
|
const { tokenHash: _hash, ...agent } = await board2.retireAgent(c.get("actor"), {
|
|
@@ -12770,7 +13356,7 @@ function createApp(deps) {
|
|
|
12770
13356
|
});
|
|
12771
13357
|
api.put("/roles/:name", async (c) => {
|
|
12772
13358
|
const charter = RoleCharterSchema.parse({
|
|
12773
|
-
...
|
|
13359
|
+
...z19.record(z19.string(), z19.unknown()).parse(await c.req.json()),
|
|
12774
13360
|
name: c.req.param("name")
|
|
12775
13361
|
});
|
|
12776
13362
|
return c.json(await board2.setRoleCharter(c.get("actor"), charter));
|
|
@@ -12809,6 +13395,15 @@ function createApp(deps) {
|
|
|
12809
13395
|
return c.json(await deps.runners.branchDiff(task.project, `task/${task.id}`, BranchPathSchema.parse(c.req.param("path"))));
|
|
12810
13396
|
});
|
|
12811
13397
|
api.get("/tasks/:id/files/:path{.+}", (c) => taskFile(c, c.req.param("path")));
|
|
13398
|
+
api.get("/images/:id", async (c) => {
|
|
13399
|
+
const { type, bytes } = await board2.readImage(UlidSchema.parse(c.req.param("id")));
|
|
13400
|
+
return c.body(new Uint8Array(bytes), 200, {
|
|
13401
|
+
"content-type": IMAGE_MEDIA_TYPES[type],
|
|
13402
|
+
"cache-control": "private, max-age=31536000, immutable",
|
|
13403
|
+
"x-content-type-options": "nosniff",
|
|
13404
|
+
"content-security-policy": "default-src 'none'; sandbox"
|
|
13405
|
+
});
|
|
13406
|
+
});
|
|
12812
13407
|
api.get("/threads", async (c) => c.json(await board2.listThreads()));
|
|
12813
13408
|
api.get("/requests", async (c) => c.json(await board2.listRequests()));
|
|
12814
13409
|
api.get("/threads/:id", async (c) => {
|
|
@@ -13163,7 +13758,10 @@ var TurnHub = class {
|
|
|
13163
13758
|
get lastSeq() {
|
|
13164
13759
|
return this.seq;
|
|
13165
13760
|
}
|
|
13166
|
-
/**
|
|
13761
|
+
/**
|
|
13762
|
+
* Records one event of a turn, in its scope and its conversation there: a thread's, a channel's,
|
|
13763
|
+
* a project's channels' in the society scope, or home.
|
|
13764
|
+
*/
|
|
13167
13765
|
push(agent, project, event, conversation = {}) {
|
|
13168
13766
|
this.seq += 1;
|
|
13169
13767
|
const item = {
|
|
@@ -13173,6 +13771,7 @@ var TurnHub = class {
|
|
|
13173
13771
|
project,
|
|
13174
13772
|
...conversation.thread === void 0 ? {} : { thread: conversation.thread },
|
|
13175
13773
|
...conversation.channel === void 0 ? {} : { channel: conversation.channel },
|
|
13774
|
+
...conversation.place === void 0 ? {} : { place: conversation.place },
|
|
13176
13775
|
event
|
|
13177
13776
|
};
|
|
13178
13777
|
this.buffer.push(item);
|
|
@@ -13231,6 +13830,7 @@ var host = new TurnHost({
|
|
|
13231
13830
|
});
|
|
13232
13831
|
var runners = new RunnerHub({ board, host, version: VERSION, log });
|
|
13233
13832
|
board.watchTurns(() => host.inFlight());
|
|
13833
|
+
board.readImagesWith((actor, file) => runners.readTurnImage(actor, file));
|
|
13234
13834
|
var concurrency = config.concurrency ?? Number.POSITIVE_INFINITY;
|
|
13235
13835
|
var timings = parseTimings(JSON.parse(process.env["STELLARIS_TIMINGS"] ?? "{}"));
|
|
13236
13836
|
var scheduler = new Scheduler({ board, runner: runners, log, concurrency, timings });
|
|
@@ -13248,8 +13848,8 @@ var view = {
|
|
|
13248
13848
|
return scheduler.activeSignals;
|
|
13249
13849
|
}
|
|
13250
13850
|
};
|
|
13251
|
-
var webDir = process.env["STELLARIS_WEB_DIR"] ??
|
|
13252
|
-
var webBuilt = await access2(
|
|
13851
|
+
var webDir = process.env["STELLARIS_WEB_DIR"] ?? path10.resolve(import.meta.dirname, "../../web/dist");
|
|
13852
|
+
var webBuilt = await access2(path10.join(webDir, "index.html")).then(() => true, () => false);
|
|
13253
13853
|
var app = createApp({
|
|
13254
13854
|
board,
|
|
13255
13855
|
version: VERSION,
|