mainflow 0.44.0-alpha.2 → 0.44.0-alpha.4
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.js +97 -9
- package/dist/facts.d.ts +3 -2
- package/dist/facts.js +4 -3
- package/dist/grant-check.d.ts +2 -2
- package/dist/grant-check.js +2 -2
- package/dist/hooks.d.ts +2 -2
- package/dist/hooks.js +18 -9
- package/dist/{host-C74j2Qox.js → host-CfBcUcg2.js} +376 -50
- package/dist/host.d.ts +1 -1
- package/dist/host.js +1 -1
- package/dist/{index-jq7lN8V8.d.ts → index-lsW80LDU.d.ts} +28 -5
- package/dist/launch.d.ts +1 -1
- package/dist/launch.js +2 -2
- package/dist/native/mainflow-swap-darwin-arm64 +0 -0
- package/dist/native/mainflow-swap-darwin-x64 +0 -0
- package/dist/{operation-BroV98VB.d.ts → operation-BTQm13OQ.d.ts} +1 -1
- package/dist/{operation-CUeOFEaf.js → operation-BmNzQC-L.js} +1 -1
- package/dist/{ui-qqVCewFz.js → ui-CnNAOM_Y.js} +2 -3
- package/dist/web/assets/{index-B4w2iRCQ.js → index-BX_V5mK5.js} +1 -1
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/dist/progress-1BfPg2zN.js +0 -10
|
@@ -1033,7 +1033,13 @@ const CONFIG_FILE = "config.yaml";
|
|
|
1033
1033
|
const ISSUES_DIR = "issues";
|
|
1034
1034
|
const FINDINGS_DIR = "findings";
|
|
1035
1035
|
const GROUPS_DIR = "groups";
|
|
1036
|
+
/** The sessions settling a turn reads: those still running, paused, or ended while kept for their family (ENDED_SESSIONS_DIR). */
|
|
1036
1037
|
const SESSIONS_DIR = "local/sessions";
|
|
1038
|
+
/**
|
|
1039
|
+
* The sessions that ended or were handed off, moved out of SESSIONS_DIR so the work a turn's end does follows the
|
|
1040
|
+
* sessions still under way, not every session the hub ever had. A session comes back to SESSIONS_DIR when it is resumed.
|
|
1041
|
+
*/
|
|
1042
|
+
const ENDED_SESSIONS_DIR = `${SESSIONS_DIR}/ended`;
|
|
1037
1043
|
/** The document inside each mainline and task directory: front matter for attributes, body for prose. */
|
|
1038
1044
|
const MAINLINE_FILE = "mainline.md";
|
|
1039
1045
|
const TASK_FILE = "task.md";
|
|
@@ -1079,7 +1085,16 @@ function decisionFileIn(hub, mainline, decision) {
|
|
|
1079
1085
|
if (!file) throw new WriteError(`decision ${mainline}/${decision} not found`);
|
|
1080
1086
|
return file;
|
|
1081
1087
|
}
|
|
1088
|
+
/** Where the session `id` is kept while it is under way; one that ended may be in endedSessionFileIn instead. */
|
|
1082
1089
|
const sessionFileIn = (id) => `${SESSIONS_DIR}/${id}.yaml`;
|
|
1090
|
+
/** Where the session `id` is kept once it ended or was handed off. */
|
|
1091
|
+
const endedSessionFileIn = (id) => `${ENDED_SESSIONS_DIR}/${id}.yaml`;
|
|
1092
|
+
/** The session a file in either session directory holds, by its path relative to the data directory; null for any other path. */
|
|
1093
|
+
function sessionIdOfPath(path) {
|
|
1094
|
+
const parts = path.split("/");
|
|
1095
|
+
const name = parts[0] === "local" && parts[1] === "sessions" ? parts.length === 3 ? parts[2] : parts.length === 4 && parts[2] === "ended" ? parts[3] : void 0 : void 0;
|
|
1096
|
+
return name?.endsWith(".yaml") && name.length > 5 ? name.slice(0, -5) : null;
|
|
1097
|
+
}
|
|
1083
1098
|
function groupFileIn(hub, id) {
|
|
1084
1099
|
const file = entryFileIn(hub, GROUPS_DIR, id);
|
|
1085
1100
|
if (!file) throw new WriteError(`group ${id} not found`);
|
|
@@ -3258,18 +3273,27 @@ function removeFile(hub, input) {
|
|
|
3258
3273
|
}
|
|
3259
3274
|
//#endregion
|
|
3260
3275
|
//#region ../core/dist/ops/sessions.js
|
|
3261
|
-
/**
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3276
|
+
/** The statuses of a session that is done with; such a session is kept in ENDED_SESSIONS_DIR (belongsInEnded). */
|
|
3277
|
+
const FINISHED = ["ended", "handed-off"];
|
|
3278
|
+
/** Read the sessions in `dir`, those named in `skip` aside; one that does not parse or validate is reported as a problem and skipped. */
|
|
3279
|
+
function readSessionDirIn(hub, dir, out, skip = () => false) {
|
|
3280
|
+
const names = hub.list(dir).filter((e) => !e.dir && e.name.endsWith(".yaml")).map((e) => e.name);
|
|
3281
|
+
for (const name of names) {
|
|
3282
|
+
const path = `${dir}/${name}`;
|
|
3267
3283
|
const file = `${DATA_DIR}/${path}`;
|
|
3284
|
+
if (skip(name)) {
|
|
3285
|
+
out.problems.push({
|
|
3286
|
+
file,
|
|
3287
|
+
field: "",
|
|
3288
|
+
message: `the session is in ${DATA_DIR}/${SESSIONS_DIR}/ as well, which is the copy read; remove the copy that is out of date`
|
|
3289
|
+
});
|
|
3290
|
+
continue;
|
|
3291
|
+
}
|
|
3268
3292
|
let data;
|
|
3269
3293
|
try {
|
|
3270
3294
|
data = parse$1(hub.read(path) ?? "");
|
|
3271
3295
|
} catch (error) {
|
|
3272
|
-
problems.push({
|
|
3296
|
+
out.problems.push({
|
|
3273
3297
|
file,
|
|
3274
3298
|
field: "",
|
|
3275
3299
|
message: `cannot parse YAML: ${error.message}`
|
|
@@ -3277,27 +3301,124 @@ function readSessionsIn(hub) {
|
|
|
3277
3301
|
continue;
|
|
3278
3302
|
}
|
|
3279
3303
|
const result = sessionSchema.safeParse(data);
|
|
3280
|
-
if (result.success)
|
|
3281
|
-
|
|
3304
|
+
if (!result.success) {
|
|
3305
|
+
for (const i of result.error.issues) out.problems.push({
|
|
3306
|
+
file,
|
|
3307
|
+
field: i.path.join("."),
|
|
3308
|
+
message: i.message
|
|
3309
|
+
});
|
|
3310
|
+
continue;
|
|
3311
|
+
}
|
|
3312
|
+
if (dir === ENDED_SESSIONS_DIR && !FINISHED.includes(result.data.status)) out.problems.push({
|
|
3282
3313
|
file,
|
|
3283
|
-
field:
|
|
3284
|
-
message:
|
|
3314
|
+
field: "status",
|
|
3315
|
+
message: `${result.data.status}, but ${DATA_DIR}/${ENDED_SESSIONS_DIR}/ keeps only sessions that ended or were handed off; run "mainflow migrate" to move it back to ${DATA_DIR}/${SESSIONS_DIR}/`
|
|
3285
3316
|
});
|
|
3317
|
+
out.sessions.push(result.data);
|
|
3286
3318
|
}
|
|
3287
|
-
return
|
|
3288
|
-
|
|
3289
|
-
|
|
3319
|
+
return names;
|
|
3320
|
+
}
|
|
3321
|
+
/**
|
|
3322
|
+
* Read every session: those in local/sessions/ and, unless `ended` is false, those in local/sessions/ended/ too. One
|
|
3323
|
+
* that does not parse or validate is reported as a problem and skipped; so is a session in both directories, whose copy
|
|
3324
|
+
* in local/sessions/ is read. Settling a turn needs only the first directory; listing, checking and looking back over
|
|
3325
|
+
* past work need both.
|
|
3326
|
+
*/
|
|
3327
|
+
function readSessionsIn(hub, options = {}) {
|
|
3328
|
+
const out = {
|
|
3329
|
+
sessions: [],
|
|
3330
|
+
problems: []
|
|
3290
3331
|
};
|
|
3332
|
+
const names = new Set(readSessionDirIn(hub, SESSIONS_DIR, out));
|
|
3333
|
+
if (options.ended !== false) readSessionDirIn(hub, ENDED_SESSIONS_DIR, out, (name) => names.has(name));
|
|
3334
|
+
return out;
|
|
3335
|
+
}
|
|
3336
|
+
/** Where the session `id` is kept: local/sessions/ while under way, local/sessions/ended/ once ended; null for a session neither holds. */
|
|
3337
|
+
function sessionPathIn(hub, id) {
|
|
3338
|
+
for (const path of [sessionFileIn(id), endedSessionFileIn(id)]) if (hub.read(path) !== null) return path;
|
|
3339
|
+
return null;
|
|
3291
3340
|
}
|
|
3292
3341
|
function readSessionIn(hub, id) {
|
|
3293
|
-
const path =
|
|
3294
|
-
return
|
|
3342
|
+
const path = sessionPathIn(hub, id);
|
|
3343
|
+
return path === null ? null : readYamlIn(hub, path, sessionSchema);
|
|
3295
3344
|
}
|
|
3296
3345
|
function requireSession(hub, id) {
|
|
3297
|
-
const path =
|
|
3298
|
-
if (
|
|
3346
|
+
const path = sessionPathIn(hub, id);
|
|
3347
|
+
if (path === null) throw new WriteError(`session ${id} not found; start one with "mainflow session start"`);
|
|
3299
3348
|
return path;
|
|
3300
3349
|
}
|
|
3350
|
+
function sessionLookup(hub, known = []) {
|
|
3351
|
+
const cache = new Map(known.map((s) => [s.id, s]));
|
|
3352
|
+
const lookup = (id) => {
|
|
3353
|
+
if (!cache.has(id)) {
|
|
3354
|
+
let found = null;
|
|
3355
|
+
try {
|
|
3356
|
+
found = readSessionIn(hub, id);
|
|
3357
|
+
} catch {}
|
|
3358
|
+
cache.set(id, found);
|
|
3359
|
+
}
|
|
3360
|
+
return cache.get(id);
|
|
3361
|
+
};
|
|
3362
|
+
return Object.assign(lookup, { set: (session) => void cache.set(session.id, session) });
|
|
3363
|
+
}
|
|
3364
|
+
/**
|
|
3365
|
+
* Whether a session belongs in local/sessions/ended/: it ended or was handed off, and each session it was delegated
|
|
3366
|
+
* from, up to the main session, tracked an action. Settling a session reads the subagents under it in local/sessions/
|
|
3367
|
+
* only (settleSession), and a subagent left there is one that may still decide it: a session whose family tracked no
|
|
3368
|
+
* action is removed with the family. A subagent moves out only once every session above it tracked an action, and no
|
|
3369
|
+
* write takes an action away, so none of them can be removed any more and the subagent, which no longer runs, changes
|
|
3370
|
+
* nothing settling decides. A subagent whose parent is missing or does not read stays, since nothing tells.
|
|
3371
|
+
*/
|
|
3372
|
+
function belongsInEnded(session, lookup) {
|
|
3373
|
+
if (!FINISHED.includes(session.status)) return false;
|
|
3374
|
+
const seen = /* @__PURE__ */ new Set([session.id]);
|
|
3375
|
+
for (let parent = session.delegatedFrom; parent && !seen.has(parent);) {
|
|
3376
|
+
seen.add(parent);
|
|
3377
|
+
const above = lookup(parent);
|
|
3378
|
+
if (!above || above.actions.length === 0) return false;
|
|
3379
|
+
parent = above.delegatedFrom;
|
|
3380
|
+
}
|
|
3381
|
+
return true;
|
|
3382
|
+
}
|
|
3383
|
+
/** The path the session belongs at (belongsInEnded). */
|
|
3384
|
+
const placeOf = (session, lookup) => belongsInEnded(session, lookup) ? endedSessionFileIn(session.id) : sessionFileIn(session.id);
|
|
3385
|
+
/**
|
|
3386
|
+
* Where the session at `from` goes when it belongs at `to`: `from` itself when another file is at `to` already, as when
|
|
3387
|
+
* an older mainflow started the session again in local/sessions/ while it was in local/sessions/ended/. Writing there
|
|
3388
|
+
* would replace that copy, and the record it holds, so the session stays and the write says so; check lists both copies.
|
|
3389
|
+
*/
|
|
3390
|
+
function destination(draft, id, from, to) {
|
|
3391
|
+
if (to === from || draft.read(to) === null) return to;
|
|
3392
|
+
const message = `${DATA_DIR}/${to} holds session ${id} too, so ${DATA_DIR}/${from} stays where it is rather than replace it; keep the copy that is up to date and remove the other`;
|
|
3393
|
+
if (!draft.notices.some((n) => n.path === from && n.message === message)) draft.notice(from, message);
|
|
3394
|
+
return from;
|
|
3395
|
+
}
|
|
3396
|
+
/** Move the file at `from` to `to`, unchanged; nothing when they are the same or another file is at `to` (destination). */
|
|
3397
|
+
function moveFile(draft, id, from, to) {
|
|
3398
|
+
if (destination(draft, id, from, to) === from) return;
|
|
3399
|
+
draft.write(to, draft.read(from));
|
|
3400
|
+
draft.remove(from);
|
|
3401
|
+
}
|
|
3402
|
+
/**
|
|
3403
|
+
* Write `next`, the session read as `current` from `from`, to the directory it belongs in now (belongsInEnded), keeping
|
|
3404
|
+
* the fields its file has beyond the schema; a session that moves is removed from `from`. One whose place another file
|
|
3405
|
+
* holds is written at `from` (destination).
|
|
3406
|
+
*/
|
|
3407
|
+
function writeSession(draft, from, current, next, lookup = sessionLookup(draft)) {
|
|
3408
|
+
const to = destination(draft, next.id, from, placeOf(next, lookup));
|
|
3409
|
+
draft.write(to, toYaml(yamlKeeping(draft, from, current, next)));
|
|
3410
|
+
if (to !== from) draft.remove(from);
|
|
3411
|
+
lookup.set(next);
|
|
3412
|
+
return next;
|
|
3413
|
+
}
|
|
3414
|
+
/** Mutate, validate and write a session, which moves to the directory its new status puts it in (writeSession). */
|
|
3415
|
+
function editSession(draft, id, mutate, lookup) {
|
|
3416
|
+
const from = requireSession(draft, id);
|
|
3417
|
+
const current = readYamlIn(draft, from, sessionSchema);
|
|
3418
|
+
const next = structuredClone(current);
|
|
3419
|
+
mutate(next);
|
|
3420
|
+
return writeSession(draft, from, current, validate(sessionSchema, next, draft.where(from)), lookup);
|
|
3421
|
+
}
|
|
3301
3422
|
/** Split `claude-code-1a2b3c4d#a003` into its session and action ids. */
|
|
3302
3423
|
function splitActionRef(ref) {
|
|
3303
3424
|
const hash = ref.lastIndexOf("#");
|
|
@@ -3412,11 +3533,11 @@ function takeUnfinished(draft, ctx, from, to, existing, at) {
|
|
|
3412
3533
|
doneAt: at
|
|
3413
3534
|
};
|
|
3414
3535
|
});
|
|
3415
|
-
|
|
3536
|
+
writeSession(draft, previousFile, previous, validate(sessionSchema, {
|
|
3416
3537
|
...previous,
|
|
3417
3538
|
status: "handed-off",
|
|
3418
3539
|
actions: left
|
|
3419
|
-
}, draft.where(previousFile)))
|
|
3540
|
+
}, draft.where(previousFile)));
|
|
3420
3541
|
return carried;
|
|
3421
3542
|
}
|
|
3422
3543
|
/**
|
|
@@ -3429,7 +3550,7 @@ function startSession(hub, input, ctx) {
|
|
|
3429
3550
|
const { minute } = stamps(ctx);
|
|
3430
3551
|
const draft = new Draft(hub);
|
|
3431
3552
|
const file = sessionFileIn(id);
|
|
3432
|
-
if (draft
|
|
3553
|
+
if (sessionPathIn(draft, id) !== null) throw new WriteError(`session ${id} already exists`);
|
|
3433
3554
|
if (input.delegatedFrom) requireSession(draft, input.delegatedFrom);
|
|
3434
3555
|
const carried = input.handoffFrom ? takeUnfinished(draft, ctx, input.handoffFrom, id, [], minute) : [];
|
|
3435
3556
|
const session = validate(sessionSchema, {
|
|
@@ -3446,8 +3567,8 @@ function startSession(hub, input, ctx) {
|
|
|
3446
3567
|
return draft.result(session);
|
|
3447
3568
|
}
|
|
3448
3569
|
/**
|
|
3449
|
-
* Start the session unless it already exists. An existing paused or ended session becomes active again,
|
|
3450
|
-
* an archived one comes back into view, since resumed work should not stay hidden.
|
|
3570
|
+
* Start the session unless it already exists. An existing paused or ended session becomes active again, back in
|
|
3571
|
+
* local/sessions/, and an archived one comes back into view, since resumed work should not stay hidden.
|
|
3451
3572
|
*/
|
|
3452
3573
|
function ensureSession(hub, input, ctx) {
|
|
3453
3574
|
const existing = readSessionIn(hub, input.id);
|
|
@@ -3466,7 +3587,7 @@ function ensureSession(hub, input, ctx) {
|
|
|
3466
3587
|
session: existing,
|
|
3467
3588
|
created: false
|
|
3468
3589
|
});
|
|
3469
|
-
const session =
|
|
3590
|
+
const session = editSession(draft, input.id, (s) => {
|
|
3470
3591
|
s.status = "active";
|
|
3471
3592
|
delete s.archivedAt;
|
|
3472
3593
|
});
|
|
@@ -3499,8 +3620,7 @@ function handoffSession(hub, input, ctx) {
|
|
|
3499
3620
|
handoffFrom: from,
|
|
3500
3621
|
actions: [...session.actions, ...carried]
|
|
3501
3622
|
}, draft.where(file));
|
|
3502
|
-
draft.
|
|
3503
|
-
return draft.result(next);
|
|
3623
|
+
return draft.result(writeSession(draft, file, session, next));
|
|
3504
3624
|
}
|
|
3505
3625
|
function setSessionInfo(hub, input) {
|
|
3506
3626
|
const draft = new Draft(hub);
|
|
@@ -3527,7 +3647,7 @@ function setCheckReported(hub, input) {
|
|
|
3527
3647
|
function setSessionStatus(hub, input) {
|
|
3528
3648
|
const value = requireOneOf(input.status, SESSION_STATUS, "session status");
|
|
3529
3649
|
const draft = new Draft(hub);
|
|
3530
|
-
return draft.result(
|
|
3650
|
+
return draft.result(editSession(draft, input.id, (s) => {
|
|
3531
3651
|
s.status = value;
|
|
3532
3652
|
}));
|
|
3533
3653
|
}
|
|
@@ -3536,28 +3656,99 @@ function setSessionStatus(hub, input) {
|
|
|
3536
3656
|
* no record, since nothing in it points at the hub's work. Then every active main session with no action left to
|
|
3537
3657
|
* do and no subagent still running ends, the others as well as this one: another session may have finished its
|
|
3538
3658
|
* last action, or it was closed before its own turn could settle it. The next prompt or action brings it back.
|
|
3659
|
+
*
|
|
3660
|
+
* Only the sessions in local/sessions/ are read, and the session itself wherever it is. An ended session moves to
|
|
3661
|
+
* local/sessions/ended/, with the finished subagents under it and under this session that belong there now
|
|
3662
|
+
* (belongsInEnded); a subagent already there cannot change what settling decides, so it is not read.
|
|
3539
3663
|
*/
|
|
3540
3664
|
function settleSession(hub, input) {
|
|
3541
3665
|
const { id } = input;
|
|
3542
3666
|
const draft = new Draft(hub);
|
|
3543
|
-
const { sessions } = readSessionsIn(draft);
|
|
3544
|
-
const
|
|
3545
|
-
|
|
3667
|
+
const { sessions } = readSessionsIn(draft, { ended: false });
|
|
3668
|
+
const lookup = sessionLookup(draft, sessions);
|
|
3669
|
+
/** Whether `session` was delegated from `parent`, directly or through other subagents. */
|
|
3670
|
+
const isUnder = (session, parent) => {
|
|
3671
|
+
const seen = /* @__PURE__ */ new Set([session.id]);
|
|
3672
|
+
for (let above = session.delegatedFrom; above && !seen.has(above); above = lookup(above)?.delegatedFrom ?? null) {
|
|
3673
|
+
if (above === parent) return true;
|
|
3674
|
+
seen.add(above);
|
|
3675
|
+
}
|
|
3676
|
+
return false;
|
|
3677
|
+
};
|
|
3678
|
+
const subagents = (parent) => sessions.filter((s) => isUnder(s, parent));
|
|
3679
|
+
const self = lookup(id);
|
|
3680
|
+
const family = self ? [self, ...subagents(id)] : [];
|
|
3546
3681
|
const removed = family.length > 0 && family.every((s) => s.actions.length === 0);
|
|
3547
|
-
if (removed) for (const s of family) draft.remove(
|
|
3682
|
+
if (removed) for (const s of family) draft.remove(sessionPathIn(draft, s.id));
|
|
3548
3683
|
let result = removed ? "removed" : "kept";
|
|
3684
|
+
const ended = [];
|
|
3549
3685
|
for (const s of sessions) {
|
|
3550
3686
|
if (s.status !== "active" || s.delegatedFrom || removed && s.id === id) continue;
|
|
3551
3687
|
if (!s.actions.every((a) => a.status === "done" || a.status === "dropped") || subagents(s.id).some((x) => x.status === "active")) continue;
|
|
3552
3688
|
if (s.id === id && s.actions.length === 0) continue;
|
|
3553
|
-
|
|
3689
|
+
writeSession(draft, sessionFileIn(s.id), s, validate(sessionSchema, {
|
|
3554
3690
|
...s,
|
|
3555
3691
|
status: "ended"
|
|
3556
|
-
}, draft.where(sessionFileIn(s.id))))
|
|
3692
|
+
}, draft.where(sessionFileIn(s.id))), lookup);
|
|
3693
|
+
ended.push(s.id);
|
|
3557
3694
|
if (s.id === id) result = "ended";
|
|
3558
3695
|
}
|
|
3696
|
+
const roots = [...self && !removed ? [id] : [], ...ended];
|
|
3697
|
+
for (const s of sessions) {
|
|
3698
|
+
if (roots.includes(s.id) || !roots.some((root) => isUnder(s, root)) || draft.read(sessionFileIn(s.id)) === null) continue;
|
|
3699
|
+
moveFile(draft, s.id, sessionFileIn(s.id), placeOf(lookup(s.id), lookup));
|
|
3700
|
+
}
|
|
3701
|
+
if (self && !removed && sessionPathIn(draft, id) === sessionFileIn(id)) moveFile(draft, id, sessionFileIn(id), placeOf(lookup(id), lookup));
|
|
3559
3702
|
return draft.result(result);
|
|
3560
3703
|
}
|
|
3704
|
+
/**
|
|
3705
|
+
* Move every session file to the directory it belongs in (belongsInEnded), as `mainflow migrate` does for a hub that
|
|
3706
|
+
* kept every session in local/sessions/: at most `limit` files, and never more than RELOCATE_BATCH, so a large hub moves
|
|
3707
|
+
* in several writes; `more` says another file is to move after them. `dryRun` moves none and lists them all. A file that
|
|
3708
|
+
* does not read, or that both directories hold, stays where it is; check reports it. Running it again once nothing is
|
|
3709
|
+
* left to move changes nothing.
|
|
3710
|
+
*/
|
|
3711
|
+
function relocateSessions$1(hub, input = {}) {
|
|
3712
|
+
const draft = new Draft(hub);
|
|
3713
|
+
const asked = typeof input.limit === "number" && Number.isFinite(input.limit) ? Math.floor(input.limit) : 500;
|
|
3714
|
+
const limit = input.dryRun ? Infinity : Math.min(Math.max(1, asked), 500);
|
|
3715
|
+
const lookup = sessionLookup(draft);
|
|
3716
|
+
const value = {
|
|
3717
|
+
ended: [],
|
|
3718
|
+
resumed: [],
|
|
3719
|
+
more: false
|
|
3720
|
+
};
|
|
3721
|
+
const names = (dir) => draft.list(dir).filter((e) => !e.dir && e.name.endsWith(".yaml")).map((e) => e.name.slice(0, -5));
|
|
3722
|
+
const active = names(SESSIONS_DIR);
|
|
3723
|
+
const ended = new Set(names(ENDED_SESSIONS_DIR));
|
|
3724
|
+
const moves = [];
|
|
3725
|
+
/** Plan the move of the file at `from`, if it is elsewhere than it belongs; false, with `more`, at a move past the limit. */
|
|
3726
|
+
const consider = (id, from, list) => {
|
|
3727
|
+
let session;
|
|
3728
|
+
try {
|
|
3729
|
+
session = readYamlIn(draft, from, sessionSchema);
|
|
3730
|
+
} catch {
|
|
3731
|
+
return true;
|
|
3732
|
+
}
|
|
3733
|
+
if (session.id !== id) return true;
|
|
3734
|
+
lookup.set(session);
|
|
3735
|
+
const to = placeOf(session, lookup);
|
|
3736
|
+
if (to === from) return true;
|
|
3737
|
+
if (moves.length >= limit) {
|
|
3738
|
+
value.more = true;
|
|
3739
|
+
return false;
|
|
3740
|
+
}
|
|
3741
|
+
moves.push([from, to]);
|
|
3742
|
+
list.push(id);
|
|
3743
|
+
return true;
|
|
3744
|
+
};
|
|
3745
|
+
for (const id of active) if (!ended.has(id) && !consider(id, sessionFileIn(id), value.ended)) break;
|
|
3746
|
+
if (!value.more) {
|
|
3747
|
+
for (const id of ended) if (!active.includes(id) && !consider(id, endedSessionFileIn(id), value.resumed)) break;
|
|
3748
|
+
}
|
|
3749
|
+
if (!input.dryRun) for (const [from, to] of moves) moveFile(draft, sessionIdOfPath(from), from, to);
|
|
3750
|
+
return draft.result(value);
|
|
3751
|
+
}
|
|
3561
3752
|
/** Archive or unarchive a session; the status stays as it is, so an ended session is visible until archived. */
|
|
3562
3753
|
function setSessionArchived(hub, input, ctx) {
|
|
3563
3754
|
const { day } = stamps(ctx);
|
|
@@ -3581,7 +3772,7 @@ function addAction(hub, input, ctx) {
|
|
|
3581
3772
|
requireServes(hub, input.serves);
|
|
3582
3773
|
const draft = new Draft(hub);
|
|
3583
3774
|
let created = "";
|
|
3584
|
-
|
|
3775
|
+
editSession(draft, input.session, (s) => {
|
|
3585
3776
|
created = draft.number(ctx, {
|
|
3586
3777
|
prefix: "a",
|
|
3587
3778
|
taken: s.actions.map((a) => a.id),
|
|
@@ -3933,8 +4124,9 @@ function migrateHub$1(hub) {
|
|
|
3933
4124
|
/**
|
|
3934
4125
|
* The operations by name, for a server that receives them from elsewhere. The names are the protocol's
|
|
3935
4126
|
* (OPERATION_NAMES in protocol.ts): the table must hold every one of them and nothing else. `migrate` and `init`
|
|
3936
|
-
* are absent:
|
|
3937
|
-
* is sent as an operation.
|
|
4127
|
+
* are absent: migrating the data format rewrites the whole hub in one go and a new hub starts on the caller's machine,
|
|
4128
|
+
* so neither is sent as an operation. Moving the session files to where they belong, which `migrate` also does, is
|
|
4129
|
+
* one (`session.relocate`), sent again until nothing is left to move.
|
|
3938
4130
|
*/
|
|
3939
4131
|
const OPERATIONS = {
|
|
3940
4132
|
"repo.add": addRepo,
|
|
@@ -3976,6 +4168,7 @@ const OPERATIONS = {
|
|
|
3976
4168
|
"session.checked": setCheckReported,
|
|
3977
4169
|
"session.set": setSessionStatus,
|
|
3978
4170
|
"session.settle": settleSession,
|
|
4171
|
+
"session.relocate": relocateSessions$1,
|
|
3979
4172
|
"session.archive": setSessionArchived,
|
|
3980
4173
|
"action.add": addAction,
|
|
3981
4174
|
"action.set": updateAction,
|
|
@@ -4015,18 +4208,20 @@ function partsOf(path, dir) {
|
|
|
4015
4208
|
if (parts.some((part) => part === "" || part === "." || part === ".." || part === ".git")) return null;
|
|
4016
4209
|
return (dir ? parts : parts.slice(0, -1)).some((part) => OWN_DIRS.has(part)) ? null : parts;
|
|
4017
4210
|
}
|
|
4018
|
-
/**
|
|
4211
|
+
/** The directories of local/ whose files are synced: the sessions under way, and those that ended. */
|
|
4212
|
+
const SESSION_DIRS = [SESSIONS_DIR, ENDED_SESSIONS_DIR];
|
|
4213
|
+
/** Whether the file at `path` is synced: not in a directory never synced, not in local/ but the session directories, and not a transient name. */
|
|
4019
4214
|
function syncedPath(path) {
|
|
4020
4215
|
const parts = partsOf(path, false);
|
|
4021
4216
|
if (parts === null) return false;
|
|
4022
|
-
if (parts[0] === "local" && !(parts
|
|
4217
|
+
if (parts[0] === "local" && !SESSION_DIRS.includes(parts.slice(0, -1).join("/"))) return false;
|
|
4023
4218
|
return !TRANSIENT.some((pattern) => pattern.test(parts.at(-1)));
|
|
4024
4219
|
}
|
|
4025
|
-
/** Whether the directory at `path` may hold synced files, so a scan walks into it: local/ itself and
|
|
4220
|
+
/** Whether the directory at `path` may hold synced files, so a scan walks into it: local/ itself and the session directories but nothing else in local/. */
|
|
4026
4221
|
function mayHoldSynced(path) {
|
|
4027
4222
|
if (path === "") return true;
|
|
4028
4223
|
if (partsOf(path, true) === null) return false;
|
|
4029
|
-
return path === "local" || !path.startsWith("local/") || path
|
|
4224
|
+
return path === "local" || !path.startsWith("local/") || SESSION_DIRS.includes(path);
|
|
4030
4225
|
}
|
|
4031
4226
|
/** Whether the directory at `path` is synced: one that may hold synced files, apart from .mainflow/ and local/, which every machine has of its own. */
|
|
4032
4227
|
const syncedDir = (path) => path !== "" && path !== "local" && mayHoldSynced(path);
|
|
@@ -4103,7 +4298,8 @@ const ENTRY = /^([mtdifw]\d{3})(-.+)$/;
|
|
|
4103
4298
|
/** The scope of a file in the data directory, from its path: the mainline and task directories above it, or its session. */
|
|
4104
4299
|
function scopeOfPath(path) {
|
|
4105
4300
|
const [top, second, third, fourth] = path.split("/");
|
|
4106
|
-
|
|
4301
|
+
const session = sessionIdOfPath(path);
|
|
4302
|
+
if (session !== null) return { session };
|
|
4107
4303
|
if (top === "groups" && second && third === void 0) {
|
|
4108
4304
|
const group = ENTRY.exec(second)?.[1];
|
|
4109
4305
|
return group ? { group } : {};
|
|
@@ -4435,6 +4631,17 @@ function pathOf(ref, target) {
|
|
|
4435
4631
|
}
|
|
4436
4632
|
}
|
|
4437
4633
|
const sessionPath = (id) => `/sessions/${id}`;
|
|
4634
|
+
/** Where a mainline's file shows on the hub's interface, under the mainline's own address. */
|
|
4635
|
+
const mainlineFilePath = (mainline, file) => `/mainlines/${mainline}/files/${file}`;
|
|
4636
|
+
/** The interface address of every reference the hub has a page or panel for, keyed by the reference. */
|
|
4637
|
+
function refPaths(hub) {
|
|
4638
|
+
const paths = {};
|
|
4639
|
+
for (const [ref, target] of refIndex(hub)) {
|
|
4640
|
+
const path = pathOf(ref, target);
|
|
4641
|
+
if (path) paths[ref] = path;
|
|
4642
|
+
}
|
|
4643
|
+
return paths;
|
|
4644
|
+
}
|
|
4438
4645
|
/** Fenced blocks, inline code, markdown links and bare URLs: text inside them is left as written. */
|
|
4439
4646
|
const PROTECTED = /(```[\s\S]*?(?:```|$)|`[^`\n]*`|\[[^\]\n]*\]\([^)\n]*\)|https?:\/\/\S+)/;
|
|
4440
4647
|
/** A file under a mainline directory, or a reference to a mainline, task, decision, issue, finding or group. */
|
|
@@ -4443,7 +4650,7 @@ const MENTION = /\.mainflow\/(?:[a-z0-9-]+\/)?mainlines\/(m\d{3})-[\w-]+\/([^\s`
|
|
|
4443
4650
|
function linkMentions(text, context) {
|
|
4444
4651
|
const index = refIndex(context.hub);
|
|
4445
4652
|
return text.split(PROTECTED).map((part, i) => i % 2 === 1 ? part : part.replace(MENTION, (all, m, file, ref) => {
|
|
4446
|
-
if (m && file) return index.has(m) ? `[${m}/${file}](${context.link(
|
|
4653
|
+
if (m && file) return index.has(m) ? `[${m}/${file}](${context.link(mainlineFilePath(m, file))})` : all;
|
|
4447
4654
|
const target = ref ? index.get(ref) : void 0;
|
|
4448
4655
|
const path = ref && target ? pathOf(ref, target) : null;
|
|
4449
4656
|
return path ? `[${ref}](${context.link(path)})` : all;
|
|
@@ -4715,9 +4922,12 @@ function grantLines(dirs, session) {
|
|
|
4715
4922
|
}
|
|
4716
4923
|
//#endregion
|
|
4717
4924
|
//#region ../core/dist/sessions.js
|
|
4718
|
-
/**
|
|
4719
|
-
|
|
4720
|
-
|
|
4925
|
+
/**
|
|
4926
|
+
* Read every session under `.mainflow/local/sessions/`, and under `.mainflow/local/sessions/ended/` unless `ended` is
|
|
4927
|
+
* false, as for what only sessions under way matter to; sessions are local and never version-controlled.
|
|
4928
|
+
*/
|
|
4929
|
+
function loadSessions(dirs, options = {}) {
|
|
4930
|
+
return readSessionsIn(nodeStorage(dirs), options);
|
|
4721
4931
|
}
|
|
4722
4932
|
function readSession(dirs, id) {
|
|
4723
4933
|
return readSessionIn(nodeStorage(dirs), id);
|
|
@@ -5067,7 +5277,7 @@ function duplicateClaimLines(dirs, sessionId, actionId) {
|
|
|
5067
5277
|
* over into this one. Subagent sessions are left out: their work returns to the session that delegated it.
|
|
5068
5278
|
*/
|
|
5069
5279
|
function handoffLines(dirs, session, cli) {
|
|
5070
|
-
const others = loadSessions(dirs).sessions.filter((s) => s.id !== session.id && !s.delegatedFrom && !s.archivedAt && (s.status === "active" || s.status === "paused") && s.actions.some(isCarried));
|
|
5280
|
+
const others = loadSessions(dirs, { ended: false }).sessions.filter((s) => s.id !== session.id && !s.delegatedFrom && !s.archivedAt && (s.status === "active" || s.status === "paused") && s.actions.some(isCarried));
|
|
5071
5281
|
if (others.length === 0) return [];
|
|
5072
5282
|
return [`其他會話還有沒做完的行動。要接續的是其中一場已經停下的工作時,先用 ${cli} session handoff <會話> 把它的行動接進這場會話,再開始做:`, ...others.map((s) => `- ${s.id}${mayBeRunning(dirs, s) ? "(15 分鐘內還有寫入對話紀錄,可能還在執行,不要接手)" : ""}:${s.actions.filter(isCarried).map((a) => `${a.id} ${a.do}(${a.status},服務 ${a.serves.join("、") || "未指定"})`).join(";")}`)];
|
|
5073
5283
|
}
|
|
@@ -5145,9 +5355,10 @@ function sessionBriefing(dirs, session, options) {
|
|
|
5145
5355
|
* The reminder for every user turn; null when there is nothing to say, or when it would be the same text as the
|
|
5146
5356
|
* reminder the model was last given. Records a digest of the whole text in the session file, only when it changes;
|
|
5147
5357
|
* `saveDigest` records it instead of writing the file here, as a host hook in remote mode sends it to the sync server.
|
|
5358
|
+
* `displayMod` leaves out the sentence that asks for the header line, for a host whose mod shows the current action.
|
|
5148
5359
|
*/
|
|
5149
5360
|
function turnReminder(dirs, session, options) {
|
|
5150
|
-
const text = reminderText(dirs, session, options.cli);
|
|
5361
|
+
const text = reminderText(dirs, session, options.cli, options.displayMod === true);
|
|
5151
5362
|
if (text === null) return null;
|
|
5152
5363
|
const digest = contentHash(text);
|
|
5153
5364
|
if (session.reminderDigest === digest) return null;
|
|
@@ -5157,13 +5368,13 @@ function turnReminder(dirs, session, options) {
|
|
|
5157
5368
|
} catch {}
|
|
5158
5369
|
return text;
|
|
5159
5370
|
}
|
|
5160
|
-
function reminderText(dirs, session, cli) {
|
|
5371
|
+
function reminderText(dirs, session, cli, displayMod) {
|
|
5161
5372
|
if (session.actions.length === 0) return openMainlines(dirs).length > 0 ? `[mainflow] ${session.id} 還沒有行動。這一輪要推進主脈絡時,先用 ${cli} action add 列出行動並標為 doing,或用 ${cli} session handoff <會話> 接手上一場沒做完的行動,再開始做。回覆結尾的標頭只看這場會話進行中的行動。` : null;
|
|
5162
5373
|
const doing = current(session);
|
|
5163
5374
|
const open = session.actions.filter(isOpen);
|
|
5164
5375
|
const blocked = session.actions.filter((a) => a.status === "blocked");
|
|
5165
5376
|
const head = doing ? `[mainflow] 目前行動 ${doing.id} ${doing.do},服務 ${doing.serves.join("、") || "(未指定)"}。這一輪的工作若不屬於它,先調整佇列再做。` : open.length > 0 ? `[mainflow] ${session.id} 目前沒有進行中的行動。` : blocked.length > 0 ? `[mainflow] ${session.id} 的行動在等外部條件:${blocked.map(waitingLine).join(";")}。有條件(until)的可以在背景執行 ${cli} wait <a編號> 等它成立。條件出現後用 ${cli} action set <a編號> --status doing 繼續;要先做別的事,用 ${cli} action add 列出行動。` : `[mainflow] ${session.id} 的行動都已結束。繼續工作前先用 ${cli} action add 列出下一個行動。`;
|
|
5166
|
-
const header = headerLine(session);
|
|
5377
|
+
const header = displayMod ? null : headerLine(session);
|
|
5167
5378
|
const marker = header ? [`- 這一輪最後一則回覆的最後一行寫「${header}」,回覆範例等其他內容都放在它前面。有顯示 hook 時,它會換成標題與連結。`] : [];
|
|
5168
5379
|
const grants = grantLines(dirs, session);
|
|
5169
5380
|
const granted = grants.length > 0 ? ["- 目前行動服務的主脈絡與所屬的組預先授權了這些動作:", ...grants.map((g) => ` ${g}`)] : [];
|
|
@@ -9158,6 +9369,31 @@ function migrateHub(dirs, options = {}) {
|
|
|
9158
9369
|
} : result;
|
|
9159
9370
|
}).value;
|
|
9160
9371
|
}
|
|
9372
|
+
/**
|
|
9373
|
+
* Move the session files of a hub on disk to the directory each belongs in: those that ended to local/sessions/ended/,
|
|
9374
|
+
* any resumed one back. The files do not depend on the data format, so this runs on a hub of any format; with `dryRun`
|
|
9375
|
+
* it only lists the moves.
|
|
9376
|
+
*/
|
|
9377
|
+
function relocateSessions(dirs, options = {}) {
|
|
9378
|
+
const storage = nodeStorage(dirs);
|
|
9379
|
+
if (storage.read("config.yaml") === null) throw new WriteError(`no hub at ${dirs.root}; run mainflow init first`);
|
|
9380
|
+
if (options.dryRun) return applyOp(storage, (hub) => ({
|
|
9381
|
+
...relocateSessions$1(hub, { dryRun: true }),
|
|
9382
|
+
changes: []
|
|
9383
|
+
})).value;
|
|
9384
|
+
const moved = {
|
|
9385
|
+
ended: [],
|
|
9386
|
+
resumed: [],
|
|
9387
|
+
more: false
|
|
9388
|
+
};
|
|
9389
|
+
for (let more = true; more;) {
|
|
9390
|
+
const batch = applyOp(storage, (hub) => relocateSessions$1(hub, { limit: 500 })).value;
|
|
9391
|
+
moved.ended.push(...batch.ended);
|
|
9392
|
+
moved.resumed.push(...batch.resumed);
|
|
9393
|
+
more = batch.more;
|
|
9394
|
+
}
|
|
9395
|
+
return moved;
|
|
9396
|
+
}
|
|
9161
9397
|
//#endregion
|
|
9162
9398
|
//#region ../core/dist/migrate-data.js
|
|
9163
9399
|
/** What the original data directory is renamed to, inside the hub repository, once the hub reads from its new place. */
|
|
@@ -9330,6 +9566,15 @@ function copyData(source, target) {
|
|
|
9330
9566
|
return target;
|
|
9331
9567
|
}
|
|
9332
9568
|
//#endregion
|
|
9569
|
+
//#region ../core/dist/progress.js
|
|
9570
|
+
function listProgress(items) {
|
|
9571
|
+
const counted = items.filter((item) => !item.dropped);
|
|
9572
|
+
return {
|
|
9573
|
+
done: counted.filter((item) => item.done).length,
|
|
9574
|
+
total: counted.length
|
|
9575
|
+
};
|
|
9576
|
+
}
|
|
9577
|
+
//#endregion
|
|
9333
9578
|
//#region ../core/dist/group-plan.js
|
|
9334
9579
|
/** Sessions that can still hold work: not archived, and active or paused. */
|
|
9335
9580
|
const live = (s) => !s.archivedAt && (s.status === "active" || s.status === "paused");
|
|
@@ -9401,6 +9646,87 @@ function groupPlan(hub, sessions, groupId) {
|
|
|
9401
9646
|
};
|
|
9402
9647
|
}
|
|
9403
9648
|
//#endregion
|
|
9649
|
+
//#region ../core/dist/session-view.js
|
|
9650
|
+
const unfinished = (a) => a.status === "todo" || a.status === "doing" || a.status === "blocked";
|
|
9651
|
+
/** The mainline and task a served reference points into; criteria of a group name neither. */
|
|
9652
|
+
function targetOf(ref) {
|
|
9653
|
+
const parsed = parseRef(ref);
|
|
9654
|
+
if (parsed?.kind === "checklistItem") return {
|
|
9655
|
+
mainline: parsed.mainline,
|
|
9656
|
+
task: parsed.task
|
|
9657
|
+
};
|
|
9658
|
+
if (parsed?.kind === "criterion") return { mainline: parsed.mainline };
|
|
9659
|
+
return null;
|
|
9660
|
+
}
|
|
9661
|
+
const taskProgress = (m) => {
|
|
9662
|
+
const parts = m.tasks.map((t) => listProgress(t.checklist));
|
|
9663
|
+
return {
|
|
9664
|
+
done: parts.reduce((n, p) => n + p.done, 0),
|
|
9665
|
+
total: parts.reduce((n, p) => n + p.total, 0)
|
|
9666
|
+
};
|
|
9667
|
+
};
|
|
9668
|
+
function sessionView({ hub, session, url, link, sync }) {
|
|
9669
|
+
const actions = session?.actions ?? [];
|
|
9670
|
+
const lead = actions.find((a) => a.status === "doing") ?? actions.find(unfinished);
|
|
9671
|
+
const doing = actions.find((a) => a.status === "doing");
|
|
9672
|
+
const focusTarget = lead?.serves.map(targetOf).find((t) => t !== null) ?? null;
|
|
9673
|
+
const focusMainline = focusTarget ? hub.mainlines.find((m) => m.id === focusTarget.mainline) : void 0;
|
|
9674
|
+
const serving = new Set((doing?.serves ?? []).flatMap((s) => targetOf(s) ?? []).filter((t) => t.task).map((t) => `${t.mainline}/${t.task}`));
|
|
9675
|
+
const mainlinePath = (id) => `/mainlines/${id}`;
|
|
9676
|
+
return {
|
|
9677
|
+
hub: {
|
|
9678
|
+
name: hub.config.hub,
|
|
9679
|
+
url,
|
|
9680
|
+
open: link("")
|
|
9681
|
+
},
|
|
9682
|
+
refs: refPaths(hub),
|
|
9683
|
+
session: session ? {
|
|
9684
|
+
id: session.id,
|
|
9685
|
+
actions: actions.map((a) => ({
|
|
9686
|
+
id: a.id,
|
|
9687
|
+
do: a.do,
|
|
9688
|
+
status: a.status,
|
|
9689
|
+
serves: a.serves,
|
|
9690
|
+
waiting: a.blockedBy ? {
|
|
9691
|
+
reason: a.blockedBy.reason,
|
|
9692
|
+
waitingFor: a.blockedBy.waitingFor ?? null,
|
|
9693
|
+
until: a.blockedBy.until ?? null
|
|
9694
|
+
} : null
|
|
9695
|
+
})),
|
|
9696
|
+
progress: {
|
|
9697
|
+
done: actions.filter((a) => a.status === "done").length,
|
|
9698
|
+
total: actions.filter((a) => a.status !== "dropped").length
|
|
9699
|
+
}
|
|
9700
|
+
} : null,
|
|
9701
|
+
focus: focusMainline ? {
|
|
9702
|
+
id: focusMainline.id,
|
|
9703
|
+
title: focusMainline.title,
|
|
9704
|
+
status: focusMainline.status,
|
|
9705
|
+
criteria: listProgress(focusMainline.criteria),
|
|
9706
|
+
checklist: taskProgress(focusMainline),
|
|
9707
|
+
link: link(mainlinePath(focusMainline.id))
|
|
9708
|
+
} : null,
|
|
9709
|
+
tasks: (focusMainline?.tasks ?? []).map((t) => ({
|
|
9710
|
+
mainline: focusMainline.id,
|
|
9711
|
+
id: t.id,
|
|
9712
|
+
title: t.title,
|
|
9713
|
+
status: t.status,
|
|
9714
|
+
checklist: listProgress(t.checklist),
|
|
9715
|
+
link: link(`${mainlinePath(focusMainline.id)}/tasks/${t.id}`),
|
|
9716
|
+
serving: serving.has(`${focusMainline.id}/${t.id}`)
|
|
9717
|
+
})),
|
|
9718
|
+
mainlines: hub.mainlines.filter(isOpenMainline).map((m) => ({
|
|
9719
|
+
id: m.id,
|
|
9720
|
+
title: m.title,
|
|
9721
|
+
status: m.status,
|
|
9722
|
+
group: m.group ?? null,
|
|
9723
|
+
checklist: taskProgress(m),
|
|
9724
|
+
link: link(mainlinePath(m.id))
|
|
9725
|
+
})),
|
|
9726
|
+
sync
|
|
9727
|
+
};
|
|
9728
|
+
}
|
|
9729
|
+
//#endregion
|
|
9404
9730
|
//#region src/host.ts
|
|
9405
9731
|
const DEFAULT_UI_PORT = 4320;
|
|
9406
9732
|
const hostName = (hub) => `${hub}.mainflow.localhost`;
|
|
@@ -9599,4 +9925,4 @@ function openInApp(hub, target, closeAfterMs = 150) {
|
|
|
9599
9925
|
return true;
|
|
9600
9926
|
}
|
|
9601
9927
|
//#endregion
|
|
9602
|
-
export {
|
|
9928
|
+
export { syncedPath as $, unregisterHub as $t, readNotices as A, requestTime as At, mayDecorate as B, endedSessionFileIn as Bt, untilState as C, MAX_FILE_BYTES as Ct, HANDLED_FILE as D, comparePaths as Dt, turnReminder as E, VERSION_HEADER as Et, grantScope as F, versionMismatch as Ft, renumberRequest as G, inRepositoryHubAt as Gt, traceTranscript as H, sessionFileIn as Ht, matchGrants as I, ENDED_SESSIONS_DIR as It, renumberText as J, readRegistry as Jt, renumberFiles as K, listHubs as Kt, decorateReply as L, MAINLINE_FILE as Lt, loadSessions as M, formatMismatch as Mt, readSession as N, hubFormatVersion as Nt, markNoticesHandled as O, exceedsFileLimit as Ot, transcriptPath as P, hubRequirements as Pt, syncedDir as Q, resolveHub as Qt, endsWithMarker as R, SESSIONS_DIR as Rt, initHub as S, MAX_CHANGE_PAGE as St, sessionBriefing as T, SOCKET_MAX_AGE_CLOSE as Tt, transcriptTitle as U, hubAtRoot as Ut, attributeTrace as V, requireSlug as Vt, applyOffline as W, hubHoldingFile as Wt, hubDigest as X, registeredName as Xt, scopeOfPath as Y, registerHub as Yt, mayHoldSynced as Z, registryFile as Zt, retroReportFromReceiver as _, readFrontMatter as _t, openInApp as a, hubPath as an, loadHub as at, ARCHIVE_KINDS as b, BLOB_HASH as bt, siteBlock as c, ConflictError as cn, dependencyGraph as ct, groupPlan as d, contentHash as dn, memberOfGroup as dt, userDataDir as en, applyOperation as et, listProgress as f, splitFrontMatter as fn, refIndex as ft, retroReport as g, nowMinute as gt, relocateSessions as h, taskFile as ht, hostName as i, gitCheckout as in, loadConfig as it, unhandledNotices as j, textBytes as jt, parseWarn as k, readsAsText as kt, syncHost as l, DATA_DIR as ln, mainlineEdges as lt, migrateHub as m, unavailableHubs as mt, DEFAULT_UI_PORT as n, currentBranch as nn, HubError as nt, removeDanglingLinks as o, realPath as on, loadMainlines as ot, migrateHubData as p, remoteMembers as pt, renumberPath as q, notRegisteredMessage as qt, caddySites as r, dataFile as rn, isOpenMainline as rt, removeHostLink as s, worktreesOf as sn, sectionText as st, APP_BUNDLE_ID as t, nodeStorage as tn, checkHubName as tt, sessionView as u, WriteError as un, linksInto as ut, receiverFromEnv as v, readYaml as vt, duplicateClaimLines as w, ROUTES as wt, setArchivedMany as x, MAX_BLOB_BYTES as xt, RETRY_RULES as y, ACCESS_HEADERS as yt, headerLine as z, TASK_FILE as zt };
|