mainflow 0.44.0-alpha.4 → 0.45.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
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";
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-qIyBpiBn.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-D7dDE3jN.js";
5
5
  import { COMMAND_SPECS, OPTIONS } from "./facts.js";
6
6
  import { t as CLI_VERSION } from "./version-Dvv4jwF1.js";
7
7
  import { claudeCodeSessionId, hookFallback, openLink, parsePayload, runHook } from "./hooks.js";
@@ -26,6 +26,7 @@ async function syncState(dirs) {
26
26
  running,
27
27
  caughtUp: running && probe.status?.caughtUp === true,
28
28
  queued: queuedCount(dirs),
29
+ lastSyncedAt: running ? probe.status?.lastSyncedAt ?? null : null,
29
30
  notices: unhandledNotices(dirs).length
30
31
  };
31
32
  }
@@ -339,7 +340,7 @@ async function syncStatus(dirs) {
339
340
  if (probe?.state === "running") {
340
341
  const { status, daemon } = probe;
341
342
  if (!status) console.log(`mainflow sync runs for this hub (pid ${daemon.pid}); it did not give its status`);
342
- else console.log(`mainflow sync runs (pid ${daemon.pid}): ${status.online ? status.caughtUp ? "online, caught up" : "online, catching up" : "offline"}, ${status.queued} queued, atomic exchange ${status.swapping ? "on" : "off"}, ${status.problems} problems so far${status.remote ? `, ${status.remote} file${status.remote === 1 ? "" : "s"} only on the server (mainflow sync fetch)` : ""}`);
343
+ else console.log(`mainflow sync runs (pid ${daemon.pid}): ${status.online ? status.caughtUp ? "online, caught up" : "online, catching up" : "offline"}, ${status.queued} queued, ${status.lastSyncedAt ? `last synced ${status.lastSyncedAt}` : "not synced yet"}, atomic exchange ${status.swapping ? "on" : "off"}, ${status.problems} problems so far${status.remote ? `, ${status.remote} file${status.remote === 1 ? "" : "s"} only on the server (mainflow sync fetch)` : ""}`);
343
344
  if (status?.waitingForUpload) console.log("! the sync server has no copy of this hub, and this environment downloads on demand, so it does not upload its copy; run mainflow sync with on-demand downloads off in an environment that keeps every file of the hub to upload it");
344
345
  return 0;
345
346
  }
@@ -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 { 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";
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-qIyBpiBn.js";
3
+ import { n as hubWriter, r as writtenLines } from "./operation-D7dDE3jN.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.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 { 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";
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-qIyBpiBn.js";
3
+ import { n as hubWriter, r as writtenLines } from "./operation-D7dDE3jN.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";
@@ -9667,7 +9667,7 @@ const taskProgress = (m) => {
9667
9667
  };
9668
9668
  function sessionView({ hub, session, url, link, sync }) {
9669
9669
  const actions = session?.actions ?? [];
9670
- const lead = actions.find((a) => a.status === "doing") ?? actions.find(unfinished);
9670
+ const lead = actions.find((a) => a.status === "doing") ?? actions.find(unfinished) ?? [...actions].reverse().find((a) => a.serves.some((s) => targetOf(s) !== null));
9671
9671
  const doing = actions.find((a) => a.status === "doing");
9672
9672
  const focusTarget = lead?.serves.map(targetOf).find((t) => t !== null) ?? null;
9673
9673
  const focusMainline = focusTarget ? hub.mainlines.find((m) => m.id === focusTarget.mainline) : void 0;
package/dist/host.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as openInApp, c as siteBlock, i as hostName, l as syncHost, n as DEFAULT_UI_PORT, o as removeDanglingLinks, r as caddySites, s as removeHostLink, t as APP_BUNDLE_ID } from "./host-CfBcUcg2.js";
1
+ import { a as openInApp, c as siteBlock, i as hostName, l as syncHost, n as DEFAULT_UI_PORT, o as removeDanglingLinks, r as caddySites, s as removeHostLink, t as APP_BUNDLE_ID } from "./host-qIyBpiBn.js";
2
2
  export { APP_BUNDLE_ID, DEFAULT_UI_PORT, caddySites, hostName, openInApp, removeDanglingLinks, removeHostLink, siteBlock, syncHost };
package/dist/launch.js CHANGED
@@ -1,4 +1,4 @@
1
- import { i as hostName, l as syncHost, n as DEFAULT_UI_PORT, s as removeHostLink, un as WriteError } from "./host-CfBcUcg2.js";
1
+ import { i as hostName, l as syncHost, n as DEFAULT_UI_PORT, s as removeHostLink, un as WriteError } from "./host-qIyBpiBn.js";
2
2
  import { t as CLI_VERSION } from "./version-Dvv4jwF1.js";
3
3
  import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
4
4
  import { join, resolve } from "node:path";
@@ -273,7 +273,7 @@ async function launchUi(dirs, hub, configured, options) {
273
273
  }
274
274
  let server;
275
275
  if (!reuse) {
276
- const { serveUi } = await import("./ui-CnNAOM_Y.js");
276
+ const { serveUi } = await import("./ui-BkGCDN2m.js");
277
277
  server = await serveUi(dirs, choice.port);
278
278
  writeUiRecord(dirs, {
279
279
  port: choice.port,
@@ -1,4 +1,4 @@
1
- import { $ as syncedPath, At as requestTime, Ct as MAX_FILE_BYTES, Dt as comparePaths, Et as VERSION_HEADER, Ft as versionMismatch, G as renumberRequest, J as renumberText, K as renumberFiles, Ot as exceedsFileLimit, Pt as hubRequirements, Q as syncedDir, Tt as SOCKET_MAX_AGE_CLOSE, W as applyOffline, X as hubDigest, Xt as registeredName, Y as scopeOfPath, Z as mayHoldSynced, bt as BLOB_HASH, cn as ConflictError, dn as contentHash, et as applyOperation, fn as splitFrontMatter, gt as nowMinute, jt as textBytes, kt as readsAsText, q as renumberPath, tn as nodeStorage, un as WriteError, wt as ROUTES, xt as MAX_BLOB_BYTES, yt as ACCESS_HEADERS } from "./host-CfBcUcg2.js";
1
+ import { $ as syncedPath, At as requestTime, Ct as MAX_FILE_BYTES, Dt as comparePaths, Et as VERSION_HEADER, Ft as versionMismatch, G as renumberRequest, J as renumberText, K as renumberFiles, Ot as exceedsFileLimit, Pt as hubRequirements, Q as syncedDir, Tt as SOCKET_MAX_AGE_CLOSE, W as applyOffline, X as hubDigest, Xt as registeredName, Y as scopeOfPath, Z as mayHoldSynced, bt as BLOB_HASH, cn as ConflictError, dn as contentHash, et as applyOperation, fn as splitFrontMatter, gt as nowMinute, jt as textBytes, kt as readsAsText, q as renumberPath, tn as nodeStorage, un as WriteError, wt as ROUTES, xt as MAX_BLOB_BYTES, yt as ACCESS_HEADERS } from "./host-qIyBpiBn.js";
2
2
  import { t as CLI_VERSION } from "./version-Dvv4jwF1.js";
3
3
  import { createHash, randomBytes } from "node:crypto";
4
4
  import { accessSync, appendFileSync, chmodSync, closeSync, constants, copyFileSync, createReadStream, createWriteStream, existsSync, linkSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, rmdirSync, statSync, watch, writeFileSync } from "node:fs";
@@ -1767,6 +1767,8 @@ var SyncClient = class extends EventEmitter {
1767
1767
  firstContact;
1768
1768
  backoff = 500;
1769
1769
  lastPong = 0;
1770
+ /** When the sync server last gave this client something over a connection (see `lastSyncedAt`); null before the first. */
1771
+ lastExchange = null;
1770
1772
  knownTimer;
1771
1773
  /**
1772
1774
  * Until when the outbox waits, in ms since the epoch, after the hub refused a write for a while: for this CLI's version
@@ -1818,6 +1820,15 @@ var SyncClient = class extends EventEmitter {
1818
1820
  get caughtUp() {
1819
1821
  return this.online && this.current && this.resyncing === null;
1820
1822
  }
1823
+ /**
1824
+ * When this client last exchanged data with the sync server, as ISO 8601; null before the first. Three events count:
1825
+ * a broadcast of the hub's changes received, a snapshot taken in, and the server's answer to an outbox write
1826
+ * (so an upload confirmed, whatever its verdict). A pong does not count: it shows the connection is alive, but
1827
+ * nothing of the hub moved. A failed or unanswered request does not count either.
1828
+ */
1829
+ get lastSyncedAt() {
1830
+ return this.lastExchange === null ? null : new Date(this.lastExchange).toISOString();
1831
+ }
1821
1832
  /** Whether files are written with the atomic exchange: there is a helper, not paused after failing in a row. */
1822
1833
  get swapping() {
1823
1834
  return this.swapper !== null && Date.now() >= this.swapPausedUntil;
@@ -2136,6 +2147,7 @@ var SyncClient = class extends EventEmitter {
2136
2147
  return;
2137
2148
  }
2138
2149
  if (!Number.isInteger(message.seq)) return;
2150
+ this.lastExchange = Date.now();
2139
2151
  const seq = message.seq;
2140
2152
  if (seq === this.hubSeq + 1) this.takenUpTo(seq);
2141
2153
  else if (seq > this.hubSeq + 1) this.ahead.add(seq);
@@ -2352,6 +2364,7 @@ var SyncClient = class extends EventEmitter {
2352
2364
  this.resyncAgain = true;
2353
2365
  } while (this.resyncAgain && !this.closing);
2354
2366
  this.current = this.connected && !this.closing;
2367
+ if (this.current) this.lastExchange = Date.now();
2355
2368
  if (this.snapshotHeld) {
2356
2369
  this.snapshotHeld = false;
2357
2370
  clearTimeout(this.snapshotTimer);
@@ -4943,6 +4956,7 @@ var SyncClient = class extends EventEmitter {
4943
4956
  return null;
4944
4957
  }
4945
4958
  this.outdatedMessage = null;
4959
+ this.lastExchange = Date.now();
4946
4960
  if (!response.ok && response.reason === "uncertain") {
4947
4961
  const paths = entryPaths(entry);
4948
4962
  if (entry.kind !== "rmdir") this.problem({
@@ -5980,6 +5994,7 @@ async function serveIpc(dirs, client) {
5980
5994
  online: client.online,
5981
5995
  caughtUp: client.caughtUp,
5982
5996
  queued: client.queued,
5997
+ lastSyncedAt: client.lastSyncedAt,
5983
5998
  swapping: client.swapping,
5984
5999
  problems: client.problems.length,
5985
6000
  remote: client.remoteCount,
@@ -1,6 +1,6 @@
1
1
  import { I as configSchema, tt as repoPaths } from "./dist-CXrUfRQp.js";
2
- import { A as readNotices$1, D as HANDLED_FILE, H as traceTranscript, M as loadSessions, N as readSession, O as markNoticesHandled, P as transcriptPath, U as transcriptTitle, Ut as hubAtRoot, V as attributeTrace, a as openInApp, an as hubPath, at as loadHub, b as ARCHIVE_KINDS, cn as ConflictError, ct as dependencyGraph, d as groupPlan, f as listProgress, ft as refIndex, it as loadConfig, k as parseWarn, ln as DATA_DIR, lt as mainlineEdges, nt as HubError, pt as remoteMembers, rn as dataFile, st as sectionText, un as WriteError, ut as linksInto, vt as readYaml, x as setArchivedMany } from "./host-CfBcUcg2.js";
3
- import { C as readRemote, F as syncPaths, N as SYNC_DIR, P as isSynced, S as watchTree, _ as DaemonUnavailable, b as readDaemon, n as hubWriter, r as writtenLines, s as remoteMode, v as callDaemon } from "./operation-BmNzQC-L.js";
2
+ import { A as readNotices$1, D as HANDLED_FILE, H as traceTranscript, M as loadSessions, N as readSession, O as markNoticesHandled, P as transcriptPath, U as transcriptTitle, Ut as hubAtRoot, V as attributeTrace, a as openInApp, an as hubPath, at as loadHub, b as ARCHIVE_KINDS, cn as ConflictError, ct as dependencyGraph, d as groupPlan, f as listProgress, ft as refIndex, it as loadConfig, k as parseWarn, ln as DATA_DIR, lt as mainlineEdges, nt as HubError, pt as remoteMembers, rn as dataFile, st as sectionText, un as WriteError, ut as linksInto, vt as readYaml, x as setArchivedMany } from "./host-qIyBpiBn.js";
3
+ import { C as readRemote, F as syncPaths, N as SYNC_DIR, P as isSynced, S as watchTree, _ as DaemonUnavailable, b as readDaemon, n as hubWriter, r as writtenLines, s as remoteMode, v as callDaemon } from "./operation-D7dDE3jN.js";
4
4
  import { t as CLI_VERSION } from "./version-Dvv4jwF1.js";
5
5
  import { readUiRecord, realRoot } from "./launch.js";
6
6
  import { createRequire } from "node:module";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mainflow",
3
- "version": "0.44.0-alpha.4",
3
+ "version": "0.45.0",
4
4
  "description": "本機多 AI 協作的主脈絡任務管理:CLI 與介面",
5
5
  "license": "MIT",
6
6
  "type": "module",