@kubitnodes/stellaris 0.3.23 → 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/dist/cli/index.js +479 -164
- package/dist/server/index.js +598 -191
- package/package.json +1 -1
- package/web/dist/assets/{BackLink-DGYs80bb.js → BackLink-DKMw_rHu.js} +1 -1
- package/web/dist/assets/{Button-f4jgBZY1.js → Button-BYJGqZ3Q.js} +1 -1
- package/web/dist/assets/{ChannelForms-q9zdj4p2.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-CLHaA65A.js → CliIcon-DVX5iLhG.js} +1 -1
- package/web/dist/assets/CronsView-BGCcO7bH.js +1 -0
- package/web/dist/assets/{KnowledgeView-Cxfi2-Pt.js → KnowledgeView-DLLYjd1G.js} +1 -1
- package/web/dist/assets/Markdown--whKJBYV.js +30 -0
- package/web/dist/assets/{MessageItem-Bk9eBp1U.js → MessageItem-BvDOeJci.js} +1 -1
- package/web/dist/assets/{MetricsView-CTEH6rJn.js → MetricsView-B-KUNOZm.js} +1 -1
- package/web/dist/assets/NeedsYouView-Dee_bOKj.js +1 -0
- package/web/dist/assets/{Overview-CS9HCSmm.js → Overview-DFiHDFnA.js} +1 -1
- package/web/dist/assets/{Pane-CJYRVkwh.js → Pane-DTmByqgA.js} +1 -1
- package/web/dist/assets/{Picker-DoxCN6C7.js → Picker-DsHm4f7s.js} +1 -1
- package/web/dist/assets/{ProjectView-CJ3r4UdN.js → ProjectView-DsRXW-qk.js} +1 -1
- package/web/dist/assets/ProposalView-B-4P1S-s.js +1 -0
- package/web/dist/assets/{ProposalsView-B-cOxTsQ.js → ProposalsView-uEuj-ooV.js} +1 -1
- package/web/dist/assets/Quota-D8WDWp5H.js +1 -0
- package/web/dist/assets/{RunnersView-DlQruhwp.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-MVP34ytV.js → StickyList-C4fC_KJv.js} +1 -1
- package/web/dist/assets/SubjectThread-BEXt-tub.js +1 -0
- package/web/dist/assets/{TaskFileView-GZ0eQFUb.js → TaskFileView-B9sex0tS.js} +2 -2
- package/web/dist/assets/{TaskView-DppFelLW.js → TaskView-BCT388kb.js} +2 -2
- package/web/dist/assets/{TasksView-DDNXzeG4.js → TasksView-CuPay34-.js} +1 -1
- package/web/dist/assets/{ThreadForms-C846TvtS.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-BmqQmcZH.js → compose-BUhbVoI-.js} +1 -1
- package/web/dist/assets/format-CvfmDNoY.js +55 -0
- package/web/dist/assets/{governance-Cr5OlQnw.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-D5hNgtg6.js → mentions-D838wlfQ.js} +1 -1
- package/web/dist/assets/{tasks-R3YgpYTe.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/ChannelView-CGs_aWL3.js +0 -2
- package/web/dist/assets/CitizenView-gcIo_sKt.js +0 -2
- package/web/dist/assets/CitizensView-DZ7S8vnJ.js +0 -1
- package/web/dist/assets/CronsView-1wns6Uwv.js +0 -1
- package/web/dist/assets/Markdown-BhtIFcWc.js +0 -30
- package/web/dist/assets/NeedsYouView-2VNqH2Sb.js +0 -1
- package/web/dist/assets/ProposalView-DLn9dNPp.js +0 -2
- package/web/dist/assets/Quota-Bo7UdWTV.js +0 -1
- package/web/dist/assets/SocietyView-Bl4ErnV8.js +0 -1
- package/web/dist/assets/SubjectThread-BFFnHxIF.js +0 -1
- package/web/dist/assets/ThreadItem-C8cou6wD.js +0 -1
- package/web/dist/assets/ThreadView-DvSGDYoD.js +0 -1
- package/web/dist/assets/citizen-B2Dlbcpe.js +0 -1
- package/web/dist/assets/format-DT52SbbR.js +0 -55
- package/web/dist/assets/index-BTVy_xFF.js +0 -23
- package/web/dist/assets/index-DzV6CCnG.css +0 -2
- package/web/dist/assets/useMatch-pEF6RJ6Q.js +0 -1
package/dist/server/index.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
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
12
|
import { z as z16 } from "zod";
|
|
13
13
|
|
|
@@ -235,6 +235,7 @@ var CronFrontmatterSchema = z3.object({
|
|
|
235
235
|
scope: NameSchema,
|
|
236
236
|
thread: UlidSchema.optional(),
|
|
237
237
|
channel: NameSchema.optional(),
|
|
238
|
+
place: NameSchema.optional(),
|
|
238
239
|
schedule: CronScheduleSchema,
|
|
239
240
|
createdBy: NameSchema,
|
|
240
241
|
createdAt: IsoDateTimeSchema,
|
|
@@ -384,15 +385,16 @@ function quotaLevel(quota, now) {
|
|
|
384
385
|
var WINDOW_NAMES = {
|
|
385
386
|
five_hour: "5h",
|
|
386
387
|
seven_day: "7d",
|
|
387
|
-
seven_day_opus: "7d
|
|
388
|
-
seven_day_sonnet: "7d
|
|
388
|
+
seven_day_opus: "7d opus",
|
|
389
|
+
seven_day_sonnet: "7d sonnet",
|
|
389
390
|
seven_day_oauth_apps: "7d apps",
|
|
390
391
|
seven_day_overage_included: "7d extra",
|
|
391
392
|
overage: "extra usage"
|
|
392
393
|
};
|
|
393
394
|
function windowName(window) {
|
|
394
395
|
if (window.label !== void 0) {
|
|
395
|
-
|
|
396
|
+
const label = window.label.toLowerCase();
|
|
397
|
+
return window.minutes === 10080 ? `7d ${label}` : label;
|
|
396
398
|
}
|
|
397
399
|
const named = WINDOW_NAMES[window.key];
|
|
398
400
|
if (named !== void 0) {
|
|
@@ -991,7 +993,8 @@ var DashboardSchema = z5.object({
|
|
|
991
993
|
var ProposalOriginSchema = z5.object({
|
|
992
994
|
scope: NameSchema,
|
|
993
995
|
thread: UlidSchema.optional(),
|
|
994
|
-
channel: NameSchema.optional()
|
|
996
|
+
channel: NameSchema.optional(),
|
|
997
|
+
place: NameSchema.optional()
|
|
995
998
|
});
|
|
996
999
|
var SKILL_FILES_LIMIT = 100;
|
|
997
1000
|
var SKILL_BYTES_LIMIT = 10 * 1024 * 1024;
|
|
@@ -1385,6 +1388,7 @@ var TurnExitReasonSchema = z8.enum([
|
|
|
1385
1388
|
"blocked",
|
|
1386
1389
|
"stopped"
|
|
1387
1390
|
]);
|
|
1391
|
+
var ResumeFailureSchema = z8.enum(["lost", "retry"]);
|
|
1388
1392
|
var AgentEventSchema = z8.discriminatedUnion("type", [
|
|
1389
1393
|
z8.object({
|
|
1390
1394
|
type: z8.literal("turn_started"),
|
|
@@ -1436,6 +1440,8 @@ var LiveTurnEventSchema = z8.object({
|
|
|
1436
1440
|
thread: UlidSchema.optional(),
|
|
1437
1441
|
/** The channel, never general, whose conversation the turn is in. */
|
|
1438
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(),
|
|
1439
1445
|
event: AgentEventSchema
|
|
1440
1446
|
});
|
|
1441
1447
|
|
|
@@ -1538,7 +1544,8 @@ var TurnWorkspaceSchema = z9.discriminatedUnion("kind", [
|
|
|
1538
1544
|
z9.object({
|
|
1539
1545
|
kind: z9.literal("home"),
|
|
1540
1546
|
thread: UlidSchema.optional(),
|
|
1541
|
-
channel: NameSchema.optional()
|
|
1547
|
+
channel: NameSchema.optional(),
|
|
1548
|
+
place: NameSchema.optional()
|
|
1542
1549
|
}),
|
|
1543
1550
|
z9.object({
|
|
1544
1551
|
kind: z9.literal("project"),
|
|
@@ -1547,7 +1554,16 @@ var TurnWorkspaceSchema = z9.discriminatedUnion("kind", [
|
|
|
1547
1554
|
thread: UlidSchema.optional(),
|
|
1548
1555
|
channel: NameSchema.optional()
|
|
1549
1556
|
}),
|
|
1550
|
-
z9.object({
|
|
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
|
+
})
|
|
1551
1567
|
]);
|
|
1552
1568
|
var TurnJobSchema = z9.object({
|
|
1553
1569
|
turnId: UlidSchema,
|
|
@@ -1563,6 +1579,8 @@ var TurnJobSchema = z9.object({
|
|
|
1563
1579
|
thread: UlidSchema.optional(),
|
|
1564
1580
|
/** The channel, never general, whose conversation the turn is in. */
|
|
1565
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(),
|
|
1566
1584
|
/**
|
|
1567
1585
|
* The session to resume, or null to start one: the CLI's adapter picks a fresh session's id, and
|
|
1568
1586
|
* the outcome reports it.
|
|
@@ -1583,6 +1601,12 @@ var TurnJobSchema = z9.object({
|
|
|
1583
1601
|
workspace: TurnWorkspaceSchema
|
|
1584
1602
|
});
|
|
1585
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
|
+
});
|
|
1586
1610
|
var TurnEventsSchema = z9.object({ entries: z9.array(TranscriptEntrySchema) });
|
|
1587
1611
|
var TurnOutcomeSchema = z9.object({
|
|
1588
1612
|
exitReason: TurnExitReasonSchema,
|
|
@@ -1592,8 +1616,18 @@ var TurnOutcomeSchema = z9.object({
|
|
|
1592
1616
|
costUsd: z9.number().nonnegative(),
|
|
1593
1617
|
/** The session's running total after this turn, for CLIs that report one. */
|
|
1594
1618
|
sessionCostUsd: z9.number().nonnegative().optional(),
|
|
1595
|
-
/**
|
|
1596
|
-
|
|
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(),
|
|
1597
1631
|
/** The model the CLI reported, when it reported one. */
|
|
1598
1632
|
model: z9.string().nullable(),
|
|
1599
1633
|
work: TurnWorkSchema.nullable(),
|
|
@@ -1602,6 +1636,11 @@ var TurnOutcomeSchema = z9.object({
|
|
|
1602
1636
|
* branch after the hand-back: uncommitted changes, or commits no branch contains.
|
|
1603
1637
|
*/
|
|
1604
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(),
|
|
1605
1644
|
/**
|
|
1606
1645
|
* The CLI's plan quota as the turn's stream last reported it, when it reported any; a runner from
|
|
1607
1646
|
* before never sends it.
|
|
@@ -1720,13 +1759,17 @@ var TurnImageSchema = z9.discriminatedUnion("kind", [
|
|
|
1720
1759
|
]);
|
|
1721
1760
|
var WorkspaceConversationSchema = z9.discriminatedUnion("kind", [
|
|
1722
1761
|
z9.object({ kind: z9.literal("thread"), id: UlidSchema }),
|
|
1723
|
-
z9.object({ kind: z9.literal("channel"), scope: NameSchema, name: NameSchema })
|
|
1762
|
+
z9.object({ kind: z9.literal("channel"), scope: NameSchema, name: NameSchema }),
|
|
1763
|
+
z9.object({ kind: z9.literal("place"), project: NameSchema })
|
|
1724
1764
|
]);
|
|
1725
1765
|
function workspaceConversationOf(turn) {
|
|
1726
1766
|
if (turn.thread !== void 0) {
|
|
1727
1767
|
return { kind: "thread", id: turn.thread };
|
|
1728
1768
|
}
|
|
1729
|
-
|
|
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 };
|
|
1730
1773
|
}
|
|
1731
1774
|
var HeldWorkspaceSchema = z9.object({
|
|
1732
1775
|
agent: NameSchema,
|
|
@@ -1832,6 +1875,7 @@ var TurnDispatchSchema = z10.object({
|
|
|
1832
1875
|
project: NameSchema,
|
|
1833
1876
|
thread: TurnThreadSchema.optional(),
|
|
1834
1877
|
channel: NameSchema.optional(),
|
|
1878
|
+
place: NameSchema.optional(),
|
|
1835
1879
|
trigger: TriggerSchema,
|
|
1836
1880
|
priority: z10.number().int().min(0).max(2),
|
|
1837
1881
|
onboarding: z10.boolean().default(false),
|
|
@@ -1839,7 +1883,13 @@ var TurnDispatchSchema = z10.object({
|
|
|
1839
1883
|
crons: z10.array(UlidSchema).optional()
|
|
1840
1884
|
});
|
|
1841
1885
|
function conversationPart(conversation) {
|
|
1842
|
-
|
|
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}`;
|
|
1843
1893
|
}
|
|
1844
1894
|
function sessionKey(agent, scope, part) {
|
|
1845
1895
|
return part === void 0 ? `${agent}/${scope}` : `${agent}/${scope}/${part}`;
|
|
@@ -1852,7 +1902,10 @@ function parseSessionKey(key) {
|
|
|
1852
1902
|
if (part === void 0) {
|
|
1853
1903
|
return { agent, scope };
|
|
1854
1904
|
}
|
|
1855
|
-
|
|
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 };
|
|
1856
1909
|
}
|
|
1857
1910
|
function channelConversation(scope, channel) {
|
|
1858
1911
|
const at = channel.indexOf("/");
|
|
@@ -1860,6 +1913,10 @@ function channelConversation(scope, channel) {
|
|
|
1860
1913
|
const name = at < 0 ? channel : channel.slice(at + 1);
|
|
1861
1914
|
return place === scope && name !== "general" ? name : void 0;
|
|
1862
1915
|
}
|
|
1916
|
+
function placeConversation(scope, channel) {
|
|
1917
|
+
const at = channel.indexOf("/");
|
|
1918
|
+
return scope === SOCIETY_SCOPE && at >= 0 ? channel.slice(0, at) : void 0;
|
|
1919
|
+
}
|
|
1863
1920
|
var TurnRecordSchema = z10.object({
|
|
1864
1921
|
/** Assigned by the board when the turn begins; the turn's transcript is filed under it. */
|
|
1865
1922
|
id: UlidSchema.optional(),
|
|
@@ -1869,6 +1926,8 @@ var TurnRecordSchema = z10.object({
|
|
|
1869
1926
|
thread: UlidSchema.optional(),
|
|
1870
1927
|
/** The channel, never general, whose conversation the turn was in. */
|
|
1871
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(),
|
|
1872
1931
|
runner: NameSchema,
|
|
1873
1932
|
cli: CliKindSchema.nullable(),
|
|
1874
1933
|
session: z10.string().nullable(),
|
|
@@ -1892,6 +1951,12 @@ var TurnRecordSchema = z10.object({
|
|
|
1892
1951
|
work: TurnWorkSchema.optional(),
|
|
1893
1952
|
/** Who stopped the turn, for one that ended `stopped`. */
|
|
1894
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(),
|
|
1895
1960
|
/** The crons whose fire started the turn, which record how it ended. */
|
|
1896
1961
|
crons: z10.array(UlidSchema).optional()
|
|
1897
1962
|
});
|
|
@@ -1901,6 +1966,7 @@ var RunningTurnSchema = z10.object({
|
|
|
1901
1966
|
scope: NameSchema,
|
|
1902
1967
|
thread: UlidSchema.optional(),
|
|
1903
1968
|
channel: NameSchema.optional(),
|
|
1969
|
+
place: NameSchema.optional(),
|
|
1904
1970
|
cli: CliKindSchema,
|
|
1905
1971
|
askedIn: ChannelRefSchema.optional(),
|
|
1906
1972
|
steerable: z10.boolean(),
|
|
@@ -1931,6 +1997,8 @@ var TurnHistoryEntrySchema = z10.object({
|
|
|
1931
1997
|
thread: UlidSchema.optional(),
|
|
1932
1998
|
/** The channel, never general, whose conversation the turn was in. */
|
|
1933
1999
|
channel: NameSchema.optional(),
|
|
2000
|
+
/** The project whose channels' conversation the turn was in, for a turn in the society scope. */
|
|
2001
|
+
place: NameSchema.optional(),
|
|
1934
2002
|
trigger: z10.string(),
|
|
1935
2003
|
exitReason: z10.string().nullable(),
|
|
1936
2004
|
costUsd: z10.number(),
|
|
@@ -2178,7 +2246,7 @@ var RunnerConfigSchema = z13.object({
|
|
|
2178
2246
|
});
|
|
2179
2247
|
|
|
2180
2248
|
// packages/shared/dist/version.js
|
|
2181
|
-
var STELLARIS_VERSION = "0.3.
|
|
2249
|
+
var STELLARIS_VERSION = "0.3.24";
|
|
2182
2250
|
|
|
2183
2251
|
// packages/shared/dist/mentions.js
|
|
2184
2252
|
import { parse, postprocess, preprocess } from "micromark";
|
|
@@ -2562,6 +2630,7 @@ var HOME_GITIGNORE = [
|
|
|
2562
2630
|
"/projects/*/last-turn.json",
|
|
2563
2631
|
"/projects/*/threads/",
|
|
2564
2632
|
"/projects/*/channels/",
|
|
2633
|
+
"/projects/*/places/",
|
|
2565
2634
|
`/${HOME_SCRATCH}/`,
|
|
2566
2635
|
"*.tmp",
|
|
2567
2636
|
"node_modules/",
|
|
@@ -2579,7 +2648,7 @@ var HOME_GITIGNORE = [
|
|
|
2579
2648
|
var IGNORE_FILE = `${HOME_GITIGNORE.join("\n")}
|
|
2580
2649
|
`;
|
|
2581
2650
|
var PRE_RECEIVE = `#!/bin/sh
|
|
2582
|
-
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/))'
|
|
2583
2652
|
while read old new ref; do
|
|
2584
2653
|
if [ "$ref" != "refs/heads/main" ]; then
|
|
2585
2654
|
echo "a home takes pushes to main only" >&2
|
|
@@ -2905,6 +2974,7 @@ var TurnPayload = z14.object({
|
|
|
2905
2974
|
project: z14.string(),
|
|
2906
2975
|
thread: z14.string().optional(),
|
|
2907
2976
|
channel: z14.string().optional(),
|
|
2977
|
+
place: z14.string().optional(),
|
|
2908
2978
|
trigger: z14.string()
|
|
2909
2979
|
});
|
|
2910
2980
|
var TaskPayload = z14.object({
|
|
@@ -2950,11 +3020,16 @@ function answers(mention, turn) {
|
|
|
2950
3020
|
if (!mention.scopes.includes(turn.project)) {
|
|
2951
3021
|
return false;
|
|
2952
3022
|
}
|
|
2953
|
-
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;
|
|
2954
3026
|
if (turn.channel !== null) {
|
|
2955
|
-
return
|
|
3027
|
+
return topLevel && own === turn.channel;
|
|
2956
3028
|
}
|
|
2957
|
-
|
|
3029
|
+
if (turn.place !== null) {
|
|
3030
|
+
return topLevel && place === turn.place;
|
|
3031
|
+
}
|
|
3032
|
+
return topLevel && own === void 0 && place === void 0 || turn.trigger === "mention";
|
|
2958
3033
|
}
|
|
2959
3034
|
function computeMetrics(input) {
|
|
2960
3035
|
const { events, now, window } = input;
|
|
@@ -3020,6 +3095,7 @@ function computeMetrics(input) {
|
|
|
3020
3095
|
at,
|
|
3021
3096
|
thread: turn.data.thread ?? null,
|
|
3022
3097
|
channel: turn.data.channel ?? null,
|
|
3098
|
+
place: turn.data.place ?? null,
|
|
3023
3099
|
project: turn.data.project,
|
|
3024
3100
|
trigger: turn.data.trigger
|
|
3025
3101
|
};
|
|
@@ -3219,6 +3295,7 @@ var TurnHistoryPayloadSchema = z15.object({
|
|
|
3219
3295
|
project: NameSchema,
|
|
3220
3296
|
thread: UlidSchema.optional(),
|
|
3221
3297
|
channel: NameSchema.optional(),
|
|
3298
|
+
place: NameSchema.optional(),
|
|
3222
3299
|
trigger: z15.string().default("manual"),
|
|
3223
3300
|
exitReason: z15.string().nullable().default(null),
|
|
3224
3301
|
costUsd: z15.number().default(0),
|
|
@@ -3457,11 +3534,14 @@ var BoardPaths = class {
|
|
|
3457
3534
|
return path5.join(this.agentProjects(name), slug);
|
|
3458
3535
|
}
|
|
3459
3536
|
/**
|
|
3460
|
-
* A thread's
|
|
3461
|
-
* 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`.
|
|
3462
3539
|
*/
|
|
3463
3540
|
agentConversation(name, slug, part) {
|
|
3464
|
-
|
|
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);
|
|
3465
3545
|
}
|
|
3466
3546
|
agentCursors(name) {
|
|
3467
3547
|
return path5.join(this.agent(name), "cursors.json");
|
|
@@ -3495,6 +3575,101 @@ var BoardPaths = class {
|
|
|
3495
3575
|
}
|
|
3496
3576
|
};
|
|
3497
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
|
+
|
|
3498
3673
|
// packages/board-core/dist/tokens.js
|
|
3499
3674
|
import { createHash as createHash2, randomBytes } from "node:crypto";
|
|
3500
3675
|
function mintToken() {
|
|
@@ -3508,7 +3683,7 @@ function hashToken(token) {
|
|
|
3508
3683
|
var SYSTEM_ACTOR = { name: "board", role: USER_ROLE };
|
|
3509
3684
|
var CLOSING_ATTEMPTS = 3;
|
|
3510
3685
|
function ownTurn(actor, turn) {
|
|
3511
|
-
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;
|
|
3512
3687
|
}
|
|
3513
3688
|
function namesList(names) {
|
|
3514
3689
|
return names.length <= 1 ? names[0] ?? "" : `${names.slice(0, -1).join(", ")} and ${names.at(-1) ?? ""}`;
|
|
@@ -3533,23 +3708,37 @@ var CursorsSchema = z16.object({
|
|
|
3533
3708
|
scopes: z16.record(z16.string(), z16.string()).default({}),
|
|
3534
3709
|
threadsFrom: z16.record(z16.string(), z16.string().nullable()).default({}),
|
|
3535
3710
|
channelsFrom: z16.record(z16.string(), z16.string().nullable()).default({}),
|
|
3711
|
+
placesFrom: z16.record(z16.string(), z16.string().nullable()).default({}),
|
|
3536
3712
|
/** How far a work role has read news, which goes to whichever of its general conversations reads first. */
|
|
3537
3713
|
news: z16.string().optional()
|
|
3538
3714
|
});
|
|
3539
|
-
var NO_CURSORS = {
|
|
3715
|
+
var NO_CURSORS = {
|
|
3716
|
+
digest: null,
|
|
3717
|
+
scopes: {},
|
|
3718
|
+
threadsFrom: {},
|
|
3719
|
+
channelsFrom: {},
|
|
3720
|
+
placesFrom: {}
|
|
3721
|
+
};
|
|
3540
3722
|
function conversationKey(scope, part) {
|
|
3541
3723
|
return part === null ? scope : `${scope}/${part}`;
|
|
3542
3724
|
}
|
|
3543
3725
|
function channelOfPart(part) {
|
|
3544
3726
|
return part?.startsWith("#") === true ? part.slice(1) : void 0;
|
|
3545
3727
|
}
|
|
3728
|
+
function placeOfPart(part) {
|
|
3729
|
+
return part?.startsWith("~") === true ? part.slice(1) : void 0;
|
|
3730
|
+
}
|
|
3546
3731
|
function threadOfPart(part) {
|
|
3547
|
-
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;
|
|
3548
3733
|
}
|
|
3549
3734
|
function channelStart(cursors, scope) {
|
|
3550
3735
|
const from = cursors.channelsFrom[scope];
|
|
3551
3736
|
return from === void 0 ? cursors.digest : from;
|
|
3552
3737
|
}
|
|
3738
|
+
function placeStart(cursors, scope) {
|
|
3739
|
+
const from = cursors.placesFrom[scope];
|
|
3740
|
+
return from === void 0 ? cursors.digest : from;
|
|
3741
|
+
}
|
|
3553
3742
|
function threadStart(cursors, scope) {
|
|
3554
3743
|
const from = cursors.threadsFrom[scope];
|
|
3555
3744
|
return from === void 0 ? cursors.digest : from;
|
|
@@ -3565,7 +3754,7 @@ function cursorOf(cursors, scope, part = null) {
|
|
|
3565
3754
|
if (part === null) {
|
|
3566
3755
|
return cursors.scopes[scope] ?? cursors.digest;
|
|
3567
3756
|
}
|
|
3568
|
-
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));
|
|
3569
3758
|
}
|
|
3570
3759
|
function byId(a, b) {
|
|
3571
3760
|
return a.message.id < b.message.id ? -1 : a.message.id > b.message.id ? 1 : 0;
|
|
@@ -3573,12 +3762,14 @@ function byId(a, b) {
|
|
|
3573
3762
|
function turnTime(record) {
|
|
3574
3763
|
return record.endedAt ?? record.startedAt;
|
|
3575
3764
|
}
|
|
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
|
+
}
|
|
3576
3768
|
var PausedSchema = z16.object({ paused: z16.boolean() });
|
|
3577
3769
|
var DEFAULT_LEASE_MS = 30 * 60 * 1e3;
|
|
3578
3770
|
function withoutMentions(text) {
|
|
3579
3771
|
return text.replaceAll(MENTION_PATTERN, "$1$2");
|
|
3580
3772
|
}
|
|
3581
|
-
var SKILL_TEXT_LIMIT_CHARS = 1e5;
|
|
3582
3773
|
function skillFolderOf(proposal) {
|
|
3583
3774
|
if (proposal.kind !== "skill") {
|
|
3584
3775
|
return null;
|
|
@@ -3587,7 +3778,7 @@ function skillFolderOf(proposal) {
|
|
|
3587
3778
|
return parsed.success && parsed.data.body === void 0 ? parsed.data.name : null;
|
|
3588
3779
|
}
|
|
3589
3780
|
function proposedFrom(origin, where) {
|
|
3590
|
-
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;
|
|
3591
3782
|
}
|
|
3592
3783
|
function megabytes(bytes) {
|
|
3593
3784
|
return (bytes / (1024 * 1024)).toFixed(1).replace(/\.0$/, "");
|
|
@@ -3609,6 +3800,7 @@ var THREAD_CONVERSATIONS = "thread-conversations";
|
|
|
3609
3800
|
var ASKS_CHANNEL_OPENED = "asks-channel-opened";
|
|
3610
3801
|
var KNOWLEDGE_REMOVAL = "knowledge-removal-granted";
|
|
3611
3802
|
var CHANNEL_CONVERSATIONS = "channel-conversations";
|
|
3803
|
+
var PLACE_CONVERSATIONS = "place-conversations";
|
|
3612
3804
|
var DASHBOARD_UPDATES = "dashboard-updates-granted";
|
|
3613
3805
|
var CRON_VERBS = "cron-verbs-granted";
|
|
3614
3806
|
var KNOWLEDGE_NOTICES = "knowledge-notices";
|
|
@@ -3683,8 +3875,8 @@ function restage(existing, edit) {
|
|
|
3683
3875
|
};
|
|
3684
3876
|
}
|
|
3685
3877
|
function reopenStage(stage) {
|
|
3686
|
-
const { completedBy: _by, completedAt: _at, ...
|
|
3687
|
-
return
|
|
3878
|
+
const { completedBy: _by, completedAt: _at, ...open3 } = stage;
|
|
3879
|
+
return open3;
|
|
3688
3880
|
}
|
|
3689
3881
|
function gateChanges(before, after) {
|
|
3690
3882
|
const changes = [];
|
|
@@ -3765,7 +3957,7 @@ var Board = class _Board {
|
|
|
3765
3957
|
homes;
|
|
3766
3958
|
constructor(dataDir, options) {
|
|
3767
3959
|
this.paths = new BoardPaths(dataDir);
|
|
3768
|
-
this.homes = new HomeRepos(
|
|
3960
|
+
this.homes = new HomeRepos(path7.join(this.paths.state(), "home-hooks"));
|
|
3769
3961
|
this.leaseMs = options.leaseMs ?? DEFAULT_LEASE_MS;
|
|
3770
3962
|
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
3771
3963
|
this.events = new EventLog(this.paths.eventLog(), () => this.newId(), this.now);
|
|
@@ -3798,7 +3990,7 @@ var Board = class _Board {
|
|
|
3798
3990
|
* repeated, so a member that later chooses otherwise keeps its choice.
|
|
3799
3991
|
*/
|
|
3800
3992
|
async applyAlignments() {
|
|
3801
|
-
const file =
|
|
3993
|
+
const file = path7.join(this.paths.state(), "alignments.json");
|
|
3802
3994
|
const { applied } = await exists(file) ? await readJson(file, AlignmentsSchema) : { applied: [] };
|
|
3803
3995
|
const pending = [
|
|
3804
3996
|
FOLLOW_DECISIONS,
|
|
@@ -3814,7 +4006,8 @@ var Board = class _Board {
|
|
|
3814
4006
|
DASHBOARD_UPDATES,
|
|
3815
4007
|
CRON_VERBS,
|
|
3816
4008
|
KNOWLEDGE_NOTICES,
|
|
3817
|
-
IMAGE_UPLOADS
|
|
4009
|
+
IMAGE_UPLOADS,
|
|
4010
|
+
PLACE_CONVERSATIONS
|
|
3818
4011
|
].filter((name) => !applied.includes(name));
|
|
3819
4012
|
if (pending.length === 0) {
|
|
3820
4013
|
return;
|
|
@@ -3862,6 +4055,9 @@ var Board = class _Board {
|
|
|
3862
4055
|
if (pending.includes(IMAGE_UPLOADS)) {
|
|
3863
4056
|
await this.grantBeside("post_message", "upload_image");
|
|
3864
4057
|
}
|
|
4058
|
+
if (pending.includes(PLACE_CONVERSATIONS)) {
|
|
4059
|
+
await this.startPlaceCursors();
|
|
4060
|
+
}
|
|
3865
4061
|
await writeJson(file, { applied: [...applied, ...pending] });
|
|
3866
4062
|
}
|
|
3867
4063
|
/**
|
|
@@ -4066,7 +4262,7 @@ var Board = class _Board {
|
|
|
4066
4262
|
}
|
|
4067
4263
|
}
|
|
4068
4264
|
for (const file of await listFiles(this.paths.tasks(slug))) {
|
|
4069
|
-
const raw = await readMarkdown(
|
|
4265
|
+
const raw = await readMarkdown(path7.join(this.paths.tasks(slug), file), z16.record(z16.string(), z16.unknown()));
|
|
4070
4266
|
const legacy = raw.data["thread"];
|
|
4071
4267
|
if (legacy === void 0) {
|
|
4072
4268
|
continue;
|
|
@@ -4074,7 +4270,7 @@ var Board = class _Board {
|
|
|
4074
4270
|
const task = { ...TaskFrontmatterSchema.parse(raw.data), body: raw.body };
|
|
4075
4271
|
if ((legacy === "open" || legacy === "closed") && !await exists(this.paths.threadFile(threads, task.id))) {
|
|
4076
4272
|
const ended = task.status === "done" || task.status === "abandoned";
|
|
4077
|
-
const
|
|
4273
|
+
const open3 = legacy === "open" && !ended;
|
|
4078
4274
|
const openedBy = opened.get(task.id);
|
|
4079
4275
|
const closedBy = closed.get(task.id);
|
|
4080
4276
|
await this.writeThread(threads, {
|
|
@@ -4082,10 +4278,10 @@ var Board = class _Board {
|
|
|
4082
4278
|
channel: channelRef(slug, "general"),
|
|
4083
4279
|
title: task.title,
|
|
4084
4280
|
subject: { kind: "task", id: task.id },
|
|
4085
|
-
state:
|
|
4281
|
+
state: open3 ? "open" : "closed",
|
|
4086
4282
|
openedBy: openedBy?.actor ?? task.createdBy,
|
|
4087
4283
|
openedAt: openedBy?.ts ?? task.createdAt,
|
|
4088
|
-
...
|
|
4284
|
+
...open3 ? {} : {
|
|
4089
4285
|
closedBy: closedBy?.actor ?? SYSTEM_ACTOR.name,
|
|
4090
4286
|
closedAt: closedBy?.ts ?? this.now().toISOString()
|
|
4091
4287
|
},
|
|
@@ -4117,6 +4313,24 @@ var Board = class _Board {
|
|
|
4117
4313
|
await writeJson(file, { ...cursors, channelsFrom });
|
|
4118
4314
|
}
|
|
4119
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
|
+
}
|
|
4120
4334
|
/**
|
|
4121
4335
|
* Threads were once read in the scope's single conversation, so a member had read every thread
|
|
4122
4336
|
* up to its scope's cursor. Each scope's threads start from that cursor now, as each thread's
|
|
@@ -4257,13 +4471,15 @@ ${charter.purpose}
|
|
|
4257
4471
|
const token = mintToken();
|
|
4258
4472
|
const thread = threadOfPart(part);
|
|
4259
4473
|
const channel = channelOfPart(part);
|
|
4474
|
+
const place = placeOfPart(part);
|
|
4260
4475
|
this.turnTokens.set(hashToken(token), {
|
|
4261
4476
|
actor: {
|
|
4262
4477
|
name: agent,
|
|
4263
4478
|
role,
|
|
4264
4479
|
...scope === void 0 ? {} : { scope },
|
|
4265
4480
|
...thread === void 0 ? {} : { thread },
|
|
4266
|
-
...channel === void 0 ? {} : { channel }
|
|
4481
|
+
...channel === void 0 ? {} : { channel },
|
|
4482
|
+
...place === void 0 ? {} : { place }
|
|
4267
4483
|
},
|
|
4268
4484
|
expiresAt: this.now().getTime() + ttlMs
|
|
4269
4485
|
});
|
|
@@ -4352,7 +4568,7 @@ ${charter.purpose}
|
|
|
4352
4568
|
});
|
|
4353
4569
|
}
|
|
4354
4570
|
signInsFile() {
|
|
4355
|
-
return
|
|
4571
|
+
return path7.join(this.paths.state(), "sign-ins.json");
|
|
4356
4572
|
}
|
|
4357
4573
|
async readSignIns() {
|
|
4358
4574
|
const file = this.signInsFile();
|
|
@@ -4449,7 +4665,7 @@ ${charter.purpose}
|
|
|
4449
4665
|
const files = await listFiles(this.paths.tasks(project));
|
|
4450
4666
|
const tasks = [];
|
|
4451
4667
|
for (const file of files) {
|
|
4452
|
-
const doc = await readMarkdown(
|
|
4668
|
+
const doc = await readMarkdown(path7.join(this.paths.tasks(project), file), TaskFrontmatterSchema);
|
|
4453
4669
|
tasks.push({ ...doc.data, body: doc.body });
|
|
4454
4670
|
}
|
|
4455
4671
|
return tasks;
|
|
@@ -4468,7 +4684,7 @@ ${charter.purpose}
|
|
|
4468
4684
|
const files = await listFiles(this.paths.proposals());
|
|
4469
4685
|
const proposals = [];
|
|
4470
4686
|
for (const file of files) {
|
|
4471
|
-
const doc = await readMarkdown(
|
|
4687
|
+
const doc = await readMarkdown(path7.join(this.paths.proposals(), file), ProposalFrontmatterSchema);
|
|
4472
4688
|
proposals.push({ ...doc.data, body: doc.body });
|
|
4473
4689
|
}
|
|
4474
4690
|
return proposals;
|
|
@@ -4495,7 +4711,7 @@ ${charter.purpose}
|
|
|
4495
4711
|
async listMembers() {
|
|
4496
4712
|
const members = [];
|
|
4497
4713
|
for (const file of await listFiles(this.paths.members())) {
|
|
4498
|
-
const doc = await readMarkdown(
|
|
4714
|
+
const doc = await readMarkdown(path7.join(this.paths.members(), file), MemberSchema);
|
|
4499
4715
|
members.push({ ...doc.data, profile: doc.body });
|
|
4500
4716
|
}
|
|
4501
4717
|
return members;
|
|
@@ -4514,7 +4730,7 @@ ${charter.purpose}
|
|
|
4514
4730
|
const dir = project === null ? this.paths.societyKnowledge() : this.paths.projectKnowledge(project);
|
|
4515
4731
|
const topics = [];
|
|
4516
4732
|
for (const file of await listFiles(dir)) {
|
|
4517
|
-
topics.push(await this.knowledgeAt(
|
|
4733
|
+
topics.push(await this.knowledgeAt(path7.join(dir, file), project, file.replace(/\.md$/, "")));
|
|
4518
4734
|
}
|
|
4519
4735
|
return topics;
|
|
4520
4736
|
}
|
|
@@ -4544,7 +4760,7 @@ ${charter.purpose}
|
|
|
4544
4760
|
const aside = this.paths.removedKnowledge(project ?? SOCIETY_SCOPE);
|
|
4545
4761
|
let highest = 0;
|
|
4546
4762
|
for (const file of (await listFiles(aside)).filter((name) => name.startsWith(`${topic}.`))) {
|
|
4547
|
-
const copy = await this.knowledgeAt(
|
|
4763
|
+
const copy = await this.knowledgeAt(path7.join(aside, file), project, topic);
|
|
4548
4764
|
highest = Math.max(highest, copy.revision);
|
|
4549
4765
|
}
|
|
4550
4766
|
return highest;
|
|
@@ -4560,7 +4776,7 @@ ${charter.purpose}
|
|
|
4560
4776
|
async listSkillsIn(dir, scope) {
|
|
4561
4777
|
const skills = [];
|
|
4562
4778
|
for (const entry of await listDirs(dir)) {
|
|
4563
|
-
const file =
|
|
4779
|
+
const file = path7.join(dir, entry, "SKILL.md");
|
|
4564
4780
|
if (!await exists(file)) {
|
|
4565
4781
|
continue;
|
|
4566
4782
|
}
|
|
@@ -4650,7 +4866,7 @@ ${charter.purpose}
|
|
|
4650
4866
|
if (!wanted.delete(file.slice(0, 26))) {
|
|
4651
4867
|
continue;
|
|
4652
4868
|
}
|
|
4653
|
-
const { data } = await readMarkdown(
|
|
4869
|
+
const { data } = await readMarkdown(path7.join(dir, file), MessageFrontmatterSchema);
|
|
4654
4870
|
found.push(MessageRefSchema.parse(data));
|
|
4655
4871
|
}
|
|
4656
4872
|
if (wanted.size === 0) {
|
|
@@ -4682,7 +4898,7 @@ ${charter.purpose}
|
|
|
4682
4898
|
if (!file.endsWith(".md")) {
|
|
4683
4899
|
continue;
|
|
4684
4900
|
}
|
|
4685
|
-
const doc = await readMarkdown(
|
|
4901
|
+
const doc = await readMarkdown(path7.join(threads, file), ThreadFrontmatterSchema);
|
|
4686
4902
|
if (doc.data.state !== "open") {
|
|
4687
4903
|
continue;
|
|
4688
4904
|
}
|
|
@@ -4709,7 +4925,7 @@ ${charter.purpose}
|
|
|
4709
4925
|
const all = [];
|
|
4710
4926
|
for (const threads of await this.threadScopes()) {
|
|
4711
4927
|
for (const file of await listFiles(threads)) {
|
|
4712
|
-
const doc = await readMarkdown(
|
|
4928
|
+
const doc = await readMarkdown(path7.join(threads, file), ThreadFrontmatterSchema);
|
|
4713
4929
|
const messages = await listFiles(this.paths.threadMessages(threads, doc.data.id));
|
|
4714
4930
|
const last = messages.at(-1);
|
|
4715
4931
|
all.push({
|
|
@@ -5076,16 +5292,20 @@ ${DASHBOARD_LINE}
|
|
|
5076
5292
|
}
|
|
5077
5293
|
/**
|
|
5078
5294
|
* How many unread digest messages a member has in each conversation: a scope's home, for its
|
|
5079
|
-
* 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
|
|
5080
5297
|
* messages are and nowhere else. Society channels count only for a member woken in the society
|
|
5081
5298
|
* scope; a project member reads them at its next turn. Two kinds of post are in the digest but not
|
|
5082
5299
|
* counted, and are read when the member wakes for something else: a task step's, which wakes
|
|
5083
5300
|
* whoever it hands work to, and the board's own, operations signals and landing announcements,
|
|
5084
|
-
* 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.
|
|
5085
5304
|
*/
|
|
5086
5305
|
async unreadByConversation(actor) {
|
|
5087
5306
|
const { cursors, upto } = await this.digestSnapshot(actor.name);
|
|
5088
5307
|
const counts = /* @__PURE__ */ new Map();
|
|
5308
|
+
const closed = /* @__PURE__ */ new Map();
|
|
5089
5309
|
const unscoped = { name: actor.name, role: actor.role };
|
|
5090
5310
|
for (const { scope, part, message, news } of await this.digestSince(unscoped, cursors, upto)) {
|
|
5091
5311
|
if (news === true || message.step !== void 0 || message.author === SYSTEM_ACTOR.name) {
|
|
@@ -5095,11 +5315,23 @@ ${DASHBOARD_LINE}
|
|
|
5095
5315
|
if (posted !== scope && scope !== SOCIETY_SCOPE) {
|
|
5096
5316
|
continue;
|
|
5097
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
|
+
}
|
|
5098
5329
|
const key = conversationKey(scope, part);
|
|
5099
5330
|
const entry = counts.get(key) ?? {
|
|
5100
5331
|
scope,
|
|
5101
|
-
thread:
|
|
5332
|
+
thread: thread ?? null,
|
|
5102
5333
|
channel: channelOfPart(part) ?? null,
|
|
5334
|
+
place: placeOfPart(part) ?? null,
|
|
5103
5335
|
count: 0
|
|
5104
5336
|
};
|
|
5105
5337
|
counts.set(key, { ...entry, count: entry.count + 1 });
|
|
@@ -5157,8 +5389,9 @@ ${DASHBOARD_LINE}
|
|
|
5157
5389
|
* thread it takes part in, and for the front desk, which every post by the user wakes, every post
|
|
5158
5390
|
* by the user wherever it is. With a conversation, only what is filed there: for a thread, every
|
|
5159
5391
|
* message of the thread, since the conversation is about it; for a channel's, that channel's
|
|
5160
|
-
* top-level messages; for a
|
|
5161
|
-
*
|
|
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
|
|
5162
5395
|
* `upto` counts (see `digestSnapshot`).
|
|
5163
5396
|
*/
|
|
5164
5397
|
async digestSince(actor, cursors, upto, conversation) {
|
|
@@ -5178,11 +5411,17 @@ ${DASHBOARD_LINE}
|
|
|
5178
5411
|
const frontDesk = charter.wakeTriggers.includes(FRONT_DESK_TRIGGER);
|
|
5179
5412
|
const threadParticipation = /* @__PURE__ */ new Map();
|
|
5180
5413
|
const openChannels = /* @__PURE__ */ new Map();
|
|
5414
|
+
const liveProjects = /* @__PURE__ */ new Map();
|
|
5181
5415
|
const collected = [];
|
|
5182
5416
|
const newsFrom = charter.societyScope ? void 0 : newsStart(cursors);
|
|
5183
5417
|
const scopes = conversation !== void 0 ? [conversation.scope] : [...agent.memberships, SOCIETY_SCOPE];
|
|
5184
5418
|
const starts = [
|
|
5185
|
-
...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)]),
|
|
5186
5425
|
...newsFrom === void 0 ? [] : [newsFrom]
|
|
5187
5426
|
];
|
|
5188
5427
|
const known = starts.filter((start) => start !== null);
|
|
@@ -5192,7 +5431,7 @@ ${DASHBOARD_LINE}
|
|
|
5192
5431
|
const thread = message.thread ?? null;
|
|
5193
5432
|
const asked = message.mentions.includes(actor.name) || frontDesk && message.author === USER_NAME;
|
|
5194
5433
|
if (newsFrom !== void 0 && thread === null && filedIn === SOCIETY_SCOPE && !asked) {
|
|
5195
|
-
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) {
|
|
5196
5435
|
collected.push({
|
|
5197
5436
|
scope: conversation?.scope ?? SOCIETY_SCOPE,
|
|
5198
5437
|
part: null,
|
|
@@ -5203,12 +5442,18 @@ ${DASHBOARD_LINE}
|
|
|
5203
5442
|
continue;
|
|
5204
5443
|
}
|
|
5205
5444
|
const named = thread === null ? channelConversation(filedIn, message.channel) : void 0;
|
|
5206
|
-
let
|
|
5207
|
-
if (named !== void 0 &&
|
|
5208
|
-
|
|
5209
|
-
openChannels.set(message.channel,
|
|
5210
|
-
}
|
|
5211
|
-
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);
|
|
5212
5457
|
if (conversation !== void 0 && (filedIn !== conversation.scope || part !== conversation.part)) {
|
|
5213
5458
|
continue;
|
|
5214
5459
|
}
|
|
@@ -5273,7 +5518,7 @@ ${task.body}`;
|
|
|
5273
5518
|
}
|
|
5274
5519
|
}
|
|
5275
5520
|
for (const file of await listFiles(this.paths.projectKnowledge(project))) {
|
|
5276
|
-
const content = await readFile4(
|
|
5521
|
+
const content = await readFile4(path7.join(this.paths.projectKnowledge(project), file), "utf8");
|
|
5277
5522
|
if (content.toLowerCase().includes(needle)) {
|
|
5278
5523
|
hits.push({
|
|
5279
5524
|
kind: "knowledge",
|
|
@@ -5294,7 +5539,7 @@ ${task.body}`;
|
|
|
5294
5539
|
shared.push({
|
|
5295
5540
|
kind: "knowledge",
|
|
5296
5541
|
ref: `society/knowledge/${file}`,
|
|
5297
|
-
file:
|
|
5542
|
+
file: path7.join(this.paths.societyKnowledge(), file)
|
|
5298
5543
|
});
|
|
5299
5544
|
}
|
|
5300
5545
|
for (const skill of await this.listSocietySkills()) {
|
|
@@ -5307,7 +5552,7 @@ ${task.body}`;
|
|
|
5307
5552
|
shared.push({
|
|
5308
5553
|
kind: "memory",
|
|
5309
5554
|
ref: `memory/${file}`,
|
|
5310
|
-
file:
|
|
5555
|
+
file: path7.join(this.paths.agentMemory(actor.name), file)
|
|
5311
5556
|
});
|
|
5312
5557
|
}
|
|
5313
5558
|
for (const entry of shared) {
|
|
@@ -5397,7 +5642,7 @@ ${current.body}`);
|
|
|
5397
5642
|
});
|
|
5398
5643
|
const aside = this.paths.removedKnowledge(args.project ?? SOCIETY_SCOPE);
|
|
5399
5644
|
await mkdir4(aside, { recursive: true });
|
|
5400
|
-
await rename3(file,
|
|
5645
|
+
await rename3(file, path7.join(aside, `${args.topic}.${removed.removedAt.replaceAll(":", "-")}.md`));
|
|
5401
5646
|
await this.events.append("knowledge.removed", actor.name, {
|
|
5402
5647
|
topic: args.topic,
|
|
5403
5648
|
project: args.project
|
|
@@ -5445,7 +5690,7 @@ ${current.body}`);
|
|
|
5445
5690
|
});
|
|
5446
5691
|
const aside = this.paths.removedSkills();
|
|
5447
5692
|
await mkdir4(aside, { recursive: true });
|
|
5448
|
-
await rename3(this.paths.societySkill(args.name),
|
|
5693
|
+
await rename3(this.paths.societySkill(args.name), path7.join(aside, `${args.name}.${removed.removedAt.replaceAll(":", "-")}`));
|
|
5449
5694
|
await this.events.append("skill.removed", actor.name, {
|
|
5450
5695
|
name: args.name,
|
|
5451
5696
|
reason: args.reason
|
|
@@ -5522,7 +5767,7 @@ ${current.body}`);
|
|
|
5522
5767
|
type,
|
|
5523
5768
|
size: bytes.byteLength
|
|
5524
5769
|
});
|
|
5525
|
-
const name =
|
|
5770
|
+
const name = path7.basename(args.path).replaceAll(/[[\]\\\r\n]/g, "");
|
|
5526
5771
|
return {
|
|
5527
5772
|
id,
|
|
5528
5773
|
type,
|
|
@@ -5548,7 +5793,7 @@ ${current.body}`);
|
|
|
5548
5793
|
async listCrons() {
|
|
5549
5794
|
const crons = [];
|
|
5550
5795
|
for (const file of await listFiles(this.paths.crons())) {
|
|
5551
|
-
crons.push(await this.readCronFile(
|
|
5796
|
+
crons.push(await this.readCronFile(path7.join(this.paths.crons(), file)));
|
|
5552
5797
|
}
|
|
5553
5798
|
return crons;
|
|
5554
5799
|
}
|
|
@@ -5670,11 +5915,16 @@ ${current.body}`);
|
|
|
5670
5915
|
}
|
|
5671
5916
|
/**
|
|
5672
5917
|
* Where a cron fires for its citizen: the thread, the channel, or the scope's home it names, else
|
|
5673
|
-
* 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.
|
|
5674
5920
|
*/
|
|
5675
5921
|
async cronConversation(actor, agent, named) {
|
|
5676
5922
|
const own = actor.name === agent.name;
|
|
5677
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
|
+
}
|
|
5678
5928
|
return own && actor.scope !== void 0 ? this.cronConversation(actor, agent, {
|
|
5679
5929
|
project: actor.scope,
|
|
5680
5930
|
thread_id: actor.thread,
|
|
@@ -5750,7 +6000,8 @@ ${current.body}`);
|
|
|
5750
6000
|
agent: cron.agent,
|
|
5751
6001
|
project: cron.scope,
|
|
5752
6002
|
...cron.thread === void 0 ? {} : { thread: cron.thread },
|
|
5753
|
-
...cron.channel === void 0 ? {} : { channel: cron.channel }
|
|
6003
|
+
...cron.channel === void 0 ? {} : { channel: cron.channel },
|
|
6004
|
+
...cron.place === void 0 ? {} : { place: cron.place }
|
|
5754
6005
|
};
|
|
5755
6006
|
}
|
|
5756
6007
|
async readCronFile(file) {
|
|
@@ -5961,17 +6212,20 @@ ${current.body}`);
|
|
|
5961
6212
|
async openThreadIds(dir, keep) {
|
|
5962
6213
|
const ids = /* @__PURE__ */ new Set();
|
|
5963
6214
|
for (const file of await listFiles(dir)) {
|
|
5964
|
-
const doc = await readMarkdown(
|
|
6215
|
+
const doc = await readMarkdown(path7.join(dir, file), ThreadFrontmatterSchema);
|
|
5965
6216
|
if (doc.data.state === "open" && keep({ ...doc.data, body: doc.body })) {
|
|
5966
6217
|
ids.add(doc.data.id);
|
|
5967
6218
|
}
|
|
5968
6219
|
}
|
|
5969
6220
|
return ids;
|
|
5970
6221
|
}
|
|
5971
|
-
/**
|
|
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
|
+
*/
|
|
5972
6226
|
async assertProjectQuiet(actor, slug) {
|
|
5973
6227
|
const threads = await this.openThreadIds(this.paths.threads(slug), () => true);
|
|
5974
|
-
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));
|
|
5975
6229
|
}
|
|
5976
6230
|
async closeThread(actor, input) {
|
|
5977
6231
|
const args = VerbInputs.close_thread.parse(input);
|
|
@@ -6512,7 +6766,8 @@ ${args.summary}`);
|
|
|
6512
6766
|
filedFrom: {
|
|
6513
6767
|
scope: actor.scope,
|
|
6514
6768
|
...actor.thread === void 0 ? {} : { thread: actor.thread },
|
|
6515
|
-
...actor.channel === void 0 ? {} : { channel: actor.channel }
|
|
6769
|
+
...actor.channel === void 0 ? {} : { channel: actor.channel },
|
|
6770
|
+
...actor.place === void 0 ? {} : { place: actor.place }
|
|
6516
6771
|
}
|
|
6517
6772
|
},
|
|
6518
6773
|
charter: parsed.data
|
|
@@ -6924,19 +7179,20 @@ ${rationale}`}`);
|
|
|
6924
7179
|
project: input.project,
|
|
6925
7180
|
...input.thread === void 0 ? {} : { thread: input.thread },
|
|
6926
7181
|
...input.channel === void 0 ? {} : { channel: input.channel },
|
|
7182
|
+
...input.place === void 0 ? {} : { place: input.place },
|
|
6927
7183
|
messages: [...input.messages]
|
|
6928
7184
|
});
|
|
6929
7185
|
});
|
|
6930
7186
|
}
|
|
6931
7187
|
/**
|
|
6932
7188
|
* Where a conversation's session ids and last turn are kept: the scope's home, or beside it a
|
|
6933
|
-
* thread's or a
|
|
7189
|
+
* thread's, a channel's, or a project's, named by `part` as `conversationPart` gives it.
|
|
6934
7190
|
*/
|
|
6935
7191
|
sessionDir(agent, scope, part) {
|
|
6936
7192
|
return part === void 0 ? this.paths.agentProject(agent, scope) : this.paths.agentConversation(agent, scope, part);
|
|
6937
7193
|
}
|
|
6938
7194
|
async readSessions(agent, project, part) {
|
|
6939
|
-
const file =
|
|
7195
|
+
const file = path7.join(this.sessionDir(agent, project, part), "sessions.json");
|
|
6940
7196
|
return await exists(file) ? readJson(file, SessionsFileSchema) : {};
|
|
6941
7197
|
}
|
|
6942
7198
|
/**
|
|
@@ -6947,15 +7203,34 @@ ${rationale}`}`);
|
|
|
6947
7203
|
await this.mutex.run(async () => {
|
|
6948
7204
|
const dir = this.sessionDir(agent, project, part);
|
|
6949
7205
|
await ensureDir(dir);
|
|
6950
|
-
const file =
|
|
7206
|
+
const file = path7.join(dir, "sessions.json");
|
|
6951
7207
|
const current = await exists(file) ? await readJson(file, SessionsFileSchema) : {};
|
|
6952
7208
|
const kept = current.runner === void 0 || current.runner === runner ? current : {};
|
|
6953
7209
|
await writeJson(file, { ...kept, [cli]: sessionId, runner });
|
|
6954
7210
|
});
|
|
6955
7211
|
}
|
|
6956
|
-
/**
|
|
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. */
|
|
6957
7232
|
async readLastTurn(agent, project, part) {
|
|
6958
|
-
const file =
|
|
7233
|
+
const file = path7.join(this.sessionDir(agent, project, part), "last-turn.json");
|
|
6959
7234
|
return await exists(file) ? readJson(file, TurnRecordSchema) : null;
|
|
6960
7235
|
}
|
|
6961
7236
|
/** The latest turn a citizen took in a scope, in any of its conversations there. */
|
|
@@ -6963,8 +7238,9 @@ ${rationale}`}`);
|
|
|
6963
7238
|
let latest = await this.readLastTurn(agent, project);
|
|
6964
7239
|
const place = this.paths.agentProject(agent, project);
|
|
6965
7240
|
const parts = [
|
|
6966
|
-
...await listDirs(
|
|
6967
|
-
...(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}`)
|
|
6968
7244
|
];
|
|
6969
7245
|
for (const part of parts) {
|
|
6970
7246
|
const turn = await this.readLastTurn(agent, project, part);
|
|
@@ -6985,6 +7261,7 @@ ${rationale}`}`);
|
|
|
6985
7261
|
project: parsed.project,
|
|
6986
7262
|
...parsed.thread === void 0 ? {} : { thread: parsed.thread },
|
|
6987
7263
|
...parsed.channel === void 0 ? {} : { channel: parsed.channel },
|
|
7264
|
+
...parsed.place === void 0 ? {} : { place: parsed.place },
|
|
6988
7265
|
trigger: parsed.trigger.kind,
|
|
6989
7266
|
session: parsed.session,
|
|
6990
7267
|
runner: parsed.runner
|
|
@@ -7014,7 +7291,8 @@ ${rationale}`}`);
|
|
|
7014
7291
|
agent: parsed.agent,
|
|
7015
7292
|
project: parsed.project,
|
|
7016
7293
|
...parsed.thread === void 0 ? {} : { thread: parsed.thread },
|
|
7017
|
-
...parsed.channel === void 0 ? {} : { channel: parsed.channel }
|
|
7294
|
+
...parsed.channel === void 0 ? {} : { channel: parsed.channel },
|
|
7295
|
+
...parsed.place === void 0 ? {} : { place: parsed.place }
|
|
7018
7296
|
});
|
|
7019
7297
|
}
|
|
7020
7298
|
await this.events.append(failed ? "turn.failed" : "turn.completed", parsed.agent, {
|
|
@@ -7022,6 +7300,7 @@ ${rationale}`}`);
|
|
|
7022
7300
|
project: parsed.project,
|
|
7023
7301
|
...parsed.thread === void 0 ? {} : { thread: parsed.thread },
|
|
7024
7302
|
...parsed.channel === void 0 ? {} : { channel: parsed.channel },
|
|
7303
|
+
...parsed.place === void 0 ? {} : { place: parsed.place },
|
|
7025
7304
|
trigger: parsed.trigger.kind,
|
|
7026
7305
|
session: parsed.session,
|
|
7027
7306
|
exitReason: parsed.exitReason,
|
|
@@ -7031,7 +7310,9 @@ ${rationale}`}`);
|
|
|
7031
7310
|
model: parsed.model,
|
|
7032
7311
|
summary: parsed.status?.summary ?? null,
|
|
7033
7312
|
needsUserDecision: parsed.status?.needsUserDecision ?? false,
|
|
7034
|
-
error: parsed.error
|
|
7313
|
+
error: parsed.error,
|
|
7314
|
+
...parsed.resumeFailed === void 0 ? {} : { resumeFailed: parsed.resumeFailed },
|
|
7315
|
+
...parsed.sessionDropped === true ? { sessionDropped: true } : {}
|
|
7035
7316
|
});
|
|
7036
7317
|
});
|
|
7037
7318
|
}
|
|
@@ -7068,6 +7349,20 @@ ${rationale}`}`);
|
|
|
7068
7349
|
return task;
|
|
7069
7350
|
});
|
|
7070
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
|
+
}
|
|
7071
7366
|
/**
|
|
7072
7367
|
* Records that a citizen's workspace in a conversation that has ended holds work on no branch, so
|
|
7073
7368
|
* the scheduler gives the citizen a closing turn there to decide about it. False, recording
|
|
@@ -7078,7 +7373,7 @@ ${rationale}`}`);
|
|
|
7078
7373
|
const payload = {
|
|
7079
7374
|
agent: input.agent,
|
|
7080
7375
|
project: input.scope,
|
|
7081
|
-
...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 }
|
|
7082
7377
|
};
|
|
7083
7378
|
return this.mutex.run(async () => {
|
|
7084
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;
|
|
@@ -7130,9 +7425,13 @@ ${rationale}`}`);
|
|
|
7130
7425
|
}
|
|
7131
7426
|
/**
|
|
7132
7427
|
* Whether a conversation with a workspace of its own has ended: a thread closed, a task ended
|
|
7133
|
-
* (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.
|
|
7134
7430
|
*/
|
|
7135
7431
|
async conversationEnded(conversation) {
|
|
7432
|
+
if (conversation.kind === "place") {
|
|
7433
|
+
return !await this.projectLive(conversation.project);
|
|
7434
|
+
}
|
|
7136
7435
|
if (conversation.kind === "channel") {
|
|
7137
7436
|
const place = conversation.scope === SOCIETY_SCOPE ? null : conversation.scope;
|
|
7138
7437
|
return !await this.channelOpen(channelRef(place, conversation.name));
|
|
@@ -7206,11 +7505,11 @@ ${rationale}`}`);
|
|
|
7206
7505
|
}
|
|
7207
7506
|
/** Small named state files under data/state, for cursors the scheduler must keep across restarts. */
|
|
7208
7507
|
async readState(name, schema, fallback) {
|
|
7209
|
-
const file =
|
|
7508
|
+
const file = path7.join(this.paths.state(), `${name}.json`);
|
|
7210
7509
|
return await exists(file) ? readJson(file, schema) : fallback;
|
|
7211
7510
|
}
|
|
7212
7511
|
async writeState(name, value) {
|
|
7213
|
-
await this.mutex.run(() => writeJson(
|
|
7512
|
+
await this.mutex.run(() => writeJson(path7.join(this.paths.state(), `${name}.json`), value));
|
|
7214
7513
|
}
|
|
7215
7514
|
/** The project's dashboard, empty at revision 0 when it has none. */
|
|
7216
7515
|
async readDashboard(slug) {
|
|
@@ -7241,7 +7540,7 @@ ${rationale}`}`);
|
|
|
7241
7540
|
async listRunners() {
|
|
7242
7541
|
const runners2 = [];
|
|
7243
7542
|
for (const file of await listFiles(this.paths.runners())) {
|
|
7244
|
-
runners2.push((await readMarkdown(
|
|
7543
|
+
runners2.push((await readMarkdown(path7.join(this.paths.runners(), file), RunnerSchema)).data);
|
|
7245
7544
|
}
|
|
7246
7545
|
return runners2;
|
|
7247
7546
|
}
|
|
@@ -7293,7 +7592,7 @@ ${rationale}`}`);
|
|
|
7293
7592
|
return this.runnerTokens.get(hashToken(token)) ?? null;
|
|
7294
7593
|
}
|
|
7295
7594
|
runnerTokensFile() {
|
|
7296
|
-
return
|
|
7595
|
+
return path7.join(this.paths.state(), "runners.json");
|
|
7297
7596
|
}
|
|
7298
7597
|
async readRunnerTokens() {
|
|
7299
7598
|
const file = this.runnerTokensFile();
|
|
@@ -7342,26 +7641,26 @@ ${rationale}`}`);
|
|
|
7342
7641
|
async listHomeConflicts(agent) {
|
|
7343
7642
|
const home = this.paths.agent(agent);
|
|
7344
7643
|
const found = [];
|
|
7345
|
-
const
|
|
7346
|
-
for (const entry of await
|
|
7644
|
+
const walk2 = async (dir, prefix) => {
|
|
7645
|
+
for (const entry of await readdir4(dir, { withFileTypes: true }).catch(() => [])) {
|
|
7347
7646
|
if (prefix === "" && SKIPPED_IN_HOME.has(entry.name)) {
|
|
7348
7647
|
continue;
|
|
7349
7648
|
}
|
|
7350
7649
|
const relative = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
7351
7650
|
if (entry.isDirectory()) {
|
|
7352
|
-
await
|
|
7651
|
+
await walk2(path7.join(dir, entry.name), relative);
|
|
7353
7652
|
continue;
|
|
7354
7653
|
}
|
|
7355
7654
|
const file = entry.isFile() ? conflictCopyOf(relative) : null;
|
|
7356
7655
|
if (file !== null) {
|
|
7357
|
-
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();
|
|
7358
7657
|
if (since !== void 0) {
|
|
7359
7658
|
found.push({ path: relative, file, since });
|
|
7360
7659
|
}
|
|
7361
7660
|
}
|
|
7362
7661
|
}
|
|
7363
7662
|
};
|
|
7364
|
-
await
|
|
7663
|
+
await walk2(home, "");
|
|
7365
7664
|
return found.toSorted((a, b) => a.path.localeCompare(b.path));
|
|
7366
7665
|
}
|
|
7367
7666
|
/** The newest `limit` changes to an agent's home, newest first; empty while it is no repository. */
|
|
@@ -7438,7 +7737,7 @@ ${rationale}`}`);
|
|
|
7438
7737
|
async writeTurnRecord(record) {
|
|
7439
7738
|
const dir = this.sessionDir(record.agent, record.project, conversationPart(record));
|
|
7440
7739
|
await ensureDir(dir);
|
|
7441
|
-
await writeJson(
|
|
7740
|
+
await writeJson(path7.join(dir, "last-turn.json"), record);
|
|
7442
7741
|
}
|
|
7443
7742
|
// ---------------------------------------------------------------------------------------------
|
|
7444
7743
|
// Internals
|
|
@@ -7557,8 +7856,8 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7557
7856
|
async ensureAgentProject(name, slug) {
|
|
7558
7857
|
const dir = this.paths.agentProject(name, slug);
|
|
7559
7858
|
await ensureDir(dir);
|
|
7560
|
-
if (!await exists(
|
|
7561
|
-
await writeJson(
|
|
7859
|
+
if (!await exists(path7.join(dir, "sessions.json"))) {
|
|
7860
|
+
await writeJson(path7.join(dir, "sessions.json"), {});
|
|
7562
7861
|
}
|
|
7563
7862
|
}
|
|
7564
7863
|
/**
|
|
@@ -7590,7 +7889,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7590
7889
|
const at = turn?.endedAt ?? turn?.startedAt;
|
|
7591
7890
|
if (turn !== null && at !== void 0 && (lastTurnAt === void 0 || at > lastTurnAt)) {
|
|
7592
7891
|
lastTurnAt = at;
|
|
7593
|
-
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)}`}`;
|
|
7594
7893
|
lastModel = turn.model ?? void 0;
|
|
7595
7894
|
}
|
|
7596
7895
|
}
|
|
@@ -7810,7 +8109,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7810
8109
|
const file = this.paths.societySkillFile(skill.name);
|
|
7811
8110
|
const replaced = await exists(file);
|
|
7812
8111
|
const taken = proposal.skillFiles;
|
|
7813
|
-
const staged =
|
|
8112
|
+
const staged = path7.join(this.paths.proposalFiles(proposal.id), "SKILL.md");
|
|
7814
8113
|
if (skill.body === void 0 && (taken === void 0 || !await exists(staged))) {
|
|
7815
8114
|
throw new BoardError("INVALID_STATE", `proposal ${proposal.id} holds no skill files`);
|
|
7816
8115
|
}
|
|
@@ -7873,8 +8172,8 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7873
8172
|
const dir = this.paths.proposalFiles(proposal.id);
|
|
7874
8173
|
await rm2(dir, { recursive: true, force: true });
|
|
7875
8174
|
for (const entry of taken.files) {
|
|
7876
|
-
const target =
|
|
7877
|
-
await ensureDir(
|
|
8175
|
+
const target = path7.join(dir, ...entry.path.split("/"));
|
|
8176
|
+
await ensureDir(path7.dirname(target));
|
|
7878
8177
|
await writeFile4(target, contents.get(entry.path) ?? Buffer.alloc(0));
|
|
7879
8178
|
await chmod2(target, entry.executable ? 493 : 420);
|
|
7880
8179
|
}
|
|
@@ -7932,7 +8231,8 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7932
8231
|
const where = {
|
|
7933
8232
|
scope: turn.project,
|
|
7934
8233
|
...turn.thread === void 0 ? {} : { thread: turn.thread },
|
|
7935
|
-
...turn.channel === void 0 ? {} : { channel: turn.channel }
|
|
8234
|
+
...turn.channel === void 0 ? {} : { channel: turn.channel },
|
|
8235
|
+
...turn.place === void 0 ? {} : { place: turn.place }
|
|
7936
8236
|
};
|
|
7937
8237
|
await this.mutex.run(async () => {
|
|
7938
8238
|
for (const proposal of await this.listProposals()) {
|
|
@@ -7964,16 +8264,25 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7964
8264
|
if (!isSafeRelativePath(entry.path)) {
|
|
7965
8265
|
continue;
|
|
7966
8266
|
}
|
|
7967
|
-
const bytes = await readFile4(
|
|
7968
|
-
const text = bytes === null || bytes.includes(0) ? null : bytes.toString("utf8");
|
|
8267
|
+
const bytes = await readFile4(path7.join(dir, ...entry.path.split("/"))).catch(() => null);
|
|
7969
8268
|
files.push({
|
|
7970
8269
|
...entry,
|
|
7971
|
-
|
|
7972
|
-
truncated: text !== null && text.length > SKILL_TEXT_LIMIT_CHARS
|
|
8270
|
+
...bytes === null ? { text: null, truncated: false } : skillFileText(bytes)
|
|
7973
8271
|
});
|
|
7974
8272
|
}
|
|
7975
8273
|
return files;
|
|
7976
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
|
+
}
|
|
7977
8286
|
async validateAddAgent(input) {
|
|
7978
8287
|
if (await exists(this.paths.agentFile(input.name))) {
|
|
7979
8288
|
throw new BoardError("ALREADY_EXISTS", `agent ${input.name} already exists`);
|
|
@@ -8104,7 +8413,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
8104
8413
|
this.assertNoTurnCut(actor, `${ref} cannot be archived`, (turn) => turn.channel === channel && turn.scope === scope || turn.thread !== void 0 && closing.has(turn.thread));
|
|
8105
8414
|
const threadsClosed = [];
|
|
8106
8415
|
for (const file of await listFiles(threads)) {
|
|
8107
|
-
const doc = await readMarkdown(
|
|
8416
|
+
const doc = await readMarkdown(path7.join(threads, file), ThreadFrontmatterSchema);
|
|
8108
8417
|
if (doc.data.channel !== ref || doc.data.state !== "open") {
|
|
8109
8418
|
continue;
|
|
8110
8419
|
}
|
|
@@ -8210,7 +8519,8 @@ ${charter.purpose}
|
|
|
8210
8519
|
}
|
|
8211
8520
|
}
|
|
8212
8521
|
/**
|
|
8213
|
-
* 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
|
|
8214
8524
|
* files, repository, and history stay. The notice is the board's, since the reason is free text
|
|
8215
8525
|
* a proposer wrote and a board-authored mention would wake whoever it names.
|
|
8216
8526
|
*/
|
|
@@ -8235,7 +8545,7 @@ ${charter.purpose}
|
|
|
8235
8545
|
const threads = this.paths.threads(slug);
|
|
8236
8546
|
const threadsClosed = [];
|
|
8237
8547
|
for (const file of await listFiles(threads)) {
|
|
8238
|
-
const doc = await readMarkdown(
|
|
8548
|
+
const doc = await readMarkdown(path7.join(threads, file), ThreadFrontmatterSchema);
|
|
8239
8549
|
if (doc.data.state !== "open") {
|
|
8240
8550
|
continue;
|
|
8241
8551
|
}
|
|
@@ -8253,7 +8563,7 @@ ${charter.purpose}
|
|
|
8253
8563
|
});
|
|
8254
8564
|
threadsClosed.push(doc.data.id);
|
|
8255
8565
|
}
|
|
8256
|
-
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));
|
|
8257
8567
|
const project = await this.updateProject(slug, (p) => ({
|
|
8258
8568
|
...p,
|
|
8259
8569
|
members: [],
|
|
@@ -8310,10 +8620,10 @@ ${charter.purpose}
|
|
|
8310
8620
|
...project,
|
|
8311
8621
|
members: project.members.filter((member) => member !== name)
|
|
8312
8622
|
}));
|
|
8313
|
-
const sessions =
|
|
8623
|
+
const sessions = path7.join(this.paths.agentProject(name, slug), "sessions.json");
|
|
8314
8624
|
if (await exists(sessions)) {
|
|
8315
8625
|
const archived = `sessions.archived.${ts.replace(/[:.]/g, "-")}.json`;
|
|
8316
|
-
await rename3(sessions,
|
|
8626
|
+
await rename3(sessions, path7.join(this.paths.agentProject(name, slug), archived));
|
|
8317
8627
|
}
|
|
8318
8628
|
}
|
|
8319
8629
|
this.tokenIndex.delete(current.tokenHash);
|
|
@@ -8393,6 +8703,17 @@ ${charter.purpose}
|
|
|
8393
8703
|
return false;
|
|
8394
8704
|
}
|
|
8395
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
|
+
}
|
|
8396
8717
|
/**
|
|
8397
8718
|
* A channel that takes new posts, threads, tasks, and followers: it exists, it is not archived,
|
|
8398
8719
|
* and its project is not archived.
|
|
@@ -8643,7 +8964,7 @@ ${charter.purpose}
|
|
|
8643
8964
|
if (since !== null && id <= since || upto !== void 0 && id > upto) {
|
|
8644
8965
|
continue;
|
|
8645
8966
|
}
|
|
8646
|
-
const doc = await readMarkdown(
|
|
8967
|
+
const doc = await readMarkdown(path7.join(dir, file), MessageFrontmatterSchema);
|
|
8647
8968
|
messages.push({ ...doc.data, body: doc.body });
|
|
8648
8969
|
}
|
|
8649
8970
|
return messages;
|
|
@@ -8739,11 +9060,21 @@ function besideFields(beside) {
|
|
|
8739
9060
|
if (beside === void 0) {
|
|
8740
9061
|
return {};
|
|
8741
9062
|
}
|
|
8742
|
-
|
|
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
|
+
};
|
|
8743
9074
|
}
|
|
8744
9075
|
function conversationKey2(agent, scope, beside) {
|
|
8745
9076
|
const fields = besideFields(beside);
|
|
8746
|
-
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 }));
|
|
8747
9078
|
}
|
|
8748
9079
|
var DEFAULT_TIMINGS = Object.freeze({
|
|
8749
9080
|
pollMs: 1e3,
|
|
@@ -9215,6 +9546,21 @@ var Scheduler = class {
|
|
|
9215
9546
|
}
|
|
9216
9547
|
return;
|
|
9217
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
|
+
}
|
|
9218
9564
|
case "agent.left": {
|
|
9219
9565
|
const name = stringOf(payload["name"]);
|
|
9220
9566
|
const project = stringOf(payload["project"]);
|
|
@@ -9516,11 +9862,12 @@ var Scheduler = class {
|
|
|
9516
9862
|
const scope = stringOf(payload["project"]);
|
|
9517
9863
|
const thread = stringOf(payload["thread"]);
|
|
9518
9864
|
const channel = stringOf(payload["channel"]);
|
|
9865
|
+
const place = stringOf(payload["place"]);
|
|
9519
9866
|
const agent = name === null ? null : await this.tryReadAgent(name);
|
|
9520
9867
|
if (agent === null || scope === null) {
|
|
9521
9868
|
return;
|
|
9522
9869
|
}
|
|
9523
|
-
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;
|
|
9524
9871
|
if (beside === null) {
|
|
9525
9872
|
return;
|
|
9526
9873
|
}
|
|
@@ -9565,7 +9912,7 @@ var Scheduler = class {
|
|
|
9565
9912
|
}
|
|
9566
9913
|
return {
|
|
9567
9914
|
scope,
|
|
9568
|
-
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 }
|
|
9569
9916
|
};
|
|
9570
9917
|
}
|
|
9571
9918
|
/**
|
|
@@ -9583,14 +9930,23 @@ var Scheduler = class {
|
|
|
9583
9930
|
}
|
|
9584
9931
|
/**
|
|
9585
9932
|
* The conversation a post wakes a citizen in, for a turn in `scope`: its thread's, else its
|
|
9586
|
-
* 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.
|
|
9587
9936
|
*/
|
|
9588
9937
|
async postedIn(scope, channel, threadId) {
|
|
9589
9938
|
if (threadId !== void 0) {
|
|
9590
9939
|
return { thread: await this.conversationOf(threadId, scope) };
|
|
9591
9940
|
}
|
|
9592
|
-
|
|
9593
|
-
|
|
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 };
|
|
9594
9950
|
}
|
|
9595
9951
|
async tryReadAgent(name) {
|
|
9596
9952
|
try {
|
|
@@ -9732,7 +10088,7 @@ var Scheduler = class {
|
|
|
9732
10088
|
};
|
|
9733
10089
|
for (const entry of unread) {
|
|
9734
10090
|
if (entry.scope === project) {
|
|
9735
|
-
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;
|
|
9736
10092
|
reasonsIn(beside).digestSize += entry.count;
|
|
9737
10093
|
}
|
|
9738
10094
|
}
|
|
@@ -9819,7 +10175,10 @@ var Scheduler = class {
|
|
|
9819
10175
|
if (cron.thread !== void 0) {
|
|
9820
10176
|
return { thread: await this.conversationOf(cron.thread, cron.scope) };
|
|
9821
10177
|
}
|
|
9822
|
-
|
|
10178
|
+
if (cron.channel !== void 0) {
|
|
10179
|
+
return { channel: cron.channel };
|
|
10180
|
+
}
|
|
10181
|
+
return cron.place === void 0 ? void 0 : { place: cron.place };
|
|
9823
10182
|
}
|
|
9824
10183
|
/** Whether a cron's wake waits in the queue or its turn is running. */
|
|
9825
10184
|
cronQueuedOrRunning(id) {
|
|
@@ -10204,8 +10563,7 @@ var Scheduler = class {
|
|
|
10204
10563
|
const attempt = steer({
|
|
10205
10564
|
agent: dispatch.agent,
|
|
10206
10565
|
project: dispatch.project,
|
|
10207
|
-
...dispatch
|
|
10208
|
-
...dispatch.channel === void 0 ? {} : { channel: dispatch.channel }
|
|
10566
|
+
...namedIn(dispatch)
|
|
10209
10567
|
}).then((outcome) => {
|
|
10210
10568
|
const current = this.pending.get(key);
|
|
10211
10569
|
if (outcome !== "busy" && current !== void 0) {
|
|
@@ -10226,7 +10584,7 @@ var Scheduler = class {
|
|
|
10226
10584
|
return false;
|
|
10227
10585
|
}
|
|
10228
10586
|
const { dispatch } = item;
|
|
10229
|
-
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)));
|
|
10230
10588
|
return cursor !== null && cursor >= item.newestMessage;
|
|
10231
10589
|
}
|
|
10232
10590
|
async dispatchReady(now) {
|
|
@@ -10263,6 +10621,7 @@ var Scheduler = class {
|
|
|
10263
10621
|
project: dispatch.project,
|
|
10264
10622
|
thread: dispatch.thread?.id,
|
|
10265
10623
|
channel: dispatch.channel,
|
|
10624
|
+
place: dispatch.place,
|
|
10266
10625
|
trigger: dispatch.trigger.kind
|
|
10267
10626
|
}, "dispatching turn");
|
|
10268
10627
|
const crons = dispatch.crons ?? [];
|
|
@@ -10379,9 +10738,9 @@ import { randomUUID } from "node:crypto";
|
|
|
10379
10738
|
|
|
10380
10739
|
// packages/turn-host/dist/render.js
|
|
10381
10740
|
var TURN_CONTRACT = [
|
|
10382
|
-
"- 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.",
|
|
10383
10742
|
"- Act through the board tools (the `board` MCP server). Holding a stage is a lease; every turn that touches the task renews it.",
|
|
10384
|
-
"- 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.",
|
|
10385
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.",
|
|
10386
10745
|
"- Silence is allowed. If the digest needs no reply, post nothing.",
|
|
10387
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.",
|
|
@@ -10454,7 +10813,7 @@ function renderInstructions(input) {
|
|
|
10454
10813
|
if (norms.length > 0) {
|
|
10455
10814
|
sections.push("", "## Society norms", "", norms);
|
|
10456
10815
|
}
|
|
10457
|
-
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);
|
|
10458
10817
|
if (input.onboarding !== void 0) {
|
|
10459
10818
|
sections.push("", "## First turn", "", renderOnboardingPreamble(input.onboarding));
|
|
10460
10819
|
}
|
|
@@ -10608,7 +10967,7 @@ function buildTurnPrompt(input) {
|
|
|
10608
10967
|
lines.push("Operations signals arrived; they are listed under Operations signals below. Decide whether a proposal is warranted, and stay silent if not.");
|
|
10609
10968
|
}
|
|
10610
10969
|
if (dispatch.trigger.kind === "stage") {
|
|
10611
|
-
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.");
|
|
10612
10971
|
}
|
|
10613
10972
|
if (dispatch.trigger.kind === "task_done") {
|
|
10614
10973
|
lines.push("A task you created is done. Tell whoever asked for it, if anyone did.");
|
|
@@ -10627,8 +10986,11 @@ function buildTurnPrompt(input) {
|
|
|
10627
10986
|
if (conversation === null && dispatch.channel !== void 0) {
|
|
10628
10987
|
const ref = channelRef(dispatch.project === SOCIETY_SCOPE ? null : dispatch.project, dispatch.channel);
|
|
10629
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.`);
|
|
10630
10991
|
} else if (conversation === null) {
|
|
10631
|
-
|
|
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.`);
|
|
10632
10994
|
} else {
|
|
10633
10995
|
const { thread, task } = conversation;
|
|
10634
10996
|
const about = thread.subject === void 0 ? "a topic" : `${thread.subject.kind} ${thread.subject.id}`;
|
|
@@ -10663,7 +11025,9 @@ function buildTurnPrompt(input) {
|
|
|
10663
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.`);
|
|
10664
11026
|
}
|
|
10665
11027
|
if (last !== null && (last.exitReason === "error" || last.exitReason === "timeout")) {
|
|
10666
|
-
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.");
|
|
10667
11031
|
}
|
|
10668
11032
|
const society = input.societyView;
|
|
10669
11033
|
if (society !== void 0 && society !== null) {
|
|
@@ -10759,6 +11123,13 @@ function capitalized(text) {
|
|
|
10759
11123
|
}
|
|
10760
11124
|
|
|
10761
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
|
+
}
|
|
10762
11133
|
var SILENT = { info() {
|
|
10763
11134
|
}, warn() {
|
|
10764
11135
|
}, error() {
|
|
@@ -10769,6 +11140,12 @@ var DEFAULT_RESIDENT_IDLE_MS = 10 * 6e4;
|
|
|
10769
11140
|
var LEASE_RENEW_MS = 10 * 6e4;
|
|
10770
11141
|
var USER_POST_TRIGGER2 = "user_post";
|
|
10771
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
|
+
}
|
|
10772
11149
|
var MAX_SIGNALS = 30;
|
|
10773
11150
|
var TurnHost = class {
|
|
10774
11151
|
board;
|
|
@@ -10813,13 +11190,15 @@ var TurnHost = class {
|
|
|
10813
11190
|
const actor = { name: agent.name, role: agent.role };
|
|
10814
11191
|
const thread = dispatch.thread;
|
|
10815
11192
|
const channel = thread === void 0 ? dispatch.channel : void 0;
|
|
10816
|
-
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 });
|
|
10817
11195
|
const startedAt = this.now().toISOString();
|
|
10818
11196
|
const base = {
|
|
10819
11197
|
agent: agent.name,
|
|
10820
11198
|
project: dispatch.project,
|
|
10821
11199
|
...thread === void 0 ? {} : { thread: thread.id },
|
|
10822
11200
|
...channel === void 0 ? {} : { channel },
|
|
11201
|
+
...place === void 0 ? {} : { place },
|
|
10823
11202
|
runner: runner.name,
|
|
10824
11203
|
cli: agent.cli,
|
|
10825
11204
|
session: null,
|
|
@@ -10844,23 +11223,25 @@ var TurnHost = class {
|
|
|
10844
11223
|
const charter = await this.board.readRole(agent.role);
|
|
10845
11224
|
const societyScope = dispatch.project === SOCIETY_SCOPE;
|
|
10846
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);
|
|
10847
11227
|
const project = societyScope ? null : await this.board.readProject(dispatch.project);
|
|
10848
|
-
const workspace = await this.workspaceOf(project, thread, channel);
|
|
11228
|
+
const workspace = await this.workspaceOf(project, thread, channel, place, task);
|
|
10849
11229
|
const sessions = await this.board.readSessions(agent.name, dispatch.project, part);
|
|
10850
11230
|
const recorded = sessions.runner === runner.name ? sessions[agent.cli] : void 0;
|
|
10851
11231
|
const newSession = recorded === void 0;
|
|
10852
11232
|
const lastTurn = await this.board.readLastTurn(agent.name, dispatch.project, part);
|
|
10853
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;
|
|
10854
11235
|
const digest = await this.board.readDigest({
|
|
10855
11236
|
...actor,
|
|
10856
11237
|
scope: dispatch.project,
|
|
10857
11238
|
...thread === void 0 ? {} : { thread: thread.id },
|
|
10858
|
-
...channel === void 0 ? {} : { channel }
|
|
11239
|
+
...channel === void 0 ? {} : { channel },
|
|
11240
|
+
...place === void 0 ? {} : { place }
|
|
10859
11241
|
}, { advance: false, limit: thread === void 0 ? 50 : 100 });
|
|
10860
11242
|
const threadSoFar = thread !== void 0 && newSession ? (await this.board.listThread(thread.id).catch(() => [])).slice(-100) : null;
|
|
10861
11243
|
const messages = threadSoFar ?? digest.messages;
|
|
10862
11244
|
const cursorAfter = threadSoFar?.at(-1)?.id ?? digest.cursor;
|
|
10863
|
-
const task = taskId === void 0 ? null : await this.board.findTask(taskId).then((found) => found.task).catch(() => null);
|
|
10864
11245
|
const held = task === null ? [] : (await this.board.heldClaims(agent.name)).filter((each) => each.id === task.id);
|
|
10865
11246
|
const onboarding = dispatch.onboarding || newSession && part === void 0 ? {
|
|
10866
11247
|
agentName: agent.name,
|
|
@@ -10919,7 +11300,7 @@ var TurnHost = class {
|
|
|
10919
11300
|
fresh: threadSoFar !== null,
|
|
10920
11301
|
...threadRecord.state === "closed" ? { replyIn: await this.replyIn(threadRecord) } : {}
|
|
10921
11302
|
};
|
|
10922
|
-
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;
|
|
10923
11304
|
const society = await this.board.society();
|
|
10924
11305
|
const crons = [];
|
|
10925
11306
|
for (const id of dispatch.crons ?? []) {
|
|
@@ -10968,6 +11349,7 @@ var TurnHost = class {
|
|
|
10968
11349
|
scope: dispatch.project,
|
|
10969
11350
|
...thread === void 0 ? {} : { thread: thread.id },
|
|
10970
11351
|
...channel === void 0 ? {} : { channel },
|
|
11352
|
+
...place === void 0 ? {} : { place },
|
|
10971
11353
|
session: recorded ?? null,
|
|
10972
11354
|
costSoFarUsd,
|
|
10973
11355
|
instructions,
|
|
@@ -10994,6 +11376,7 @@ var TurnHost = class {
|
|
|
10994
11376
|
stopRequestedBy: null,
|
|
10995
11377
|
held: held.map((each) => each.id),
|
|
10996
11378
|
costSoFarUsd,
|
|
11379
|
+
resumeRetries,
|
|
10997
11380
|
configuredModel: agent.model ?? null,
|
|
10998
11381
|
transcript: [],
|
|
10999
11382
|
coldToken: residentKey === null ? token : null,
|
|
@@ -11007,6 +11390,7 @@ var TurnHost = class {
|
|
|
11007
11390
|
project: dispatch.project,
|
|
11008
11391
|
thread: thread?.id,
|
|
11009
11392
|
channel,
|
|
11393
|
+
place,
|
|
11010
11394
|
runner: runner.name,
|
|
11011
11395
|
session: recorded,
|
|
11012
11396
|
newSession,
|
|
@@ -11043,8 +11427,7 @@ var TurnHost = class {
|
|
|
11043
11427
|
turnId,
|
|
11044
11428
|
agent: turn.job.agent,
|
|
11045
11429
|
project: turn.job.scope,
|
|
11046
|
-
|
|
11047
|
-
channel: turn.job.channel,
|
|
11430
|
+
...conversationOfJob(turn.job),
|
|
11048
11431
|
messages: sent.messages
|
|
11049
11432
|
}).catch((error) => {
|
|
11050
11433
|
this.log.warn({ turnId, error: String(error) }, "could not record a steer");
|
|
@@ -11052,17 +11435,14 @@ var TurnHost = class {
|
|
|
11052
11435
|
}
|
|
11053
11436
|
}
|
|
11054
11437
|
turn.transcript.push(entry);
|
|
11055
|
-
this.onEvent?.(turn.job.agent, turn.job.scope, entry.event,
|
|
11056
|
-
thread: turn.job.thread,
|
|
11057
|
-
channel: turn.job.channel
|
|
11058
|
-
});
|
|
11438
|
+
this.onEvent?.(turn.job.agent, turn.job.scope, entry.event, conversationOfJob(turn.job));
|
|
11059
11439
|
}
|
|
11060
11440
|
}
|
|
11061
11441
|
/** The turn in flight in a conversation, if any. */
|
|
11062
11442
|
turnIn(agent, scope, conversation = {}) {
|
|
11063
11443
|
for (const turn of this.open.values()) {
|
|
11064
11444
|
const { job } = turn;
|
|
11065
|
-
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) {
|
|
11066
11446
|
return { turnId: job.turnId, runner: turn.runner, cli: job.cli };
|
|
11067
11447
|
}
|
|
11068
11448
|
}
|
|
@@ -11073,8 +11453,7 @@ var TurnHost = class {
|
|
|
11073
11453
|
return [...this.open.values()].map(({ job }) => ({
|
|
11074
11454
|
agent: job.agent,
|
|
11075
11455
|
scope: job.scope,
|
|
11076
|
-
...job
|
|
11077
|
-
...job.channel === void 0 ? {} : { channel: job.channel }
|
|
11456
|
+
...conversationOfJob(job)
|
|
11078
11457
|
}));
|
|
11079
11458
|
}
|
|
11080
11459
|
/** The turns in flight, for the interface: their conversations and where a mention reaches them. */
|
|
@@ -11083,8 +11462,7 @@ var TurnHost = class {
|
|
|
11083
11462
|
turnId: turn.job.turnId,
|
|
11084
11463
|
agent: turn.job.agent,
|
|
11085
11464
|
scope: turn.job.scope,
|
|
11086
|
-
...turn.job
|
|
11087
|
-
...turn.job.channel === void 0 ? {} : { channel: turn.job.channel },
|
|
11465
|
+
...conversationOfJob(turn.job),
|
|
11088
11466
|
cli: turn.job.cli,
|
|
11089
11467
|
...turn.record.trigger.channel === void 0 ? {} : { askedIn: turn.record.trigger.channel },
|
|
11090
11468
|
runner: turn.runner
|
|
@@ -11104,12 +11482,7 @@ var TurnHost = class {
|
|
|
11104
11482
|
return "busy";
|
|
11105
11483
|
}
|
|
11106
11484
|
const { job } = turn;
|
|
11107
|
-
const digest = await this.board.readDigest({
|
|
11108
|
-
...turn.actor,
|
|
11109
|
-
scope: job.scope,
|
|
11110
|
-
...job.thread === void 0 ? {} : { thread: job.thread },
|
|
11111
|
-
...job.channel === void 0 ? {} : { channel: job.channel }
|
|
11112
|
-
}, {
|
|
11485
|
+
const digest = await this.board.readDigest({ ...turn.actor, scope: job.scope, ...conversationOfJob(job) }, {
|
|
11113
11486
|
...turn.delivered === null ? {} : { since_cursor: turn.delivered },
|
|
11114
11487
|
advance: false,
|
|
11115
11488
|
limit: 50
|
|
@@ -11169,6 +11542,8 @@ var TurnHost = class {
|
|
|
11169
11542
|
}
|
|
11170
11543
|
const { job } = turn;
|
|
11171
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);
|
|
11172
11547
|
const finished = {
|
|
11173
11548
|
...turn.record,
|
|
11174
11549
|
session: outcome.session,
|
|
@@ -11182,20 +11557,35 @@ var TurnHost = class {
|
|
|
11182
11557
|
toolCalls: turn.transcript.filter((entry) => entry.event.type === "tool_call").length,
|
|
11183
11558
|
model: outcome.model ?? turn.configuredModel,
|
|
11184
11559
|
...outcome.work === null ? {} : { work: outcome.work },
|
|
11185
|
-
...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 } : {}
|
|
11186
11564
|
};
|
|
11187
11565
|
const ended = await this.conversationEnded(job);
|
|
11188
11566
|
const closing = turn.record.trigger.kind === "closing";
|
|
11189
11567
|
const decided = closing && outcome.exitReason !== "error" && outcome.exitReason !== "timeout";
|
|
11190
11568
|
const recorded = await this.recorded(turn, async () => {
|
|
11191
|
-
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) {
|
|
11192
11582
|
await this.board.writeSession(job.agent, job.scope, job.cli, outcome.session, turn.runner, part);
|
|
11193
11583
|
}
|
|
11194
11584
|
if (outcome.exitReason === "completed" || outcome.exitReason === "blocked" || outcome.exitReason === "stopped") {
|
|
11195
11585
|
await this.board.setDigestCursor(job.agent, job.scope, turn.delivered, part);
|
|
11196
11586
|
await this.renewLeases(turn.actor, turn.held);
|
|
11197
11587
|
if (outcome.status?.needsUserDecision === true) {
|
|
11198
|
-
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);
|
|
11199
11589
|
}
|
|
11200
11590
|
}
|
|
11201
11591
|
await this.board.finishTurn(finished, turn.transcript);
|
|
@@ -11207,7 +11597,7 @@ var TurnHost = class {
|
|
|
11207
11597
|
conversation
|
|
11208
11598
|
});
|
|
11209
11599
|
if (!again) {
|
|
11210
|
-
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`;
|
|
11211
11601
|
await this.board.publishSignal({
|
|
11212
11602
|
kind: "stuck_workspace",
|
|
11213
11603
|
key: `stuck_workspace:${job.agent}:${job.scope}:${conversationPart(job) ?? ""}`,
|
|
@@ -11224,12 +11614,18 @@ var TurnHost = class {
|
|
|
11224
11614
|
this.log.warn({ runner: turn.runner, cli: job.cli, error: String(error) }, "could not record a quota");
|
|
11225
11615
|
});
|
|
11226
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
|
+
}
|
|
11227
11622
|
if (recorded) {
|
|
11228
11623
|
this.log.info({
|
|
11229
11624
|
agent: job.agent,
|
|
11230
11625
|
project: job.scope,
|
|
11231
11626
|
thread: job.thread,
|
|
11232
11627
|
channel: job.channel,
|
|
11628
|
+
place: job.place,
|
|
11233
11629
|
exitReason: finished.exitReason
|
|
11234
11630
|
}, "turn finished");
|
|
11235
11631
|
await this.takeSkillFiles(finished);
|
|
@@ -11296,6 +11692,7 @@ var TurnHost = class {
|
|
|
11296
11692
|
project: dispatch.project,
|
|
11297
11693
|
...dispatch.thread === void 0 ? {} : { thread: dispatch.thread.id },
|
|
11298
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 },
|
|
11299
11696
|
runner,
|
|
11300
11697
|
cli: agent?.cli ?? null,
|
|
11301
11698
|
session: null,
|
|
@@ -11342,24 +11739,26 @@ var TurnHost = class {
|
|
|
11342
11739
|
return token;
|
|
11343
11740
|
}
|
|
11344
11741
|
/**
|
|
11345
|
-
* 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
|
|
11346
11744
|
* place of its own, and the home in the citizen's own.
|
|
11347
11745
|
*/
|
|
11348
|
-
async workspaceOf(project, thread, channel) {
|
|
11746
|
+
async workspaceOf(project, thread, channel, place, task) {
|
|
11349
11747
|
const own = thread !== void 0 ? thread.task ? {} : { thread: thread.id } : channel === void 0 ? {} : { channel };
|
|
11350
11748
|
if (project === null) {
|
|
11351
|
-
return { kind: "home", ...own };
|
|
11749
|
+
return { kind: "home", ...own, ...place === void 0 ? {} : { place } };
|
|
11352
11750
|
}
|
|
11353
11751
|
const repo = projectRepo(project);
|
|
11354
11752
|
if (thread?.task === true) {
|
|
11355
|
-
|
|
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 } };
|
|
11356
11755
|
}
|
|
11357
|
-
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}`);
|
|
11358
11757
|
return { kind: "project", repo, branches, ...own };
|
|
11359
11758
|
}
|
|
11360
11759
|
/**
|
|
11361
|
-
* Whether a thread conversation's task or thread has ended,
|
|
11362
|
-
*
|
|
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.
|
|
11363
11762
|
*/
|
|
11364
11763
|
async conversationEnded(job) {
|
|
11365
11764
|
const conversation = workspaceConversationOf(job);
|
|
@@ -11373,14 +11772,14 @@ var TurnHost = class {
|
|
|
11373
11772
|
* How a closing turn's conversation ended, and where its citizen asks someone else to take over
|
|
11374
11773
|
* work that is not its own: the thread's channel while it is open, else the place's general.
|
|
11375
11774
|
*/
|
|
11376
|
-
async endingOf(scope, thread, task, channel) {
|
|
11775
|
+
async endingOf(scope, thread, task, channel, project) {
|
|
11377
11776
|
const place = scope === SOCIETY_SCOPE ? null : scope;
|
|
11378
11777
|
if (thread !== void 0) {
|
|
11379
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}`}`;
|
|
11380
11779
|
return { how: closed, askIn: await this.replyIn(thread) };
|
|
11381
11780
|
}
|
|
11382
11781
|
if (channel === void 0) {
|
|
11383
|
-
return null;
|
|
11782
|
+
return project === void 0 ? null : { how: `project ${project} was archived`, askIn: channelRef(place, "general") };
|
|
11384
11783
|
}
|
|
11385
11784
|
const ref = channelRef(place, channel);
|
|
11386
11785
|
const archived = (await this.board.listChannels()).find((each) => each.ref === ref)?.archived;
|
|
@@ -11394,13 +11793,15 @@ var TurnHost = class {
|
|
|
11394
11793
|
* the user, which is what puts it before them. One that mentioned the user nowhere asks in its
|
|
11395
11794
|
* own thread when it was in one that is still open, so the answer comes back to the same
|
|
11396
11795
|
* conversation, and otherwise in a thread of its own on its conversation's channel, or its scope's
|
|
11397
|
-
* general for the home, opened as the citizen with its summary
|
|
11398
|
-
*
|
|
11399
|
-
*
|
|
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.
|
|
11400
11800
|
*/
|
|
11401
|
-
async askUser(actor, job, since, summary) {
|
|
11801
|
+
async askUser(actor, job, askedIn, since, summary) {
|
|
11402
11802
|
const threadId = job.thread;
|
|
11403
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");
|
|
11404
11805
|
try {
|
|
11405
11806
|
if (await this.board.hasMentioned(actor.name, USER_NAME, since)) {
|
|
11406
11807
|
return;
|
|
@@ -11418,7 +11819,7 @@ var TurnHost = class {
|
|
|
11418
11819
|
return;
|
|
11419
11820
|
}
|
|
11420
11821
|
const thread = await this.board.openThread(actor, {
|
|
11421
|
-
channel
|
|
11822
|
+
channel,
|
|
11422
11823
|
title: `${actor.name} asks for a decision: ${summary}`.slice(0, 200)
|
|
11423
11824
|
});
|
|
11424
11825
|
await this.board.postMessage(actor, {
|
|
@@ -11734,7 +12135,8 @@ var RunnerHub = class {
|
|
|
11734
12135
|
async steer(conversation) {
|
|
11735
12136
|
const turn = this.host.turnIn(conversation.agent, conversation.project, {
|
|
11736
12137
|
thread: conversation.thread,
|
|
11737
|
-
channel: conversation.channel
|
|
12138
|
+
channel: conversation.channel,
|
|
12139
|
+
place: conversation.place
|
|
11738
12140
|
});
|
|
11739
12141
|
const seat = turn === null ? void 0 : this.seats.get(turn.runner);
|
|
11740
12142
|
if (turn === null || seat === void 0 || seat.send === null) {
|
|
@@ -11951,11 +12353,11 @@ var RunnerHub = class {
|
|
|
11951
12353
|
* NOT_FOUND when the project has no runner yet or the branch or the path does not exist, and
|
|
11952
12354
|
* `RunnerAwayError` while its runner is away.
|
|
11953
12355
|
*/
|
|
11954
|
-
async readBranch(project, branch,
|
|
12356
|
+
async readBranch(project, branch, path11) {
|
|
11955
12357
|
const { seat, repo } = await this.projectSeat(project, "branchReads", "read files");
|
|
11956
|
-
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));
|
|
11957
12359
|
if (file === null) {
|
|
11958
|
-
throw new BoardError("NOT_FOUND", `${branch} has no ${
|
|
12360
|
+
throw new BoardError("NOT_FOUND", `${branch} has no ${path11 === "" ? "commit" : path11}`);
|
|
11959
12361
|
}
|
|
11960
12362
|
return file;
|
|
11961
12363
|
}
|
|
@@ -11975,11 +12377,11 @@ var RunnerHub = class {
|
|
|
11975
12377
|
* What a branch of a project's repository did to one file since it left the default branch, read
|
|
11976
12378
|
* on the runner the project lives on, with the same refusals as `branchChanges`.
|
|
11977
12379
|
*/
|
|
11978
|
-
async branchDiff(project, branch,
|
|
12380
|
+
async branchDiff(project, branch, path11) {
|
|
11979
12381
|
const { seat, repo } = await this.projectSeat(project, "branchChanges", "list changes");
|
|
11980
|
-
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));
|
|
11981
12383
|
if (diff === null) {
|
|
11982
|
-
throw new BoardError("NOT_FOUND", `${branch} did not change ${
|
|
12384
|
+
throw new BoardError("NOT_FOUND", `${branch} did not change ${path11}`);
|
|
11983
12385
|
}
|
|
11984
12386
|
return diff;
|
|
11985
12387
|
}
|
|
@@ -11990,7 +12392,7 @@ var RunnerHub = class {
|
|
|
11990
12392
|
* back as a `BoardError` in the runner's words.
|
|
11991
12393
|
*/
|
|
11992
12394
|
async readTurnImage(actor, file) {
|
|
11993
|
-
const turn = this.host.runningTurns().find((each) => each.agent === actor.name && each.scope === actor.scope && each.thread === actor.thread && each.channel === actor.channel);
|
|
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);
|
|
11994
12396
|
if (turn === void 0) {
|
|
11995
12397
|
throw new BoardError("INVALID_STATE", `${actor.name} has no turn running in this conversation to read ${file} from`);
|
|
11996
12398
|
}
|
|
@@ -12490,7 +12892,7 @@ function rateLimit(options) {
|
|
|
12490
12892
|
}
|
|
12491
12893
|
|
|
12492
12894
|
// apps/server/dist/runner-routes.js
|
|
12493
|
-
import
|
|
12895
|
+
import path8 from "node:path";
|
|
12494
12896
|
import { Hono as Hono2 } from "hono";
|
|
12495
12897
|
import { streamSSE } from "hono/streaming";
|
|
12496
12898
|
|
|
@@ -12683,7 +13085,7 @@ function runnerRoutes(board2, hub, enrollments, enrollLimit) {
|
|
|
12683
13085
|
const marker = `/homes/${agent}/git`;
|
|
12684
13086
|
const at = c.req.path.indexOf(marker);
|
|
12685
13087
|
return homeGit.serve({
|
|
12686
|
-
root:
|
|
13088
|
+
root: path8.dirname(repository),
|
|
12687
13089
|
agent,
|
|
12688
13090
|
rest: at === -1 ? "" : c.req.path.slice(at + marker.length),
|
|
12689
13091
|
request: c.req.raw,
|
|
@@ -12696,7 +13098,7 @@ function runnerRoutes(board2, hub, enrollments, enrollLimit) {
|
|
|
12696
13098
|
|
|
12697
13099
|
// apps/server/dist/static.js
|
|
12698
13100
|
import { readFile as readFile5, stat as stat4 } from "node:fs/promises";
|
|
12699
|
-
import
|
|
13101
|
+
import path9 from "node:path";
|
|
12700
13102
|
var CONTENT_TYPES = {
|
|
12701
13103
|
".html": "text/html; charset=utf-8",
|
|
12702
13104
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -12724,11 +13126,11 @@ function within2(root, pathname) {
|
|
|
12724
13126
|
} catch {
|
|
12725
13127
|
return null;
|
|
12726
13128
|
}
|
|
12727
|
-
const candidate =
|
|
12728
|
-
return candidate === root || candidate.startsWith(root +
|
|
13129
|
+
const candidate = path9.resolve(root, `.${decoded}`);
|
|
13130
|
+
return candidate === root || candidate.startsWith(root + path9.sep) ? candidate : null;
|
|
12729
13131
|
}
|
|
12730
13132
|
function spaHandler(staticDir) {
|
|
12731
|
-
const root =
|
|
13133
|
+
const root = path9.resolve(staticDir);
|
|
12732
13134
|
return async (c) => {
|
|
12733
13135
|
const { pathname } = new URL(c.req.url);
|
|
12734
13136
|
if (pathname.startsWith("/api/")) {
|
|
@@ -12739,14 +13141,14 @@ function spaHandler(staticDir) {
|
|
|
12739
13141
|
if (file !== null && await isFile(file)) {
|
|
12740
13142
|
return c.body(await readFile5(file), 200, {
|
|
12741
13143
|
...headers,
|
|
12742
|
-
"content-type": CONTENT_TYPES[
|
|
13144
|
+
"content-type": CONTENT_TYPES[path9.extname(file)] ?? "application/octet-stream",
|
|
12743
13145
|
"cache-control": pathname.startsWith("/assets/") ? "public, max-age=31536000, immutable" : "no-cache"
|
|
12744
13146
|
});
|
|
12745
13147
|
}
|
|
12746
13148
|
if (pathname.startsWith("/assets/")) {
|
|
12747
13149
|
return c.text("not found", 404, headers);
|
|
12748
13150
|
}
|
|
12749
|
-
const index =
|
|
13151
|
+
const index = path9.join(root, "index.html");
|
|
12750
13152
|
if (!await isFile(index)) {
|
|
12751
13153
|
return c.text("The interface is not built: run pnpm build:web.", 404);
|
|
12752
13154
|
}
|
|
@@ -12912,6 +13314,7 @@ function createApp(deps) {
|
|
|
12912
13314
|
});
|
|
12913
13315
|
api.get("/society/knowledge", async (c) => c.json(await board2.listKnowledge(null)));
|
|
12914
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")))));
|
|
12915
13318
|
api.post("/agents/:name/retire", async (c) => {
|
|
12916
13319
|
const body = RetireBodySchema.parse(await c.req.json());
|
|
12917
13320
|
const { tokenHash: _hash, ...agent } = await board2.retireAgent(c.get("actor"), {
|
|
@@ -13355,7 +13758,10 @@ var TurnHub = class {
|
|
|
13355
13758
|
get lastSeq() {
|
|
13356
13759
|
return this.seq;
|
|
13357
13760
|
}
|
|
13358
|
-
/**
|
|
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
|
+
*/
|
|
13359
13765
|
push(agent, project, event, conversation = {}) {
|
|
13360
13766
|
this.seq += 1;
|
|
13361
13767
|
const item = {
|
|
@@ -13365,6 +13771,7 @@ var TurnHub = class {
|
|
|
13365
13771
|
project,
|
|
13366
13772
|
...conversation.thread === void 0 ? {} : { thread: conversation.thread },
|
|
13367
13773
|
...conversation.channel === void 0 ? {} : { channel: conversation.channel },
|
|
13774
|
+
...conversation.place === void 0 ? {} : { place: conversation.place },
|
|
13368
13775
|
event
|
|
13369
13776
|
};
|
|
13370
13777
|
this.buffer.push(item);
|
|
@@ -13441,8 +13848,8 @@ var view = {
|
|
|
13441
13848
|
return scheduler.activeSignals;
|
|
13442
13849
|
}
|
|
13443
13850
|
};
|
|
13444
|
-
var webDir = process.env["STELLARIS_WEB_DIR"] ??
|
|
13445
|
-
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);
|
|
13446
13853
|
var app = createApp({
|
|
13447
13854
|
board,
|
|
13448
13855
|
version: VERSION,
|