@m13v/s4l 1.6.202 → 1.6.203-rc.1

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/mcp/dist/index.js CHANGED
@@ -1794,70 +1794,52 @@ tool("project_config", {
1794
1794
  }
1795
1795
  // Status / discovery mode: no project name supplied, or explicitly asked.
1796
1796
  if (args.status === true || !args.name) {
1797
- const projects = listManagedProjectStatus();
1798
- const rtReady = runtimeReady();
1799
- // On a bare .mcpb install the runtime step also materializes the pipeline
1800
- // source that xStatus shells into. Status must still work before that first
1801
- // install, otherwise the agent cannot discover that installation is the
1802
- // next milestone. Avoid probing Python until the owned runtime is ready.
1803
- const x = rtReady
1804
- ? await xStatus().catch(() => ({ connected: false, state: "status_unavailable" }))
1805
- : { connected: false, state: "runtime_not_ready" };
1806
- await ensureDoctorPhase(x.connected ? "full" : "pre_connect");
1807
- const ver = await versionStatus();
1808
- const configured = projects.some((p) => p.ready);
1809
- if (rtReady)
1810
- completeOnboardingMilestone("runtime_ready");
1811
- if (x.connected) {
1812
- completeOnboardingMilestone("x_connected", { state: x.state || "connected" });
1813
- }
1814
- if (configured) {
1815
- completeOnboardingMilestone("project_ready", {
1816
- missing_count: 0,
1817
- });
1818
- }
1819
- // mode_chosen completes when the user explicitly picked a mode (mode.json
1820
- // exists) OR this is a legacy install already past setup (a ready product),
1821
- // so adding this step never regresses an already-onboarded box.
1822
- if (modeChosen() || configured) {
1823
- completeOnboardingMilestone("mode_chosen", {
1824
- source: modeChosen() ? "chosen" : "backfilled_legacy",
1825
- });
1826
- }
1797
+ // ONE compute path: buildSnapshot -> scripts/snapshot.py, the same source
1798
+ // the menu bar and browser dashboard read. This branch used to recompute
1799
+ // projects/X/version inline (listManagedProjectStatus + xStatus), which
1800
+ // excluded the persona project and carried no setup_complete so the
1801
+ // panel's refresh() disagreed with the dashboard tool and the menu bar on
1802
+ // persona-only setups. Do not reintroduce a parallel compute here.
1803
+ const snap = await buildSnapshot();
1804
+ const projects = Array.isArray(snap.projects) ? snap.projects : [];
1805
+ const rtReady = !!snap.runtime_ready;
1806
+ const xConnected = !!snap.x_connected;
1807
+ const configured = (snap.projects_ready || 0) > 0;
1827
1808
  return jsonContent({
1828
1809
  configured,
1829
1810
  projects,
1830
1811
  runtime_ready: rtReady,
1831
- x_connected: x.connected,
1832
- x_state: x.state,
1833
- x_handle: x.handle ?? null,
1834
- mcp_version: ver.installed,
1835
- latest_version: ver.latest,
1836
- update_available: ver.update_available,
1837
- mode: currentMode(),
1838
- flags: currentFlags(),
1839
- update_hint: ver.update_available
1840
- ? `A newer version (${ver.latest}) is available — you're on ${ver.installed}. ` +
1812
+ x_connected: xConnected,
1813
+ x_state: snap.x_state,
1814
+ x_handle: snap.x_handle ?? null,
1815
+ setup_complete: !!snap.setup_complete,
1816
+ mcp_version: snap.version,
1817
+ latest_version: snap.latest_version,
1818
+ update_available: !!snap.update_available,
1819
+ mode: snap.mode,
1820
+ flags: snap.flags,
1821
+ update_hint: snap.update_available
1822
+ ? `A newer version (${snap.latest_version}) is available — you're on ${snap.version}. ` +
1841
1823
  `Tell the user and offer to run the \`runtime\` tool with action:'update' ` +
1842
1824
  `(or \`npx social-autoposter@latest update\`).`
1843
1825
  : undefined,
1844
1826
  required_fields: REQUIRED_FIELDS,
1845
1827
  recommended_fields: RECOMMENDED_FIELDS,
1846
1828
  config_path: configPath(),
1847
- ready_for_verification: rtReady && configured && x.connected,
1848
- onboarding: onboardingSnapshot(),
1829
+ ready_for_verification: !!snap.setup_complete,
1830
+ onboarding: snap.onboarding,
1849
1831
  next_step: !rtReady
1850
1832
  ? "Runtime is not ready yet. It provisions automatically on boot — poll runtime action:'status' until ready (only call runtime action:'install' to retry if status shows the boot provision failed or stalled), then continue setup automatically."
1851
1833
  : projects.length === 0
1852
1834
  ? "No projects yet. Discover the product from conversation context and the connected X profile; research its website, infer a conservative complete project, and call project_config. Ask only if no product can be identified." +
1853
- (x.connected ? "" : " X is not connected yet either — detect_x_sources, warn about keychain prompts, then run connect_x with confirm:true without a separate permission turn.")
1835
+ (xConnected ? "" : " X is not connected yet either — detect_x_sources, warn about keychain prompts, then run connect_x with confirm:true without a separate permission turn.")
1854
1836
  : projects.every((p) => p.ready)
1855
- ? (x.connected
1837
+ ? (xConnected
1856
1838
  ? "All configured projects are ready and X is connected. SCHEDULE THE AUTOPILOT: (1) call queue_setup and create each returned task with create_scheduled_task (prompt verbatim; 'already exists' is fine); (2) the autopilot then runs on its own (launchd kicker + queue worker). Call the `dashboard` tool to confirm the schedule is firing (schedule_state 'ok') — that is the terminal step; do NOT wait for or verify a draft card. Do NOT pause to ask the user to review drafts."
1857
1839
  : "All configured projects are ready, but X is NOT connected — posting needs a logged-in " +
1858
1840
  "x.com session. Detect sources and run project_config action:'connect_x', confirm:true; do not ask whether to proceed.")
1859
1841
  : "Some projects are missing required fields (see each project's missing_required). Derive them from config, context, profile_scan, and website research, then call project_config again. Ask only if a required field is genuinely unknowable." +
1860
- (x.connected ? "" : " X is also not connected yet; detect sources and run connect_x with confirm:true."),
1842
+ (xConnected ? "" : " X is also not connected yet; detect sources and run connect_x with confirm:true."),
1861
1843
  });
1862
1844
  }
1863
1845
  // Apply mode (incremental): merge whatever fields were supplied onto the
@@ -3428,6 +3410,29 @@ async function buildSnapshot() {
3428
3410
  completeOnboardingMilestone("project_ready", { missing_count: 0 });
3429
3411
  if (snap.schedule_state === "ok")
3430
3412
  completeOnboardingMilestone("tasks_scheduled");
3413
+ // mode_chosen completes when the user explicitly picked a mode (mode.json
3414
+ // exists) OR this is a legacy install already past setup (a ready project),
3415
+ // so adding this step never regresses an already-onboarded box. (Moved here
3416
+ // from project_config's status branch so ALL milestone truth lives in one
3417
+ // place next to the snapshot.)
3418
+ if (modeChosen() || (snap.projects_ready || 0) > 0) {
3419
+ completeOnboardingMilestone("mode_chosen", {
3420
+ source: modeChosen() ? "chosen" : "backfilled_legacy",
3421
+ });
3422
+ }
3423
+ // profile_scanned has no file signal of its own, but a provisioned persona
3424
+ // project can only come from the scan+dictation flow — treat its presence as
3425
+ // the completed scan so old installs can't stick at "profile pending".
3426
+ if ((Array.isArray(snap.projects) ? snap.projects : []).some((p) => p?.persona)) {
3427
+ completeOnboardingMilestone("profile_scanned", { backfill: true });
3428
+ }
3429
+ // topics_seeded has no cheap live signal (it's a DB fact, not a file fact), so
3430
+ // installs that finished setup before the persona-path completion landed stay
3431
+ // stuck at 7/8 forever. Heal by re-running the idempotent seed for the ready
3432
+ // project(s) and completing the milestone on success. Fire-and-forget: the
3433
+ // ledger flip shows on the next snapshot.
3434
+ if ((snap.projects_ready || 0) > 0)
3435
+ void healTopicsSeeded(snap);
3431
3436
  // Persist this snapshot so the menu bar can answer "set up?" the SAME way when
3432
3437
  // the loopback server is unreachable (Claude Desktop closed or mid-restart)
3433
3438
  // instead of falling back to a divergent local rule. Refreshed on every
@@ -3436,6 +3441,37 @@ async function buildSnapshot() {
3436
3441
  persistStatusSummary(snap);
3437
3442
  return snap;
3438
3443
  }
3444
+ // One attempt per process: a failed seed (offline DB) retries on the next MCP
3445
+ // launch, not on every dashboard poll.
3446
+ let topicsSeedHealAttempted = false;
3447
+ async function healTopicsSeeded(snap) {
3448
+ if (topicsSeedHealAttempted)
3449
+ return;
3450
+ topicsSeedHealAttempted = true;
3451
+ try {
3452
+ const ledger = onboardingLedger();
3453
+ if (ledger?.milestones?.topics_seeded?.status === "complete")
3454
+ return;
3455
+ const ready = (Array.isArray(snap.projects) ? snap.projects : []).filter((p) => p && p.ready && typeof p.name === "string");
3456
+ for (const p of ready) {
3457
+ const seed = await runPython("scripts/seed_search_topics.py", ["--project", p.name], {
3458
+ timeoutMs: 60_000,
3459
+ });
3460
+ if (seed.code === 0) {
3461
+ const m = /planned=(\d+)\s+inserted=(\d+)\s+updated=(\d+)/.exec(seed.stdout);
3462
+ completeOnboardingMilestone("topics_seeded", {
3463
+ project: p.name,
3464
+ topic_count: m ? Number(m[1]) : 0,
3465
+ backfill: true,
3466
+ });
3467
+ return;
3468
+ }
3469
+ }
3470
+ }
3471
+ catch (e) {
3472
+ console.error("[snapshot] topics_seeded heal failed:", e?.message || e);
3473
+ }
3474
+ }
3439
3475
  // ---- dashboard localhost fallback -----------------------------------------
3440
3476
  // When the connected host doesn't support MCP Apps UI (Claude Code / Cowork
3441
3477
  // today), serve the SAME dist/panel.html from a loopback HTTP server. The page