@kubitnodes/stellaris 0.3.24 → 0.3.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +99 -553
- package/dist/server/index.js +303 -579
- package/package.json +4 -4
- package/web/dist/assets/BackLink-UTiLF1J4.js +1 -0
- package/web/dist/assets/Button-Cp4cNRUX.js +1 -0
- package/web/dist/assets/ChannelForms-DD2B0tey.js +1 -0
- package/web/dist/assets/{ChannelView-BIXouQMX.js → ChannelView-CY3oG9hZ.js} +2 -2
- package/web/dist/assets/CitizenView-BNK5ro-6.js +2 -0
- package/web/dist/assets/{CitizensView-CYFS3f9U.js → CitizensView-WPQ6Jhl3.js} +1 -1
- package/web/dist/assets/{CliIcon-DVX5iLhG.js → CliIcon-C7bwChMP.js} +1 -1
- package/web/dist/assets/CronsView-_pdgwX1I.js +1 -0
- package/web/dist/assets/FileTree-jyDUSu7g.js +1 -0
- package/web/dist/assets/FileView-DcQvlc8V.js +4 -0
- package/web/dist/assets/KnowledgeView-DDl0HQPG.js +1 -0
- package/web/dist/assets/{Markdown--whKJBYV.js → Markdown-DT3V-EVe.js} +7 -7
- package/web/dist/assets/{MessageItem-BvDOeJci.js → MessageItem-agtEugfG.js} +1 -1
- package/web/dist/assets/MetricsView-DiXyYeWP.js +1 -0
- package/web/dist/assets/NeedsYouView-CKmuYan_.js +1 -0
- package/web/dist/assets/Overview-CKcTqsMM.js +1 -0
- package/web/dist/assets/{Pane-DTmByqgA.js → Pane-BaM0TdE6.js} +1 -1
- package/web/dist/assets/{Picker-DsHm4f7s.js → Picker-C2ig-0dT.js} +1 -1
- package/web/dist/assets/ProjectView-T2OHTh5j.js +1 -0
- package/web/dist/assets/ProposalView-B5yjTAcn.js +1 -0
- package/web/dist/assets/ProposalsView-p4KjwV9p.js +1 -0
- package/web/dist/assets/{Quota-D8WDWp5H.js → Quota-CfFGIql9.js} +1 -1
- package/web/dist/assets/RunnersView-DdvwoOur.js +1 -0
- package/web/dist/assets/{SkillFileList-DhdEcFQA.js → SkillFileList-CgrCnuaC.js} +1 -1
- package/web/dist/assets/SkillView-Di5Z9h2L.js +1 -0
- package/web/dist/assets/SocietyView-BDiVxUC5.js +1 -0
- package/web/dist/assets/StickyList-BaV_KZ8x.js +1 -0
- package/web/dist/assets/{SubjectThread-BEXt-tub.js → SubjectThread-DD_ZCCnr.js} +1 -1
- package/web/dist/assets/TaskView-CKcs-MOC.js +3 -0
- package/web/dist/assets/TasksView-oZMjncOU.js +1 -0
- package/web/dist/assets/{ThreadForms-DzoRDeBh.js → ThreadForms-CG5Y926b.js} +1 -1
- package/web/dist/assets/{ThreadItem-CDVppcOZ.js → ThreadItem-D-n2arFA.js} +1 -1
- package/web/dist/assets/{ThreadView-B4F10_j1.js → ThreadView-CT9vd0P4.js} +1 -1
- package/web/dist/assets/{citizen-BPIJzKdn.js → citizen-BiHfph2A.js} +1 -1
- package/web/dist/assets/{compose-BUhbVoI-.js → compose-DY1WL8zB.js} +1 -1
- package/web/dist/assets/format-CENiwkJC.js +55 -0
- package/web/dist/assets/governance-C6ez5MLh.js +1 -0
- package/web/dist/assets/index-CPXALUV6.js +23 -0
- package/web/dist/assets/{index-Bj8MlyAn.css → index-DIa8Dlav.css} +1 -1
- package/web/dist/assets/mentions-BEL5U6ra.js +3 -0
- package/web/dist/assets/{tasks-CTaxXtGL.js → tasks-CAI8Eci7.js} +1 -1
- package/web/dist/assets/useMatch-CINzyDWa.js +1 -0
- package/web/dist/index.html +9 -9
- package/web/dist/assets/BackLink-DKMw_rHu.js +0 -1
- package/web/dist/assets/Button-BYJGqZ3Q.js +0 -1
- package/web/dist/assets/ChannelForms-CkYupEL2.js +0 -1
- package/web/dist/assets/CitizenView-C3AMFixP.js +0 -2
- package/web/dist/assets/CronsView-BGCcO7bH.js +0 -1
- package/web/dist/assets/KnowledgeView-DLLYjd1G.js +0 -1
- package/web/dist/assets/MetricsView-B-KUNOZm.js +0 -1
- package/web/dist/assets/NeedsYouView-Dee_bOKj.js +0 -1
- package/web/dist/assets/Overview-DFiHDFnA.js +0 -1
- package/web/dist/assets/ProjectView-DsRXW-qk.js +0 -1
- package/web/dist/assets/ProposalView-B-4P1S-s.js +0 -1
- package/web/dist/assets/ProposalsView-uEuj-ooV.js +0 -1
- package/web/dist/assets/RunnersView-BB3q65rG.js +0 -1
- package/web/dist/assets/SkillView-vAv7zvtJ.js +0 -1
- package/web/dist/assets/SocietyView-Dv4phSkz.js +0 -1
- package/web/dist/assets/StickyList-C4fC_KJv.js +0 -1
- package/web/dist/assets/TaskFileView-B9sex0tS.js +0 -4
- package/web/dist/assets/TaskView-BCT388kb.js +0 -3
- package/web/dist/assets/TasksView-CuPay34-.js +0 -1
- package/web/dist/assets/format-CvfmDNoY.js +0 -55
- package/web/dist/assets/governance-Bba0OHvJ.js +0 -1
- package/web/dist/assets/index-DD3J11PT.js +0 -23
- package/web/dist/assets/mentions-D838wlfQ.js +0 -3
- package/web/dist/assets/useMatch-B8j0hm1q.js +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -715,7 +715,6 @@ var ReallocationProposalSchema = z5.object({
|
|
|
715
715
|
changes: RoleChangesSchema,
|
|
716
716
|
description: z5.string().min(1)
|
|
717
717
|
});
|
|
718
|
-
var LegacyReallocationSchema = z5.object({ description: z5.string().min(1) });
|
|
719
718
|
function applyRoleChanges(role2, changes) {
|
|
720
719
|
return {
|
|
721
720
|
...role2,
|
|
@@ -760,8 +759,7 @@ var ArchiveProposalSchema = z5.object({
|
|
|
760
759
|
});
|
|
761
760
|
var SkillProposalSchema = z5.object({
|
|
762
761
|
name: NameSchema,
|
|
763
|
-
summary: z5.string().min(1).max(200)
|
|
764
|
-
body: z5.string().min(1).optional()
|
|
762
|
+
summary: z5.string().min(1).max(200)
|
|
765
763
|
});
|
|
766
764
|
var ProposalCharterSchemas = {
|
|
767
765
|
role: RoleCharterSchema,
|
|
@@ -838,14 +836,10 @@ function describeCharter(kind, charter) {
|
|
|
838
836
|
}
|
|
839
837
|
case "reallocation": {
|
|
840
838
|
const parsed = ReallocationProposalSchema.safeParse(charter);
|
|
841
|
-
if (parsed.success)
|
|
842
|
-
const { role: role2, changes, description } = parsed.data;
|
|
843
|
-
return `reallocation of role ${role2} (${describeRoleChanges(changes)}): ${description}`;
|
|
844
|
-
}
|
|
845
|
-
const legacy = LegacyReallocationSchema.safeParse(charter);
|
|
846
|
-
if (!legacy.success)
|
|
839
|
+
if (!parsed.success)
|
|
847
840
|
break;
|
|
848
|
-
|
|
841
|
+
const { role: role2, changes, description } = parsed.data;
|
|
842
|
+
return `reallocation of role ${role2} (${describeRoleChanges(changes)}): ${description}`;
|
|
849
843
|
}
|
|
850
844
|
case "skill": {
|
|
851
845
|
const parsed = SkillProposalSchema.safeParse(charter);
|
|
@@ -1050,12 +1044,10 @@ var VerbInputs = {
|
|
|
1050
1044
|
channel: ChannelRefSchema.optional(),
|
|
1051
1045
|
limit: z7.number().int().positive().max(100).default(20)
|
|
1052
1046
|
}),
|
|
1053
|
-
/** On a
|
|
1047
|
+
/** On a channel with a title; a task's and a proposal's open with them. */
|
|
1054
1048
|
open_thread: z7.object({
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
channel: ChannelRefSchema.optional(),
|
|
1058
|
-
title: z7.string().min(1).max(200).optional()
|
|
1049
|
+
channel: ChannelRefSchema,
|
|
1050
|
+
title: z7.string().min(1).max(200)
|
|
1059
1051
|
}),
|
|
1060
1052
|
close_thread: z7.object({ thread_id: UlidSchema, summary: z7.string().min(1) }),
|
|
1061
1053
|
create_task: z7.object({
|
|
@@ -1519,6 +1511,12 @@ var TurnOutcomeSchema = z9.object({
|
|
|
1519
1511
|
* runner from before never sends it.
|
|
1520
1512
|
*/
|
|
1521
1513
|
resumeFailed: ResumeFailureSchema.optional(),
|
|
1514
|
+
/**
|
|
1515
|
+
* Set when the model service turned the turn away for now, as when it was overloaded or failed
|
|
1516
|
+
* on its side, which the scheduler answers by sending the same wake again within minutes. A
|
|
1517
|
+
* runner from before never sends it.
|
|
1518
|
+
*/
|
|
1519
|
+
transient: z9.boolean().optional(),
|
|
1522
1520
|
/** The model the CLI reported, when it reported one. */
|
|
1523
1521
|
model: z9.string().nullable(),
|
|
1524
1522
|
work: TurnWorkSchema.nullable(),
|
|
@@ -1819,6 +1817,8 @@ var TurnRecordSchema = z10.object({
|
|
|
1819
1817
|
stoppedBy: NameSchema.optional(),
|
|
1820
1818
|
/** Why the CLI could not resume the turn's session, for a turn that failed so. */
|
|
1821
1819
|
resumeFailed: ResumeFailureSchema.optional(),
|
|
1820
|
+
/** Whether the model service turned the turn away for now: overloaded, or failing on its side. */
|
|
1821
|
+
transient: z10.boolean().optional(),
|
|
1822
1822
|
/** How many turns in a row, this one included, could not resume the session for a `retry`. */
|
|
1823
1823
|
resumeRetries: z10.number().int().positive().optional(),
|
|
1824
1824
|
/** Whether the server dropped the conversation's session after this turn, which the next starts anew. */
|
|
@@ -1893,9 +1893,19 @@ var OPS_SIGNAL_KINDS = [
|
|
|
1893
1893
|
"runner",
|
|
1894
1894
|
"home_conflict",
|
|
1895
1895
|
"stuck_workspace",
|
|
1896
|
-
"cron_failing"
|
|
1896
|
+
"cron_failing",
|
|
1897
|
+
"turn_refused"
|
|
1897
1898
|
];
|
|
1898
1899
|
var OpsSignalKindSchema = z11.enum(OPS_SIGNAL_KINDS);
|
|
1900
|
+
var WAKING_SIGNAL_KINDS = [
|
|
1901
|
+
"backlog",
|
|
1902
|
+
"role_gap",
|
|
1903
|
+
"churn",
|
|
1904
|
+
"stale_thread",
|
|
1905
|
+
"idle_member",
|
|
1906
|
+
"blocked_capability",
|
|
1907
|
+
"scaled"
|
|
1908
|
+
];
|
|
1899
1909
|
var OpsSignalSchema = z11.object({
|
|
1900
1910
|
kind: OpsSignalKindSchema,
|
|
1901
1911
|
/** Identifies the condition, for example `backlog:demo:engineer`, so a persisting one is not logged again every pass. */
|
|
@@ -1908,8 +1918,15 @@ var OpsSignalSchema = z11.object({
|
|
|
1908
1918
|
project: NameSchema.optional(),
|
|
1909
1919
|
agent: NameSchema.optional(),
|
|
1910
1920
|
role: NameSchema.optional(),
|
|
1911
|
-
taskId: UlidSchema.optional()
|
|
1921
|
+
taskId: UlidSchema.optional(),
|
|
1922
|
+
/**
|
|
1923
|
+
* Whether this log of the condition wakes the roles charted for `ops_event`, as its publisher
|
|
1924
|
+
* decided: the operations pass wakes them when a condition is new, and again only at the steps
|
|
1925
|
+
* it names, not at every repeat. Absent, the kind decides.
|
|
1926
|
+
*/
|
|
1927
|
+
wakes: z11.boolean().optional()
|
|
1912
1928
|
});
|
|
1929
|
+
var WAKING = new Set(WAKING_SIGNAL_KINDS);
|
|
1913
1930
|
|
|
1914
1931
|
// packages/shared/dist/metrics.js
|
|
1915
1932
|
import { z as z12 } from "zod";
|
|
@@ -2067,7 +2084,7 @@ var RunnerConfigSchema = z13.object({
|
|
|
2067
2084
|
});
|
|
2068
2085
|
|
|
2069
2086
|
// packages/shared/dist/version.js
|
|
2070
|
-
var STELLARIS_VERSION = "0.3.
|
|
2087
|
+
var STELLARIS_VERSION = "0.3.25";
|
|
2071
2088
|
|
|
2072
2089
|
// packages/shared/dist/mentions.js
|
|
2073
2090
|
import { parse, postprocess, preprocess } from "micromark";
|
|
@@ -3395,7 +3412,7 @@ var BoardPaths = class {
|
|
|
3395
3412
|
|
|
3396
3413
|
// packages/board-core/dist/skill-folder.js
|
|
3397
3414
|
import { constants } from "node:fs";
|
|
3398
|
-
import { lstat, open as open2, readdir as readdir3, realpath } from "node:fs/promises";
|
|
3415
|
+
import { lstat, open as open2, readdir as readdir3, readlink, realpath } from "node:fs/promises";
|
|
3399
3416
|
import path6 from "node:path";
|
|
3400
3417
|
var SKILL_TEXT_LIMIT_CHARS = 1e5;
|
|
3401
3418
|
var SKILL_TEXT_LIMIT_BYTES = SKILL_TEXT_LIMIT_CHARS * 4;
|
|
@@ -3410,6 +3427,13 @@ function inside(root, file) {
|
|
|
3410
3427
|
const relative = path6.relative(root, file);
|
|
3411
3428
|
return relative !== ".." && !relative.startsWith(`..${path6.sep}`) && !path6.isAbsolute(relative);
|
|
3412
3429
|
}
|
|
3430
|
+
async function handleInside(root, handle) {
|
|
3431
|
+
if (process.platform !== "linux") {
|
|
3432
|
+
return true;
|
|
3433
|
+
}
|
|
3434
|
+
const file = await readlink(`/proc/self/fd/${handle.fd}`).catch(() => null);
|
|
3435
|
+
return file !== null && inside(root, file);
|
|
3436
|
+
}
|
|
3413
3437
|
async function walk(root, prefix, found) {
|
|
3414
3438
|
const entries = await readdir3(path6.join(root, ...prefix === "" ? [] : prefix.split("/")), {
|
|
3415
3439
|
withFileTypes: true
|
|
@@ -3444,6 +3468,9 @@ async function readEntry(root, relative) {
|
|
|
3444
3468
|
return null;
|
|
3445
3469
|
}
|
|
3446
3470
|
try {
|
|
3471
|
+
if (!await handleInside(root, handle)) {
|
|
3472
|
+
return null;
|
|
3473
|
+
}
|
|
3447
3474
|
const stats = await handle.stat();
|
|
3448
3475
|
if (!stats.isFile()) {
|
|
3449
3476
|
return null;
|
|
@@ -3514,17 +3541,16 @@ var SignInsSchema = z16.object({
|
|
|
3514
3541
|
signIns: z16.array(z16.object({
|
|
3515
3542
|
tokenHash: z16.string().min(1),
|
|
3516
3543
|
login: z16.string().min(1),
|
|
3517
|
-
/** The account's picture
|
|
3544
|
+
/** The account's picture, when GitHub gave one on its own avatar host. */
|
|
3518
3545
|
avatarUrl: z16.url().optional(),
|
|
3519
3546
|
createdAt: IsoDateTimeSchema,
|
|
3520
3547
|
expiresAt: IsoDateTimeSchema
|
|
3521
3548
|
})).default([])
|
|
3522
3549
|
});
|
|
3523
|
-
var TurnStartPayloadSchema = z16.object({
|
|
3550
|
+
var TurnStartPayloadSchema = z16.object({ turnId: UlidSchema });
|
|
3524
3551
|
var CursorsSchema = z16.object({
|
|
3525
3552
|
digest: z16.string().nullable(),
|
|
3526
3553
|
scopes: z16.record(z16.string(), z16.string()).default({}),
|
|
3527
|
-
threadsFrom: z16.record(z16.string(), z16.string().nullable()).default({}),
|
|
3528
3554
|
channelsFrom: z16.record(z16.string(), z16.string().nullable()).default({}),
|
|
3529
3555
|
placesFrom: z16.record(z16.string(), z16.string().nullable()).default({}),
|
|
3530
3556
|
/** How far a work role has read news, which goes to whichever of its general conversations reads first. */
|
|
@@ -3533,7 +3559,6 @@ var CursorsSchema = z16.object({
|
|
|
3533
3559
|
var NO_CURSORS = {
|
|
3534
3560
|
digest: null,
|
|
3535
3561
|
scopes: {},
|
|
3536
|
-
threadsFrom: {},
|
|
3537
3562
|
channelsFrom: {},
|
|
3538
3563
|
placesFrom: {}
|
|
3539
3564
|
};
|
|
@@ -3557,10 +3582,6 @@ function placeStart(cursors, scope) {
|
|
|
3557
3582
|
const from = cursors.placesFrom[scope];
|
|
3558
3583
|
return from === void 0 ? cursors.digest : from;
|
|
3559
3584
|
}
|
|
3560
|
-
function threadStart(cursors, scope) {
|
|
3561
|
-
const from = cursors.threadsFrom[scope];
|
|
3562
|
-
return from === void 0 ? cursors.digest : from;
|
|
3563
|
-
}
|
|
3564
3585
|
function newsStart(cursors) {
|
|
3565
3586
|
if (cursors.news !== void 0) {
|
|
3566
3587
|
return cursors.news;
|
|
@@ -3572,7 +3593,7 @@ function cursorOf(cursors, scope, part = null) {
|
|
|
3572
3593
|
if (part === null) {
|
|
3573
3594
|
return cursors.scopes[scope] ?? cursors.digest;
|
|
3574
3595
|
}
|
|
3575
|
-
return cursors.scopes[conversationKey(scope, part)] ?? (part.startsWith("#") ? channelStart(cursors, scope) : part.startsWith("~") ? placeStart(cursors, scope) :
|
|
3596
|
+
return cursors.scopes[conversationKey(scope, part)] ?? (part.startsWith("#") ? channelStart(cursors, scope) : part.startsWith("~") ? placeStart(cursors, scope) : cursors.digest);
|
|
3576
3597
|
}
|
|
3577
3598
|
function byId(a, b) {
|
|
3578
3599
|
return a.message.id < b.message.id ? -1 : a.message.id > b.message.id ? 1 : 0;
|
|
@@ -3593,7 +3614,7 @@ function skillFolderOf(proposal2) {
|
|
|
3593
3614
|
return null;
|
|
3594
3615
|
}
|
|
3595
3616
|
const parsed = SkillProposalSchema.safeParse(proposal2.charter);
|
|
3596
|
-
return parsed.success
|
|
3617
|
+
return parsed.success ? parsed.data.name : null;
|
|
3597
3618
|
}
|
|
3598
3619
|
function proposedFrom(origin, where) {
|
|
3599
3620
|
return origin !== void 0 && origin.scope === where.scope && origin.thread === where.thread && origin.channel === where.channel && origin.place === where.place;
|
|
@@ -3608,28 +3629,6 @@ var FRONT_DESK_TRIGGER = "user_post";
|
|
|
3608
3629
|
var OPS_CHANNELS = ["governance"];
|
|
3609
3630
|
var FRONT_DESK_CHANNELS = [ASK_CHANNEL];
|
|
3610
3631
|
var AlignmentsSchema = z16.object({ applied: z16.array(z16.string()).default([]) });
|
|
3611
|
-
var FOLLOW_DECISIONS = "ops-readers-follow-decisions";
|
|
3612
|
-
var THREAD_RECORDS = "threads-as-records";
|
|
3613
|
-
var DIGEST_CURSORS = "digest-replaces-inbox";
|
|
3614
|
-
var TASK_RETURNS = "task-returns-recorded";
|
|
3615
|
-
var OPS_CHANNEL_RETIRED = "ops-channel-retired";
|
|
3616
|
-
var DECISIONS_CHANNEL_RETIRED = "decisions-channel-retired";
|
|
3617
|
-
var THREAD_CONVERSATIONS = "thread-conversations";
|
|
3618
|
-
var ASKS_CHANNEL_OPENED = "asks-channel-opened";
|
|
3619
|
-
var KNOWLEDGE_REMOVAL = "knowledge-removal-granted";
|
|
3620
|
-
var CHANNEL_CONVERSATIONS = "channel-conversations";
|
|
3621
|
-
var PLACE_CONVERSATIONS = "place-conversations";
|
|
3622
|
-
var DASHBOARD_UPDATES = "dashboard-updates-granted";
|
|
3623
|
-
var CRON_VERBS = "cron-verbs-granted";
|
|
3624
|
-
var KNOWLEDGE_NOTICES = "knowledge-notices";
|
|
3625
|
-
var IMAGE_UPLOADS = "image-uploads-granted";
|
|
3626
|
-
var KnowledgeEventSchema = z16.object({
|
|
3627
|
-
topic: NameSchema,
|
|
3628
|
-
project: NameSchema.nullable(),
|
|
3629
|
-
replaced: z16.boolean().optional()
|
|
3630
|
-
});
|
|
3631
|
-
var CRON_VERB_NAMES = ["create_cron", "update_cron", "remove_cron"];
|
|
3632
|
-
var OLD_DASHBOARD_LINE = "Agents may edit this file. It is rendered by the board UI.";
|
|
3633
3632
|
var DASHBOARD_LINE = "The project's members keep this page current with update_dashboard.";
|
|
3634
3633
|
var PLANNING_ROLES = [USER_ROLE, "steward", "concierge"];
|
|
3635
3634
|
var REALLOCATING_ROLES = [USER_ROLE, "steward", "concierge"];
|
|
@@ -3649,9 +3648,9 @@ function sentence(summary) {
|
|
|
3649
3648
|
return summary.trim().replace(/\.+$/, "");
|
|
3650
3649
|
}
|
|
3651
3650
|
function skillSummary(data, body) {
|
|
3652
|
-
const
|
|
3653
|
-
if (typeof
|
|
3654
|
-
return
|
|
3651
|
+
const description = data["description"];
|
|
3652
|
+
if (typeof description === "string" && description.trim().length > 0) {
|
|
3653
|
+
return description.trim();
|
|
3655
3654
|
}
|
|
3656
3655
|
return body.split("\n").map((line) => line.trim()).find((line) => line.length > 0 && !line.startsWith("#")) ?? "";
|
|
3657
3656
|
}
|
|
@@ -3804,390 +3803,22 @@ var Board = class _Board {
|
|
|
3804
3803
|
return board;
|
|
3805
3804
|
}
|
|
3806
3805
|
/**
|
|
3807
|
-
* Applies each one-time alignment an existing society has not had yet
|
|
3808
|
-
*
|
|
3806
|
+
* Applies each one-time alignment an existing society has not had yet, and remembers it, so a
|
|
3807
|
+
* member that later chooses otherwise keeps its choice. A new alignment is a name and a method in
|
|
3808
|
+
* this list; once every board has applied it, both are deleted. None is pending.
|
|
3809
3809
|
*/
|
|
3810
3810
|
async applyAlignments() {
|
|
3811
|
+
const alignments = [];
|
|
3811
3812
|
const file = path7.join(this.paths.state(), "alignments.json");
|
|
3812
3813
|
const { applied } = await exists(file) ? await readJson(file, AlignmentsSchema) : { applied: [] };
|
|
3813
|
-
const pending =
|
|
3814
|
-
FOLLOW_DECISIONS,
|
|
3815
|
-
THREAD_RECORDS,
|
|
3816
|
-
DIGEST_CURSORS,
|
|
3817
|
-
TASK_RETURNS,
|
|
3818
|
-
OPS_CHANNEL_RETIRED,
|
|
3819
|
-
DECISIONS_CHANNEL_RETIRED,
|
|
3820
|
-
THREAD_CONVERSATIONS,
|
|
3821
|
-
ASKS_CHANNEL_OPENED,
|
|
3822
|
-
KNOWLEDGE_REMOVAL,
|
|
3823
|
-
CHANNEL_CONVERSATIONS,
|
|
3824
|
-
DASHBOARD_UPDATES,
|
|
3825
|
-
CRON_VERBS,
|
|
3826
|
-
KNOWLEDGE_NOTICES,
|
|
3827
|
-
IMAGE_UPLOADS,
|
|
3828
|
-
PLACE_CONVERSATIONS
|
|
3829
|
-
].filter((name) => !applied.includes(name));
|
|
3814
|
+
const pending = alignments.filter(({ name }) => !applied.includes(name));
|
|
3830
3815
|
if (pending.length === 0) {
|
|
3831
3816
|
return;
|
|
3832
3817
|
}
|
|
3833
|
-
|
|
3834
|
-
await
|
|
3835
|
-
}
|
|
3836
|
-
if (pending.includes(THREAD_RECORDS)) {
|
|
3837
|
-
await this.recordTaskThreads();
|
|
3838
|
-
}
|
|
3839
|
-
if (pending.includes(DIGEST_CURSORS)) {
|
|
3840
|
-
await this.renameInboxCursors();
|
|
3841
|
-
}
|
|
3842
|
-
if (pending.includes(TASK_RETURNS)) {
|
|
3843
|
-
await this.recordTaskReturns();
|
|
3844
|
-
}
|
|
3845
|
-
if (pending.includes(OPS_CHANNEL_RETIRED)) {
|
|
3846
|
-
await this.retireSocietyChannel("ops");
|
|
3847
|
-
}
|
|
3848
|
-
if (pending.includes(DECISIONS_CHANNEL_RETIRED)) {
|
|
3849
|
-
await this.retireSocietyChannel("decisions");
|
|
3850
|
-
}
|
|
3851
|
-
if (pending.includes(THREAD_CONVERSATIONS)) {
|
|
3852
|
-
await this.startThreadCursors();
|
|
3853
|
-
}
|
|
3854
|
-
if (pending.includes(ASKS_CHANNEL_OPENED)) {
|
|
3855
|
-
await this.openAsksChannel();
|
|
3856
|
-
}
|
|
3857
|
-
if (pending.includes(KNOWLEDGE_REMOVAL)) {
|
|
3858
|
-
await this.grantBeside("write_knowledge", "remove_knowledge");
|
|
3859
|
-
}
|
|
3860
|
-
if (pending.includes(CHANNEL_CONVERSATIONS)) {
|
|
3861
|
-
await this.startChannelCursors();
|
|
3862
|
-
}
|
|
3863
|
-
if (pending.includes(DASHBOARD_UPDATES)) {
|
|
3864
|
-
await this.grantBeside("write_knowledge", "update_dashboard");
|
|
3865
|
-
await this.rewordDashboards();
|
|
3866
|
-
}
|
|
3867
|
-
if (pending.includes(CRON_VERBS)) {
|
|
3868
|
-
await this.grantToEveryRole(CRON_VERB_NAMES);
|
|
3869
|
-
}
|
|
3870
|
-
if (pending.includes(KNOWLEDGE_NOTICES)) {
|
|
3871
|
-
await this.noticeKnowledgeNotes();
|
|
3872
|
-
}
|
|
3873
|
-
if (pending.includes(IMAGE_UPLOADS)) {
|
|
3874
|
-
await this.grantBeside("post_message", "upload_image");
|
|
3875
|
-
}
|
|
3876
|
-
if (pending.includes(PLACE_CONVERSATIONS)) {
|
|
3877
|
-
await this.startPlaceCursors();
|
|
3878
|
-
}
|
|
3879
|
-
await writeJson(file, { applied: [...applied, ...pending] });
|
|
3880
|
-
}
|
|
3881
|
-
/**
|
|
3882
|
-
* Gives each knowledge note from before notes carried their notice the notice it would carry
|
|
3883
|
-
* now. The event log, not the note's words, says which post is one: a knowledge verb appends
|
|
3884
|
-
* its event and then its note under the mutex, so a note is the post right after such an event,
|
|
3885
|
-
* by the same actor, at the top of the general channel of the topic's place. A post written to
|
|
3886
|
-
* look like a note follows no such event and is left as it is.
|
|
3887
|
-
*/
|
|
3888
|
-
async noticeKnowledgeNotes() {
|
|
3889
|
-
let before;
|
|
3890
|
-
for (const event of await this.events.readSince(null, Number.POSITIVE_INFINITY)) {
|
|
3891
|
-
const verb = before;
|
|
3892
|
-
before = event;
|
|
3893
|
-
if (event.type !== "message.posted" || verb === void 0 || verb.actor !== event.actor || verb.type !== "knowledge.written" && verb.type !== "knowledge.removed") {
|
|
3894
|
-
continue;
|
|
3895
|
-
}
|
|
3896
|
-
const knowledge2 = KnowledgeEventSchema.safeParse(verb.payload);
|
|
3897
|
-
const id = UlidSchema.safeParse(event.payload["id"]);
|
|
3898
|
-
if (!knowledge2.success || !id.success || (event.payload["thread"] ?? null) !== null || event.payload["channel"] !== channelRef(knowledge2.data.project, "general")) {
|
|
3899
|
-
continue;
|
|
3900
|
-
}
|
|
3901
|
-
const { topic, project: project2, replaced } = knowledge2.data;
|
|
3902
|
-
const file = this.paths.messageFile(this.paths.channelDir(channelRef(project2, "general")), id.data, event.actor);
|
|
3903
|
-
if (!await exists(file)) {
|
|
3904
|
-
continue;
|
|
3905
|
-
}
|
|
3906
|
-
const { data, body, error } = await readLooseMarkdown(file);
|
|
3907
|
-
if (error !== null || data["notice"] !== void 0) {
|
|
3908
|
-
continue;
|
|
3909
|
-
}
|
|
3910
|
-
const notice = {
|
|
3911
|
-
kind: "knowledge",
|
|
3912
|
-
action: verb.type === "knowledge.removed" ? "removed" : replaced === true ? "updated" : "written",
|
|
3913
|
-
topic,
|
|
3914
|
-
project: project2
|
|
3915
|
-
};
|
|
3916
|
-
await writeMarkdown(file, { ...data, notice }, body);
|
|
3917
|
-
}
|
|
3918
|
-
}
|
|
3919
|
-
/**
|
|
3920
|
-
* Grants `verb` once to every charter granting `beside`, the work roles' charters in practice;
|
|
3921
|
-
* seed roles have it from their seed.
|
|
3922
|
-
*/
|
|
3923
|
-
async grantBeside(beside, verb) {
|
|
3924
|
-
for (const charter of await this.listRoles()) {
|
|
3925
|
-
if (charter.verbs.includes(beside) && !charter.verbs.includes(verb)) {
|
|
3926
|
-
await this.writeRoleUnlocked(USER_NAME, { ...charter, verbs: [...charter.verbs, verb] }, { verbsAdded: [verb] });
|
|
3927
|
-
}
|
|
3928
|
-
}
|
|
3929
|
-
}
|
|
3930
|
-
/** Grants verbs once to every charter but the user's, which holds every verb already. */
|
|
3931
|
-
async grantToEveryRole(verbs) {
|
|
3932
|
-
for (const charter of await this.listRoles()) {
|
|
3933
|
-
const missing = verbs.filter((verb) => !charter.verbs.includes(verb));
|
|
3934
|
-
if (charter.name !== USER_ROLE && missing.length > 0) {
|
|
3935
|
-
await this.writeRoleUnlocked(USER_NAME, { ...charter, verbs: [...charter.verbs, ...missing] }, { verbsAdded: missing });
|
|
3936
|
-
}
|
|
3818
|
+
for (const { apply } of pending) {
|
|
3819
|
+
await apply();
|
|
3937
3820
|
}
|
|
3938
|
-
|
|
3939
|
-
/** A dashboard still saying agents may edit its file says how it changes now. */
|
|
3940
|
-
async rewordDashboards() {
|
|
3941
|
-
for (const slug of await listDirs(this.paths.projects())) {
|
|
3942
|
-
const file = this.paths.dashboard(slug);
|
|
3943
|
-
if (!await exists(file)) {
|
|
3944
|
-
continue;
|
|
3945
|
-
}
|
|
3946
|
-
const { data, body } = await readLooseMarkdown(file);
|
|
3947
|
-
if (body.includes(OLD_DASHBOARD_LINE)) {
|
|
3948
|
-
await writeMarkdown(file, data, body.replace(OLD_DASHBOARD_LINE, DASHBOARD_LINE));
|
|
3949
|
-
}
|
|
3950
|
-
}
|
|
3951
|
-
}
|
|
3952
|
-
/**
|
|
3953
|
-
* A task sent back before tasks recorded it kept no trace outside the event log. A task still
|
|
3954
|
-
* short of the stage its latest `task.moved` came from is in that rework, and records it.
|
|
3955
|
-
*/
|
|
3956
|
-
async recordTaskReturns() {
|
|
3957
|
-
const moves = /* @__PURE__ */ new Map();
|
|
3958
|
-
for (const event of await this.events.readSince(null, Number.POSITIVE_INFINITY)) {
|
|
3959
|
-
const taskId = event.payload["taskId"];
|
|
3960
|
-
if (event.type === "task.moved" && typeof taskId === "string") {
|
|
3961
|
-
moves.set(taskId, event);
|
|
3962
|
-
}
|
|
3963
|
-
}
|
|
3964
|
-
for (const slug of await listDirs(this.paths.projects())) {
|
|
3965
|
-
for (const task2 of await this.listTasks(slug)) {
|
|
3966
|
-
const move = moves.get(task2.id);
|
|
3967
|
-
const from = move?.payload["from"];
|
|
3968
|
-
if (move === void 0 || typeof from !== "string" || task2.returned !== void 0 || task2.completing || task2.status !== "open" && task2.status !== "claimed") {
|
|
3969
|
-
continue;
|
|
3970
|
-
}
|
|
3971
|
-
const back = stageIndex(task2, from);
|
|
3972
|
-
if (back > stageIndex(task2, task2.stage)) {
|
|
3973
|
-
await this.writeTask(slug, {
|
|
3974
|
-
...task2,
|
|
3975
|
-
returned: { from, by: move.actor, at: move.ts }
|
|
3976
|
-
});
|
|
3977
|
-
}
|
|
3978
|
-
}
|
|
3979
|
-
}
|
|
3980
|
-
}
|
|
3981
|
-
/**
|
|
3982
|
-
* A society channel the board no longer uses leaves the society's list and every member's
|
|
3983
|
-
* subscriptions; its old posts stay on disk. `ops` carried operations signals, which are only
|
|
3984
|
-
* logged now; `decisions` carried proposals' outcomes, which are posts in their threads now, and
|
|
3985
|
-
* requests to the user, which are mentions of the user wherever the question belongs.
|
|
3986
|
-
*/
|
|
3987
|
-
async retireSocietyChannel(name) {
|
|
3988
|
-
const doc = await readMarkdown(this.paths.societyFile(), SocietySchema);
|
|
3989
|
-
if (doc.data.channels.includes(name)) {
|
|
3990
|
-
await writeMarkdown(this.paths.societyFile(), { ...doc.data, channels: doc.data.channels.filter((channel) => channel !== name) }, doc.body);
|
|
3991
|
-
}
|
|
3992
|
-
for (const agent2 of await this.listAgents()) {
|
|
3993
|
-
if (agent2.subscriptions.includes(name)) {
|
|
3994
|
-
await this.updateAgent(agent2.name, (a) => ({
|
|
3995
|
-
...a,
|
|
3996
|
-
subscriptions: a.subscriptions.filter((ref) => ref !== name)
|
|
3997
|
-
}));
|
|
3998
|
-
await this.refreshMember(agent2.name);
|
|
3999
|
-
}
|
|
4000
|
-
}
|
|
4001
|
-
}
|
|
4002
|
-
/**
|
|
4003
|
-
* Asks once opened their threads in general, where closing one posted its summary to every
|
|
4004
|
-
* citizen and woke the steward's heartbeat for it. The society gains the asks channel, and the
|
|
4005
|
-
* front desk follows it.
|
|
4006
|
-
*/
|
|
4007
|
-
async openAsksChannel() {
|
|
4008
|
-
const doc = await readMarkdown(this.paths.societyFile(), SocietySchema);
|
|
4009
|
-
if (!doc.data.channels.includes(ASK_CHANNEL)) {
|
|
4010
|
-
await writeMarkdown(this.paths.societyFile(), { ...doc.data, channels: [...doc.data.channels, ASK_CHANNEL] }, doc.body);
|
|
4011
|
-
}
|
|
4012
|
-
await ensureDir(this.paths.societyChannel(ASK_CHANNEL));
|
|
4013
|
-
for (const agent2 of await this.listAgents()) {
|
|
4014
|
-
if (agent2.status !== "active" || agent2.subscriptions.includes(ASK_CHANNEL)) {
|
|
4015
|
-
continue;
|
|
4016
|
-
}
|
|
4017
|
-
if (!(await this.readRole(agent2.role)).wakeTriggers.includes(FRONT_DESK_TRIGGER)) {
|
|
4018
|
-
continue;
|
|
4019
|
-
}
|
|
4020
|
-
await this.updateAgent(agent2.name, (a) => ({
|
|
4021
|
-
...a,
|
|
4022
|
-
subscriptions: [...a.subscriptions, ASK_CHANNEL]
|
|
4023
|
-
}));
|
|
4024
|
-
await this.refreshMember(agent2.name);
|
|
4025
|
-
await this.events.append("subscription.changed", agent2.name, {
|
|
4026
|
-
channel: ASK_CHANNEL,
|
|
4027
|
-
subscribed: true,
|
|
4028
|
-
aligned: true
|
|
4029
|
-
});
|
|
4030
|
-
}
|
|
4031
|
-
}
|
|
4032
|
-
/** Members that predate `decisions` among the ops channels read proposals without their outcome. */
|
|
4033
|
-
async followDecisions() {
|
|
4034
|
-
if (!(await this.society()).channels.includes("decisions")) {
|
|
4035
|
-
return;
|
|
4036
|
-
}
|
|
4037
|
-
for (const agent2 of await this.listAgents()) {
|
|
4038
|
-
if (agent2.status !== "active" || agent2.subscriptions.includes("decisions")) {
|
|
4039
|
-
continue;
|
|
4040
|
-
}
|
|
4041
|
-
if (!(await this.readRole(agent2.role)).wakeTriggers.includes(OPS_WAKE_TRIGGER)) {
|
|
4042
|
-
continue;
|
|
4043
|
-
}
|
|
4044
|
-
await this.updateAgent(agent2.name, (a) => ({
|
|
4045
|
-
...a,
|
|
4046
|
-
subscriptions: [...a.subscriptions, "decisions"]
|
|
4047
|
-
}));
|
|
4048
|
-
await this.refreshMember(agent2.name);
|
|
4049
|
-
await this.events.append("subscription.changed", agent2.name, {
|
|
4050
|
-
channel: "decisions",
|
|
4051
|
-
subscribed: true,
|
|
4052
|
-
aligned: true
|
|
4053
|
-
});
|
|
4054
|
-
}
|
|
4055
|
-
}
|
|
4056
|
-
/**
|
|
4057
|
-
* Threads were once a state field on their task. Each becomes a record under the task's project,
|
|
4058
|
-
* on its general channel, closed if its task has ended, and the field leaves the task file.
|
|
4059
|
-
*/
|
|
4060
|
-
async recordTaskThreads() {
|
|
4061
|
-
const opened = /* @__PURE__ */ new Map();
|
|
4062
|
-
const closed = /* @__PURE__ */ new Map();
|
|
4063
|
-
for (const event of await this.events.readSince(null, Number.POSITIVE_INFINITY)) {
|
|
4064
|
-
const taskId = event.payload["taskId"];
|
|
4065
|
-
if (typeof taskId !== "string") {
|
|
4066
|
-
continue;
|
|
4067
|
-
}
|
|
4068
|
-
if (event.type === "thread.opened") {
|
|
4069
|
-
opened.set(taskId, event);
|
|
4070
|
-
} else if (event.type === "thread.closed") {
|
|
4071
|
-
closed.set(taskId, event);
|
|
4072
|
-
}
|
|
4073
|
-
}
|
|
4074
|
-
for (const slug of await listDirs(this.paths.projects())) {
|
|
4075
|
-
const threads = this.paths.threads(slug);
|
|
4076
|
-
const summaries = /* @__PURE__ */ new Map();
|
|
4077
|
-
for (const message of await this.readMessagesIn(this.paths.projectChannel(slug, "general"), null)) {
|
|
4078
|
-
if (message.task !== void 0) {
|
|
4079
|
-
summaries.set(message.task, message.body.replace(/^Thread closed[^\n]*\n\n/, ""));
|
|
4080
|
-
}
|
|
4081
|
-
}
|
|
4082
|
-
for (const file of await listFiles(this.paths.tasks(slug))) {
|
|
4083
|
-
const raw = await readMarkdown(path7.join(this.paths.tasks(slug), file), z16.record(z16.string(), z16.unknown()));
|
|
4084
|
-
const legacy = raw.data["thread"];
|
|
4085
|
-
if (legacy === void 0) {
|
|
4086
|
-
continue;
|
|
4087
|
-
}
|
|
4088
|
-
const task2 = { ...TaskFrontmatterSchema.parse(raw.data), body: raw.body };
|
|
4089
|
-
if ((legacy === "open" || legacy === "closed") && !await exists(this.paths.threadFile(threads, task2.id))) {
|
|
4090
|
-
const ended = task2.status === "done" || task2.status === "abandoned";
|
|
4091
|
-
const open4 = legacy === "open" && !ended;
|
|
4092
|
-
const openedBy = opened.get(task2.id);
|
|
4093
|
-
const closedBy = closed.get(task2.id);
|
|
4094
|
-
await this.writeThread(threads, {
|
|
4095
|
-
id: task2.id,
|
|
4096
|
-
channel: channelRef(slug, "general"),
|
|
4097
|
-
title: task2.title,
|
|
4098
|
-
subject: { kind: "task", id: task2.id },
|
|
4099
|
-
state: open4 ? "open" : "closed",
|
|
4100
|
-
openedBy: openedBy?.actor ?? task2.createdBy,
|
|
4101
|
-
openedAt: openedBy?.ts ?? task2.createdAt,
|
|
4102
|
-
...open4 ? {} : {
|
|
4103
|
-
closedBy: closedBy?.actor ?? SYSTEM_ACTOR.name,
|
|
4104
|
-
closedAt: closedBy?.ts ?? this.now().toISOString()
|
|
4105
|
-
},
|
|
4106
|
-
body: summaries.get(task2.id) ?? ""
|
|
4107
|
-
});
|
|
4108
|
-
}
|
|
4109
|
-
await this.writeTask(slug, task2);
|
|
4110
|
-
}
|
|
4111
|
-
}
|
|
4112
|
-
}
|
|
4113
|
-
/**
|
|
4114
|
-
* Every channel of a scope was once read in its home conversation, so a member had read each up
|
|
4115
|
-
* to its scope's cursor. Each channel beside general starts from that cursor now, as each takes a
|
|
4116
|
-
* conversation of its own.
|
|
4117
|
-
*/
|
|
4118
|
-
async startChannelCursors() {
|
|
4119
|
-
for (const agent2 of await this.listAgents()) {
|
|
4120
|
-
const file = this.paths.agentCursors(agent2.name);
|
|
4121
|
-
if (!await exists(file)) {
|
|
4122
|
-
continue;
|
|
4123
|
-
}
|
|
4124
|
-
const cursors = await readJson(file, CursorsSchema);
|
|
4125
|
-
const scopes = /* @__PURE__ */ new Set([
|
|
4126
|
-
...Object.keys(cursors.scopes).filter((key) => !key.includes("/")),
|
|
4127
|
-
...agent2.memberships,
|
|
4128
|
-
SOCIETY_SCOPE
|
|
4129
|
-
]);
|
|
4130
|
-
const channelsFrom = Object.fromEntries([...scopes].map((scope) => [scope, cursorOf(cursors, scope)]));
|
|
4131
|
-
await writeJson(file, { ...cursors, channelsFrom });
|
|
4132
|
-
}
|
|
4133
|
-
}
|
|
4134
|
-
/**
|
|
4135
|
-
* A project's channels were once read, from the society scope, in the society's home
|
|
4136
|
-
* conversation, so a member had read each up to that cursor. Each project's conversation in the
|
|
4137
|
-
* society scope starts from it now, as each takes a conversation of its own.
|
|
4138
|
-
*/
|
|
4139
|
-
async startPlaceCursors() {
|
|
4140
|
-
for (const agent2 of await this.listAgents()) {
|
|
4141
|
-
const file = this.paths.agentCursors(agent2.name);
|
|
4142
|
-
if (!await exists(file)) {
|
|
4143
|
-
continue;
|
|
4144
|
-
}
|
|
4145
|
-
const cursors = await readJson(file, CursorsSchema);
|
|
4146
|
-
await writeJson(file, {
|
|
4147
|
-
...cursors,
|
|
4148
|
-
placesFrom: { [SOCIETY_SCOPE]: cursorOf(cursors, SOCIETY_SCOPE) }
|
|
4149
|
-
});
|
|
4150
|
-
}
|
|
4151
|
-
}
|
|
4152
|
-
/**
|
|
4153
|
-
* Threads were once read in the scope's single conversation, so a member had read every thread
|
|
4154
|
-
* up to its scope's cursor. Each scope's threads start from that cursor now, as each thread's
|
|
4155
|
-
* conversation takes a cursor of its own.
|
|
4156
|
-
*/
|
|
4157
|
-
async startThreadCursors() {
|
|
4158
|
-
for (const agent2 of await this.listAgents()) {
|
|
4159
|
-
const file = this.paths.agentCursors(agent2.name);
|
|
4160
|
-
if (!await exists(file)) {
|
|
4161
|
-
continue;
|
|
4162
|
-
}
|
|
4163
|
-
const cursors = await readJson(file, CursorsSchema);
|
|
4164
|
-
const scopes = /* @__PURE__ */ new Set([
|
|
4165
|
-
...Object.keys(cursors.scopes).filter((key) => !key.includes("/")),
|
|
4166
|
-
...agent2.memberships,
|
|
4167
|
-
SOCIETY_SCOPE
|
|
4168
|
-
]);
|
|
4169
|
-
const threadsFrom = Object.fromEntries([...scopes].map((scope) => [scope, cursorOf(cursors, scope)]));
|
|
4170
|
-
await writeJson(file, { ...cursors, threadsFrom });
|
|
4171
|
-
}
|
|
4172
|
-
}
|
|
4173
|
-
/**
|
|
4174
|
-
* The digest was once called the inbox: each cursor file keyed its cursor `inbox`, and the user
|
|
4175
|
-
* followed every society channel and project general channel to read one. The user takes no
|
|
4176
|
-
* turns, so it follows nothing now.
|
|
4177
|
-
*/
|
|
4178
|
-
async renameInboxCursors() {
|
|
4179
|
-
const Legacy = z16.object({ inbox: z16.string().nullable() });
|
|
4180
|
-
for (const agent2 of await this.listAgents()) {
|
|
4181
|
-
const file = this.paths.agentCursors(agent2.name);
|
|
4182
|
-
if (await exists(file)) {
|
|
4183
|
-
const legacy = Legacy.safeParse(JSON.parse(await readFile4(file, "utf8")));
|
|
4184
|
-
if (legacy.success) {
|
|
4185
|
-
await writeJson(file, { digest: legacy.data.inbox });
|
|
4186
|
-
}
|
|
4187
|
-
}
|
|
4188
|
-
}
|
|
4189
|
-
await this.updateAgent(USER_NAME, (user2) => ({ ...user2, subscriptions: [] }));
|
|
4190
|
-
await this.refreshMember(USER_NAME);
|
|
3821
|
+
await writeJson(file, { applied: [...applied, ...pending.map(({ name }) => name)] });
|
|
4191
3822
|
}
|
|
4192
3823
|
/** Writes every missing seed charter and aligns the existing ones with their seed. */
|
|
4193
3824
|
async ensureSeedRoles() {
|
|
@@ -4991,7 +4622,7 @@ ${DASHBOARD_LINE}
|
|
|
4991
4622
|
if (event.type === "turn.started") {
|
|
4992
4623
|
const start = TurnStartPayloadSchema.safeParse(event.payload);
|
|
4993
4624
|
if (start.success)
|
|
4994
|
-
starts.set(start.data.turnId
|
|
4625
|
+
starts.set(start.data.turnId, event.ts);
|
|
4995
4626
|
continue;
|
|
4996
4627
|
}
|
|
4997
4628
|
if (event.type !== "turn.completed" && event.type !== "turn.failed") {
|
|
@@ -4999,9 +4630,11 @@ ${DASHBOARD_LINE}
|
|
|
4999
4630
|
}
|
|
5000
4631
|
const parsed = TurnHistoryPayloadSchema.safeParse(event.payload);
|
|
5001
4632
|
if (parsed.success) {
|
|
5002
|
-
const
|
|
5003
|
-
const startedAt = starts.get(
|
|
5004
|
-
|
|
4633
|
+
const { turnId } = parsed.data;
|
|
4634
|
+
const startedAt = turnId === void 0 ? void 0 : starts.get(turnId);
|
|
4635
|
+
if (turnId !== void 0) {
|
|
4636
|
+
starts.delete(turnId);
|
|
4637
|
+
}
|
|
5005
4638
|
entries.push({
|
|
5006
4639
|
id: event.id,
|
|
5007
4640
|
ts: event.ts,
|
|
@@ -5236,7 +4869,7 @@ ${DASHBOARD_LINE}
|
|
|
5236
4869
|
const starts = [
|
|
5237
4870
|
...scopes.flatMap((each) => conversation === void 0 ? [
|
|
5238
4871
|
cursorOf(cursors, each),
|
|
5239
|
-
|
|
4872
|
+
cursors.digest,
|
|
5240
4873
|
channelStart(cursors, each),
|
|
5241
4874
|
placeStart(cursors, each)
|
|
5242
4875
|
] : [cursorOf(cursors, each, conversation.part)]),
|
|
@@ -5852,11 +5485,12 @@ ${current.body}`);
|
|
|
5852
5485
|
const args = VerbInputs.open_thread.parse(input);
|
|
5853
5486
|
await this.authorize(actor, "open_thread");
|
|
5854
5487
|
return this.mutex.run(async () => {
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5488
|
+
await this.assertChannelOpen(args.channel);
|
|
5489
|
+
return this.openThreadUnlocked(actor.name, {
|
|
5490
|
+
id: this.newId(),
|
|
5491
|
+
channel: args.channel,
|
|
5492
|
+
title: args.title
|
|
5493
|
+
});
|
|
5860
5494
|
});
|
|
5861
5495
|
}
|
|
5862
5496
|
/** Writes a new open thread and its event. Callers hold the mutex and chose where it hangs. */
|
|
@@ -5891,28 +5525,13 @@ ${current.body}`);
|
|
|
5891
5525
|
subject: { kind: "proposal", id: proposal2.id }
|
|
5892
5526
|
};
|
|
5893
5527
|
}
|
|
5894
|
-
/**
|
|
5895
|
-
|
|
5896
|
-
* threads gets one now, opened as its proposer, and a thread closed before the decision opens
|
|
5897
|
-
* again.
|
|
5898
|
-
*/
|
|
5899
|
-
async proposalThreadUnlocked(by, proposal2) {
|
|
5528
|
+
/** The open thread of a proposal being decided, which `propose` opened with it. */
|
|
5529
|
+
async proposalThreadUnlocked(proposal2) {
|
|
5900
5530
|
const found = await this.tryFindThread(proposal2.id);
|
|
5901
|
-
if (found?.thread.state
|
|
5902
|
-
|
|
5903
|
-
}
|
|
5904
|
-
if (found === null) {
|
|
5905
|
-
return this.openThreadUnlocked(proposal2.proposedBy, this.proposalThreadOpening(proposal2));
|
|
5531
|
+
if (found?.thread.state !== "open") {
|
|
5532
|
+
throw new BoardError("INVALID_STATE", `proposal ${proposal2.id} has no open thread`);
|
|
5906
5533
|
}
|
|
5907
|
-
|
|
5908
|
-
const reopened = await this.writeThread(found.threads, { ...kept, state: "open" });
|
|
5909
|
-
await this.events.append("thread.opened", by, {
|
|
5910
|
-
threadId: proposal2.id,
|
|
5911
|
-
channel: reopened.channel,
|
|
5912
|
-
subject: reopened.subject ?? null,
|
|
5913
|
-
reopened: true
|
|
5914
|
-
});
|
|
5915
|
-
return reopened;
|
|
5534
|
+
return found.thread;
|
|
5916
5535
|
}
|
|
5917
5536
|
/** Where a task's thread hangs, what it is called, and what it is about. */
|
|
5918
5537
|
taskThreadOpening(task2) {
|
|
@@ -5924,79 +5543,21 @@ ${current.body}`);
|
|
|
5924
5543
|
};
|
|
5925
5544
|
}
|
|
5926
5545
|
/**
|
|
5927
|
-
* The open thread of a task in play, for a verb about to post into it
|
|
5928
|
-
*
|
|
5929
|
-
* again; an ended task's thread stays closed.
|
|
5546
|
+
* The open thread of a task in play, for a verb about to post into it, which `createTask` opened
|
|
5547
|
+
* with the task; an ended task's thread closed with it.
|
|
5930
5548
|
*/
|
|
5931
|
-
async taskThreadUnlocked(
|
|
5549
|
+
async taskThreadUnlocked(task2) {
|
|
5932
5550
|
const found = await this.tryFindThread(task2.id);
|
|
5933
5551
|
if (found?.thread.state === "open") {
|
|
5934
5552
|
return found.thread;
|
|
5935
5553
|
}
|
|
5936
|
-
|
|
5937
|
-
throw new BoardError("INVALID_STATE", `task ${task2.id} is ${task2.status} and its thread closed with it`);
|
|
5938
|
-
}
|
|
5939
|
-
if (found === null) {
|
|
5940
|
-
return this.openThreadUnlocked(by, this.taskThreadOpening(task2));
|
|
5941
|
-
}
|
|
5942
|
-
const { closedBy: _by, closedAt: _at, ...kept } = found.thread;
|
|
5943
|
-
const reopened = await this.writeThread(found.threads, { ...kept, state: "open" });
|
|
5944
|
-
await this.events.append("thread.opened", by, {
|
|
5945
|
-
threadId: task2.id,
|
|
5946
|
-
channel: reopened.channel,
|
|
5947
|
-
subject: reopened.subject ?? null,
|
|
5948
|
-
reopened: true
|
|
5949
|
-
});
|
|
5950
|
-
return reopened;
|
|
5554
|
+
throw new BoardError("INVALID_STATE", task2.status === "done" || task2.status === "abandoned" ? `task ${task2.id} is ${task2.status} and its thread closed with it` : `task ${task2.id} has no open thread`);
|
|
5951
5555
|
}
|
|
5952
5556
|
/** Posts what a task verb was told to say into the task's thread, as its author, with the step it recorded. */
|
|
5953
5557
|
async postTaskNote(author, task2, body, step) {
|
|
5954
|
-
const thread2 = await this.taskThreadUnlocked(
|
|
5558
|
+
const thread2 = await this.taskThreadUnlocked(task2);
|
|
5955
5559
|
return this.appendThreadMessage(author, thread2.id, body, step === void 0 ? {} : { step });
|
|
5956
5560
|
}
|
|
5957
|
-
/** Where a new thread hangs, what it is called, and what it is about. */
|
|
5958
|
-
async threadToOpen(args) {
|
|
5959
|
-
if (args.task_id !== void 0 && args.proposal_id !== void 0) {
|
|
5960
|
-
throw new BoardError("VALIDATION", "a thread is about one task or one proposal, not both");
|
|
5961
|
-
}
|
|
5962
|
-
if (args.task_id !== void 0) {
|
|
5963
|
-
const { task: task2, project: project2 } = await this.findTask(args.task_id);
|
|
5964
|
-
if (task2.status === "done" || task2.status === "abandoned") {
|
|
5965
|
-
throw new BoardError("INVALID_STATE", `task ${task2.id} is ${task2.status}`);
|
|
5966
|
-
}
|
|
5967
|
-
const channel = args.channel ?? channelRef(project2, task2.channel);
|
|
5968
|
-
if (parseChannelRef(channel).project !== project2) {
|
|
5969
|
-
throw new BoardError("VALIDATION", `a task's thread hangs off a channel of its project, ${project2}`);
|
|
5970
|
-
}
|
|
5971
|
-
await this.assertChannelOpen(channel);
|
|
5972
|
-
return {
|
|
5973
|
-
id: task2.id,
|
|
5974
|
-
channel,
|
|
5975
|
-
title: args.title ?? task2.title,
|
|
5976
|
-
subject: { kind: "task", id: task2.id }
|
|
5977
|
-
};
|
|
5978
|
-
}
|
|
5979
|
-
if (args.proposal_id !== void 0) {
|
|
5980
|
-
const proposal2 = await this.readProposal(args.proposal_id);
|
|
5981
|
-
if (proposal2.status !== "proposed") {
|
|
5982
|
-
throw new BoardError("INVALID_STATE", `proposal ${proposal2.id} is already ${proposal2.status}`);
|
|
5983
|
-
}
|
|
5984
|
-
const channel = args.channel ?? "governance";
|
|
5985
|
-
await this.assertChannelOpen(channel);
|
|
5986
|
-
const described = `${proposal2.kind} proposal: ${describeCharter(proposal2.kind, proposal2.charter)}`;
|
|
5987
|
-
return {
|
|
5988
|
-
id: proposal2.id,
|
|
5989
|
-
channel,
|
|
5990
|
-
title: args.title ?? described.slice(0, 200),
|
|
5991
|
-
subject: { kind: "proposal", id: proposal2.id }
|
|
5992
|
-
};
|
|
5993
|
-
}
|
|
5994
|
-
if (args.channel === void 0 || args.title === void 0) {
|
|
5995
|
-
throw new BoardError("VALIDATION", "a thread needs a task_id, a proposal_id, or a channel and a title");
|
|
5996
|
-
}
|
|
5997
|
-
await this.assertChannelOpen(args.channel);
|
|
5998
|
-
return { id: this.newId(), channel: args.channel, title: args.title };
|
|
5999
|
-
}
|
|
6000
5561
|
/**
|
|
6001
5562
|
* Where the board learns which turns are in flight, from the server that hosts them, so it
|
|
6002
5563
|
* refuses to end a conversation under someone else's turn.
|
|
@@ -7130,6 +6691,7 @@ ${rationale}`}`);
|
|
|
7130
6691
|
needsUserDecision: parsed.status?.needsUserDecision ?? false,
|
|
7131
6692
|
error: parsed.error,
|
|
7132
6693
|
...parsed.resumeFailed === void 0 ? {} : { resumeFailed: parsed.resumeFailed },
|
|
6694
|
+
...parsed.transient === true ? { transient: true } : {},
|
|
7133
6695
|
...parsed.sessionDropped === true ? { sessionDropped: true } : {}
|
|
7134
6696
|
});
|
|
7135
6697
|
});
|
|
@@ -7837,15 +7399,9 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7837
7399
|
case "archive":
|
|
7838
7400
|
await this.validateArchive(ArchiveProposalSchema.parse(charter).project);
|
|
7839
7401
|
return;
|
|
7840
|
-
case "reallocation":
|
|
7841
|
-
|
|
7842
|
-
if (parsed.success) {
|
|
7843
|
-
await this.reallocatedRole(parsed.data);
|
|
7844
|
-
} else {
|
|
7845
|
-
LegacyReallocationSchema.parse(charter);
|
|
7846
|
-
}
|
|
7402
|
+
case "reallocation":
|
|
7403
|
+
await this.reallocatedRole(ReallocationProposalSchema.parse(charter));
|
|
7847
7404
|
return;
|
|
7848
|
-
}
|
|
7849
7405
|
default:
|
|
7850
7406
|
return;
|
|
7851
7407
|
}
|
|
@@ -7905,11 +7461,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7905
7461
|
return { project: input.project, members };
|
|
7906
7462
|
}
|
|
7907
7463
|
case "reallocation": {
|
|
7908
|
-
const
|
|
7909
|
-
if (!parsed.success) {
|
|
7910
|
-
return void 0;
|
|
7911
|
-
}
|
|
7912
|
-
const { next, changed } = await this.reallocatedRole(parsed.data);
|
|
7464
|
+
const { next, changed } = await this.reallocatedRole(ReallocationProposalSchema.parse(proposal2.charter));
|
|
7913
7465
|
await this.writeRoleUnlocked(by, next, meta);
|
|
7914
7466
|
return { role: next.name, changed };
|
|
7915
7467
|
}
|
|
@@ -7919,8 +7471,8 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7919
7471
|
}
|
|
7920
7472
|
/**
|
|
7921
7473
|
* Publishes a promoted skill under the society's skills, where every citizen's skills index lists
|
|
7922
|
-
* it: the folder the board took from its proposer's home
|
|
7923
|
-
*
|
|
7474
|
+
* it: the folder the board took from its proposer's home. A replacement is the whole skill, so
|
|
7475
|
+
* files the new version dropped go with the old one.
|
|
7924
7476
|
*/
|
|
7925
7477
|
async promoteSkillUnlocked(by, proposal2, skill2, meta) {
|
|
7926
7478
|
const dir = this.paths.societySkill(skill2.name);
|
|
@@ -7928,16 +7480,12 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7928
7480
|
const replaced = await exists(file);
|
|
7929
7481
|
const taken = proposal2.skillFiles;
|
|
7930
7482
|
const staged = path7.join(this.paths.proposalFiles(proposal2.id), "SKILL.md");
|
|
7931
|
-
if (
|
|
7483
|
+
if (taken === void 0 || !await exists(staged)) {
|
|
7932
7484
|
throw new BoardError("INVALID_STATE", `proposal ${proposal2.id} holds no skill files`);
|
|
7933
7485
|
}
|
|
7934
7486
|
await rm2(dir, { recursive: true, force: true });
|
|
7935
|
-
|
|
7936
|
-
|
|
7937
|
-
} else {
|
|
7938
|
-
await ensureDir(dir);
|
|
7939
|
-
}
|
|
7940
|
-
const body = skill2.body ?? (await readLooseMarkdown(file)).body;
|
|
7487
|
+
await cp(this.paths.proposalFiles(proposal2.id), dir, { recursive: true });
|
|
7488
|
+
const { body } = await readLooseMarkdown(file);
|
|
7941
7489
|
await writeMarkdown(file, {
|
|
7942
7490
|
name: skill2.name,
|
|
7943
7491
|
description: skill2.summary,
|
|
@@ -7945,7 +7493,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7945
7493
|
proposedBy: proposal2.proposedBy,
|
|
7946
7494
|
promotedBy: by,
|
|
7947
7495
|
promotedAt: this.now().toISOString(),
|
|
7948
|
-
|
|
7496
|
+
commit: taken.commit
|
|
7949
7497
|
}
|
|
7950
7498
|
}, body);
|
|
7951
7499
|
await this.events.append("skill.promoted", by, {
|
|
@@ -7953,7 +7501,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7953
7501
|
summary: skill2.summary,
|
|
7954
7502
|
proposedBy: proposal2.proposedBy,
|
|
7955
7503
|
replaced,
|
|
7956
|
-
|
|
7504
|
+
files: taken.files.length,
|
|
7957
7505
|
...meta
|
|
7958
7506
|
});
|
|
7959
7507
|
await this.appendMessage(SYSTEM_ACTOR.name, "general", `Skill ${skill2.name} ${replaced ? "updated in" : "promoted to"} the society: ${sentence(skill2.summary)}. Every citizen's skills index now lists it.`);
|
|
@@ -8642,6 +8190,7 @@ ${charter.purpose}
|
|
|
8642
8190
|
await this.validateProvision(filed.kind, filed.charter);
|
|
8643
8191
|
}
|
|
8644
8192
|
this.assertNoTurnCut(actor, `proposal ${proposalId} cannot be decided`, (turn2) => turn2.thread === proposalId);
|
|
8193
|
+
const thread2 = await this.proposalThreadUnlocked(filed);
|
|
8645
8194
|
if (outcome === "approved" && filed.kind === "archive") {
|
|
8646
8195
|
await this.assertProjectQuiet(actor, ArchiveProposalSchema.parse(filed.charter).project);
|
|
8647
8196
|
}
|
|
@@ -8683,7 +8232,6 @@ ${charter.purpose}
|
|
|
8683
8232
|
const verdict = outcome === "approved" ? "Approved" : "Rejected";
|
|
8684
8233
|
const why = reason === void 0 ? "" : ` Reason: ${reason}.`;
|
|
8685
8234
|
const result = provision === void 0 ? "" : ` Provisioned: ${Object.entries(provision).map(([key, value]) => `${key} ${plain(value)}`).join(", ")}.`;
|
|
8686
|
-
const thread2 = await this.proposalThreadUnlocked(actor.name, proposal2);
|
|
8687
8235
|
const post = `${verdict}: ${describeCharter(proposal2.kind, proposal2.charter)}.${why}${result}`;
|
|
8688
8236
|
await this.appendThreadMessage(actor.name, thread2.id, actor.name === SYSTEM_ACTOR.name ? withoutMentions(post) : post, { step: { action: outcome } });
|
|
8689
8237
|
await this.endThreadOf(proposalId, actor.name, outcome);
|
|
@@ -9182,13 +8730,11 @@ task.command("list <project>").description("List a project's tasks with their cu
|
|
|
9182
8730
|
}).join("\n"));
|
|
9183
8731
|
});
|
|
9184
8732
|
var thread = program.command("thread").description("Manage threads");
|
|
9185
|
-
thread.command("open
|
|
8733
|
+
thread.command("open").description("Open a thread on a channel with a title; a task's or a proposal's opens with it").requiredOption("--channel <ref>", "the channel it hangs off").requiredOption("--title <text>", "the thread's title").option("--as <agent>", "act as this agent instead of the user").action(async (opts) => {
|
|
9186
8734
|
const board = await open3();
|
|
9187
8735
|
const opened = await board.openThread(await actorFor(board, opts.as), {
|
|
9188
|
-
|
|
9189
|
-
|
|
9190
|
-
...opts.channel === void 0 ? {} : { channel: opts.channel },
|
|
9191
|
-
...opts.title === void 0 ? {} : { title: opts.title }
|
|
8736
|
+
channel: opts.channel,
|
|
8737
|
+
title: opts.title
|
|
9192
8738
|
});
|
|
9193
8739
|
print(opened, () => `Thread ${opened.id} opened on ${opened.channel}: ${opened.title}`);
|
|
9194
8740
|
});
|