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
package/dist/cli.js
CHANGED
|
@@ -1,18 +1,46 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { P as UNTIL_FORMS, X as parseUntil, Y as parseRef, Z as refExample, ct as stampMs, it as untilProblem, n as CLOSED_STATUS } from "./dist-CXrUfRQp.js";
|
|
3
|
-
import { $t as
|
|
4
|
-
import { t as
|
|
5
|
-
import { A as hubName, C as readRemote, D as Unreachable, E as ServerError, F as syncPaths, M as SETTINGS_FILE, O as ENV, T as ServerApi, _ as DaemonUnavailable, a as versionGate, b as readDaemon, c as oversizedFiles, d as planAnnounce, f as planFetch, g as DaemonRunning, h as startDaemon, i as requireWritableVersion, j as syncSettings, k as earlierToken, l as fetchDenial, m as toolCallOf, n as hubWriter, o as queuedCount, p as planFlush, r as writtenLines, s as remoteMode, t as CONFIRM_MS, u as hookAnswer, v as callDaemon, w as HubRewritingError, x as silentHint, y as probeDaemon } from "./operation-CUeOFEaf.js";
|
|
2
|
+
import { D as SESSION_ID, P as UNTIL_FORMS, X as parseUntil, Y as parseRef, Z as refExample, ct as stampMs, it as untilProblem, n as CLOSED_STATUS } from "./dist-CXrUfRQp.js";
|
|
3
|
+
import { $t as unregisterHub, At as requestTime, Bt as endedSessionFileIn, C as untilState, Ct as MAX_FILE_BYTES, Ht as sessionFileIn, Jt as readRegistry, Kt as listHubs, Lt as MAINLINE_FILE, M as loadSessions, N as readSession, Nt as hubFormatVersion, Qt as resolveHub, S as initHub, St as MAX_CHANGE_PAGE, T as sessionBriefing, Ut as hubAtRoot, Vt as requireSlug, Wt as hubHoldingFile, Xt as registeredName, Yt as registerHub, Zt as registryFile, _ as retroReportFromReceiver, at as loadHub, bt as BLOB_HASH, d as groupPlan, dt as memberOfGroup, en as userDataDir, et as applyOperation, f as listProgress, g as retroReport, gt as nowMinute, h as relocateSessions, i as hostName, j as unhandledNotices, ln as DATA_DIR, m as migrateHub, mt as unavailableHubs, nt as HubError, on as realPath, p as migrateHubData, pt as remoteMembers, qt as notRegisteredMessage, rn as dataFile, st as sectionText, tn as nodeStorage, tt as checkHubName, u as sessionView, un as WriteError, v as receiverFromEnv, w as duplicateClaimLines, y as RETRY_RULES, zt as TASK_FILE } from "./host-CfBcUcg2.js";
|
|
4
|
+
import { A as hubName, C as readRemote, D as Unreachable, E as ServerError, F as syncPaths, M as SETTINGS_FILE, O as ENV, T as ServerApi, _ as DaemonUnavailable, a as versionGate, b as readDaemon, c as oversizedFiles, d as planAnnounce, f as planFetch, g as DaemonRunning, h as startDaemon, i as requireWritableVersion, j as syncSettings, k as earlierToken, l as fetchDenial, m as toolCallOf, n as hubWriter, o as queuedCount, p as planFlush, r as writtenLines, s as remoteMode, t as CONFIRM_MS, u as hookAnswer, v as callDaemon, w as HubRewritingError, x as silentHint, y as probeDaemon } from "./operation-BmNzQC-L.js";
|
|
6
5
|
import { COMMAND_SPECS, OPTIONS } from "./facts.js";
|
|
7
6
|
import { t as CLI_VERSION } from "./version-Dvv4jwF1.js";
|
|
8
|
-
import { hookFallback, parsePayload, runHook } from "./hooks.js";
|
|
7
|
+
import { claudeCodeSessionId, hookFallback, openLink, parsePayload, runHook } from "./hooks.js";
|
|
9
8
|
import { runGrantCheck } from "./grant-check.js";
|
|
10
9
|
import { exitCodeFor, launchUi, networkProbe, readUiRecord, startUiInBackground, watchHubGone } from "./launch.js";
|
|
11
|
-
import { randomBytes } from "node:crypto";
|
|
10
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
12
11
|
import { existsSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
|
|
13
12
|
import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
14
13
|
import { fileURLToPath } from "node:url";
|
|
15
14
|
import { parseArgs } from "node:util";
|
|
15
|
+
//#region src/session-json.ts
|
|
16
|
+
/** The mainflow session a host's session id stands for: the id itself when such a session exists, else Claude Code's. */
|
|
17
|
+
function sessionIdOf(dirs, raw) {
|
|
18
|
+
return SESSION_ID.test(raw) && readSession(dirs, raw) ? raw : claudeCodeSessionId(raw);
|
|
19
|
+
}
|
|
20
|
+
/** The sync client's state; null when the hub is not synced, that is no client runs and writes go to the files. */
|
|
21
|
+
async function syncState(dirs) {
|
|
22
|
+
const probe = await probeDaemon(dirs);
|
|
23
|
+
if (!probe && !remoteMode(dirs)) return null;
|
|
24
|
+
const running = probe?.state === "running";
|
|
25
|
+
return {
|
|
26
|
+
running,
|
|
27
|
+
caughtUp: running && probe.status?.caughtUp === true,
|
|
28
|
+
queued: queuedCount(dirs),
|
|
29
|
+
notices: unhandledNotices(dirs).length
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** What `session show --json` prints for a mainflow or host session id; a missing session gives `session: null`. */
|
|
33
|
+
async function sessionJson(dirs, raw) {
|
|
34
|
+
const hub = loadHub(dirs, { remotes: false });
|
|
35
|
+
return sessionView({
|
|
36
|
+
hub,
|
|
37
|
+
session: readSession(dirs, sessionIdOf(dirs, raw)),
|
|
38
|
+
url: `https://${hostName(hub.config.hub)}`,
|
|
39
|
+
link: openLink(hub.config.hub),
|
|
40
|
+
sync: await syncState(dirs)
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
16
44
|
//#region src/migration.ts
|
|
17
45
|
/**
|
|
18
46
|
* What `mainflow migrate` prints of a migration's report, the same whether the hub's files are here or on the sync
|
|
@@ -26,6 +54,19 @@ function migrationLines(report, dryRun) {
|
|
|
26
54
|
...report.missingDefinitions.length ? [`${report.missingDefinitions.length} open entries still need a definition of done; fill them in the files:`, ...report.missingDefinitions.map((entry) => ` ${entry}`)] : []
|
|
27
55
|
];
|
|
28
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* What `mainflow migrate` prints of moving the session files: each session moved to local/sessions/ended/, as one that
|
|
59
|
+
* ended or was handed off, and each moved back, as one under way; one line when every file is where it belongs.
|
|
60
|
+
*/
|
|
61
|
+
function relocationLines(moved, dryRun) {
|
|
62
|
+
if (moved.ended.length === 0 && moved.resumed.length === 0) return [`sessions: every session file is in place already${dryRun ? " (dry run)" : ""}`];
|
|
63
|
+
const line = (from, to) => ` ${dryRun ? "would move" : "moved"} ${DATA_DIR}/${from} → ${DATA_DIR}/${to}`;
|
|
64
|
+
return [
|
|
65
|
+
`sessions: ${moved.ended.length} to ${DATA_DIR}/local/sessions/ended/, ${moved.resumed.length} back to ${DATA_DIR}/local/sessions/${dryRun ? " (dry run, nothing moved)" : ""}`,
|
|
66
|
+
...moved.ended.map((id) => line(sessionFileIn(id), endedSessionFileIn(id))),
|
|
67
|
+
...moved.resumed.map((id) => line(endedSessionFileIn(id), sessionFileIn(id)))
|
|
68
|
+
];
|
|
69
|
+
}
|
|
29
70
|
//#endregion
|
|
30
71
|
//#region src/remote/sync.ts
|
|
31
72
|
/**
|
|
@@ -699,6 +740,50 @@ async function syncMigrate(dirs, opts) {
|
|
|
699
740
|
if (!readDaemon(dirs)) console.log("! mainflow sync is not running for this hub; this copy takes the migration in once it runs");
|
|
700
741
|
return 0;
|
|
701
742
|
}
|
|
743
|
+
/**
|
|
744
|
+
* Move the session files of a hub the sync server keeps to the directory each belongs in (session.relocate), a batch a
|
|
745
|
+
* request until none is left, straight on the server as `mainflow migrate` converts the data format there. The sync
|
|
746
|
+
* client takes the moves in as it takes any write; one that does not run takes them once it runs. A dry run lists the
|
|
747
|
+
* moves of this machine's copy, which the sync client keeps as the server has it, since the server refuses any
|
|
748
|
+
* operation on a hub whose data format a dry run of the conversion left as it was.
|
|
749
|
+
*/
|
|
750
|
+
async function syncRelocate(dirs, opts) {
|
|
751
|
+
if (opts.dryRun) {
|
|
752
|
+
for (const line of relocationLines(relocateSessions(dirs, { dryRun: true }), true)) console.log(line);
|
|
753
|
+
console.log(" (listed from this machine's copy of the hub)");
|
|
754
|
+
return 0;
|
|
755
|
+
}
|
|
756
|
+
const settings = serverOf(dirs);
|
|
757
|
+
if (!settings) return 1;
|
|
758
|
+
const outcome = await onServer(settings, async (api) => {
|
|
759
|
+
const moved = {
|
|
760
|
+
ended: [],
|
|
761
|
+
resumed: []
|
|
762
|
+
};
|
|
763
|
+
for (;;) {
|
|
764
|
+
const request = {
|
|
765
|
+
protocol: 3,
|
|
766
|
+
op: "session.relocate",
|
|
767
|
+
input: { limit: 500 },
|
|
768
|
+
now: requestTime(),
|
|
769
|
+
requestId: randomUUID()
|
|
770
|
+
};
|
|
771
|
+
const answer = await api.send(request);
|
|
772
|
+
if (!answer.ok) return { refused: answer.reason === "unknown-operation" ? `${answer.message}; deploy the sync server of this mainflow version (apps/sync-server)` : answer.message };
|
|
773
|
+
moved.ended.push(...answer.value.ended);
|
|
774
|
+
moved.resumed.push(...answer.value.resumed);
|
|
775
|
+
if (!answer.value.more) return moved;
|
|
776
|
+
}
|
|
777
|
+
}, "operation route");
|
|
778
|
+
if (!outcome) return 1;
|
|
779
|
+
if ("refused" in outcome) {
|
|
780
|
+
console.error(`✗ the sync server refused moving the session files: ${outcome.refused}`);
|
|
781
|
+
return 1;
|
|
782
|
+
}
|
|
783
|
+
for (const line of relocationLines(outcome, false)) console.log(line);
|
|
784
|
+
if (outcome.ended.length + outcome.resumed.length > 0 && !readDaemon(dirs)) console.log("! mainflow sync is not running for this hub; this copy takes the moves in once it runs");
|
|
785
|
+
return 0;
|
|
786
|
+
}
|
|
702
787
|
//#endregion
|
|
703
788
|
//#region src/retro.ts
|
|
704
789
|
const int = (n) => Math.round(n).toLocaleString("en-US");
|
|
@@ -1587,8 +1672,10 @@ const RUNS = {
|
|
|
1587
1672
|
},
|
|
1588
1673
|
migrate: ({ dirs, opts }) => {
|
|
1589
1674
|
const dryRun = opts["dry-run"] ?? false;
|
|
1590
|
-
if (remoteMode(dirs)) return syncMigrate(dirs, { dryRun });
|
|
1591
|
-
|
|
1675
|
+
if (remoteMode(dirs)) return syncMigrate(dirs, { dryRun }).then((code) => code === 0 ? syncRelocate(dirs, { dryRun }) : code);
|
|
1676
|
+
if (hubFormatVersion(nodeStorage(dirs)) === 3) console.log(`format 3: the data format is current already`);
|
|
1677
|
+
else for (const line of migrationLines(migrateHub(dirs, { dryRun }), dryRun)) console.log(line);
|
|
1678
|
+
for (const line of relocationLines(relocateSessions(dirs, { dryRun }), dryRun)) console.log(line);
|
|
1592
1679
|
return 0;
|
|
1593
1680
|
},
|
|
1594
1681
|
ui: ({ dirs, opts }) => {
|
|
@@ -1604,8 +1691,9 @@ const RUNS = {
|
|
|
1604
1691
|
});
|
|
1605
1692
|
return 0;
|
|
1606
1693
|
},
|
|
1607
|
-
"session show": ({ dirs, args, opts }) => {
|
|
1694
|
+
"session show": async ({ dirs, args, opts }) => {
|
|
1608
1695
|
const id = sessionOf(opts, args[0]);
|
|
1696
|
+
if (opts.json) return done(JSON.stringify(await sessionJson(dirs, id)));
|
|
1609
1697
|
const session = readSession(dirs, id);
|
|
1610
1698
|
if (!session) throw new WriteError(`session ${id} not found; start one with "mainflow session start"`);
|
|
1611
1699
|
return done(sessionBriefing(dirs, session, { cli: CLI }));
|
package/dist/facts.d.ts
CHANGED
|
@@ -97,8 +97,8 @@ export declare const COMMAND_SPECS: {
|
|
|
97
97
|
readonly needsHub: true;
|
|
98
98
|
};
|
|
99
99
|
readonly 'session show': {
|
|
100
|
-
readonly usage: 'session show [session]';
|
|
101
|
-
readonly summary: 'Print the briefing a hook would give this session';
|
|
100
|
+
readonly usage: 'session show [session] [--json]';
|
|
101
|
+
readonly summary: 'Print the briefing a hook would give this session. --json prints one object for a host to read: {"hub","session","focus","tasks","mainlines","sync"}; session is null, and the exit code 0, when the session does not exist. The session is a mainflow session id or the host\'s own (a Claude Code UUID)';
|
|
102
102
|
readonly args: 0;
|
|
103
103
|
readonly optionalArgs: 1;
|
|
104
104
|
readonly needsHub: true;
|
|
@@ -741,6 +741,7 @@ export declare const LAYOUT: {
|
|
|
741
741
|
readonly group: '.mainflow/groups/w{NNN}-{slug}.md';
|
|
742
742
|
readonly ops: '.mainflow/ops/{YYYY-MM}.log';
|
|
743
743
|
readonly sessions: '.mainflow/local/sessions/{sessionId}.yaml';
|
|
744
|
+
readonly endedSessions: '.mainflow/local/sessions/ended/{sessionId}.yaml';
|
|
744
745
|
};
|
|
745
746
|
/** Statuses that close each kind of document; the CLI stamps closedAt when a document enters one. */
|
|
746
747
|
export declare const CLOSED_STATUSES: {
|
package/dist/facts.js
CHANGED
|
@@ -87,8 +87,8 @@ const COMMAND_SPECS = {
|
|
|
87
87
|
needsHub: true
|
|
88
88
|
},
|
|
89
89
|
"session show": {
|
|
90
|
-
usage: "session show [session]",
|
|
91
|
-
summary: "Print the briefing a hook would give this session",
|
|
90
|
+
usage: "session show [session] [--json]",
|
|
91
|
+
summary: "Print the briefing a hook would give this session. --json prints one object for a host to read: {\"hub\",\"session\",\"focus\",\"tasks\",\"mainlines\",\"sync\"}; session is null, and the exit code 0, when the session does not exist. The session is a mainflow session id or the host's own (a Claude Code UUID)",
|
|
92
92
|
args: 0,
|
|
93
93
|
optionalArgs: 1,
|
|
94
94
|
needsHub: true
|
|
@@ -646,7 +646,8 @@ const LAYOUT = {
|
|
|
646
646
|
finding: ".mainflow/findings/f{NNN}-{slug}.md",
|
|
647
647
|
group: ".mainflow/groups/w{NNN}-{slug}.md",
|
|
648
648
|
ops: ".mainflow/ops/{YYYY-MM}.log",
|
|
649
|
-
sessions: ".mainflow/local/sessions/{sessionId}.yaml"
|
|
649
|
+
sessions: ".mainflow/local/sessions/{sessionId}.yaml",
|
|
650
|
+
endedSessions: ".mainflow/local/sessions/ended/{sessionId}.yaml"
|
|
650
651
|
};
|
|
651
652
|
/** Statuses that close each kind of document; the CLI stamps closedAt when a document enters one. */
|
|
652
653
|
const CLOSED_STATUSES = CLOSED_STATUS;
|
package/dist/grant-check.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { o as HubDirs } from "./index-
|
|
2
|
-
import { t as HubWrite } from "./operation-
|
|
1
|
+
import { o as HubDirs } from "./index-lsW80LDU.js";
|
|
2
|
+
import { t as HubWrite } from "./operation-BTQm13OQ.js";
|
|
3
3
|
//#region src/grant-check.d.ts
|
|
4
4
|
/** What grant check prints. `grants` lists every grant used when more than one was, as for an install of several units. */
|
|
5
5
|
export interface GrantVerdict {
|
package/dist/grant-check.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { D as SESSION_ID, I as configSchema, ft as describeRequest, gt as hasControl, tt as repoPaths } from "./dist-CXrUfRQp.js";
|
|
2
|
-
import {
|
|
3
|
-
import { n as hubWriter, r as writtenLines } from "./operation-
|
|
2
|
+
import { F as grantScope, I as matchGrants, Mt as formatMismatch, N as readSession, Nt as hubFormatVersion, Qt as resolveHub, in as gitCheckout, nn as currentBranch, on as realPath, tn as nodeStorage, vt as readYaml } from "./host-CfBcUcg2.js";
|
|
3
|
+
import { n as hubWriter, r as writtenLines } from "./operation-BmNzQC-L.js";
|
|
4
4
|
import { OWNER_REPO, githubOrigin, quote, readManifest, resolveFixed, scanCommand, scopedCwd } from "./grant-commands.js";
|
|
5
5
|
import { claudeCodeSessionId, parsePayload } from "./hooks.js";
|
|
6
6
|
import { readFileSync } from "node:fs";
|
package/dist/hooks.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { o as HubDirs } from "./index-
|
|
2
|
-
import { t as HubWrite } from "./operation-
|
|
1
|
+
import { o as HubDirs } from "./index-lsW80LDU.js";
|
|
2
|
+
import { t as HubWrite } from "./operation-BTQm13OQ.js";
|
|
3
3
|
//#region src/hooks.d.ts
|
|
4
4
|
/** Fields Claude Code sends on stdin to every command hook, plus the ones specific to the events handled here. */
|
|
5
5
|
export interface ClaudeCodePayload {
|
package/dist/hooks.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { I as configSchema, Y as parseRef, ft as describeRequest, rt as taskSchema, tt as repoPaths } from "./dist-CXrUfRQp.js";
|
|
2
|
-
import {
|
|
3
|
-
import { n as hubWriter, r as writtenLines } from "./operation-
|
|
2
|
+
import { B as mayDecorate, E as turnReminder, F as grantScope, Gt as inRepositoryHubAt, I as matchGrants, It as ENDED_SESSIONS_DIR, L as decorateReply, M as loadSessions, Mt as formatMismatch, N as readSession, Nt as hubFormatVersion, Qt as resolveHub, R as endsWithMarker, Rt as SESSIONS_DIR, T as sessionBriefing, Wt as hubHoldingFile, _t as readFrontMatter, an as hubPath, at as loadHub, ht as taskFile, i as hostName, ln as DATA_DIR, nn as currentBranch, nt as HubError, on as realPath, ot as loadMainlines, qt as notRegisteredMessage, rn as dataFile, rt as isOpenMainline, sn as worktreesOf, tn as nodeStorage, vt as readYaml, z as headerLine } from "./host-CfBcUcg2.js";
|
|
3
|
+
import { n as hubWriter, r as writtenLines } from "./operation-BmNzQC-L.js";
|
|
4
4
|
import { SCENARIO_LINES, SCENARIO_SKIP_KINDS } from "./facts.js";
|
|
5
5
|
import { resolveFixed, scanCommand, scopedCwd } from "./grant-commands.js";
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
@@ -102,13 +102,17 @@ const settleOnEnd = async (_root, { payload, write }) => {
|
|
|
102
102
|
};
|
|
103
103
|
/** How a hub's interface is linked from a reply: through its /open route, which shows the address in the app. */
|
|
104
104
|
const openLink = (hub) => (path) => `https://${hostName(hub)}/open${path}`;
|
|
105
|
+
/** Whether a host's display mod is loaded, which it says by setting MAINFLOW_DISPLAY_MOD; it then shows the current action itself. */
|
|
106
|
+
const displayModOn = (env = process.env) => !!env.MAINFLOW_DISPLAY_MOD;
|
|
105
107
|
/**
|
|
106
108
|
* MessageDisplay: link the references in the lines about to be shown and expand the trailing header line into
|
|
107
109
|
* the mainline › task › action its ids name, titles included, whatever state that action is in now. Display
|
|
108
110
|
* only, so the model keeps reading what it wrote. Text with nothing to change
|
|
109
|
-
* returns before the hub is loaded, because Claude Code holds every batch until the hook answers.
|
|
111
|
+
* returns before the hub is loaded, because Claude Code holds every batch until the hook answers. With a display
|
|
112
|
+
* mod loaded it returns null at once.
|
|
110
113
|
*/
|
|
111
|
-
const displayReply = (dirs, { payload }) => {
|
|
114
|
+
const displayReply = (dirs, { payload, env }) => {
|
|
115
|
+
if (displayModOn(env)) return null;
|
|
112
116
|
const text = payload.delta;
|
|
113
117
|
if (!text || !mayDecorate(text)) return null;
|
|
114
118
|
const hub = loadHub(dirs, { remotes: false });
|
|
@@ -183,18 +187,22 @@ function turnTouchedMainflow(transcript, cli, env) {
|
|
|
183
187
|
* an action first when the queue is empty, the reply talks about an open mainline and the turn ran a mainflow
|
|
184
188
|
* command or wrote a file of the hub, since the header only names the session's own action in progress. A reply
|
|
185
189
|
* that only mentions a mainline, in a draft or a question, asks for nothing. Null when there is nothing to ask for.
|
|
190
|
+
* With a display mod loaded the mod shows the current action, so no header is asked for, but an empty queue
|
|
191
|
+
* still is: the action is what grants and the hub's tracking hang on.
|
|
186
192
|
*/
|
|
187
193
|
function missingHeaderRequest(dirs, { payload, cli, env }) {
|
|
188
194
|
if (!payload.session_id || payload.stop_hook_active) return null;
|
|
195
|
+
const displayMod = displayModOn(env);
|
|
189
196
|
const message = payload.last_assistant_message ?? "";
|
|
190
|
-
if (endsWithMarker(message)) return null;
|
|
197
|
+
if (!displayMod && endsWithMarker(message)) return null;
|
|
191
198
|
const session = readSession(dirs, claudeCodeSessionId(payload.session_id));
|
|
192
199
|
const header = headerLine(session);
|
|
193
|
-
if (header) return `[mainflow] 這一輪最後一則回覆的最後一行不是標頭。請只回一則內容為「${header}」的訊息,不加其他文字。`;
|
|
200
|
+
if (header) return displayMod ? null : `[mainflow] 這一輪最後一則回覆的最後一行不是標頭。請只回一則內容為「${header}」的訊息,不加其他文字。`;
|
|
194
201
|
if (session && session.actions.length > 0) return null;
|
|
195
202
|
const mainlines = openMainlinesIn(dirs, message);
|
|
196
203
|
if (mainlines.length === 0 || !turnTouchedMainflow(payload.transcript_path, cli, env)) return null;
|
|
197
|
-
|
|
204
|
+
const list = `先用 ${cli} action add "<做法>" --serves <m/t#c>(還沒拆任務時用解決條件 <m#r>)列出行動並用 ${cli} action set <a編號> --status doing 標為進行中,或用 ${cli} session handoff <會話> 接手上一場沒做完的行動`;
|
|
205
|
+
return displayMod ? `[mainflow] 這一輪的回覆提到 ${mainlines.join("、")},也執行了 mainflow 指令或寫入 hub 的資料目錄,但這場會話沒有行動。這一輪若在推進它,${list}。只是提到、沒有推進時,不必處理。` : `[mainflow] 這一輪的回覆提到 ${mainlines.join("、")},也執行了 mainflow 指令或寫入 hub 的資料目錄,但這場會話沒有行動,所以回覆結尾沒有標頭。這一輪若在推進它,${list};再只回一則內容為「▸ <m/t> · <a編號>」的訊息,不加其他文字。只是提到、沒有推進時,不必處理。`;
|
|
198
206
|
}
|
|
199
207
|
/**
|
|
200
208
|
* Stop: ask for what the turn's header needs, then settle the session. Only the model knows which message closes a
|
|
@@ -231,12 +239,13 @@ const CLAUDE_CODE_EVENTS = {
|
|
|
231
239
|
envNote: envFile ? "已設定環境變數 MAINFLOW_SESSION,指令不必再帶 --session" : `執行指令時帶 --session ${id}`
|
|
232
240
|
});
|
|
233
241
|
},
|
|
234
|
-
"user-prompt-submit": async (dirs, { payload, cli, write }) => {
|
|
242
|
+
"user-prompt-submit": async (dirs, { payload, cli, write, env }) => {
|
|
235
243
|
if (!payload.session_id) return null;
|
|
236
244
|
const session = await recordSession(write, dirs, payload, claudeCodeSessionId(payload.session_id), false);
|
|
237
245
|
const saved = [];
|
|
238
246
|
const text = turnReminder(dirs, session, {
|
|
239
247
|
cli,
|
|
248
|
+
displayMod: displayModOn(env),
|
|
240
249
|
saveDigest: (digest) => saved.push(write("session.reminder", {
|
|
241
250
|
id: session.id,
|
|
242
251
|
digest
|
|
@@ -454,7 +463,7 @@ function inSessionScope(file, session) {
|
|
|
454
463
|
const mainlines = `${DATA_DIR}/mainlines/`;
|
|
455
464
|
const sessions = `${DATA_DIR}/${SESSIONS_DIR}/`;
|
|
456
465
|
if (file.startsWith(mainlines)) return [...servedMainlines(session)].some((m) => file.startsWith(`${mainlines}${m}-`));
|
|
457
|
-
if (file.startsWith(sessions)) return file === `${sessions}${session.id}.yaml`;
|
|
466
|
+
if (file.startsWith(sessions)) return file === `${sessions}${session.id}.yaml` || file === `.mainflow/${ENDED_SESSIONS_DIR}/${session.id}.yaml`;
|
|
458
467
|
return true;
|
|
459
468
|
}
|
|
460
469
|
/**
|